Merge "Rename TileProviderService -> TileService." into androidx-main
diff --git a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/PerfettoHelperTest.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/PerfettoHelperTest.kt
index cfa0050..17134b9 100644
--- a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/PerfettoHelperTest.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/PerfettoHelperTest.kt
@@ -33,7 +33,7 @@
 import kotlin.test.assertTrue
 
 @LargeTest
-@SdkSuppress(minSdkVersion = 28)
+@SdkSuppress(minSdkVersion = 21)
 @RunWith(AndroidJUnit4::class)
 class PerfettoHelperTest {
     @Before
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Shell.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Shell.kt
index 83b1cb1..1e3b309 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Shell.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Shell.kt
@@ -36,6 +36,26 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 object Shell {
+    /**
+     * Returns true if the line from ps output contains the given process/package name.
+     *
+     * NOTE: On API 25 and earlier, the processName of unbundled executables will include the
+     * relative path they were invoked from:
+     *
+     * ```
+     * root      10065 10061 14848  3932  poll_sched 7bcaf1fc8c S /data/local/tmp/tracebox
+     * root      10109 1     11552  1140  poll_sched 78c86eac8c S ./tracebox
+     * ```
+     *
+     * On higher API levels, the process name will simply be e.g. "tracebox".
+     *
+     * As this function is also used for package names (which never have a leading `/`), we
+     * simply check for either.
+     */
+    private fun psLineContainsProcess(psOutputLine: String, processName: String): Boolean {
+        return psOutputLine.endsWith(" $processName") || psOutputLine.endsWith("/$processName")
+    }
+
     fun connectUiAutomation() {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
             ShellImpl // force initialization
@@ -213,7 +233,7 @@
         return executeScript("ps | grep $processName")
             .split(Regex("\r?\n"))
             .map { it.trim() }
-            .filter { it.endsWith(" $processName") }
+            .filter { psLineContainsProcess(psOutputLine = it, processName = processName) }
             .map {
                 // map to int - split, and take 2nd column (PID)
                 it.split(Regex("\\s+"))[1]
@@ -230,7 +250,7 @@
     fun isProcessAlive(pid: Int, processName: String): Boolean {
         return executeCommand("ps $pid")
             .split(Regex("\r?\n"))
-            .any { it.trim().endsWith(" $processName") }
+            .any { psLineContainsProcess(psOutputLine = it, processName = processName) }
     }
 
     @RequiresApi(21)
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt
index 21fb7ce..5d5e266a 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt
@@ -160,7 +160,7 @@
      *
      * @return true if perfetto is stopped successfully.
      */
-    public fun stopPerfetto() {
+    private fun stopPerfetto() {
         val pid = perfettoPid
 
         require(pid != null)
@@ -187,7 +187,7 @@
             // Unbundled perfetto can read configuration from a file that it has permissions to
             // read from. This because it assumes the identity of the shell and therefore has
             // access to /data/local/tmp directory.
-            "$UNBUNDLED_ENV_PREFIX $unbundledPerfettoShellPath --background" +
+            "$unbundledPerfettoShellPath --background" +
                 " -c $configFilePath" +
                 " -o $outputPath"
         }
@@ -290,13 +290,6 @@
         private const val UNBUNDLED_TEMP_OUTPUT_FILE =
             "$UNBUNDLED_PERFETTO_ROOT_DIR/trace_output.pb"
 
-        // The environment variables necessary for unbundled perfetto (unnamed domain sockets).
-        // We need unnamed sockets here because SELinux dictates that we cannot use real, file
-        // based, domain sockets on Platform versions prior to S.
-        private const val UNBUNDLED_ENV_PREFIX =
-            "PERFETTO_PRODUCER_SOCK_NAME=@macrobenchmark_producer " +
-                "PERFETTO_CONSUMER_SOCK_NAME=@macrobenchmark_consumer"
-
         // A set of supported ABIs
         private val SUPPORTED_64_ABIS = setOf("arm64-v8a", "x86_64")
         private val SUPPORTED_32_ABIS = setOf("armeabi")
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoCaptureTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoCaptureTest.kt
index 32bdfaa..4cf3f2b 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoCaptureTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoCaptureTest.kt
@@ -16,7 +16,6 @@
 
 package androidx.benchmark.macro.perfetto
 
-import android.graphics.Bitmap
 import androidx.benchmark.macro.FileLinkingRule
 import androidx.benchmark.macro.Packages
 import androidx.benchmark.perfetto.PerfettoCapture
@@ -37,7 +36,6 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import java.io.File
 import kotlin.test.assertEquals
 import kotlin.test.assertFailsWith
 
@@ -47,7 +45,7 @@
  * Note: this test is defined in benchmark-macro instead of benchmark-common so that it can
  * validate trace contents with PerfettoTraceProcessor
  */
-@SdkSuppress(minSdkVersion = 28) // Lowering blocked by b/131359446
+@SdkSuppress(minSdkVersion = 23)
 @RunWith(AndroidJUnit4::class)
 class PerfettoCaptureTest {
     @get:Rule
@@ -60,7 +58,7 @@
     }
 
     @SdkSuppress(
-        minSdkVersion = 28, // must redeclare, or minSdkVersion from this annotation (unset) wins
+        minSdkVersion = 21,
         maxSdkVersion = LOWEST_BUNDLED_VERSION_SUPPORTED - 1
     )
     @SmallTest
@@ -82,18 +80,6 @@
     @Test
     fun captureAndValidateTrace_unbundled() = captureAndValidateTrace(unbundled = true)
 
-    /** Trigger tracing that doesn't require app tracing tag enabled */
-    private fun triggerBitmapTraceSection() {
-        val outFile = File.createTempFile("tempJpg", "jpg")
-        try {
-            Bitmap
-                .createBitmap(100, 100, Bitmap.Config.ARGB_8888)
-                .compress(Bitmap.CompressFormat.JPEG, 100, outFile.outputStream())
-        } finally {
-            outFile.delete()
-        }
-    }
-
     private fun captureAndValidateTrace(unbundled: Boolean) {
         assumeTrue(isAbiSupported())
 
@@ -109,36 +95,33 @@
         // TODO: figure out why this sleep (200ms+) is needed - possibly related to b/194105203
         Thread.sleep(500)
 
-        triggerBitmapTraceSection()
-        trace(CUSTOM_TRACE_SECTION_LABEL) {
-            // Tracing non-trivial duration for manual debugging/verification
-            Thread.sleep(20)
-        }
+        // Tracing non-trivial duration for manual debugging/verification
+        trace(CUSTOM_TRACE_SECTION_LABEL_1) { Thread.sleep(20) }
+        trace(CUSTOM_TRACE_SECTION_LABEL_2) { Thread.sleep(20) }
 
         perfettoCapture.stop(traceFilePath)
 
         val matchingSlices = PerfettoTraceProcessor.querySlices(
             absoluteTracePath = traceFilePath,
-            CUSTOM_TRACE_SECTION_LABEL,
-            BITMAP_TRACE_SECTION_LABEL
+            CUSTOM_TRACE_SECTION_LABEL_1,
+            CUSTOM_TRACE_SECTION_LABEL_2
         )
 
-        // We trigger and verify both bitmap trace section (res-tag), and then custom trace
-        // section (app-tag) which makes it easier to identify when app-tag-specific issues arise
+        // Note: this test avoids validating platform-triggered trace sections, to avoid flakes
+        // from legitimate (and coincidental) platform use during test.
         assertEquals(
-            listOf(BITMAP_TRACE_SECTION_LABEL, CUSTOM_TRACE_SECTION_LABEL),
+            listOf(CUSTOM_TRACE_SECTION_LABEL_1, CUSTOM_TRACE_SECTION_LABEL_2),
             matchingSlices.sortedBy { it.ts }.map { it.name }
         )
         matchingSlices
-            .single { it.name == CUSTOM_TRACE_SECTION_LABEL }
-            .apply {
-                assertTrue(dur > 15_000_000) // should be at least 15ms
+            .forEach {
+                assertTrue(it.dur > 15_000_000) // should be at least 15ms
             }
     }
 
     companion object {
-        const val CUSTOM_TRACE_SECTION_LABEL = "PerfettoCaptureTest"
-        const val BITMAP_TRACE_SECTION_LABEL = "Bitmap.compress"
+        const val CUSTOM_TRACE_SECTION_LABEL_1 = "PerfettoCaptureTest_1"
+        const val CUSTOM_TRACE_SECTION_LABEL_2 = "PerfettoCaptureTest_2"
     }
 }
 
diff --git a/buildSrc-tests/build.gradle b/buildSrc-tests/build.gradle
index 75dad66..17d4740 100644
--- a/buildSrc-tests/build.gradle
+++ b/buildSrc-tests/build.gradle
@@ -27,7 +27,7 @@
 dependencies {
     implementation(gradleApi())
     testImplementation(libs.junit)
-    implementation(project.files(new File(BuildServerConfigurationKt.getRootOutDirectory(project), "buildSrc/build/libs/buildSrc.jar")))
+    implementation(project.files(new File(BuildServerConfigurationKt.getRootOutDirectory(project), "buildSrc/private/build/libs/private.jar")))
 }
 
 // Also do style checking of the buildSrc project from within this project too
@@ -51,4 +51,4 @@
 // Broken in AGP 7.0-alpha15 due to b/180408027
 tasks["lint"].configure { t ->
     t.enabled = false
-}
\ No newline at end of file
+}
diff --git a/buildSrc/apply/applyAndroidXComposeImplPlugin.gradle b/buildSrc/apply/applyAndroidXComposeImplPlugin.gradle
index d6119a6..b3cdbe7 100644
--- a/buildSrc/apply/applyAndroidXComposeImplPlugin.gradle
+++ b/buildSrc/apply/applyAndroidXComposeImplPlugin.gradle
@@ -2,7 +2,7 @@
 
 buildscript {
     dependencies {
-        classpath(project.files("${project.rootProject.ext["outDir"]}/buildSrc/build/buildSrc.jar"))
+        classpath(project.files("${project.rootProject.ext["outDir"]}/buildSrc/private/build/libs/private.jar"))
     }
 }
 
diff --git a/buildSrc/apply/applyAndroidXDocsImplPlugin.gradle b/buildSrc/apply/applyAndroidXDocsImplPlugin.gradle
index b429e58..2800850 100644
--- a/buildSrc/apply/applyAndroidXDocsImplPlugin.gradle
+++ b/buildSrc/apply/applyAndroidXDocsImplPlugin.gradle
@@ -2,7 +2,7 @@
 
 buildscript {
     dependencies {
-        classpath(project.files("${project.rootProject.ext["outDir"]}/buildSrc/build/buildSrc.jar"))
+        classpath(project.files("${project.rootProject.ext["outDir"]}/buildSrc/private/build/libs/private.jar"))
     }
 }
 
diff --git a/buildSrc/apply/applyAndroidXImplPlugin.gradle b/buildSrc/apply/applyAndroidXImplPlugin.gradle
index 6dabde0..e4441dc 100644
--- a/buildSrc/apply/applyAndroidXImplPlugin.gradle
+++ b/buildSrc/apply/applyAndroidXImplPlugin.gradle
@@ -2,7 +2,7 @@
 
 buildscript {
     dependencies {
-        classpath(project.files("${project.rootProject.ext["outDir"]}/buildSrc/build/buildSrc.jar"))
+        classpath(project.files("${project.rootProject.ext["outDir"]}/buildSrc/private/build/libs/private.jar"))
     }
 }
 
diff --git a/buildSrc/apply/applyAndroidXPlaygroundRootImplPlugin.gradle b/buildSrc/apply/applyAndroidXPlaygroundRootImplPlugin.gradle
index c3e7988..eb6b13a 100644
--- a/buildSrc/apply/applyAndroidXPlaygroundRootImplPlugin.gradle
+++ b/buildSrc/apply/applyAndroidXPlaygroundRootImplPlugin.gradle
@@ -2,7 +2,7 @@
 
 buildscript {
     dependencies {
-        classpath(project.files("${project.rootProject.ext["outDir"]}/buildSrc/build/buildSrc.jar"))
+        classpath(project.files("${project.rootProject.ext["outDir"]}/buildSrc/private/build/libs/private.jar"))
     }
 }
 
diff --git a/buildSrc/apply/applyAndroidXRootImplPlugin.gradle b/buildSrc/apply/applyAndroidXRootImplPlugin.gradle
index fbb73e3..eb77a06 100644
--- a/buildSrc/apply/applyAndroidXRootImplPlugin.gradle
+++ b/buildSrc/apply/applyAndroidXRootImplPlugin.gradle
@@ -2,7 +2,7 @@
 
 buildscript {
     dependencies {
-        classpath(project.files("${project.rootProject.ext["outDir"]}/buildSrc/build/buildSrc.jar"))
+        classpath(project.files("${project.rootProject.ext["outDir"]}/buildSrc/private/build/libs/private.jar"))
     }
 }
 
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index 707fd6e..4af4322 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -24,7 +24,6 @@
 ext.supportRootFolder = project.projectDir.getParentFile()
 apply from: "repos.gradle"
 apply plugin: "kotlin"
-apply from: "kotlin-dsl-dependency.gradle"
 
 allprojects {
     repos.addMavenRepositories(repositories)
@@ -46,157 +45,6 @@
     }
 }
 
-configurations {
-    // Dependencies added to these configurations get copied into the corresponding configuration
-    // (cacheableApi gets copied into api, etc).
-    // Because we cache the resolutions of these configurations, performance is faster when
-    // artifacts are put into these configurations than when those artifacts are put into their
-    // corresponding configuration.
-    cacheableApi
-    cacheableImplementation {
-        extendsFrom(project.configurations.cacheableApi)
-    }
-    cacheableRuntimeOnly
-}
-
 dependencies {
-    cacheableApi(libs.androidGradlePluginz)
-    cacheableImplementation(libs.dexMemberList)
-    cacheableApi(libs.kotlinGradlePluginz)
-    cacheableImplementation(gradleApi())
-    cacheableApi(libs.dokkaGradlePluginz)
-    // needed by inspection plugin
-    cacheableImplementation(libs.protobufGradlePluginz)
-    cacheableImplementation(libs.wireGradlePluginz)
-    cacheableImplementation(libs.shadow)
-    // dependencies that aren't used by buildSrc directly but that we resolve here so that the
-    // root project doesn't need to re-resolve them and their dependencies on every build
-    cacheableRuntimeOnly(libs.hiltAndroidGradlePluginz)
-    // room kotlintestapp uses the ksp plugin but it does not publish a plugin marker yet
-    cacheableApi(libs.kspGradlePluginz)
-    cacheableApi(libs.japicmpPluginz)
-    // dependencies whose resolutions we don't need to cache
-    compileOnly(findGradleKotlinDsl()) // Only one file in this configuration, no need to cache it
-    implementation(project("jetpad-integration")) // Doesn't have a .pom, so not slow to load
-}
-
-// Exclude dokka coming from AGP. We don't need it and it conflicts with dackka: b/195305339
-configurations.configureEach { conf ->
-  conf.exclude(group:"org.jetbrains.dokka", module:"dokka-core")
-}
-
-apply plugin: "java-gradle-plugin"
-
-sourceSets {
-    ["public", "private", "plugins"].each { subdir ->
-      main.java.srcDirs += "${subdir}/src/main/kotlin"
-      main.resources.srcDirs += "${subdir}/src/main/resources"
-    }
-
-
-    main.java.srcDirs += "${supportRootFolder}/benchmark/gradle-plugin/src/main/kotlin"
-    main.resources.srcDirs += "${supportRootFolder}/benchmark/gradle-plugin/src/main/resources"
-
-    main.java.srcDirs += "${supportRootFolder}/inspection/inspection-gradle-plugin/src/main/kotlin"
-    main.resources.srcDirs += "${supportRootFolder}/inspection/inspection-gradle-plugin/src/main" +
-            "/resources"
-
-    main.java.srcDirs += "${supportRootFolder}/compose/material/material/icons/generator/src/main" +
-            "/kotlin"
-}
-
-gradlePlugin {
-    plugins {
-        benchmark {
-            id = "androidx.benchmark"
-            implementationClass = "androidx.benchmark.gradle.BenchmarkPlugin"
-        }
-        inspection {
-            id = "androidx.inspection"
-            implementationClass = "androidx.inspection.gradle.InspectionPlugin"
-        }
-    }
-}
-
-// Saves configuration into destFile
-// Each line of destFile will be the absolute filepath of one of the files in configuration
-def saveConfigurationResolution(configuration, destFile) {
-    def resolvedConfiguration = configuration.resolvedConfiguration
-    def files = resolvedConfiguration.files
-    def paths = files.collect { f -> f.toString() }
-    def serialized = paths.join("\n")
-    destFile.text = serialized
-}
-
-// Parses a file into a list of Dependency objects representing a ResolvedConfiguration
-def parseConfigurationResolution(savedFile, throwOnError) {
-    def savedText = savedFile.text
-    def filenames = savedText.split("\n")
-    def valid = true
-    def dependencies = filenames.collect { filename ->
-        if (!project.file(filename).exists()) {
-            if (throwOnError) {
-                throw new GradleException("\nFile " + filename + " listed as a resolved dependency in " + savedFile + " does not exist!\n\nFor more information, see b/187075069")
-            } else {
-                valid = false
-            }
-        }
-        project.dependencies.create(project.files(filename))
-    }
-    if (!valid) {
-        return null
-    }
-    return dependencies
-}
-
-// Resolves a Configuration into a list of Dependency objects
-def resolveConfiguration(configuration) {
-    def resolvedName = configuration.name
-    def cacheDir = new File(project.buildDir, "/" + resolvedName)
-    def inputsFile = new File(cacheDir, "/deps")
-    def outputsFile = new File(cacheDir, "/result")
-
-    def inputText = fingerprintConfiguration(configuration)
-    def parsed = null
-    if (inputsFile.exists() && inputsFile.text == inputText) {
-        // Try to parse the previously resolved configuration, but don't give up if it mentions a
-        // nonexistent file. If something has since deleted one of the referenced files, we will
-        // try to reresolve that file later
-        parsed = parseConfigurationResolution(outputsFile, false)
-    }
-    // If the configuration has changed or if any of its files have been deleted, reresolve it
-    if (parsed == null) {
-        cacheDir.mkdirs()
-        saveConfigurationResolution(configuration, outputsFile)
-        inputsFile.text = inputText
-        // confirm that the resolved configuration parses successfully
-        parsed = parseConfigurationResolution(outputsFile, true)
-    }
-    return parsed
-}
-
-// Computes a unique string from a Configuration based on its dependencies
-// This is used for up-to-date checks
-def fingerprintConfiguration(configuration) {
-    def dependencies = configuration.allDependencies
-    def dependencyTexts = dependencies.collect { dep -> dep.group + ":" + dep.name + ":" + dep.version }
-    return dependencyTexts.join("\n")
-}
-
-// Imports the contents of fromConf into toConf
-// Uses caching to often short-circuit the resolution of fromConf
-def loadConfigurationQuicklyInto(fromConf, toConf) {
-    def resolved = resolveConfiguration(fromConf)
-    resolved.each { dep ->
-        project.dependencies.add(toConf.name, dep)
-    }
-}
-
-loadConfigurationQuicklyInto(configurations.cacheableApi, configurations.api)
-loadConfigurationQuicklyInto(configurations.cacheableImplementation, configurations.implementation)
-loadConfigurationQuicklyInto(configurations.cacheableRuntimeOnly, configurations.runtimeOnly)
-
-project.tasks.withType(Jar) { task ->
-    task.reproducibleFileOrder = true
-    task.preserveFileTimestamps = false
+  api(project("plugins"))
 }
diff --git a/buildSrc/plugins/build.gradle b/buildSrc/plugins/build.gradle
new file mode 100644
index 0000000..6779c30
--- /dev/null
+++ b/buildSrc/plugins/build.gradle
@@ -0,0 +1 @@
+apply from: "../shared.gradle"
diff --git a/buildSrc/private/build.gradle b/buildSrc/private/build.gradle
new file mode 100644
index 0000000..6779c30
--- /dev/null
+++ b/buildSrc/private/build.gradle
@@ -0,0 +1 @@
+apply from: "../shared.gradle"
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
index 44c3b9f..eba54c5 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
@@ -16,13 +16,13 @@
 
 package androidx.build
 
+import androidx.build.Multiplatform.Companion.isMultiplatformEnabled
 import androidx.build.checkapi.shouldConfigureApiTasks
 import groovy.lang.Closure
 import org.gradle.api.GradleException
 import org.gradle.api.Project
 import org.gradle.api.provider.Property
 import java.util.ArrayList
-
 /**
  * Extension for [AndroidXImplPlugin] that's responsible for holding configuration options.
  */
@@ -159,7 +159,13 @@
 
     var benchmarkRunAlsoInterpreted = false
 
-    var multiplatform = false
+    var multiplatform: Boolean
+        set(value) {
+            Multiplatform.setEnabledForProject(project, value)
+        }
+        get() {
+            return project.isMultiplatformEnabled()
+        }
 
     fun shouldEnforceKotlinStrictApiMode(): Boolean {
         return !legacyDisableKotlinStrictApiMode &&
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt b/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
index c763b1b..71e9139 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
@@ -25,11 +25,13 @@
 import org.gradle.api.artifacts.VersionCatalogsExtension
 import org.gradle.api.file.FileCollection
 import org.gradle.api.provider.ListProperty
+import org.gradle.api.provider.Property
 import org.gradle.api.provider.SetProperty
 import org.gradle.process.ExecOperations
 import org.gradle.workers.WorkAction
 import org.gradle.workers.WorkParameters
 import org.gradle.workers.WorkerExecutor
+import java.io.ByteArrayOutputStream
 import java.io.File
 import javax.inject.Inject
 
@@ -66,15 +68,26 @@
     private val execOperations: ExecOperations
 ) : WorkAction<MetalavaParams> {
     override fun execute() {
-        execOperations.javaexec {
-            // Intellij core reflects into java.util.ResourceBundle
-            it.jvmArgs = listOf(
-                "--add-opens",
-                "java.base/java.util=ALL-UNNAMED"
-            )
-            it.classpath(parameters.metalavaClasspath.get())
-            it.mainClass.set("com.android.tools.metalava.Driver")
-            it.args = parameters.args.get()
+        val outputStream = ByteArrayOutputStream()
+        var successful = false
+        try {
+            execOperations.javaexec {
+                // Intellij core reflects into java.util.ResourceBundle
+                it.jvmArgs = listOf(
+                    "--add-opens",
+                    "java.base/java.util=ALL-UNNAMED"
+                )
+                it.classpath(parameters.metalavaClasspath.get())
+                it.mainClass.set("com.android.tools.metalava.Driver")
+                it.args = parameters.args.get()
+                it.setStandardOutput(outputStream)
+                it.setErrorOutput(outputStream)
+            }
+            successful = true
+        } finally {
+            if (!successful) {
+                System.err.println(outputStream.toString(Charsets.UTF_8))
+            }
         }
     }
 }
diff --git a/buildSrc/public/README.md b/buildSrc/public/README.md
index 17062a3..a3e8122 100644
--- a/buildSrc/public/README.md
+++ b/buildSrc/public/README.md
@@ -1,3 +1,5 @@
-This is the :buildSrc:public project
+This directory contains code that other projects in this repository expect to be able to import and reference from their build.gradle files
 
-It contains code that other projects in this repository expect to be able to import and reference from their build.gradle files
+The files in this directory are used by the buildSrc:plugins and buildSrc:private projects.
+
+The files in this directory are essentially a project and can be turned into a real Gradle project if needed; at the moment, they are simply included in the corresponding builds because that runs more quickly.
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/public/src/main/kotlin/androidx/build/LibraryVersions.kt
index c18f695..31e2934 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -46,7 +46,7 @@
     val COMPOSE_MATERIAL3 = Version(System.getenv("COMPOSE_CUSTOM_VERSION") ?: "1.1.0-alpha01")
     val COMPOSE = Version(System.getenv("COMPOSE_CUSTOM_VERSION") ?: "1.1.0-alpha03")
     val COORDINATORLAYOUT = Version("1.2.0-alpha01")
-    val CORE = Version("1.7.0-alpha01")
+    val CORE = Version("1.7.0-alpha02")
     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")
@@ -114,7 +114,7 @@
     val SLICE_BENCHMARK = Version("1.1.0-alpha02")
     val SLICE_BUILDERS_KTX = Version("1.0.0-alpha08")
     val SLICE_REMOTECALLBACK = Version("1.0.0-alpha01")
-    val SLIDINGPANELAYOUT = Version("1.2.0-alpha05")
+    val SLIDINGPANELAYOUT = Version("1.2.0-beta01")
     val STARTUP = Version("1.2.0-alpha01")
     val SQLITE = Version("2.2.0-alpha03")
     val SQLITE_INSPECTOR = Version("2.1.0-alpha01")
@@ -151,7 +151,7 @@
     val WEAR_WATCHFACE_EDITOR_GUAVA = WEAR_WATCHFACE_EDITOR
     val WEAR_WATCHFACE_STYLE = Version("1.0.0-alpha21")
     val WEBKIT = Version("1.5.0-alpha01")
-    val WINDOW = Version("1.0.0-beta01")
+    val WINDOW = Version("1.0.0-beta02")
     val WINDOW_EXTENSIONS = Version("1.0.0-alpha01")
     val WINDOW_SIDECAR = Version("0.1.0-beta01")
     val WORK = Version("2.7.0-beta01")
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/Multiplatform.kt b/buildSrc/public/src/main/kotlin/androidx/build/Multiplatform.kt
index c422169..b3f1195 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/Multiplatform.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/Multiplatform.kt
@@ -17,8 +17,8 @@
 package androidx.build
 
 import androidx.build.COMPOSE_MPP_ENABLED
-import androidx.build.AndroidXExtension
 import org.gradle.api.Project
+import org.gradle.kotlin.dsl.extra
 
 /**
  * Setting this property enables multiplatform builds of Compose
@@ -27,14 +27,12 @@
 
 class Multiplatform {
     companion object {
-        @JvmStatic
         fun Project.isMultiplatformEnabled(): Boolean {
-            return properties.get(COMPOSE_MPP_ENABLED)?.toString()?.toBoolean()
-                ?: androidxExtension()?.multiplatform ?: false
+            return properties.get(COMPOSE_MPP_ENABLED)?.toString()?.toBoolean() ?: false
         }
 
-        private fun Project.androidxExtension(): AndroidXExtension? {
-            return extensions.findByType(AndroidXExtension::class.java)
+        fun setEnabledForProject(project: Project, enabled: Boolean) {
+            project.extra.set(COMPOSE_MPP_ENABLED, enabled)
         }
     }
 }
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/SdkHelper.kt b/buildSrc/public/src/main/kotlin/androidx/build/SdkHelper.kt
index 00d00a3..155a1d9 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/SdkHelper.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/SdkHelper.kt
@@ -54,7 +54,7 @@
  * Returns the root project's platform-specific SDK path as a file.
  */
 fun Project.getSdkPath(): File {
-    if (rootProject.plugins.hasPlugin(AndroidXPlaygroundRootPlugin::class.java) ||
+    if (rootProject.plugins.hasPlugin("androix.build.AndroidXPlaygroundRootImplPlugin") ||
         System.getenv("COMPOSE_DESKTOP_GITHUB_BUILD") != null
     ) {
         // This is not full checkout, use local settings instead.
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/SingleFileCopy.kt b/buildSrc/public/src/main/kotlin/androidx/build/SingleFileCopy.kt
similarity index 100%
rename from buildSrc/private/src/main/kotlin/androidx/build/SingleFileCopy.kt
rename to buildSrc/public/src/main/kotlin/androidx/build/SingleFileCopy.kt
diff --git a/buildSrc/settings.gradle b/buildSrc/settings.gradle
index 28bcb0f..3d32906 100644
--- a/buildSrc/settings.gradle
+++ b/buildSrc/settings.gradle
@@ -15,6 +15,8 @@
  */
 
 include ":jetpad-integration"
+include ":plugins"
+include ":private"
 
 enableFeaturePreview("VERSION_CATALOGS")
 
diff --git a/buildSrc/shared.gradle b/buildSrc/shared.gradle
new file mode 100644
index 0000000..a82ce62
--- /dev/null
+++ b/buildSrc/shared.gradle
@@ -0,0 +1,166 @@
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+apply plugin: "kotlin"
+apply from: "../kotlin-dsl-dependency.gradle"
+
+buildscript {
+    project.ext.supportRootFolder = project.projectDir.getParentFile().getParentFile()
+    apply from: "../repos.gradle"
+    repos.addMavenRepositories(repositories)
+    dependencies {
+        classpath(libs.kotlinGradlePluginz)
+    }
+}
+
+configurations {
+    // Dependencies added to these configurations get copied into the corresponding configuration
+    // (cacheableApi gets copied into api, etc).
+    // Because we cache the resolutions of these configurations, performance is faster when
+    // artifacts are put into these configurations than when those artifacts are put into their
+    // corresponding configuration.
+    cacheableApi
+    cacheableImplementation {
+        extendsFrom(project.configurations.cacheableApi)
+    }
+    cacheableRuntimeOnly
+}
+
+dependencies {
+    cacheableApi(libs.androidGradlePluginz)
+    cacheableImplementation(libs.dexMemberList)
+    cacheableApi(libs.kotlinGradlePluginz)
+    cacheableImplementation(gradleApi())
+    cacheableApi(libs.dokkaGradlePluginz)
+    // needed by inspection plugin
+    cacheableImplementation(libs.protobufGradlePluginz)
+    cacheableImplementation(libs.wireGradlePluginz)
+    cacheableImplementation(libs.shadow)
+    // dependencies that aren't used by buildSrc directly but that we resolve here so that the
+    // root project doesn't need to re-resolve them and their dependencies on every build
+    cacheableRuntimeOnly(libs.hiltAndroidGradlePluginz)
+    // room kotlintestapp uses the ksp plugin but it does not publish a plugin marker yet
+    cacheableApi(libs.kspGradlePluginz)
+    cacheableApi(libs.japicmpPluginz)
+    // dependencies whose resolutions we don't need to cache
+    compileOnly(findGradleKotlinDsl()) // Only one file in this configuration, no need to cache it
+    implementation(project(":jetpad-integration")) // Doesn't have a .pom, so not slow to load
+}
+
+// Exclude dokka coming from AGP. We don't need it and it conflicts with dackka: b/195305339
+configurations.configureEach { conf ->
+  conf.exclude(group:"org.jetbrains.dokka", module:"dokka-core")
+}
+
+apply plugin: "java-gradle-plugin"
+
+sourceSets {
+    main.java.srcDirs += "../public/src/main/kotlin"
+    main.resources.srcDirs += "../public/src/main/resources"
+
+
+    main.java.srcDirs += "${supportRootFolder}/benchmark/gradle-plugin/src/main/kotlin"
+    main.resources.srcDirs += "${supportRootFolder}/benchmark/gradle-plugin/src/main/resources"
+
+    main.java.srcDirs += "${supportRootFolder}/inspection/inspection-gradle-plugin/src/main/kotlin"
+    main.resources.srcDirs += "${supportRootFolder}/inspection/inspection-gradle-plugin/src/main" +
+            "/resources"
+
+    main.java.srcDirs += "${supportRootFolder}/compose/material/material/icons/generator/src/main" +
+            "/kotlin"
+}
+
+gradlePlugin {
+    plugins {
+        benchmark {
+            id = "androidx.benchmark"
+            implementationClass = "androidx.benchmark.gradle.BenchmarkPlugin"
+        }
+        inspection {
+            id = "androidx.inspection"
+            implementationClass = "androidx.inspection.gradle.InspectionPlugin"
+        }
+    }
+}
+
+// Saves configuration into destFile
+// Each line of destFile will be the absolute filepath of one of the files in configuration
+def saveConfigurationResolution(configuration, destFile) {
+    def resolvedConfiguration = configuration.resolvedConfiguration
+    def files = resolvedConfiguration.files
+    def paths = files.collect { f -> f.toString() }
+    def serialized = paths.join("\n")
+    destFile.text = serialized
+}
+
+// Parses a file into a list of Dependency objects representing a ResolvedConfiguration
+def parseConfigurationResolution(savedFile, throwOnError) {
+    def savedText = savedFile.text
+    def filenames = savedText.split("\n")
+    def valid = true
+    def dependencies = filenames.collect { filename ->
+        if (!project.file(filename).exists()) {
+            if (throwOnError) {
+                throw new GradleException("\nFile " + filename + " listed as a resolved dependency in " + savedFile + " does not exist!\n\nFor more information, see b/187075069")
+            } else {
+                valid = false
+            }
+        }
+        project.dependencies.create(project.files(filename))
+    }
+    if (!valid) {
+        return null
+    }
+    return dependencies
+}
+
+// Resolves a Configuration into a list of Dependency objects
+def resolveConfiguration(configuration) {
+    def resolvedName = configuration.name
+    def cacheDir = new File(project.buildDir, "/" + resolvedName)
+    def inputsFile = new File(cacheDir, "/deps")
+    def outputsFile = new File(cacheDir, "/result")
+
+    def inputText = fingerprintConfiguration(configuration)
+    def parsed = null
+    if (inputsFile.exists() && inputsFile.text == inputText) {
+        // Try to parse the previously resolved configuration, but don't give up if it mentions a
+        // nonexistent file. If something has since deleted one of the referenced files, we will
+        // try to reresolve that file later
+        parsed = parseConfigurationResolution(outputsFile, false)
+    }
+    // If the configuration has changed or if any of its files have been deleted, reresolve it
+    if (parsed == null) {
+        cacheDir.mkdirs()
+        saveConfigurationResolution(configuration, outputsFile)
+        inputsFile.text = inputText
+        // confirm that the resolved configuration parses successfully
+        parsed = parseConfigurationResolution(outputsFile, true)
+    }
+    return parsed
+}
+
+// Computes a unique string from a Configuration based on its dependencies
+// This is used for up-to-date checks
+def fingerprintConfiguration(configuration) {
+    def dependencies = configuration.allDependencies
+    def dependencyTexts = dependencies.collect { dep -> dep.group + ":" + dep.name + ":" + dep.version }
+    return dependencyTexts.join("\n")
+}
+
+// Imports the contents of fromConf into toConf
+// Uses caching to often short-circuit the resolution of fromConf
+def loadConfigurationQuicklyInto(fromConf, toConf) {
+    def resolved = resolveConfiguration(fromConf)
+    resolved.each { dep ->
+        project.dependencies.add(toConf.name, dep)
+    }
+}
+
+loadConfigurationQuicklyInto(configurations.cacheableApi, configurations.api)
+loadConfigurationQuicklyInto(configurations.cacheableImplementation, configurations.implementation)
+loadConfigurationQuicklyInto(configurations.cacheableRuntimeOnly, configurations.runtimeOnly)
+
+project.tasks.withType(Jar) { task ->
+    task.reproducibleFileOrder = true
+    task.preserveFileTimestamps = false
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
index 8938aee..3a982dc 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
@@ -48,11 +48,13 @@
  */
 @UseCaseCameraScope
 interface UseCaseCameraRequestControl {
-
+    /**
+     * The declaration order is the ordering to merge.
+     */
     enum class Type {
+        SESSION_CONFIG,
         DEFAULT,
         CAMERA2_CAMERA_CONTROL,
-        SESSION_CONFIG,
     }
 
     // Repeating parameters
@@ -117,16 +119,12 @@
         template: RequestTemplate?,
         listeners: Set<Request.Listener>
     ): Deferred<Unit> = synchronized(lock) {
-        if (infoBundleMap[type] == null) {
-            infoBundleMap[type] = InfoBundle()
-        }
-
-        infoBundleMap[type]?.let {
+        infoBundleMap.getOrPut(type) { InfoBundle() }.let {
             it.options.addAllCaptureRequestOptionsWithPriority(values, optionPriority)
             it.tags.putAll(tags)
             it.listeners.addAll(listeners)
         }
-        infoBundleMap.toMap()
+        infoBundleMap.merge()
     }.updateCameraStateAsync(
         streams = streams,
         template = template,
@@ -149,7 +147,7 @@
             tags.toMutableMap(),
             listeners.toMutableSet()
         )
-        infoBundleMap.toMap()
+        infoBundleMap.merge()
     }.updateCameraStateAsync(
         streams = streams,
         template = template,
@@ -209,48 +207,43 @@
         state.capture(requests)
     }
 
-    private fun Map<UseCaseCameraRequestControl.Type, InfoBundle>.updateCameraStateAsync(
+    /**
+     * The merge order is the same as the [UseCaseCameraRequestControl.Type] declaration order.
+     *
+     * Option merge: The earlier merged option in [Config.OptionPriority.OPTIONAL] could be
+     * overridden by later merged options.
+     * Tag merge: If there is the same tagKey but tagValue is different, the later merge would
+     * override the earlier one.
+     * Listener merge: merge the listeners into a set.
+     */
+    private fun Map<UseCaseCameraRequestControl.Type, InfoBundle>.merge(): InfoBundle =
+        InfoBundle().also {
+            UseCaseCameraRequestControl.Type.values().forEach { type ->
+                getOrElse(type) { InfoBundle() }.also { infoBundleInType ->
+                    it.options.insertAllOptions(infoBundleInType.options.mutableConfig)
+                    it.tags.putAll(infoBundleInType.tags)
+                    it.listeners.addAll(infoBundleInType.listeners)
+                }
+            }
+        }
+
+    private fun InfoBundle.toTagBundle(): TagBundle =
+        MutableTagBundle.create().also { tagBundle ->
+            tags.forEach { (tagKey, tagValue) ->
+                tagBundle.putTag(tagKey, tagValue)
+            }
+        }
+
+    private fun InfoBundle.updateCameraStateAsync(
         streams: Set<StreamId>? = null,
         template: RequestTemplate? = null,
     ) = state.updateAsync(
-        parameters = toParameter(),
+        parameters = options.build().toParameters(),
         appendParameters = false,
         internalParameters = mapOf(CAMERAX_TAG_BUNDLE to toTagBundle()),
         appendInternalParameters = false,
         streams = streams,
         template = template,
-        listeners = getListeners(),
+        listeners = listeners,
     )
-
-    /**
-     * Merge and return all the request parameters from different types. it throws an exception
-     * If there are any parameter conflicts.
-     */
-    private fun Map<UseCaseCameraRequestControl.Type, InfoBundle>.toParameter() =
-        Camera2ImplConfig.Builder().also {
-            this.values.forEach { infoBundle ->
-                it.insertAllOptions(infoBundle.options.mutableConfig)
-            }
-        }.build().toParameters()
-
-    /**
-     * Merge all the tags together and store them in the TagBundle. It doesn't check the conflict
-     * of the tag key. i.e. doesn't check two different values but using the same key.
-     */
-    private fun Map<UseCaseCameraRequestControl.Type, InfoBundle>.toTagBundle(): TagBundle =
-        MutableTagBundle.create().also { tagBundle ->
-            this.values.map { it.tags }.forEach { requestTag ->
-                requestTag.forEach { (tagKey, tagValue) -> tagBundle.putTag(tagKey, tagValue) }
-            }
-        }
-
-    /**
-     * Merge and return the Request.listeners from different types.
-     */
-    private fun Map<UseCaseCameraRequestControl.Type, InfoBundle>.getListeners():
-        Set<Request.Listener> = mutableSetOf<Request.Listener>().also {
-            this.values.map { it.listeners }.forEach { listenerSet ->
-                it.addAll(listenerSet)
-            }
-        }
 }
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CaptureConfigAdapterTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CaptureConfigAdapterTest.kt
index 69fcd62..4b37638 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CaptureConfigAdapterTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CaptureConfigAdapterTest.kt
@@ -18,17 +18,14 @@
 
 import android.hardware.camera2.CaptureRequest
 import android.os.Build
-import android.view.Surface
 import androidx.camera.camera2.pipe.StreamId
 import androidx.camera.camera2.pipe.integration.impl.CAMERAX_TAG_BUNDLE
+import androidx.camera.camera2.pipe.integration.testing.FakeSurface
 import androidx.camera.core.impl.CameraCaptureCallback
 import androidx.camera.core.impl.CaptureConfig
-import androidx.camera.core.impl.DeferrableSurface
 import androidx.camera.core.impl.TagBundle
-import androidx.camera.core.impl.utils.futures.Futures
 import androidx.testutils.assertThrows
 import com.google.common.truth.Truth.assertThat
-import com.google.common.util.concurrent.ListenableFuture
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.runBlocking
 import org.junit.Test
@@ -156,10 +153,4 @@
         val tagBundle = request.extras[CAMERAX_TAG_BUNDLE] as TagBundle
         assertThat(tagBundle.getTag(tagKey)).isEqualTo(tagValue)
     }
-}
-
-private class FakeSurface : DeferrableSurface() {
-    override fun provideSurface(): ListenableFuture<Surface> {
-        return Futures.immediateFuture(null)
-    }
 }
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapterTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapterTest.kt
index 0d46290..06a5ec4 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapterTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapterTest.kt
@@ -20,10 +20,9 @@
 import android.hardware.camera2.CameraDevice
 import android.os.Build
 import android.view.Surface
-import androidx.camera.camera2.pipe.CameraGraph
-import androidx.camera.camera2.pipe.StreamGraph
 import androidx.camera.camera2.pipe.StreamId
 import androidx.camera.camera2.pipe.integration.impl.UseCaseThreads
+import androidx.camera.camera2.pipe.integration.testing.FakeCameraGraph
 import androidx.camera.core.impl.DeferrableSurface
 import androidx.camera.core.impl.SessionConfig
 import androidx.camera.core.impl.utils.futures.Futures
@@ -246,35 +245,4 @@
         testSurface.release()
         surfaceTexture.release()
     }
-}
-
-private class FakeCameraGraph : CameraGraph {
-    val setSurfaceResults = mutableMapOf<StreamId, Surface?>()
-
-    override val streams: StreamGraph
-        get() = throw NotImplementedError("Not used in testing")
-
-    override suspend fun acquireSession(): CameraGraph.Session {
-        throw NotImplementedError("Not used in testing")
-    }
-
-    override fun acquireSessionOrNull(): CameraGraph.Session? {
-        throw NotImplementedError("Not used in testing")
-    }
-
-    override fun close() {
-        throw NotImplementedError("Not used in testing")
-    }
-
-    override fun setSurface(stream: StreamId, surface: Surface?) {
-        setSurfaceResults[stream] = surface
-    }
-
-    override fun start() {
-        throw NotImplementedError("Not used in testing")
-    }
-
-    override fun stop() {
-        throw NotImplementedError("Not used in testing")
-    }
-}
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControlTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControlTest.kt
new file mode 100644
index 0000000..817cf80
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControlTest.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.impl
+
+import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.CaptureRequest
+import android.os.Build
+import androidx.camera.camera2.pipe.StreamId
+import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
+import androidx.camera.camera2.pipe.integration.testing.FakeCameraGraph
+import androidx.camera.camera2.pipe.integration.testing.FakeSurface
+import androidx.camera.core.impl.DeferrableSurface
+import androidx.camera.core.impl.SessionConfig
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+
+@RunWith(RobolectricCameraPipeTestRunner::class)
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+@DoNotInstrument
+class UseCaseCameraRequestControlTest {
+    private val surface = FakeSurface()
+    private val surfaceToStreamMap: Map<DeferrableSurface, StreamId> = mapOf(surface to StreamId(0))
+    private val useCaseThreads by lazy {
+        val dispatcher = Dispatchers.Default
+        val cameraScope = CoroutineScope(Job() + dispatcher)
+
+        UseCaseThreads(
+            cameraScope,
+            dispatcher.asExecutor(),
+            dispatcher
+        )
+    }
+    private val fakeCameraGraph = FakeCameraGraph()
+    private val requestControl = UseCaseCameraRequestControlImpl(
+        fakeCameraGraph, surfaceToStreamMap, useCaseThreads
+    )
+
+    @Test
+    fun testMergeRequestOptions(): Unit = runBlocking {
+        // Arrange
+        val sessionConfigBuilder = SessionConfig.Builder().also { sessionConfigBuilder ->
+            sessionConfigBuilder.setTemplateType(CameraDevice.TEMPLATE_PREVIEW)
+            sessionConfigBuilder.addSurface(surface)
+            sessionConfigBuilder.addImplementationOptions(
+                Camera2ImplConfig.Builder()
+                    .setCaptureRequestOption<Int>(
+                        CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON
+                    ).build()
+            )
+        }
+        val camera2CameraControlConfig = Camera2ImplConfig.Builder()
+            .setCaptureRequestOption(
+                CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_SINGLE
+            ).build()
+
+        // Act
+        requestControl.setSessionConfigAsync(
+            sessionConfigBuilder.build()
+        ).await()
+        requestControl.appendParametersAsync(
+            values = mapOf(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION to 5)
+        ).await()
+        requestControl.setConfigAsync(
+            type = UseCaseCameraRequestControl.Type.CAMERA2_CAMERA_CONTROL,
+            config = camera2CameraControlConfig
+        ).await()
+
+        // Assert
+        assertThat(fakeCameraGraph.fakeCameraGraphSession.repeatingRequests.size).isEqualTo(3)
+
+        val lastRequest = fakeCameraGraph.fakeCameraGraphSession.repeatingRequests.removeLast()
+        assertThat(
+            lastRequest.parameters[CaptureRequest.CONTROL_AE_MODE]
+        ).isEqualTo(CaptureRequest.CONTROL_AE_MODE_ON)
+        assertThat(
+            lastRequest.parameters[CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION]
+        ).isEqualTo(5)
+        assertThat(
+            lastRequest.parameters[CaptureRequest.FLASH_MODE]
+        ).isEqualTo(CaptureRequest.FLASH_MODE_SINGLE)
+        assertThat(lastRequest.parameters.size).isEqualTo(3)
+
+        val secondLastRequest =
+            fakeCameraGraph.fakeCameraGraphSession.repeatingRequests.removeLast()
+        assertThat(
+            secondLastRequest.parameters[
+                CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION
+            ]
+        ).isEqualTo(5)
+        assertThat(
+            secondLastRequest.parameters[CaptureRequest.CONTROL_AE_MODE]
+        ).isEqualTo(CaptureRequest.CONTROL_AE_MODE_ON)
+        assertThat(secondLastRequest.parameters.size).isEqualTo(2)
+
+        val firstRequest = fakeCameraGraph.fakeCameraGraphSession.repeatingRequests.last()
+        assertThat(
+            firstRequest.parameters[
+                CaptureRequest.CONTROL_AE_MODE
+            ]
+        ).isEqualTo(CaptureRequest.CONTROL_AE_MODE_ON)
+        assertThat(firstRequest.parameters.size).isEqualTo(1)
+    }
+
+    @Test
+    fun testMergeConflictRequestOptions(): Unit = runBlocking {
+        // Arrange
+        val sessionConfigBuilder = SessionConfig.Builder().also { sessionConfigBuilder ->
+            sessionConfigBuilder.setTemplateType(CameraDevice.TEMPLATE_PREVIEW)
+            sessionConfigBuilder.addSurface(surface)
+            sessionConfigBuilder.addImplementationOptions(
+                Camera2ImplConfig.Builder()
+                    .setCaptureRequestOption<Int>(
+                        CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON
+                    ).build()
+            )
+        }
+        val camera2CameraControlConfig = Camera2ImplConfig.Builder()
+            .setCaptureRequestOption(
+                CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH
+            ).build()
+
+        // Act
+        requestControl.setConfigAsync(
+            type = UseCaseCameraRequestControl.Type.CAMERA2_CAMERA_CONTROL,
+            config = camera2CameraControlConfig
+        )
+        requestControl.appendParametersAsync(
+            values = mapOf(CaptureRequest.CONTROL_AE_MODE to CaptureRequest.CONTROL_AE_MODE_OFF)
+        )
+        requestControl.setSessionConfigAsync(
+            sessionConfigBuilder.build()
+        ).await()
+
+        // Assert. The option conflict, the last request should only keep the Camera2CameraControl
+        // options.
+        val lastRequest = fakeCameraGraph.fakeCameraGraphSession.repeatingRequests.last()
+        assertThat(
+            lastRequest.parameters[CaptureRequest.CONTROL_AE_MODE]
+        ).isEqualTo(CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH)
+        assertThat(lastRequest.parameters.size).isEqualTo(1)
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraTest.kt
index 220f86a..2ed9a0f 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraTest.kt
@@ -17,29 +17,17 @@
 package androidx.camera.camera2.pipe.integration.impl
 
 import android.hardware.camera2.CameraDevice
-import android.hardware.camera2.params.MeteringRectangle
 import android.os.Build
-import android.view.Surface
-import androidx.camera.camera2.pipe.AeMode
-import androidx.camera.camera2.pipe.AfMode
-import androidx.camera.camera2.pipe.AwbMode
-import androidx.camera.camera2.pipe.CameraGraph
-import androidx.camera.camera2.pipe.Lock3ABehavior
-import androidx.camera.camera2.pipe.Request
-import androidx.camera.camera2.pipe.Result3A
-import androidx.camera.camera2.pipe.StreamGraph
 import androidx.camera.camera2.pipe.StreamId
-import androidx.camera.camera2.pipe.TorchState
 import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
+import androidx.camera.camera2.pipe.integration.testing.FakeCameraGraph
+import androidx.camera.camera2.pipe.integration.testing.FakeSurface
 import androidx.camera.core.impl.DeferrableSurface
 import androidx.camera.core.impl.SessionConfig
-import androidx.camera.core.impl.utils.futures.Futures
 import androidx.camera.testing.fakes.FakeUseCase
 import androidx.camera.testing.fakes.FakeUseCaseConfig
 import com.google.common.truth.Truth.assertThat
-import com.google.common.util.concurrent.ListenableFuture
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.asExecutor
@@ -47,11 +35,12 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.robolectric.annotation.Config
-import java.util.concurrent.Semaphore
+import org.robolectric.annotation.internal.DoNotInstrument
 import java.util.concurrent.TimeUnit
 
 @RunWith(RobolectricCameraPipeTestRunner::class)
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+@DoNotInstrument
 class UseCaseCameraTest {
     private val surface = FakeSurface()
     private val surfaceToStreamMap: Map<DeferrableSurface, StreamId> = mapOf(surface to StreamId(0))
@@ -92,7 +81,7 @@
             it.activeUseCases = setOf(fakeUseCase)
         }
         assumeTrue(
-            fakeCameraGraph.fakeCameraGraphSession.repeatingRequests.tryAcquire(
+            fakeCameraGraph.fakeCameraGraphSession.repeatingRequestSemaphore.tryAcquire(
                 1, 3, TimeUnit.SECONDS
             )
         )
@@ -107,7 +96,7 @@
 
         // Assert. The stopRepeating() should be called.
         assertThat(
-            fakeCameraGraph.fakeCameraGraphSession.stopRepeating.tryAcquire(
+            fakeCameraGraph.fakeCameraGraphSession.stopRepeatingSemaphore.tryAcquire(
                 1, 3, TimeUnit.SECONDS
             )
         ).isTrue()
@@ -122,124 +111,4 @@
         updateSessionConfig(sessionConfigBuilder.build())
         notifyActive()
     }
-}
-
-private class FakeSurface : DeferrableSurface() {
-    override fun provideSurface(): ListenableFuture<Surface> {
-        return Futures.immediateFuture(null)
-    }
-}
-
-private class FakeCameraGraph : CameraGraph {
-    val fakeCameraGraphSession = FakeCameraGraphSession()
-
-    override val streams: StreamGraph
-        get() = throw NotImplementedError("Not used in testing")
-
-    override suspend fun acquireSession(): CameraGraph.Session {
-        return fakeCameraGraphSession
-    }
-
-    override fun acquireSessionOrNull(): CameraGraph.Session {
-        return fakeCameraGraphSession
-    }
-
-    override fun close() {
-        throw NotImplementedError("Not used in testing")
-    }
-
-    override fun setSurface(stream: StreamId, surface: Surface?) {
-        // No-op
-    }
-
-    override fun start() {
-        throw NotImplementedError("Not used in testing")
-    }
-
-    override fun stop() {
-        throw NotImplementedError("Not used in testing")
-    }
-}
-
-private class FakeCameraGraphSession : CameraGraph.Session {
-    val repeatingRequests = Semaphore(0)
-    val stopRepeating = Semaphore(0)
-
-    override fun abort() {
-        // No-op
-    }
-
-    override fun close() {
-        // No-op
-    }
-
-    override suspend fun lock3A(
-        aeMode: AeMode?,
-        afMode: AfMode?,
-        awbMode: AwbMode?,
-        aeRegions: List<MeteringRectangle>?,
-        afRegions: List<MeteringRectangle>?,
-        awbRegions: List<MeteringRectangle>?,
-        aeLockBehavior: Lock3ABehavior?,
-        afLockBehavior: Lock3ABehavior?,
-        awbLockBehavior: Lock3ABehavior?,
-        frameLimit: Int,
-        timeLimitNs: Long
-    ): Deferred<Result3A> {
-        throw NotImplementedError("Not used in testing")
-    }
-
-    override suspend fun lock3AForCapture(frameLimit: Int, timeLimitNs: Long): Deferred<Result3A> {
-        throw NotImplementedError("Not used in testing")
-    }
-
-    override fun setTorch(torchState: TorchState): Deferred<Result3A> {
-        throw NotImplementedError("Not used in testing")
-    }
-
-    override fun startRepeating(request: Request) {
-        repeatingRequests.release()
-    }
-
-    override fun stopRepeating() {
-        stopRepeating.release()
-    }
-
-    override fun submit(request: Request) {
-        throw NotImplementedError("Not used in testing")
-    }
-
-    override fun submit(requests: List<Request>) {
-        // No-op
-    }
-
-    override suspend fun submit3A(
-        aeMode: AeMode?,
-        afMode: AfMode?,
-        awbMode: AwbMode?,
-        aeRegions: List<MeteringRectangle>?,
-        afRegions: List<MeteringRectangle>?,
-        awbRegions: List<MeteringRectangle>?
-    ): Deferred<Result3A> {
-        throw NotImplementedError("Not used in testing")
-    }
-
-    override suspend fun unlock3A(ae: Boolean?, af: Boolean?, awb: Boolean?): Deferred<Result3A> {
-        throw NotImplementedError("Not used in testing")
-    }
-
-    override suspend fun unlock3APostCapture(): Deferred<Result3A> {
-        throw NotImplementedError("Not used in testing")
-    }
-
-    override fun update3A(
-        aeMode: AeMode?,
-        afMode: AfMode?,
-        awbMode: AwbMode?,
-        aeRegions: List<MeteringRectangle>?,
-        afRegions: List<MeteringRectangle>?,
-        awbRegions: List<MeteringRectangle>?
-    ): Deferred<Result3A> {
-        throw NotImplementedError("Not used in testing")
-    }
-}
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraGraph.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraGraph.kt
new file mode 100644
index 0000000..42550a1
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraGraph.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.testing
+
+import android.view.Surface
+import androidx.camera.camera2.pipe.CameraGraph
+import androidx.camera.camera2.pipe.StreamGraph
+import androidx.camera.camera2.pipe.StreamId
+
+class FakeCameraGraph(
+    val fakeCameraGraphSession: FakeCameraGraphSession = FakeCameraGraphSession()
+) : CameraGraph {
+
+    val setSurfaceResults = mutableMapOf<StreamId, Surface?>()
+
+    override val streams: StreamGraph
+        get() = throw NotImplementedError("Not used in testing")
+
+    override suspend fun acquireSession(): CameraGraph.Session {
+        return fakeCameraGraphSession
+    }
+
+    override fun acquireSessionOrNull(): CameraGraph.Session {
+        return fakeCameraGraphSession
+    }
+
+    override fun close() {
+        throw NotImplementedError("Not used in testing")
+    }
+
+    override fun setSurface(stream: StreamId, surface: Surface?) {
+        setSurfaceResults[stream] = surface
+    }
+
+    override fun start() {
+        throw NotImplementedError("Not used in testing")
+    }
+
+    override fun stop() {
+        throw NotImplementedError("Not used in testing")
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraGraphSession.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraGraphSession.kt
new file mode 100644
index 0000000..17193a5
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraGraphSession.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.testing
+
+import android.hardware.camera2.params.MeteringRectangle
+import androidx.camera.camera2.pipe.AeMode
+import androidx.camera.camera2.pipe.AfMode
+import androidx.camera.camera2.pipe.AwbMode
+import androidx.camera.camera2.pipe.CameraGraph
+import androidx.camera.camera2.pipe.Lock3ABehavior
+import androidx.camera.camera2.pipe.Request
+import androidx.camera.camera2.pipe.Result3A
+import androidx.camera.camera2.pipe.TorchState
+import kotlinx.coroutines.Deferred
+import java.util.concurrent.Semaphore
+
+class FakeCameraGraphSession : CameraGraph.Session {
+
+    val repeatingRequests = mutableListOf<Request>()
+    val repeatingRequestSemaphore = Semaphore(0)
+    val stopRepeatingSemaphore = Semaphore(0)
+
+    override fun abort() {
+        // No-op
+    }
+
+    override fun close() {
+        // No-op
+    }
+
+    override suspend fun lock3A(
+        aeMode: AeMode?,
+        afMode: AfMode?,
+        awbMode: AwbMode?,
+        aeRegions: List<MeteringRectangle>?,
+        afRegions: List<MeteringRectangle>?,
+        awbRegions: List<MeteringRectangle>?,
+        aeLockBehavior: Lock3ABehavior?,
+        afLockBehavior: Lock3ABehavior?,
+        awbLockBehavior: Lock3ABehavior?,
+        frameLimit: Int,
+        timeLimitNs: Long
+    ): Deferred<Result3A> {
+        throw NotImplementedError("Not used in testing")
+    }
+
+    override suspend fun lock3AForCapture(frameLimit: Int, timeLimitNs: Long): Deferred<Result3A> {
+        throw NotImplementedError("Not used in testing")
+    }
+
+    override fun setTorch(torchState: TorchState): Deferred<Result3A> {
+        throw NotImplementedError("Not used in testing")
+    }
+
+    override fun startRepeating(request: Request) {
+        repeatingRequests.add(request)
+        repeatingRequestSemaphore.release()
+    }
+
+    override fun stopRepeating() {
+        stopRepeatingSemaphore.release()
+    }
+
+    override fun submit(request: Request) {
+        throw NotImplementedError("Not used in testing")
+    }
+
+    override fun submit(requests: List<Request>) {
+        // No-op
+    }
+
+    override suspend fun submit3A(
+        aeMode: AeMode?,
+        afMode: AfMode?,
+        awbMode: AwbMode?,
+        aeRegions: List<MeteringRectangle>?,
+        afRegions: List<MeteringRectangle>?,
+        awbRegions: List<MeteringRectangle>?
+    ): Deferred<Result3A> {
+        throw NotImplementedError("Not used in testing")
+    }
+
+    override suspend fun unlock3A(ae: Boolean?, af: Boolean?, awb: Boolean?): Deferred<Result3A> {
+        throw NotImplementedError("Not used in testing")
+    }
+
+    override suspend fun unlock3APostCapture(): Deferred<Result3A> {
+        throw NotImplementedError("Not used in testing")
+    }
+
+    override fun update3A(
+        aeMode: AeMode?,
+        afMode: AfMode?,
+        awbMode: AwbMode?,
+        aeRegions: List<MeteringRectangle>?,
+        afRegions: List<MeteringRectangle>?,
+        awbRegions: List<MeteringRectangle>?
+    ): Deferred<Result3A> {
+        throw NotImplementedError("Not used in testing")
+    }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeSurface.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeSurface.kt
new file mode 100644
index 0000000..67e6e9e
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeSurface.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.testing
+
+import android.view.Surface
+import androidx.camera.core.impl.DeferrableSurface
+import androidx.camera.core.impl.utils.futures.Futures
+import com.google.common.util.concurrent.ListenableFuture
+
+class FakeSurface(
+    val surface: Surface? = null
+) : DeferrableSurface() {
+    override fun provideSurface(): ListenableFuture<Surface> {
+        return Futures.immediateFuture(surface)
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-core/api/public_plus_experimental_current.txt b/camera/camera-core/api/public_plus_experimental_current.txt
index e50dbfd..135a5c7 100644
--- a/camera/camera-core/api/public_plus_experimental_current.txt
+++ b/camera/camera-core/api/public_plus_experimental_current.txt
@@ -140,9 +140,6 @@
   @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalGetImage {
   }
 
-  @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalUseCaseGroup {
-  }
-
   public interface ExposureState {
     method public int getExposureCompensationIndex();
     method public android.util.Range<java.lang.Integer!> getExposureCompensationRange();
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalUseCaseGroup.java b/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalUseCaseGroup.java
deleted file mode 100644
index eaa2bd6..0000000
--- a/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalUseCaseGroup.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.core;
-
-import static java.lang.annotation.RetentionPolicy.CLASS;
-
-import androidx.annotation.RequiresOptIn;
-
-import java.lang.annotation.Retention;
-
-
-/**
- * Denotes that the annotated classes and methods uses the experimental feature which provides
- * a grouping mechanism for {@link UseCase}s.
- *
- * <p> The {@link UseCaseGroup} is a class that groups {@link UseCase}s together. All the
- * {@link UseCase}s in the same group share certain properties. The {@link ViewPort} is a
- * collection of shared {@link UseCase} properties for synchronizing the visible rectangle across
- * all the use cases.
- */
-@Retention(CLASS)
-@RequiresOptIn
-public @interface ExperimentalUseCaseGroup {
-}
diff --git a/camera/camera-view/api/current.txt b/camera/camera-view/api/current.txt
index 2d8e12b..4205f5b 100644
--- a/camera/camera-view/api/current.txt
+++ b/camera/camera-view/api/current.txt
@@ -107,7 +107,6 @@
   public final class RotationProvider {
     ctor public RotationProvider(android.content.Context);
     method @CheckResult public boolean addListener(java.util.concurrent.Executor, androidx.camera.view.RotationProvider.Listener);
-    method public void removeAllListeners();
     method public void removeListener(androidx.camera.view.RotationProvider.Listener);
   }
 
diff --git a/camera/camera-view/api/public_plus_experimental_current.txt b/camera/camera-view/api/public_plus_experimental_current.txt
index 970410d..f741896 100644
--- a/camera/camera-view/api/public_plus_experimental_current.txt
+++ b/camera/camera-view/api/public_plus_experimental_current.txt
@@ -115,7 +115,6 @@
   public final class RotationProvider {
     ctor public RotationProvider(android.content.Context);
     method @CheckResult public boolean addListener(java.util.concurrent.Executor, androidx.camera.view.RotationProvider.Listener);
-    method public void removeAllListeners();
     method public void removeListener(androidx.camera.view.RotationProvider.Listener);
   }
 
diff --git a/camera/camera-view/api/restricted_current.txt b/camera/camera-view/api/restricted_current.txt
index 84dfbad..de3dc58 100644
--- a/camera/camera-view/api/restricted_current.txt
+++ b/camera/camera-view/api/restricted_current.txt
@@ -107,7 +107,6 @@
   public final class RotationProvider {
     ctor public RotationProvider(android.content.Context);
     method @CheckResult public boolean addListener(java.util.concurrent.Executor, androidx.camera.view.RotationProvider.Listener);
-    method public void removeAllListeners();
     method public void removeListener(androidx.camera.view.RotationProvider.Listener);
   }
 
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java b/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
index 38946e9f..ddd7578 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
@@ -45,7 +45,6 @@
 import androidx.camera.core.CameraInfoUnavailableException;
 import androidx.camera.core.CameraSelector;
 import androidx.camera.core.CameraUnavailableException;
-import androidx.camera.core.ExperimentalUseCaseGroup;
 import androidx.camera.core.FocusMeteringAction;
 import androidx.camera.core.FocusMeteringResult;
 import androidx.camera.core.ImageAnalysis;
@@ -529,7 +528,7 @@
 
     private void stopListeningToRotationEvents() {
         getDisplayManager().unregisterDisplayListener(mDisplayRotationListener);
-        mRotationProvider.removeAllListeners();
+        mRotationProvider.removeListener(mDeviceRotationListener);
     }
 
     private DisplayManager getDisplayManager() {
@@ -1647,7 +1646,9 @@
      */
     @Nullable
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    @OptIn(markerClass = {ExperimentalUseCaseGroup.class, ExperimentalVideo.class})
+    // TODO(b/185869869) Remove the UnsafeOptInUsageError once view's version matches core's.
+    @SuppressLint("UnsafeOptInUsageError")
+    @OptIn(markerClass = {ExperimentalVideo.class})
     protected UseCaseGroup createUseCaseGroup() {
         if (!isCameraInitialized()) {
             Logger.d(TAG, CAMERA_NOT_INITIALIZED);
@@ -1702,9 +1703,9 @@
         public void onDisplayRemoved(int displayId) {
         }
 
-        @SuppressLint("WrongConstant")
+        // TODO(b/185869869) Remove the UnsafeOptInUsageError once view's version matches core's.
+        @SuppressLint({"UnsafeOptInUsageError", "WrongConstant"})
         @Override
-        @OptIn(markerClass = ExperimentalUseCaseGroup.class)
         public void onDisplayChanged(int displayId) {
             if (mPreviewDisplay != null && mPreviewDisplay.getDisplayId() == displayId) {
                 mPreview.setTargetRotation(mPreviewDisplay.getRotation());
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/RotationProvider.java b/camera/camera-view/src/main/java/androidx/camera/view/RotationProvider.java
index f97c051..94f5464 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/RotationProvider.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/RotationProvider.java
@@ -151,16 +151,6 @@
     }
 
     /**
-     * Removes all {@link Listener} from this object.
-     */
-    public void removeAllListeners() {
-        synchronized (mLock) {
-            mOrientationListener.disable();
-            mListeners.clear();
-        }
-    }
-
-    /**
      * Converts orientation degrees to {@link Surface} rotation.
      */
     @VisibleForTesting
diff --git a/camera/camera-view/src/test/java/androidx/camera/view/RotationProviderTest.kt b/camera/camera-view/src/test/java/androidx/camera/view/RotationProviderTest.kt
index ccd823d..24030e3 100644
--- a/camera/camera-view/src/test/java/androidx/camera/view/RotationProviderTest.kt
+++ b/camera/camera-view/src/test/java/androidx/camera/view/RotationProviderTest.kt
@@ -54,23 +54,6 @@
     }
 
     @Test
-    fun addAndRemoveAllListener_noCallback() {
-        // Arrange.
-        var rotation = INVALID_ROTATION
-        rotationProvider.addListener(CameraXExecutors.mainThreadExecutor()) {
-            rotation = it
-        }
-
-        // Act.
-        rotationProvider.removeAllListeners()
-        rotationProvider.mOrientationListener.onOrientationChanged(0)
-        shadowOf(getMainLooper()).idle()
-
-        // Assert.
-        assertThat(rotation).isEqualTo(INVALID_ROTATION)
-    }
-
-    @Test
     fun addAndRemoveListener_noCallback() {
         var rotationNoChange = INVALID_ROTATION
         var rotationChanged = INVALID_ROTATION
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java
index 35f3a85..73bc82e9 100644
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java
+++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java
@@ -100,6 +100,7 @@
     // Listen to accelerometer rotation change and pass it to tests.
     private RotationProvider mRotationProvider;
     private int mRotation;
+    private final RotationProvider.Listener mRotationListener = rotation -> mRotation = rotation;
 
     // Wrapped analyzer for tests to receive callbacks.
     @Nullable
@@ -132,9 +133,8 @@
             @Nullable Bundle savedInstanceState) {
         mExecutorService = Executors.newSingleThreadExecutor();
         mRotationProvider = new RotationProvider(requireContext());
-        boolean canDetectRotation =
-                mRotationProvider.addListener(CameraXExecutors.mainThreadExecutor(),
-                        rotation -> mRotation = rotation);
+        boolean canDetectRotation = mRotationProvider.addListener(
+                CameraXExecutors.mainThreadExecutor(), mRotationListener);
         if (!canDetectRotation) {
             Logger.e(TAG, "The device cannot detect rotation with motion sensor.");
         }
@@ -340,7 +340,7 @@
         if (mExecutorService != null) {
             mExecutorService.shutdown();
         }
-        mRotationProvider.removeAllListeners();
+        mRotationProvider.removeListener(mRotationListener);
     }
 
     void checkFailedFuture(ListenableFuture<Void> voidFuture) {
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/PreviewViewFragment.java b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/PreviewViewFragment.java
index df6af0c..2313833 100644
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/PreviewViewFragment.java
+++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/PreviewViewFragment.java
@@ -37,12 +37,10 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.OptIn;
 import androidx.annotation.VisibleForTesting;
 import androidx.camera.core.Camera;
 import androidx.camera.core.CameraInfoUnavailableException;
 import androidx.camera.core.CameraSelector;
-import androidx.camera.core.ExperimentalUseCaseGroup;
 import androidx.camera.core.FocusMeteringAction;
 import androidx.camera.core.FocusMeteringResult;
 import androidx.camera.core.MeteringPoint;
@@ -166,7 +164,8 @@
         }
     }
 
-    @OptIn(markerClass = ExperimentalUseCaseGroup.class)
+    // TODO(b/185869869) Remove the UnsafeOptInUsageError once view's version matches core's.
+    @SuppressLint("UnsafeOptInUsageError")
     void setUpTargetRotationButton(@NonNull final ProcessCameraProvider cameraProvider,
             @NonNull final View rootView) {
         Button button = rootView.findViewById(R.id.target_rotation);
diff --git a/car/app/app/build.gradle b/car/app/app/build.gradle
index c6f85d4..0379226 100644
--- a/car/app/app/build.gradle
+++ b/car/app/app/build.gradle
@@ -34,6 +34,15 @@
 
 import static androidx.build.dependencies.DependenciesKt.*
 
+buildscript {
+    dependencies {
+        // This dependency means that tasks in this project might become out-of-date whenever
+        // certain classes in buildSrc change, and should generally be avoided.
+        // See b/140265324 for more information
+        classpath(project.files("${project.rootProject.ext["outDir"]}/buildSrc/private/build/libs/private.jar"))
+    }
+}
+
 plugins {
     id("AndroidXPlugin")
     id("com.android.library")
diff --git a/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt b/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
index fda1622..9584821 100644
--- a/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
+++ b/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
@@ -23,8 +23,10 @@
 import androidx.compose.animation.core.tween
 import androidx.compose.animation.core.updateTransition
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
@@ -43,6 +45,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
+import kotlinx.coroutines.delay
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
 import org.junit.Rule
@@ -322,4 +325,64 @@
             }
         }
     }
+
+    @OptIn(ExperimentalAnimationApi::class, InternalAnimationApi::class)
+    @Test
+    fun AnimatedContentSlideInAndOutOfContainerTest() {
+        val transitionState = MutableTransitionState(true).apply { targetState = false }
+        val animSpec = tween<IntOffset>(200, easing = LinearEasing)
+        rule.setContent {
+            CompositionLocalProvider(LocalDensity provides Density(1f, 1f)) {
+                if (!transitionState.targetState && !transitionState.currentState) {
+                    transitionState.targetState = true
+                }
+                val rootTransition = updateTransition(transitionState)
+                rootTransition.AnimatedContent(
+                    transitionSpec = {
+                        if (true isTransitioningTo false) {
+                            slideIntoContainer(
+                                towards = AnimatedContentScope.SlideDirection.Start, animSpec
+                            ) with
+                                slideOutOfContainer(
+                                    towards = AnimatedContentScope.SlideDirection.Start, animSpec
+                                )
+                        } else {
+                            slideIntoContainer(
+                                towards = AnimatedContentScope.SlideDirection.End, animSpec
+                            ) with
+                                slideOutOfContainer(
+                                    towards = AnimatedContentScope.SlideDirection.End,
+                                    animSpec
+                                )
+                        }
+                    }
+                ) { target ->
+                    Box(Modifier.requiredSize(200.dp))
+                    LaunchedEffect(transitionState.targetState) {
+                        while (transition.animations.size == 0) {
+                            delay(10)
+                        }
+                        val anim = transition.animations[0]
+                        while (transitionState.currentState != transitionState.targetState) {
+                            val playTime = (transition.playTimeNanos / 1000_000L).toInt()
+                            if (!transitionState.targetState) {
+                                if (target) {
+                                    assertEquals(IntOffset(-playTime, 0), anim.value)
+                                } else {
+                                    assertEquals(IntOffset(200 - playTime, 0), anim.value)
+                                }
+                            } else {
+                                if (target) {
+                                    assertEquals(IntOffset(playTime - 200, 0), anim.value)
+                                } else {
+                                    assertEquals(IntOffset(playTime, 0), anim.value)
+                                }
+                            }
+                            delay(10)
+                        }
+                    }
+                }
+            }
+        }
+    }
 }
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt
index f09de2d..a4b21dc 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt
@@ -426,16 +426,16 @@
         ),
         targetOffset: (offsetForFullSlide: Int) -> Int = { it }
     ): ExitTransition {
-        return when (towards) {
+        return when {
             // Note: targetSize could be 0 for empty composables
-            Left -> slideOutHorizontally(
+            towards.isLeft -> slideOutHorizontally(
                 {
                     val targetSize = targetSizeMap[transition.targetState]?.value ?: IntSize.Zero
                     targetOffset.invoke(-calculateOffset(IntSize(it, it), targetSize).x - it)
                 },
                 animationSpec
             )
-            Right -> slideOutHorizontally(
+            towards.isRight -> slideOutHorizontally(
                 {
                     val targetSize = targetSizeMap[transition.targetState]?.value ?: IntSize.Zero
                     targetOffset.invoke(
@@ -444,14 +444,14 @@
                 },
                 animationSpec
             )
-            Up -> slideOutVertically(
+            towards == Up -> slideOutVertically(
                 {
                     val targetSize = targetSizeMap[transition.targetState]?.value ?: IntSize.Zero
                     targetOffset.invoke(-calculateOffset(IntSize(it, it), targetSize).y - it)
                 },
                 animationSpec
             )
-            Down -> slideOutVertically(
+            towards == Down -> slideOutVertically(
                 {
                     val targetSize = targetSizeMap[transition.targetState]?.value ?: IntSize.Zero
                     targetOffset.invoke(
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/KeyEventHelpers.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/KeyEventHelpers.android.kt
index c180325..29d09f6 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/KeyEventHelpers.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/KeyEventHelpers.android.kt
@@ -23,4 +23,7 @@
 
 internal actual fun KeyEvent.cancelsTextSelection(): Boolean {
     return nativeKeyEvent.keyCode == NativeKeyEvent.KEYCODE_BACK && type == KeyEventType.KeyUp
-}
\ No newline at end of file
+}
+
+// It's platform-specific behavior, Android doesn't have such a concept
+internal actual fun showCharacterPalette() { }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/KeyCommand.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/KeyCommand.kt
index 5b8d48d..4d3f9bc 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/KeyCommand.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/KeyCommand.kt
@@ -87,5 +87,6 @@
     TAB(true),
 
     UNDO(true),
-    REDO(true)
+    REDO(true),
+    CHARACTER_PALETTE(true)
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/KeyEventHelpers.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/KeyEventHelpers.kt
index 6fd1d04..9aebb2c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/KeyEventHelpers.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/KeyEventHelpers.kt
@@ -28,4 +28,10 @@
  *
  * @return true if selection should be cancelled based on this KeyEvent
  */
-internal expect fun KeyEvent.cancelsTextSelection(): Boolean
\ No newline at end of file
+internal expect fun KeyEvent.cancelsTextSelection(): Boolean
+
+/**
+ * macOS has a character/emoji palette, which has to be ordered by application. This
+ * platform-specific helper implements this action on MacOS and noop on other platforms.
+ */
+internal expect fun showCharacterPalette()
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldKeyInput.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldKeyInput.kt
index 5b41b85..29b4218 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldKeyInput.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldKeyInput.kt
@@ -176,6 +176,7 @@
                 KeyCommand.REDO -> {
                     undoManager?.redo()?.let { [email protected](it) }
                 }
+                KeyCommand.CHARACTER_PALETTE -> { showCharacterPalette() }
             }
         }
         undoManager?.forceNextSnapshot()
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/KeyEventHelpers.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/KeyEventHelpers.desktop.kt
index c19f436..1461736 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/KeyEventHelpers.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/KeyEventHelpers.desktop.kt
@@ -17,5 +17,8 @@
 package androidx.compose.foundation.text
 
 import androidx.compose.ui.input.key.KeyEvent
+import org.jetbrains.skiko.orderEmojiAndSymbolsPopup
 
-internal actual fun KeyEvent.cancelsTextSelection(): Boolean = false
\ No newline at end of file
+internal actual fun KeyEvent.cancelsTextSelection(): Boolean = false
+
+internal actual fun showCharacterPalette() = orderEmojiAndSymbolsPopup()
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/KeyMapping.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/KeyMapping.desktop.kt
index a5ff8c9..416eea4 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/KeyMapping.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/KeyMapping.desktop.kt
@@ -33,6 +33,11 @@
             object : KeyMapping {
                 override fun map(event: KeyEvent): KeyCommand? {
                     return when {
+                        event.isMetaPressed && event.isCtrlPressed ->
+                            when (event.key) {
+                                MappedKeys.Space -> KeyCommand.CHARACTER_PALETTE
+                                else -> null
+                            }
                         event.isShiftPressed && event.isAltPressed ->
                             when (event.key) {
                                 MappedKeys.DirectionLeft -> KeyCommand.SELECT_LEFT_WORD
@@ -160,4 +165,5 @@
     actual val Cut: Key = Key(AwtKeyEvent.VK_CUT)
     val Copy: Key = Key(AwtKeyEvent.VK_COPY)
     actual val Tab: Key = Key(AwtKeyEvent.VK_TAB)
+    val Space: Key = Key(AwtKeyEvent.VK_SPACE)
 }
diff --git a/compose/material3/material3/build.gradle b/compose/material3/material3/build.gradle
index 48b3f76..220c3ff 100644
--- a/compose/material3/material3/build.gradle
+++ b/compose/material3/material3/build.gradle
@@ -38,9 +38,10 @@
          */
         implementation(libs.kotlinStdlibCommon)
 
+        api(project(":compose:foundation:foundation"))
         api(project(":compose:runtime:runtime"))
         api(project(":compose:ui:ui-graphics"))
-
+        api(project(":compose:ui:ui-text"))
     }
 }
 
@@ -57,8 +58,10 @@
             commonMain.dependencies {
                 implementation(libs.kotlinStdlibCommon)
 
+                api(project(":compose:foundation:foundation"))
                 api(project(":compose:runtime:runtime"))
                 api(project(":compose:ui:ui-graphics"))
+                api(project(":compose:ui:ui-text"))
             }
 
             androidMain.dependencies {
@@ -75,7 +78,8 @@
 androidx {
     name = "Compose Material3 Components"
     type = LibraryType.PUBLISHED_LIBRARY
-    publish = Publish.NONE
+    // Remove this `publish = Publish.` line when ready start publishing module
+    publish = Publish.SNAPSHOT_ONLY
     mavenGroup = LibraryGroups.Compose.MATERIAL3
     inceptionYear = "2021"
     description = "Compose Material You Design Components library"
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/Shape.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/Shape.kt
new file mode 100644
index 0000000..7d09b6d
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/Shape.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.compose.material3.tokens
+
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.ui.unit.dp
+
+internal object Shape {
+    val Large = RoundedCornerShape(8.0.dp)
+    val Medium = RoundedCornerShape(8.0.dp)
+    val Small = RoundedCornerShape(4.0.dp)
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TypeScale.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TypeScale.kt
new file mode 100644
index 0000000..10c72bc
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TypeScale.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.
+ */
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.compose.material3.tokens
+
+import androidx.compose.ui.unit.sp
+
+internal object TypeScale {
+    val BodyLargeFont = Typeface.PlainRegular
+    val BodyLargeLineHeightPoints = 24.0.sp
+    val BodyLargeSizePoints = 16.sp
+    val BodyLargeTrackingPoints = 0.5.sp
+    val BodyLargeWeight = Typeface.WeightRegular
+    val BodyMediumFont = Typeface.PlainRegular
+    val BodyMediumLineHeightPoints = 20.0.sp
+    val BodyMediumSizePoints = 14.sp
+    val BodyMediumTrackingPoints = 0.2.sp
+    val BodyMediumWeight = Typeface.WeightRegular
+    val BodySmallFont = Typeface.PlainRegular
+    val BodySmallLineHeightPoints = 16.0.sp
+    val BodySmallSizePoints = 12.sp
+    val BodySmallTrackingPoints = 0.4.sp
+    val BodySmallWeight = Typeface.WeightRegular
+    val Display1Font = Typeface.BrandRegular
+    val Display1LineHeightPoints = 76.0.sp
+    val Display1SizePoints = 64.sp
+    val Display1TrackingPoints = -0.5.sp
+    val Display1Weight = Typeface.WeightRegular
+    val DisplayLargeFont = Typeface.BrandRegular
+    val DisplayLargeLineHeightPoints = 64.0.sp
+    val DisplayLargeSizePoints = 57.sp
+    val DisplayLargeTrackingPoints = -0.2.sp
+    val DisplayLargeWeight = Typeface.WeightRegular
+    val DisplayMediumFont = Typeface.BrandRegular
+    val DisplayMediumLineHeightPoints = 52.0.sp
+    val DisplayMediumSizePoints = 45.sp
+    val DisplayMediumTrackingPoints = 0.0.sp
+    val DisplayMediumWeight = Typeface.WeightRegular
+    val DisplaySmallFont = Typeface.BrandRegular
+    val DisplaySmallLineHeightPoints = 44.0.sp
+    val DisplaySmallSizePoints = 36.sp
+    val DisplaySmallTrackingPoints = 0.0.sp
+    val DisplaySmallWeight = Typeface.WeightRegular
+    val Headline6Font = Typeface.BrandRegular
+    val Headline6LineHeightPoints = 24.0.sp
+    val Headline6SizePoints = 18.sp
+    val Headline6TrackingPoints = 0.0.sp
+    val Headline6Weight = Typeface.WeightRegular
+    val HeadlineLargeFont = Typeface.BrandRegular
+    val HeadlineLargeLineHeightPoints = 40.0.sp
+    val HeadlineLargeSizePoints = 32.sp
+    val HeadlineLargeTrackingPoints = 0.0.sp
+    val HeadlineLargeWeight = Typeface.WeightRegular
+    val HeadlineMediumFont = Typeface.BrandRegular
+    val HeadlineMediumLineHeightPoints = 36.0.sp
+    val HeadlineMediumSizePoints = 28.sp
+    val HeadlineMediumTrackingPoints = 0.0.sp
+    val HeadlineMediumWeight = Typeface.WeightRegular
+    val HeadlineSmallFont = Typeface.BrandRegular
+    val HeadlineSmallLineHeightPoints = 32.0.sp
+    val HeadlineSmallSizePoints = 24.sp
+    val HeadlineSmallTrackingPoints = 0.0.sp
+    val HeadlineSmallWeight = Typeface.WeightRegular
+    val LabelLargeFont = Typeface.PlainMedium
+    val LabelLargeLineHeightPoints = 20.0.sp
+    val LabelLargeSizePoints = 14.sp
+    val LabelLargeTrackingPoints = 0.1.sp
+    val LabelLargeWeight = Typeface.WeightMedium
+    val LabelMediumFont = Typeface.PlainMedium
+    val LabelMediumLineHeightPoints = 16.0.sp
+    val LabelMediumSizePoints = 12.sp
+    val LabelMediumTrackingPoints = 0.5.sp
+    val LabelMediumWeight = Typeface.WeightMedium
+    val LabelSmallFont = Typeface.PlainMedium
+    val LabelSmallLineHeightPoints = 16.0.sp
+    val LabelSmallSizePoints = 11.sp
+    val LabelSmallTrackingPoints = 0.5.sp
+    val LabelSmallWeight = Typeface.WeightMedium
+    val Subtitle1Font = Typeface.PlainMedium
+    val Subtitle1LineHeightPoints = 24.0.sp
+    val Subtitle1SizePoints = 16.sp
+    val Subtitle1TrackingPoints = 0.2.sp
+    val Subtitle1Weight = Typeface.WeightMedium
+    val Subtitle2Font = Typeface.PlainMedium
+    val Subtitle2LineHeightPoints = 20.0.sp
+    val Subtitle2SizePoints = 14.sp
+    val Subtitle2TrackingPoints = 0.1.sp
+    val Subtitle2Weight = Typeface.WeightMedium
+    val TitleLargeFont = Typeface.BrandRegular
+    val TitleLargeLineHeightPoints = 28.0.sp
+    val TitleLargeSizePoints = 22.sp
+    val TitleLargeTrackingPoints = 0.0.sp
+    val TitleLargeWeight = Typeface.WeightRegular
+    val TitleMediumFont = Typeface.PlainMedium
+    val TitleMediumLineHeightPoints = 24.0.sp
+    val TitleMediumSizePoints = 16.sp
+    val TitleMediumTrackingPoints = 0.2.sp
+    val TitleMediumWeight = Typeface.WeightMedium
+    val TitleSmallFont = Typeface.PlainMedium
+    val TitleSmallLineHeightPoints = 20.0.sp
+    val TitleSmallSizePoints = 14.sp
+    val TitleSmallTrackingPoints = 0.1.sp
+    val TitleSmallWeight = Typeface.WeightMedium
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/Typeface.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/Typeface.kt
new file mode 100644
index 0000000..06b1d9f
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/Typeface.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.compose.material3.tokens
+
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+
+internal object Typeface {
+    val BrandDisplayRegular = FontFamily.SansSerif
+    val BrandMedium = FontFamily.SansSerif
+    val BrandRegular = FontFamily.SansSerif
+    val PlainMedium = FontFamily.SansSerif
+    val PlainRegular = FontFamily.SansSerif
+
+    val WeightBold = FontWeight.Bold
+    val WeightMedium = FontWeight.Medium
+    val WeightRegular = FontWeight.Normal
+}
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotState.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotState.kt
index 8ec0098..ffa0316 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotState.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotState.kt
@@ -43,6 +43,7 @@
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
 import kotlin.experimental.ExperimentalTypeInference
+import kotlin.jvm.JvmName
 import kotlin.reflect.KProperty
 
 /**
@@ -210,6 +211,15 @@
     override operator fun component1(): T = value
 
     override operator fun component2(): (T) -> Unit = { value = it }
+
+    /**
+     * A function used by the debugger to display the value of the current value of the mutable
+     * state object without triggering read observers.
+     */
+    @Suppress("unused")
+    val debuggerDisplayValue: T
+        @JvmName("getDebuggerDisplayValue")
+        get() = next.withCurrent { it }.value
 }
 
 /**
@@ -320,7 +330,7 @@
     SnapshotStateList<T>().also { it.addAll(elements.toList()) }
 
 /**
- * Create an instance of MutableList<T> from a collection that is observerable and can be snapshot.
+ * Create an instance of MutableList<T> from a collection that is observable and can be snapshot.
  */
 fun <T> Collection<T>.toMutableStateList() = SnapshotStateList<T>().also { it.addAll(this) }
 
@@ -460,7 +470,7 @@
     override val value: T get() {
         // Unlike most state objects, the record list of a derived state can change during a read
         // because reading updates the cache. To account for this, instead of calling readable,
-        // which sends the read notification, the read observer is notfied directly and current
+        // which sends the read notification, the read observer is notified directly and current
         // value is used instead which doesn't notify. This allow the read observer to read the
         // value and only update the cache once.
         Snapshot.current.readObserver?.invoke(this)
@@ -482,6 +492,19 @@
         "DerivedState(value=${displayValue()})@${hashCode()}"
     }
 
+    /**
+     * A function used by the debugger to display the value of the current value of the mutable
+     * state object without triggering read observers.
+     */
+    @Suppress("unused")
+    val debuggerDisplayValue: T?
+        @JvmName("getDebuggerDisplayValue")
+        get() = first.withCurrent {
+            if (it.isValid(this, Snapshot.current))
+                it.result
+            else null
+        }
+
     private fun displayValue(): String {
         first.withCurrent {
             if (it.isValid(this, Snapshot.current)) {
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateList.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateList.kt
index 86777cd..12d0ca2 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateList.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateList.kt
@@ -20,6 +20,7 @@
 import androidx.compose.runtime.external.kotlinx.collections.immutable.PersistentList
 import androidx.compose.runtime.external.kotlinx.collections.immutable.persistentListOf
 import androidx.compose.runtime.synchronized
+import kotlin.jvm.JvmName
 
 /**
  * An implementation of [MutableList] that can be observed and snapshot. This is the result type
@@ -79,11 +80,13 @@
         require(fromIndex in 0..toIndex && toIndex <= size)
         return SubList(this, fromIndex, toIndex)
     }
+
     override fun add(element: T) = conditionalUpdate { it.add(element) }
     override fun add(index: Int, element: T) = update { it.add(index, element) }
     override fun addAll(index: Int, elements: Collection<T>) = mutateBoolean {
         it.addAll(index, elements)
     }
+
     override fun addAll(elements: Collection<T>) = conditionalUpdate { it.addAll(elements) }
     override fun clear() {
         synchronized(sync) {
@@ -115,6 +118,15 @@
         return startSize - size
     }
 
+    /**
+     * An internal function used by the debugger to display the value of the current list without
+     * triggering read observers.
+     */
+    @Suppress("unused")
+    internal val debuggerDisplayValue: List<T>
+        @JvmName("getDebuggerDisplayValue")
+        get() = withCurrent { list }
+
     private inline fun <R> writable(block: StateListStateRecord<T>.() -> R): R =
         @Suppress("UNCHECKED_CAST")
         (firstStateRecord as StateListStateRecord<T>).writable(this, block)
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateMap.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateMap.kt
index 97514c0..2b14ca4 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateMap.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateMap.kt
@@ -20,6 +20,7 @@
 import androidx.compose.runtime.external.kotlinx.collections.immutable.PersistentMap
 import androidx.compose.runtime.external.kotlinx.collections.immutable.persistentHashMapOf
 import androidx.compose.runtime.synchronized
+import kotlin.jvm.JvmName
 
 /**
  * An implementation of [MutableMap] that can be observed and snapshot. This is the result type
@@ -90,6 +91,15 @@
         return true
     }
 
+    /**
+     * An internal function used by the debugger to display the value of the current value of the
+     * mutable state object without triggering read observers.
+     */
+    @Suppress("unused")
+    internal val debuggerDisplayValue: Map<K, V>
+        @JvmName("getDebuggerDisplayValue")
+        get() = withCurrent { map }
+
     private inline fun <R> withCurrent(block: StateMapStateRecord<K, V>.() -> R): R =
         @Suppress("UNCHECKED_CAST")
         (firstStateRecord as StateMapStateRecord<K, V>).withCurrent(block)
diff --git a/compose/ui/ui-test/api/current.txt b/compose/ui/ui-test/api/current.txt
index f10e454..d0c455c 100644
--- a/compose/ui/ui-test/api/current.txt
+++ b/compose/ui/ui-test/api/current.txt
@@ -5,6 +5,8 @@
     method public static androidx.compose.ui.test.SemanticsNodeInteraction performClick(androidx.compose.ui.test.SemanticsNodeInteraction);
     method public static androidx.compose.ui.test.SemanticsNodeInteraction performGesture(androidx.compose.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.GestureScope,kotlin.Unit> block);
     method public static androidx.compose.ui.test.SemanticsNodeInteraction performScrollTo(androidx.compose.ui.test.SemanticsNodeInteraction);
+    method public static androidx.compose.ui.test.SemanticsNodeInteraction performScrollToIndex(androidx.compose.ui.test.SemanticsNodeInteraction, int index);
+    method public static androidx.compose.ui.test.SemanticsNodeInteraction performScrollToKey(androidx.compose.ui.test.SemanticsNodeInteraction, Object key);
     method public static <T extends kotlin.Function<? extends java.lang.Boolean>> void performSemanticsAction(androidx.compose.ui.test.SemanticsNodeInteraction, androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<T>> key, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> invocation);
     method public static void performSemanticsAction(androidx.compose.ui.test.SemanticsNodeInteraction, androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> key);
   }
@@ -84,6 +86,8 @@
     method public static androidx.compose.ui.test.SemanticsMatcher hasParent(androidx.compose.ui.test.SemanticsMatcher matcher);
     method public static androidx.compose.ui.test.SemanticsMatcher hasProgressBarRangeInfo(androidx.compose.ui.semantics.ProgressBarRangeInfo rangeInfo);
     method public static androidx.compose.ui.test.SemanticsMatcher hasScrollAction();
+    method public static androidx.compose.ui.test.SemanticsMatcher hasScrollToIndexAction();
+    method public static androidx.compose.ui.test.SemanticsMatcher hasScrollToKeyAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasSetTextAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasStateDescription(String value);
     method public static androidx.compose.ui.test.SemanticsMatcher hasTestTag(String testTag);
diff --git a/compose/ui/ui-test/api/public_plus_experimental_current.txt b/compose/ui/ui-test/api/public_plus_experimental_current.txt
index 3955208..eadc75a 100644
--- a/compose/ui/ui-test/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-test/api/public_plus_experimental_current.txt
@@ -5,8 +5,8 @@
     method public static androidx.compose.ui.test.SemanticsNodeInteraction performClick(androidx.compose.ui.test.SemanticsNodeInteraction);
     method public static androidx.compose.ui.test.SemanticsNodeInteraction performGesture(androidx.compose.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.GestureScope,kotlin.Unit> block);
     method public static androidx.compose.ui.test.SemanticsNodeInteraction performScrollTo(androidx.compose.ui.test.SemanticsNodeInteraction);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsNodeInteraction performScrollToIndex(androidx.compose.ui.test.SemanticsNodeInteraction, int index);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsNodeInteraction performScrollToKey(androidx.compose.ui.test.SemanticsNodeInteraction, Object key);
+    method public static androidx.compose.ui.test.SemanticsNodeInteraction performScrollToIndex(androidx.compose.ui.test.SemanticsNodeInteraction, int index);
+    method public static androidx.compose.ui.test.SemanticsNodeInteraction performScrollToKey(androidx.compose.ui.test.SemanticsNodeInteraction, Object key);
     method public static <T extends kotlin.Function<? extends java.lang.Boolean>> void performSemanticsAction(androidx.compose.ui.test.SemanticsNodeInteraction, androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<T>> key, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> invocation);
     method public static void performSemanticsAction(androidx.compose.ui.test.SemanticsNodeInteraction, androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> key);
   }
@@ -89,8 +89,8 @@
     method public static androidx.compose.ui.test.SemanticsMatcher hasParent(androidx.compose.ui.test.SemanticsMatcher matcher);
     method public static androidx.compose.ui.test.SemanticsMatcher hasProgressBarRangeInfo(androidx.compose.ui.semantics.ProgressBarRangeInfo rangeInfo);
     method public static androidx.compose.ui.test.SemanticsMatcher hasScrollAction();
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasScrollToIndexAction();
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasScrollToKeyAction();
+    method public static androidx.compose.ui.test.SemanticsMatcher hasScrollToIndexAction();
+    method public static androidx.compose.ui.test.SemanticsMatcher hasScrollToKeyAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasSetTextAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasStateDescription(String value);
     method public static androidx.compose.ui.test.SemanticsMatcher hasTestTag(String testTag);
diff --git a/compose/ui/ui-test/api/restricted_current.txt b/compose/ui/ui-test/api/restricted_current.txt
index f10e454..d0c455c 100644
--- a/compose/ui/ui-test/api/restricted_current.txt
+++ b/compose/ui/ui-test/api/restricted_current.txt
@@ -5,6 +5,8 @@
     method public static androidx.compose.ui.test.SemanticsNodeInteraction performClick(androidx.compose.ui.test.SemanticsNodeInteraction);
     method public static androidx.compose.ui.test.SemanticsNodeInteraction performGesture(androidx.compose.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.GestureScope,kotlin.Unit> block);
     method public static androidx.compose.ui.test.SemanticsNodeInteraction performScrollTo(androidx.compose.ui.test.SemanticsNodeInteraction);
+    method public static androidx.compose.ui.test.SemanticsNodeInteraction performScrollToIndex(androidx.compose.ui.test.SemanticsNodeInteraction, int index);
+    method public static androidx.compose.ui.test.SemanticsNodeInteraction performScrollToKey(androidx.compose.ui.test.SemanticsNodeInteraction, Object key);
     method public static <T extends kotlin.Function<? extends java.lang.Boolean>> void performSemanticsAction(androidx.compose.ui.test.SemanticsNodeInteraction, androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<T>> key, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> invocation);
     method public static void performSemanticsAction(androidx.compose.ui.test.SemanticsNodeInteraction, androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> key);
   }
@@ -84,6 +86,8 @@
     method public static androidx.compose.ui.test.SemanticsMatcher hasParent(androidx.compose.ui.test.SemanticsMatcher matcher);
     method public static androidx.compose.ui.test.SemanticsMatcher hasProgressBarRangeInfo(androidx.compose.ui.semantics.ProgressBarRangeInfo rangeInfo);
     method public static androidx.compose.ui.test.SemanticsMatcher hasScrollAction();
+    method public static androidx.compose.ui.test.SemanticsMatcher hasScrollToIndexAction();
+    method public static androidx.compose.ui.test.SemanticsMatcher hasScrollToKeyAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasSetTextAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasStateDescription(String value);
     method public static androidx.compose.ui.test.SemanticsMatcher hasTestTag(String testTag);
diff --git a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/actions/ScrollToIndexTest.kt b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/actions/ScrollToIndexTest.kt
index d8175bf..5051b90 100644
--- a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/actions/ScrollToIndexTest.kt
+++ b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/actions/ScrollToIndexTest.kt
@@ -26,7 +26,6 @@
 import androidx.compose.testutils.expectError
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.hasScrollToIndexAction
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
@@ -40,7 +39,6 @@
 
 @MediumTest
 @RunWith(AndroidJUnit4::class)
-@OptIn(ExperimentalTestApi::class)
 class ScrollToIndexTest {
     @get:Rule
     val rule = createComposeRule()
diff --git a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/actions/ScrollToKeyTest.kt b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/actions/ScrollToKeyTest.kt
index e0577e7..e5e8fb5 100644
--- a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/actions/ScrollToKeyTest.kt
+++ b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/actions/ScrollToKeyTest.kt
@@ -29,7 +29,6 @@
 import androidx.compose.ui.semantics.indexForKey
 import androidx.compose.ui.semantics.scrollToIndex
 import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.hasScrollToKeyAction
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
@@ -43,7 +42,6 @@
 
 @MediumTest
 @RunWith(AndroidJUnit4::class)
-@OptIn(ExperimentalTestApi::class)
 class ScrollToKeyTest {
     @get:Rule
     val rule = createComposeRule()
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Actions.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Actions.kt
index 76e4aec..5603acb 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Actions.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Actions.kt
@@ -133,7 +133,6 @@
  * @param index The index of the item to scroll to
  * @see hasScrollToIndexAction
  */
-@ExperimentalTestApi
 fun SemanticsNodeInteraction.performScrollToIndex(index: Int): SemanticsNodeInteraction {
     val node = fetchSemanticsNode("Failed: performScrollToIndex($index)")
     requireSemantics(node, ScrollToIndex) {
@@ -160,7 +159,6 @@
  * @param key The key of the item to scroll to
  * @see hasScrollToKeyAction
  */
-@ExperimentalTestApi
 fun SemanticsNodeInteraction.performScrollToKey(key: Any): SemanticsNodeInteraction {
     val node = fetchSemanticsNode("Failed: performScrollToKey(\"$key\")")
     requireSemantics(node, IndexForKey, ScrollToIndex) {
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Filters.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Filters.kt
index 37950a0..c983631 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Filters.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Filters.kt
@@ -390,7 +390,6 @@
  * [scrollable][androidx.compose.foundation.gestures.scrollable] doesn't have items with an
  * index, while [LazyColumn][androidx.compose.foundation.lazy.LazyColumn] does.
  */
-@ExperimentalTestApi
 fun hasScrollToIndexAction() =
     SemanticsMatcher.keyIsDefined(SemanticsActions.ScrollToIndex)
 
@@ -399,7 +398,6 @@
  * [LazyColumn][androidx.compose.foundation.lazy.LazyColumn] or
  * [LazyRow][androidx.compose.foundation.lazy.LazyRow].
  */
-@ExperimentalTestApi
 fun hasScrollToKeyAction() =
     SemanticsMatcher.keyIsDefined(SemanticsActions.ScrollToIndex)
         .and(SemanticsMatcher.keyIsDefined(SemanticsProperties.IndexForKey))
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index 4ac18a1..a4f0623 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -1534,6 +1534,8 @@
     method @FloatRange(from=0, to=java.lang.Float.MAX_VALUE) public float getMinUpdateDistanceMeters();
     method @IntRange(from=0) public long getMinUpdateIntervalMillis();
     method public int getQuality();
+    method @RequiresApi(31) public android.location.LocationRequest toLocationRequest();
+    method @RequiresApi(19) public android.location.LocationRequest toLocationRequest(String);
     field public static final long PASSIVE_INTERVAL = 9223372036854775807L; // 0x7fffffffffffffffL
     field public static final int QUALITY_BALANCED_POWER_ACCURACY = 102; // 0x66
     field public static final int QUALITY_HIGH_ACCURACY = 100; // 0x64
diff --git a/core/core/api/public_plus_experimental_current.txt b/core/core/api/public_plus_experimental_current.txt
index 68458ec..396525b 100644
--- a/core/core/api/public_plus_experimental_current.txt
+++ b/core/core/api/public_plus_experimental_current.txt
@@ -1534,6 +1534,8 @@
     method @FloatRange(from=0, to=java.lang.Float.MAX_VALUE) public float getMinUpdateDistanceMeters();
     method @IntRange(from=0) public long getMinUpdateIntervalMillis();
     method public int getQuality();
+    method @RequiresApi(31) public android.location.LocationRequest toLocationRequest();
+    method @RequiresApi(19) public android.location.LocationRequest toLocationRequest(String);
     field public static final long PASSIVE_INTERVAL = 9223372036854775807L; // 0x7fffffffffffffffL
     field public static final int QUALITY_BALANCED_POWER_ACCURACY = 102; // 0x66
     field public static final int QUALITY_HIGH_ACCURACY = 100; // 0x64
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index ebd90e7..74c14f1b 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -1859,6 +1859,8 @@
     method @FloatRange(from=0, to=java.lang.Float.MAX_VALUE) public float getMinUpdateDistanceMeters();
     method @IntRange(from=0) public long getMinUpdateIntervalMillis();
     method public int getQuality();
+    method @RequiresApi(31) public android.location.LocationRequest toLocationRequest();
+    method @RequiresApi(19) public android.location.LocationRequest toLocationRequest(String);
     field public static final long PASSIVE_INTERVAL = 9223372036854775807L; // 0x7fffffffffffffffL
     field public static final int QUALITY_BALANCED_POWER_ACCURACY = 102; // 0x66
     field public static final int QUALITY_HIGH_ACCURACY = 100; // 0x64
diff --git a/core/core/src/androidTest/java/androidx/core/location/LocationRequestCompatTest.java b/core/core/src/androidTest/java/androidx/core/location/LocationRequestCompatTest.java
index 93cfca4..5d4e4aa 100644
--- a/core/core/src/androidTest/java/androidx/core/location/LocationRequestCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/location/LocationRequestCompatTest.java
@@ -24,6 +24,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import android.annotation.TargetApi;
 import android.location.LocationRequest;
 import android.os.Build.VERSION;
 import android.os.SystemClock;
@@ -96,6 +97,7 @@
     }
 
     @SdkSuppress(minSdkVersion = 19, maxSdkVersion = 30)
+    @TargetApi(31)
     @Test
     public void testConversion_19Plus() throws Exception {
         LocationRequestCompat.Builder builder = new LocationRequestCompat.Builder(0);
@@ -187,6 +189,7 @@
                 builder.build().toLocationRequest().getIntervalMillis());
     }
 
+    @TargetApi(31)
     private static String getProvider(LocationRequest request)
             throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
         if (sGetProviderMethod == null) {
@@ -197,6 +200,7 @@
         return (String) sGetProviderMethod.invoke(request);
     }
 
+    @TargetApi(31)
     private static long getInterval(LocationRequest request)
             throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
         if (sGetIntervalMethod == null) {
@@ -207,6 +211,7 @@
         return (Long) Preconditions.checkNotNull(sGetIntervalMethod.invoke(request));
     }
 
+    @TargetApi(31)
     private static long getFastestInterval(LocationRequest request)
             throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
         if (sGetFastestIntervalMethod == null) {
@@ -218,6 +223,7 @@
         return (Long) Preconditions.checkNotNull(sGetFastestIntervalMethod.invoke(request));
     }
 
+    @TargetApi(31)
     private static long getExpireAt(LocationRequest request)
             throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
         if (sGetExpireAtMethod == null) {
@@ -229,6 +235,7 @@
     }
 
     @RequiresApi(30)
+    @TargetApi(31)
     private static long getExpireIn(LocationRequest request)
             throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
         if (sGetExpireInMethod == null) {
@@ -239,6 +246,7 @@
         return (Long) Preconditions.checkNotNull(sGetExpireInMethod.invoke(request));
     }
 
+    @TargetApi(31)
     private static int getNumUpdates(LocationRequest request)
             throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
         if (sGetNumUpdatesMethod == null) {
@@ -249,6 +257,7 @@
         return (Integer) Preconditions.checkNotNull(sGetNumUpdatesMethod.invoke(request));
     }
 
+    @TargetApi(31)
     private static float getSmallestDisplacement(LocationRequest request)
             throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
         if (sGetSmallestDisplacementMethod == null) {
diff --git a/core/core/src/main/java/androidx/core/content/LocusIdCompat.java b/core/core/src/main/java/androidx/core/content/LocusIdCompat.java
index 4c8fe22..5c52d68 100644
--- a/core/core/src/main/java/androidx/core/content/LocusIdCompat.java
+++ b/core/core/src/main/java/androidx/core/content/LocusIdCompat.java
@@ -30,8 +30,9 @@
  * backup / restore.
  *
  * <p>Locus is a new concept introduced on
- * {@link android.os.Build.VERSION_CODES#Q Android Q} and it lets the Android system correlate
- * state between different subsystems such as content capture, shortcuts, and notifications.
+ * {@link android.os.Build.VERSION_CODES#Q Android Q} and it lets the intelligence service provided
+ * by the Android system correlate state between different subsystems such as content capture,
+ * shortcuts, and notifications.
  *
  * <p>For example, if your app provides an activity representing a chat between 2 users
  * (say {@code A} and {@code B}, this chat state could be represented by:
@@ -61,6 +62,9 @@
  *   <li>Configuring your app to launch the chat conversation through the
  *   {@link Intent#ACTION_VIEW_LOCUS} intent.
  * </ul>
+ *
+ * NOTE: The LocusId is only used by a on-device intelligence service provided by the Android
+ * System, see {@link ContentCaptureManager} for more details.
  */
 public final class LocusIdCompat {
 
diff --git a/core/core/src/main/java/androidx/core/location/LocationManagerCompat.java b/core/core/src/main/java/androidx/core/location/LocationManagerCompat.java
index c450492..680be75 100644
--- a/core/core/src/main/java/androidx/core/location/LocationManagerCompat.java
+++ b/core/core/src/main/java/androidx/core/location/LocationManagerCompat.java
@@ -260,6 +260,8 @@
                 return;
             } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
                 // ignored
+            } catch (UnsupportedOperationException e) {
+                // ignored
             }
         }
 
@@ -283,6 +285,8 @@
                 return;
             } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
                 // ignored
+            } catch (UnsupportedOperationException e) {
+                // ignored
             }
         }
 
@@ -330,6 +334,8 @@
                 return;
             } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
                 // ignored
+            } catch (UnsupportedOperationException e) {
+                // ignored
             }
         }
 
@@ -630,7 +636,17 @@
             List<WeakReference<LocationListenerTransport>> transports =
                     sLocationListeners.get(listener);
             if (transports != null) {
-                transports.removeIf(reference -> reference.get() == null);
+                // clean unreferenced transports
+                if (VERSION.SDK_INT >= VERSION_CODES.N) {
+                    transports.removeIf(reference -> reference.get() == null);
+                } else {
+                    Iterator<WeakReference<LocationListenerTransport>> it = transports.iterator();
+                    while (it.hasNext()) {
+                        if (it.next().get() == null) {
+                            it.remove();
+                        }
+                    }
+                }
                 if (transports.isEmpty()) {
                     sLocationListeners.remove(listener);
                 }
diff --git a/core/core/src/main/java/androidx/core/location/LocationRequestCompat.java b/core/core/src/main/java/androidx/core/location/LocationRequestCompat.java
index d84ae0d..84f82f2 100644
--- a/core/core/src/main/java/androidx/core/location/LocationRequestCompat.java
+++ b/core/core/src/main/java/androidx/core/location/LocationRequestCompat.java
@@ -209,9 +209,15 @@
         return mMaxUpdateDelayMillis;
     }
 
+    /**
+     * Converts an instance to an equivalent {@link LocationRequest}.
+     *
+     * @return platform class object
+     * @see LocationRequest
+     */
     @RequiresApi(31)
     @NonNull
-    LocationRequest toLocationRequest() {
+    public LocationRequest toLocationRequest() {
         return new LocationRequest.Builder(mIntervalMillis)
                 .setQuality(mQuality)
                 .setMinUpdateIntervalMillis(mMinUpdateIntervalMillis)
@@ -222,70 +228,82 @@
                 .build();
     }
 
+    /**
+     * Converts an instance to an equivalent {@link LocationRequest}, with the provider field of
+     * the resulting LocationRequest set to the provider argument provided to this method.
+     *
+     * <p>May throw an {@link UnsupportedOperationException} on some SDKs if various reflective
+     * operations fail. This should only occur on non-standard Android devices, and thus should
+     * be rare.
+     *
+     * @return platform class object
+     * @see LocationRequest
+     */
     @SuppressWarnings("JavaReflectionMemberAccess")
     @RequiresApi(19)
     @NonNull
-    LocationRequest toLocationRequest(@NonNull String provider)
-            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+    public LocationRequest toLocationRequest(@NonNull String provider) {
         if (VERSION.SDK_INT >= 31) {
             return toLocationRequest();
-        } else if (VERSION.SDK_INT >= 19) {
-            if (sCreateFromDeprecatedProviderMethod == null) {
-                sCreateFromDeprecatedProviderMethod = LocationRequest.class.getDeclaredMethod(
-                        "createFromDeprecatedProvider", String.class, long.class, float.class,
-                        boolean.class);
-                sCreateFromDeprecatedProviderMethod.setAccessible(true);
-            }
-
-            LocationRequest request =
-                    (LocationRequest) sCreateFromDeprecatedProviderMethod.invoke(null, provider,
-                            mIntervalMillis,
-                            mMinUpdateDistanceMeters, false);
-            if (request == null) {
-                // should never happen
-                throw new InvocationTargetException(new NullPointerException());
-            }
-
-            if (sSetQualityMethod == null) {
-                sSetQualityMethod = LocationRequest.class.getDeclaredMethod(
-                        "setQuality", int.class);
-                sSetQualityMethod.setAccessible(true);
-            }
-            sSetQualityMethod.invoke(request, mQuality);
-
-            if (getMinUpdateIntervalMillis() != mIntervalMillis) {
-                if (sSetFastestIntervalMethod == null) {
-                    sSetFastestIntervalMethod = LocationRequest.class.getDeclaredMethod(
-                            "setFastestInterval", long.class);
-                    sSetFastestIntervalMethod.setAccessible(true);
+        } else {
+            try {
+                if (sCreateFromDeprecatedProviderMethod == null) {
+                    sCreateFromDeprecatedProviderMethod = LocationRequest.class.getDeclaredMethod(
+                            "createFromDeprecatedProvider", String.class, long.class, float.class,
+                            boolean.class);
+                    sCreateFromDeprecatedProviderMethod.setAccessible(true);
                 }
 
-                sSetFastestIntervalMethod.invoke(request, mMinUpdateIntervalMillis);
-            }
-
-            if (mMaxUpdates < Integer.MAX_VALUE) {
-                if (sSetNumUpdatesMethod == null) {
-                    sSetNumUpdatesMethod = LocationRequest.class.getDeclaredMethod(
-                            "setNumUpdates", int.class);
-                    sSetNumUpdatesMethod.setAccessible(true);
+                LocationRequest request =
+                        (LocationRequest) sCreateFromDeprecatedProviderMethod.invoke(null, provider,
+                                mIntervalMillis,
+                                mMinUpdateDistanceMeters, false);
+                if (request == null) {
+                    throw new UnsupportedOperationException();
                 }
 
-                sSetNumUpdatesMethod.invoke(request, mMaxUpdates);
-            }
+                if (sSetQualityMethod == null) {
+                    sSetQualityMethod = LocationRequest.class.getDeclaredMethod(
+                            "setQuality", int.class);
+                    sSetQualityMethod.setAccessible(true);
+                }
+                sSetQualityMethod.invoke(request, mQuality);
 
-            if (mDurationMillis < Long.MAX_VALUE) {
-                if (sSetExpireInMethod == null) {
-                    sSetExpireInMethod = LocationRequest.class.getDeclaredMethod(
-                            "setExpireIn", long.class);
-                    sSetExpireInMethod.setAccessible(true);
+                if (getMinUpdateIntervalMillis() != mIntervalMillis) {
+                    if (sSetFastestIntervalMethod == null) {
+                        sSetFastestIntervalMethod = LocationRequest.class.getDeclaredMethod(
+                                "setFastestInterval", long.class);
+                        sSetFastestIntervalMethod.setAccessible(true);
+                    }
+
+                    sSetFastestIntervalMethod.invoke(request, mMinUpdateIntervalMillis);
                 }
 
-                sSetExpireInMethod.invoke(request, mDurationMillis);
+                if (mMaxUpdates < Integer.MAX_VALUE) {
+                    if (sSetNumUpdatesMethod == null) {
+                        sSetNumUpdatesMethod = LocationRequest.class.getDeclaredMethod(
+                                "setNumUpdates", int.class);
+                        sSetNumUpdatesMethod.setAccessible(true);
+                    }
+
+                    sSetNumUpdatesMethod.invoke(request, mMaxUpdates);
+                }
+
+                if (mDurationMillis < Long.MAX_VALUE) {
+                    if (sSetExpireInMethod == null) {
+                        sSetExpireInMethod = LocationRequest.class.getDeclaredMethod(
+                                "setExpireIn", long.class);
+                        sSetExpireInMethod.setAccessible(true);
+                    }
+
+                    sSetExpireInMethod.invoke(request, mDurationMillis);
+                }
+
+                return request;
+            } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+                throw new UnsupportedOperationException(e);
             }
-            return request;
         }
-
-        throw new NoClassDefFoundError();
     }
 
     @Override
diff --git a/documentfile/documentfile/build.gradle b/documentfile/documentfile/build.gradle
index 10030ef..d3f6a76 100644
--- a/documentfile/documentfile/build.gradle
+++ b/documentfile/documentfile/build.gradle
@@ -7,7 +7,8 @@
 }
 
 dependencies {
-    api("androidx.annotation:annotation:1.1.0")
+    implementation("androidx.annotation:annotation:1.1.0")
+    implementation(project(":core:core"))
 
     annotationProcessor(libs.nullaway)
 
diff --git a/documentfile/documentfile/src/androidTest/java/androidx/documentfile/provider/DocumentFileTest.java b/documentfile/documentfile/src/androidTest/java/androidx/documentfile/provider/DocumentFileTest.java
index 1af61b2..98d4edc 100644
--- a/documentfile/documentfile/src/androidTest/java/androidx/documentfile/provider/DocumentFileTest.java
+++ b/documentfile/documentfile/src/androidTest/java/androidx/documentfile/provider/DocumentFileTest.java
@@ -17,13 +17,10 @@
 package androidx.documentfile.provider;
 
 import static org.hamcrest.core.IsEqual.equalTo;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
 import android.content.Context;
 import android.net.Uri;
-import android.os.Build;
-import android.provider.DocumentsContract;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -54,64 +51,4 @@
         DocumentFile subDirDoc = DocumentFile.fromTreeUri(context, DOWNLOAD_URI);
         assertThat(subDirDoc.getUri(), equalTo(DOWNLOAD_URI));
     }
-
-    @Test
-    public void testDocumentsContractApi_isDocumentId() {
-        Context context = ApplicationProvider.getApplicationContext();
-        final boolean isDocumentUri = DocumentsContractApi.isDocumentUri(context,
-                DOWNLOAD_URI);
-
-        final boolean expectedIsDocumentUri;
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
-            expectedIsDocumentUri = DocumentsContract.isDocumentUri(context, DOWNLOAD_URI);
-        } else {
-            expectedIsDocumentUri = false;
-        }
-        assertEquals(expectedIsDocumentUri, isDocumentUri);
-    }
-
-    @Test
-    public void testDocumentsContractApi_getDocumentId() {
-        final String documentId = DocumentsContractApi.getDocumentId(DOWNLOAD_URI);
-
-        final String expectedDocumentId;
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
-            expectedDocumentId = DocumentsContract.getDocumentId(DOWNLOAD_URI);
-        } else {
-            expectedDocumentId = null;
-        }
-        assertEquals(expectedDocumentId, documentId);
-    }
-
-    @Test
-    public void testDocumentsContractApi_getTreeDocumentId() {
-        final String treeDocumentId = DocumentsContractApi.getTreeDocumentId(
-                DOWNLOAD_URI);
-
-        final String expectedTreeDocumentId;
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-            expectedTreeDocumentId = DocumentsContract.getTreeDocumentId(DOWNLOAD_URI);
-        } else {
-            expectedTreeDocumentId = null;
-        }
-        assertEquals(expectedTreeDocumentId, treeDocumentId);
-    }
-
-    @Test
-    public void testDocumentsContractApi_buildDocumentUriUsingTree() {
-        final String treeDocumentId = DocumentsContractApi.getTreeDocumentId(
-                DOWNLOAD_URI);
-        final Uri treeUri = DocumentsContractApi.buildDocumentUriUsingTree(
-                DOWNLOAD_URI, treeDocumentId);
-
-        final Uri expectedTreeUri;
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-            final String expectedTreeDocId = DocumentsContract.getTreeDocumentId(DOWNLOAD_URI);
-            expectedTreeUri = DocumentsContract.buildDocumentUriUsingTree(DOWNLOAD_URI,
-                    expectedTreeDocId);
-        } else {
-            expectedTreeUri = null;
-        }
-        assertEquals(expectedTreeUri, treeUri);
-    }
 }
diff --git a/documentfile/documentfile/src/main/java/androidx/documentfile/provider/DocumentFile.java b/documentfile/documentfile/src/main/java/androidx/documentfile/provider/DocumentFile.java
index 8385813..f29d0a1 100644
--- a/documentfile/documentfile/src/main/java/androidx/documentfile/provider/DocumentFile.java
+++ b/documentfile/documentfile/src/main/java/androidx/documentfile/provider/DocumentFile.java
@@ -24,6 +24,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.core.provider.DocumentsContractCompat;
 
 import java.io.File;
 
@@ -132,16 +133,16 @@
     @Nullable
     public static DocumentFile fromTreeUri(@NonNull Context context, @NonNull Uri treeUri) {
         if (Build.VERSION.SDK_INT >= 21) {
-            String documentId = DocumentsContractApi.getTreeDocumentId(treeUri);
-            if (DocumentsContractApi.isDocumentUri(context, treeUri)) {
-                documentId = DocumentsContractApi.getDocumentId(treeUri);
+            String documentId = DocumentsContractCompat.getTreeDocumentId(treeUri);
+            if (DocumentsContractCompat.isDocumentUri(context, treeUri)) {
+                documentId = DocumentsContractCompat.getDocumentId(treeUri);
             }
             if (documentId == null) {
                 throw new IllegalArgumentException(
                         "Could not get document ID from Uri: " + treeUri);
             }
             Uri treeDocumentUri =
-                    DocumentsContractApi.buildDocumentUriUsingTree(treeUri, documentId);
+                    DocumentsContractCompat.buildDocumentUriUsingTree(treeUri, documentId);
             if (treeDocumentUri == null) {
                 throw new NullPointerException(
                         "Failed to build documentUri from a tree: " + treeUri);
@@ -157,7 +158,7 @@
      * {@link android.provider.DocumentsProvider}.
      */
     public static boolean isDocumentUri(@NonNull Context context, @Nullable Uri uri) {
-        return DocumentsContractApi.isDocumentUri(context, uri);
+        return DocumentsContractCompat.isDocumentUri(context, uri);
     }
 
     /**
diff --git a/documentfile/documentfile/src/main/java/androidx/documentfile/provider/DocumentsContractApi.java b/documentfile/documentfile/src/main/java/androidx/documentfile/provider/DocumentsContractApi.java
deleted file mode 100644
index 2da4d7e..0000000
--- a/documentfile/documentfile/src/main/java/androidx/documentfile/provider/DocumentsContractApi.java
+++ /dev/null
@@ -1,94 +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.documentfile.provider;
-
-import android.content.Context;
-import android.net.Uri;
-import android.os.Build;
-import android.provider.DocumentsContract;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-
-abstract class DocumentsContractApi {
-
-    static boolean isDocumentUri(Context context, @Nullable Uri uri) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
-            return DocumentsContractApi19Impl.isDocumentUri(context, uri);
-        } else {
-            return false;
-        }
-    }
-
-    @Nullable
-    static String getDocumentId(Uri documentUri) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
-            return DocumentsContractApi19Impl.getDocumentId(documentUri);
-        } else {
-            return null;
-        }
-    }
-
-    @Nullable
-    static String getTreeDocumentId(Uri documentUri) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-            return DocumentsContractApi21Impl.getTreeDocumentId(documentUri);
-        } else {
-            return null;
-        }
-    }
-
-    @Nullable
-    static Uri buildDocumentUriUsingTree(Uri treeUri, String documentId) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-            return DocumentsContractApi21Impl.buildDocumentUriUsingTree(treeUri, documentId);
-        } else {
-            return null;
-        }
-    }
-
-    @RequiresApi(19)
-    private static class DocumentsContractApi19Impl {
-        static boolean isDocumentUri(Context context, @Nullable Uri uri) {
-            return DocumentsContract.isDocumentUri(context, uri);
-        }
-
-        static String getDocumentId(Uri documentUri) {
-            return DocumentsContract.getDocumentId(documentUri);
-        }
-
-        private DocumentsContractApi19Impl() {
-        }
-    }
-
-    @RequiresApi(21)
-    private static class DocumentsContractApi21Impl {
-        static String getTreeDocumentId(Uri documentUri) {
-            return DocumentsContract.getTreeDocumentId(documentUri);
-        }
-
-        static Uri buildDocumentUriUsingTree(Uri treeUri, String documentId) {
-            return DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId);
-        }
-
-        private DocumentsContractApi21Impl() {
-        }
-    }
-
-    private DocumentsContractApi() {
-    }
-}
diff --git a/documentfile/documentfile/src/main/java/androidx/documentfile/provider/DocumentsContractApi19.java b/documentfile/documentfile/src/main/java/androidx/documentfile/provider/DocumentsContractApi19.java
index 45338e5..bf83778 100644
--- a/documentfile/documentfile/src/main/java/androidx/documentfile/provider/DocumentsContractApi19.java
+++ b/documentfile/documentfile/src/main/java/androidx/documentfile/provider/DocumentsContractApi19.java
@@ -16,6 +16,8 @@
 
 package androidx.documentfile.provider;
 
+import static androidx.core.provider.DocumentsContractCompat.DocumentCompat.FLAG_VIRTUAL_DOCUMENT;
+
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -33,9 +35,6 @@
 class DocumentsContractApi19 {
     private static final String TAG = "DocumentFile";
 
-    // DocumentsContract API level 24.
-    private static final int FLAG_VIRTUAL_DOCUMENT = 1 << 9;
-
     public static boolean isVirtual(Context context, Uri self) {
         if (!DocumentsContract.isDocumentUri(context, self)) {
             return false;
@@ -142,8 +141,8 @@
 
         Cursor c = null;
         try {
-            c = resolver.query(self, new String[] {
-                    DocumentsContract.Document.COLUMN_DOCUMENT_ID }, null, null, null);
+            c = resolver.query(self, new String[]{
+                    DocumentsContract.Document.COLUMN_DOCUMENT_ID}, null, null, null);
             return c.getCount() > 0;
         } catch (Exception e) {
             Log.w(TAG, "Failed query: " + e);
@@ -160,7 +159,7 @@
 
         Cursor c = null;
         try {
-            c = resolver.query(self, new String[] { column }, null, null, null);
+            c = resolver.query(self, new String[]{column}, null, null, null);
             if (c.moveToFirst() && !c.isNull(0)) {
                 return c.getString(0);
             } else {
@@ -185,7 +184,7 @@
 
         Cursor c = null;
         try {
-            c = resolver.query(self, new String[] { column }, null, null, null);
+            c = resolver.query(self, new String[]{column}, null, null, null);
             if (c.moveToFirst() && !c.isNull(0)) {
                 return c.getLong(0);
             } else {
diff --git a/glance/glance-appwidget/api/current.txt b/glance/glance-appwidget/api/current.txt
index c95c400..55ee60a 100644
--- a/glance/glance-appwidget/api/current.txt
+++ b/glance/glance-appwidget/api/current.txt
@@ -1,9 +1,26 @@
 // Signature format: 4.0
 package androidx.glance.appwidget {
 
+  public final class ApplyModifiersKt {
+  }
+
+  public final class CompositionLocalsKt {
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<android.os.Bundle> getLocalAppWidgetOptions();
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.glance.appwidget.GlanceId> getLocalGlanceId();
+  }
+
   public final class CoroutineBroadcastReceiverKt {
   }
 
+  public abstract class GlanceAppWidget {
+    ctor public GlanceAppWidget();
+    method @androidx.compose.runtime.Composable public abstract void Content();
+    method public final suspend Object? update(android.content.Context context, androidx.glance.appwidget.GlanceId glanceId, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+  }
+
+  public interface GlanceId {
+  }
+
   public final class RemoteViewsTranslatorKt {
   }
 
diff --git a/glance/glance-appwidget/api/public_plus_experimental_current.txt b/glance/glance-appwidget/api/public_plus_experimental_current.txt
index c95c400..55ee60a 100644
--- a/glance/glance-appwidget/api/public_plus_experimental_current.txt
+++ b/glance/glance-appwidget/api/public_plus_experimental_current.txt
@@ -1,9 +1,26 @@
 // Signature format: 4.0
 package androidx.glance.appwidget {
 
+  public final class ApplyModifiersKt {
+  }
+
+  public final class CompositionLocalsKt {
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<android.os.Bundle> getLocalAppWidgetOptions();
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.glance.appwidget.GlanceId> getLocalGlanceId();
+  }
+
   public final class CoroutineBroadcastReceiverKt {
   }
 
+  public abstract class GlanceAppWidget {
+    ctor public GlanceAppWidget();
+    method @androidx.compose.runtime.Composable public abstract void Content();
+    method public final suspend Object? update(android.content.Context context, androidx.glance.appwidget.GlanceId glanceId, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+  }
+
+  public interface GlanceId {
+  }
+
   public final class RemoteViewsTranslatorKt {
   }
 
diff --git a/glance/glance-appwidget/api/restricted_current.txt b/glance/glance-appwidget/api/restricted_current.txt
index c95c400..55ee60a 100644
--- a/glance/glance-appwidget/api/restricted_current.txt
+++ b/glance/glance-appwidget/api/restricted_current.txt
@@ -1,9 +1,26 @@
 // Signature format: 4.0
 package androidx.glance.appwidget {
 
+  public final class ApplyModifiersKt {
+  }
+
+  public final class CompositionLocalsKt {
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<android.os.Bundle> getLocalAppWidgetOptions();
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.glance.appwidget.GlanceId> getLocalGlanceId();
+  }
+
   public final class CoroutineBroadcastReceiverKt {
   }
 
+  public abstract class GlanceAppWidget {
+    ctor public GlanceAppWidget();
+    method @androidx.compose.runtime.Composable public abstract void Content();
+    method public final suspend Object? update(android.content.Context context, androidx.glance.appwidget.GlanceId glanceId, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+  }
+
+  public interface GlanceId {
+  }
+
   public final class RemoteViewsTranslatorKt {
   }
 
diff --git a/glance/glance-appwidget/build.gradle b/glance/glance-appwidget/build.gradle
index 136e1ef..2ecfdee 100644
--- a/glance/glance-appwidget/build.gradle
+++ b/glance/glance-appwidget/build.gradle
@@ -46,6 +46,8 @@
     testImplementation(libs.truth)
     testImplementation(libs.robolectric)
     testImplementation(libs.kotlinCoroutinesTest)
+    testImplementation(libs.kotlinTest)
+    testImplementation(project(":core:core-ktx"))
 }
 
 android {
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/ApplyModifiers.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/ApplyModifiers.kt
new file mode 100644
index 0000000..65451de
--- /dev/null
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/ApplyModifiers.kt
@@ -0,0 +1,59 @@
+@file:OptIn(GlanceInternalApi::class)
+/*
+ * 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.glance.appwidget
+
+import android.content.Context
+import android.content.res.Resources
+import android.util.DisplayMetrics
+import android.util.TypedValue
+import android.view.View
+import android.widget.RemoteViews
+import androidx.glance.GlanceInternalApi
+import androidx.glance.Modifier
+import androidx.glance.layout.PaddingModifier
+import androidx.glance.unit.Dp
+
+private fun applyPadding(
+    rv: RemoteViews,
+    modifier: PaddingModifier,
+    resources: Resources
+) {
+    val displayMetrics = resources.displayMetrics
+    val isRtl = modifier.rtlAware &&
+        resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
+    val start = modifier.start.toPixel(displayMetrics)
+    val end = modifier.end.toPixel(displayMetrics)
+    rv.setViewPadding(
+        R.id.glanceView,
+        if (isRtl) end else start,
+        modifier.top.toPixel(displayMetrics),
+        if (isRtl) start else end,
+        modifier.bottom.toPixel(displayMetrics),
+    )
+}
+
+internal fun applyModifiers(context: Context, rv: RemoteViews, modifiers: Modifier) {
+    modifiers.foldOut(Unit) { modifier, _ ->
+        when (modifier) {
+            is PaddingModifier -> applyPadding(rv, modifier, context.resources)
+        }
+    }
+}
+
+private fun Dp.toPixel(displayMetrics: DisplayMetrics) =
+    TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, displayMetrics).toInt()
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/CompositionLocals.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/CompositionLocals.kt
new file mode 100644
index 0000000..f999727
--- /dev/null
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/CompositionLocals.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.glance.appwidget
+
+import android.os.Bundle
+import androidx.compose.runtime.ProvidableCompositionLocal
+import androidx.compose.runtime.staticCompositionLocalOf
+
+/**
+ * Option Bundle accessible when generating an App Widget.
+ *
+ * See [AppWidgetManager#getAppWidgetOptions] for details
+ */
+public val LocalAppWidgetOptions: ProvidableCompositionLocal<Bundle> =
+    staticCompositionLocalOf { error("No default app widget options") }
+
+/**
+ * Unique Id for the glance view being generated by the current composition.
+ */
+public val LocalGlanceId = staticCompositionLocalOf<GlanceId> { error("No default glance id") }
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceAppWidget.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceAppWidget.kt
new file mode 100644
index 0000000..006ac39
--- /dev/null
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceAppWidget.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.glance.appwidget
+
+import android.appwidget.AppWidgetManager
+import android.content.Context
+import android.os.Bundle
+import android.widget.RemoteViews
+import androidx.annotation.VisibleForTesting
+import androidx.compose.runtime.BroadcastFrameClock
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Composition
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.Recomposer
+import androidx.glance.Applier
+import androidx.glance.GlanceInternalApi
+import androidx.glance.LocalContext
+import androidx.glance.LocalSize
+import androidx.glance.unit.DpSize
+import androidx.glance.unit.dp
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * Object handling the composition and the communication with [AppWidgetManager].
+ *
+ * The UI is defined by the [Content] composable function. Calling [update] will start
+ * the composition and translate [Content] into a [RemoteViews] which is then sent to the
+ * [AppWidgetManager].
+ */
+@OptIn(GlanceInternalApi::class)
+public abstract class GlanceAppWidget {
+    /**
+     * Definition of the UI.
+     */
+    @Composable
+    public abstract fun Content()
+
+    /**
+     * Triggers the composition of [Content] and sends the result to the [AppWidgetManager].
+     */
+    public suspend fun update(context: Context, glanceId: GlanceId) {
+        require(glanceId is AppWidgetId) {
+            "The glanceId '$glanceId' is not a valid App Widget glance id"
+        }
+        update(context, glanceId.appWidgetId)
+    }
+
+    /**
+     * Internal version of [update], to be used by the broadcast receiver directly.
+     */
+    internal suspend fun update(
+        context: Context,
+        appWidgetId: Int,
+        options: Bundle? = null,
+    ) {
+        val appWidgetManager = AppWidgetManager.getInstance(context)
+        val opts = options ?: appWidgetManager.getAppWidgetOptions(appWidgetId)!!
+        val info = appWidgetManager.getAppWidgetInfo(appWidgetId)
+        val size = DpSize(info.minWidth.dp, info.minHeight.dp)
+        appWidgetManager.updateAppWidget(
+            appWidgetId,
+            compose(context, appWidgetId, opts, size)
+        )
+    }
+
+    @VisibleForTesting
+    internal suspend fun compose(
+        context: Context,
+        appWidgetId: Int,
+        options: Bundle,
+        size: DpSize,
+    ): RemoteViews {
+        return withContext(BroadcastFrameClock()) {
+            val root = RemoteViewsRoot(maxDepth = MaxDepth)
+            val applier = Applier(root)
+            val recomposer = Recomposer(coroutineContext)
+            val composition = Composition(applier, recomposer)
+            val glanceId = AppWidgetId(appWidgetId)
+            composition.setContent {
+                CompositionLocalProvider(
+                    LocalContext provides context,
+                    LocalGlanceId provides glanceId,
+                    LocalAppWidgetOptions provides options,
+                    LocalSize provides size,
+                ) { Content() }
+            }
+            launch { recomposer.runRecomposeAndApplyChanges() }
+            recomposer.close()
+            recomposer.join()
+
+            translateComposition(context, root)
+        }
+    }
+
+    private companion object {
+        /** Maximum depth for a composition. The system defines a maximum recursion level of 10,
+         * but the first level is for composed [RemoteViews]. */
+        private const val MaxDepth = 9
+    }
+}
+
+private data class AppWidgetId(val appWidgetId: Int) : GlanceId
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceId.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceId.kt
new file mode 100644
index 0000000..2ad6f21
--- /dev/null
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceId.kt
@@ -0,0 +1,20 @@
+/*
+ * 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.glance.appwidget
+
+/** Opaque object used to describe a glance view. */
+interface GlanceId
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/RemoteViewsTranslator.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/RemoteViewsTranslator.kt
index bad1551..97e1479 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/RemoteViewsTranslator.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/RemoteViewsTranslator.kt
@@ -18,21 +18,48 @@
 package androidx.glance.appwidget
 
 import android.content.Context
-import android.content.res.Resources
-import android.util.DisplayMetrics
+import android.graphics.Typeface
+import android.os.Build
+import android.text.ParcelableSpan
+import android.text.SpannableString
+import android.text.Spanned
+import android.text.style.StrikethroughSpan
+import android.text.style.StyleSpan
+import android.text.style.TypefaceSpan
+import android.text.style.UnderlineSpan
 import android.util.TypedValue
 import android.view.Gravity
-import android.view.View
 import android.widget.RemoteViews
+import androidx.annotation.DoNotInline
 import androidx.annotation.LayoutRes
+import androidx.annotation.RequiresApi
 import androidx.glance.Emittable
 import androidx.glance.GlanceInternalApi
-import androidx.glance.Modifier
 import androidx.glance.layout.Alignment
 import androidx.glance.layout.EmittableBox
-import androidx.glance.layout.PaddingModifier
-import androidx.glance.unit.Dp
-import kotlin.math.floor
+import androidx.glance.layout.EmittableText
+import androidx.glance.layout.FontStyle
+import androidx.glance.layout.FontWeight
+import androidx.glance.layout.TextDecoration
+import androidx.glance.layout.TextStyle
+
+internal fun translateComposition(context: Context, element: RemoteViewsRoot): RemoteViews {
+    if (element.children.size == 1) {
+        return translateChild(context, element.children[0])
+    }
+    return translateChild(context, EmittableBox().also { it.children.addAll(element.children) })
+}
+
+private fun translateChild(context: Context, element: Emittable): RemoteViews {
+    return when (element) {
+        is EmittableBox -> translateEmittableBox(context, element)
+        is EmittableText -> translateEmittableText(context, element)
+        else -> throw IllegalArgumentException("Unknown element type ${element::javaClass}")
+    }
+}
+
+private fun remoteViews(context: Context, @LayoutRes layoutId: Int) =
+    RemoteViews(context.packageName, layoutId)
 
 private fun Alignment.Horizontal.toGravity(): Int =
     when (this) {
@@ -52,33 +79,6 @@
 
 private fun Alignment.toGravity() = horizontal.toGravity() or vertical.toGravity()
 
-private fun applyPadding(
-    rv: RemoteViews,
-    modifier: PaddingModifier,
-    resources: Resources
-) {
-    val displayMetrics = resources.displayMetrics
-    val isRtl = modifier.rtlAware &&
-        resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
-    val start = dpToPixel(modifier.start, displayMetrics)
-    val end = dpToPixel(modifier.end, displayMetrics)
-    rv.setViewPadding(
-        R.id.glanceView,
-        if (isRtl) end else start,
-        dpToPixel(modifier.top, displayMetrics),
-        if (isRtl) start else end,
-        dpToPixel(modifier.bottom, displayMetrics),
-    )
-}
-
-private fun applyModifiers(context: Context, rv: RemoteViews, modifiers: Modifier) {
-    modifiers.foldOut(Unit) { modifier, _ ->
-        when (modifier) {
-            is PaddingModifier -> applyPadding(rv, modifier, context.resources)
-        }
-    }
-}
-
 private fun translateEmittableBox(context: Context, element: EmittableBox): RemoteViews =
     remoteViews(context, R.layout.box_layout)
         .also { rv ->
@@ -89,22 +89,57 @@
             }
         }
 
-private fun translateChild(context: Context, element: Emittable): RemoteViews {
-    return when (element) {
-        is EmittableBox -> translateEmittableBox(context, element)
-        else -> throw IllegalArgumentException("Unknown element type ${element::javaClass}")
+private fun translateEmittableText(context: Context, element: EmittableText): RemoteViews =
+    remoteViews(context, R.layout.text_layout)
+        .also { rv ->
+            rv.setText(element.text, element.style)
+            applyModifiers(context, rv, element.modifier)
+        }
+
+private fun RemoteViews.setText(text: String, style: TextStyle?) {
+    if (style == null) {
+        setTextViewText(R.id.glanceView, text)
+        return
     }
+    val content = SpannableString(text)
+    val length = content.length
+    style.size?.let { setTextViewTextSize(R.id.glanceView, TypedValue.COMPLEX_UNIT_SP, it.value) }
+    val spans = mutableListOf<ParcelableSpan>()
+    style.textDecoration?.let {
+        if (TextDecoration.LineThrough in it) {
+            spans.add(StrikethroughSpan())
+        }
+        if (TextDecoration.Underline in it) {
+            spans.add(UnderlineSpan())
+        }
+    }
+    val isItalic = style.fontStyle == FontStyle.Italic
+    val fontWeight = style.fontWeight ?: FontWeight.Normal
+    if (isItalic || fontWeight != FontWeight.Normal) {
+        spans.add(createStyleSpan(fontWeight, isItalic = isItalic))
+    }
+    spans.forEach { span ->
+        content.setSpan(span, 0, length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
+    }
+    setTextViewText(R.id.glanceView, content)
 }
 
-private fun dpToPixel(dp: Dp, displayMetrics: DisplayMetrics) =
-    floor(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp.value, displayMetrics)).toInt()
-
-private fun remoteViews(context: Context, @LayoutRes layoutId: Int) =
-    RemoteViews(context.packageName, layoutId)
-
-internal fun translateComposition(context: Context, element: RemoteViewsRoot): RemoteViews {
-    if (element.children.size == 1) {
-        return translateChild(context, element.children[0])
+private fun createStyleSpan(fontWeight: FontWeight, isItalic: Boolean): ParcelableSpan {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+        return Api28Impl.createStyleSpan(fontWeight, isItalic)
     }
-    return translateChild(context, EmittableBox().also { it.children.addAll(element.children) })
+    val boldStyle = if (fontWeight in listOf(FontWeight.Medium, FontWeight.Bold)) {
+        Typeface.BOLD
+    } else {
+        Typeface.NORMAL
+    }
+    val italicStyle = if (isItalic) Typeface.ITALIC else Typeface.NORMAL
+    return StyleSpan(boldStyle or italicStyle)
+}
+
+@RequiresApi(Build.VERSION_CODES.P)
+private object Api28Impl {
+    @DoNotInline
+    fun createStyleSpan(fontWeight: FontWeight, isItalic: Boolean): ParcelableSpan =
+        TypefaceSpan(Typeface.create(Typeface.DEFAULT, fontWeight.value, isItalic))
 }
diff --git a/glance/glance-appwidget/src/androidMain/res/layout/box_layout.xml b/glance/glance-appwidget/src/androidMain/res/layout/box_layout.xml
index 27a0ae1..cee2309 100644
--- a/glance/glance-appwidget/src/androidMain/res/layout/box_layout.xml
+++ b/glance/glance-appwidget/src/androidMain/res/layout/box_layout.xml
@@ -16,5 +16,5 @@
 
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@id/glanceView"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent" />
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content" />
diff --git a/glance/glance-appwidget/src/androidMain/res/layout/text_layout.xml b/glance/glance-appwidget/src/androidMain/res/layout/text_layout.xml
new file mode 100644
index 0000000..b627d38
--- /dev/null
+++ b/glance/glance-appwidget/src/androidMain/res/layout/text_layout.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.
+  -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@id/glanceView"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content" />
diff --git a/glance/glance-appwidget/src/test/AndroidManifest.xml b/glance/glance-appwidget/src/test/AndroidManifest.xml
new file mode 100644
index 0000000..3d41d53
--- /dev/null
+++ b/glance/glance-appwidget/src/test/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2021 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="androidx.glance.appwidget">
+    <uses-permission android:name="android.permission.BIND_APPWIDGET" />
+    <application>
+        <receiver
+            android:name="androidx.glance.appwidget.SampleAppWidgetProvider"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+            </intent-filter>
+            <meta-data android:name="android.appwidget.provider"
+                android:resource="@xml/appwidget_info" />
+        </receiver>
+
+    </application>
+</manifest>
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/GlanceAppWidgetTest.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/GlanceAppWidgetTest.kt
new file mode 100644
index 0000000..c0dda68
--- /dev/null
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/GlanceAppWidgetTest.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.glance.appwidget
+
+import android.content.Context
+import android.os.Bundle
+import android.widget.RelativeLayout
+import android.widget.TextView
+import androidx.compose.runtime.Composable
+import androidx.core.os.bundleOf
+import androidx.glance.GlanceInternalApi
+import androidx.glance.LocalSize
+import androidx.glance.layout.Text
+import androidx.glance.unit.DpSize
+import androidx.glance.unit.dp
+import androidx.test.core.app.ApplicationProvider
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import kotlin.test.assertIs
+
+@OptIn(GlanceInternalApi::class, ExperimentalCoroutinesApi::class)
+@RunWith(RobolectricTestRunner::class)
+class GlanceAppWidgetTest {
+
+    private lateinit var fakeCoroutineScope: TestCoroutineScope
+    private val context = ApplicationProvider.getApplicationContext<Context>()
+
+    @Before
+    fun setUp() {
+        fakeCoroutineScope = TestCoroutineScope()
+    }
+
+    @Test
+    fun createEmptyUI() = fakeCoroutineScope.runBlockingTest {
+        val composer = SampleGlanceAppWidget { }
+
+        val rv = composer.compose(context, 1, Bundle(), DpSize(40.dp, 50.dp))
+
+        val view = context.applyRemoteViews(rv)
+        assertIs<RelativeLayout>(view)
+        assertThat(view.childCount).isEqualTo(0)
+    }
+
+    @Test
+    fun createUiWithSize() = fakeCoroutineScope.runBlockingTest {
+        val composer = SampleGlanceAppWidget {
+            val size = LocalSize.current
+            Text("${size.width} x ${size.height}")
+        }
+
+        val rv = composer.compose(context, 1, Bundle(), DpSize(40.dp, 50.dp))
+
+        val view = context.applyRemoteViews(rv)
+        assertIs<TextView>(view)
+        assertThat(view.text).isEqualTo("40.0.dp x 50.0.dp")
+    }
+
+    @Test
+    fun createUiFromOptionBundle() = fakeCoroutineScope.runBlockingTest {
+        val composer = SampleGlanceAppWidget {
+            val options = LocalAppWidgetOptions.current
+
+            Text(options.getString("StringKey", "<NOT FOUND>"))
+        }
+
+        val bundle = Bundle()
+        bundle.putString("StringKey", "FOUND")
+        val rv = composer.compose(context, 1, bundle, DpSize(40.dp, 50.dp))
+
+        val view = context.applyRemoteViews(rv)
+        assertIs<TextView>(view)
+        assertThat(view.text).isEqualTo("FOUND")
+    }
+
+    @Test
+    fun createUiFromGlanceId() = fakeCoroutineScope.runBlockingTest {
+        val composer = SampleGlanceAppWidget {
+            val glanceId = LocalGlanceId.current
+
+            Text(glanceId.toString())
+        }
+
+        val bundle = bundleOf("StringKey" to "FOUND")
+        val rv = composer.compose(context, 1, bundle, DpSize(40.dp, 50.dp))
+
+        val view = context.applyRemoteViews(rv)
+        assertIs<TextView>(view)
+        assertThat(view.text).isEqualTo("AppWidgetId(appWidgetId=1)")
+    }
+
+    private class SampleGlanceAppWidget(val ui: @Composable () -> Unit) : GlanceAppWidget() {
+        @Composable
+        override fun Content() {
+            ui()
+        }
+    }
+}
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/RemoteViewsTranslatorKtTest.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/RemoteViewsTranslatorKtTest.kt
index 8b971c6..add37e7 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/RemoteViewsTranslatorKtTest.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/RemoteViewsTranslatorKtTest.kt
@@ -18,22 +18,36 @@
 
 import android.content.Context
 import android.content.res.Configuration
+import android.graphics.Typeface
+import android.os.Build
+import android.text.SpannedString
 import android.text.TextUtils
+import android.text.style.StrikethroughSpan
+import android.text.style.StyleSpan
+import android.text.style.TypefaceSpan
+import android.text.style.UnderlineSpan
 import android.util.TypedValue
 import android.view.Gravity
 import android.view.View
-import android.widget.FrameLayout
 import android.widget.RelativeLayout
 import android.widget.RemoteViews
+import android.widget.TextView
 import androidx.compose.runtime.Composable
 import androidx.glance.GlanceInternalApi
 import androidx.glance.Modifier
 import androidx.glance.layout.Alignment
 import androidx.glance.layout.Box
+import androidx.glance.layout.FontStyle
+import androidx.glance.layout.FontWeight
+import androidx.glance.layout.Text
+import androidx.glance.layout.TextDecoration
+import androidx.glance.layout.TextStyle
 import androidx.glance.layout.absolutePadding
 import androidx.glance.layout.padding
 import androidx.glance.unit.Dp
+import androidx.glance.unit.Sp
 import androidx.glance.unit.dp
+import androidx.glance.unit.sp
 import androidx.test.core.app.ApplicationProvider
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -43,8 +57,10 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
 import java.util.Locale
 import kotlin.math.floor
+import kotlin.test.assertIs
 
 @OptIn(GlanceInternalApi::class, ExperimentalCoroutinesApi::class)
 @RunWith(RobolectricTestRunner::class)
@@ -62,10 +78,9 @@
     @Test
     fun canTranslateBox() = fakeCoroutineScope.runBlockingTest {
         val rv = runAndTranslate { Box {} }
-        val view = applyRemoteViews(rv)
+        val view = context.applyRemoteViews(rv)
 
-        assertThat(view).isInstanceOf(RelativeLayout::class.java)
-        require(view is RelativeLayout)
+        assertIs<RelativeLayout>(view)
         assertThat(view.childCount).isEqualTo(0)
     }
 
@@ -74,10 +89,9 @@
         val rv = runAndTranslate {
             Box(contentAlignment = Alignment.BottomEnd) { }
         }
-        val view = applyRemoteViews(rv)
+        val view = context.applyRemoteViews(rv)
 
-        assertThat(view).isInstanceOf(RelativeLayout::class.java)
-        require(view is RelativeLayout)
+        assertIs<RelativeLayout>(view)
         assertThat(view.gravity).isEqualTo(Gravity.BOTTOM or Gravity.END)
     }
 
@@ -89,10 +103,9 @@
                 Box(contentAlignment = Alignment.BottomEnd) {}
             }
         }
-        val view = applyRemoteViews(rv)
+        val view = context.applyRemoteViews(rv)
 
-        assertThat(view).isInstanceOf(RelativeLayout::class.java)
-        require(view is RelativeLayout)
+        assertIs<RelativeLayout>(view)
         assertThat(view.childCount).isEqualTo(2)
         assertThat(view.getChildAt(0)).isInstanceOf(RelativeLayout::class.java)
         assertThat(view.getChildAt(1)).isInstanceOf(RelativeLayout::class.java)
@@ -108,10 +121,9 @@
             Box(contentAlignment = Alignment.Center) {}
             Box(contentAlignment = Alignment.BottomEnd) {}
         }
-        val view = applyRemoteViews(rv)
+        val view = context.applyRemoteViews(rv)
 
-        assertThat(view).isInstanceOf(RelativeLayout::class.java)
-        require(view is RelativeLayout)
+        assertIs<RelativeLayout>(view)
         assertThat(view.childCount).isEqualTo(2)
         assertThat(view.getChildAt(0)).isInstanceOf(RelativeLayout::class.java)
         assertThat(view.getChildAt(1)).isInstanceOf(RelativeLayout::class.java)
@@ -133,9 +145,9 @@
                 )
             ) { }
         }
-        val view = applyRemoteViews(rv)
+        val view = context.applyRemoteViews(rv)
 
-        assertThat(view).isInstanceOf(RelativeLayout::class.java)
+        assertIs<RelativeLayout>(view)
         assertThat(view.paddingLeft).isEqualTo(dpToPixel(4.dp))
         assertThat(view.paddingRight).isEqualTo(dpToPixel(5.dp))
         assertThat(view.paddingTop).isEqualTo(dpToPixel(6.dp))
@@ -154,9 +166,9 @@
                 )
             ) { }
         }
-        val view = applyRemoteViews(rv)
+        val view = context.applyRemoteViews(rv)
 
-        assertThat(view).isInstanceOf(RelativeLayout::class.java)
+        assertIs<RelativeLayout>(view)
         assertThat(view.paddingLeft).isEqualTo(dpToPixel(5.dp))
         assertThat(view.paddingRight).isEqualTo(dpToPixel(4.dp))
         assertThat(view.paddingTop).isEqualTo(dpToPixel(6.dp))
@@ -175,15 +187,151 @@
                 )
             ) { }
         }
-        val view = applyRemoteViews(rv)
+        val view = context.applyRemoteViews(rv)
 
-        assertThat(view).isInstanceOf(RelativeLayout::class.java)
+        assertIs<RelativeLayout>(view)
         assertThat(view.paddingLeft).isEqualTo(dpToPixel(4.dp))
         assertThat(view.paddingRight).isEqualTo(dpToPixel(5.dp))
         assertThat(view.paddingTop).isEqualTo(dpToPixel(6.dp))
         assertThat(view.paddingBottom).isEqualTo(dpToPixel(7.dp))
     }
 
+    @Test
+    fun canTranslateText() = fakeCoroutineScope.runBlockingTest {
+        val rv = runAndTranslate {
+            Text("test")
+        }
+        val view = context.applyRemoteViews(rv)
+
+        assertIs<TextView>(view)
+        assertThat(view.text.toString()).isEqualTo("test")
+    }
+
+    @Test
+    @Config(sdk = [23, 29])
+    fun canTranslateText_withStyleWeightAndSize() = fakeCoroutineScope.runBlockingTest {
+        val rv = runAndTranslate {
+            Text(
+                "test",
+                style = TextStyle(fontWeight = FontWeight.Medium, size = 12.sp),
+            )
+        }
+        val view = context.applyRemoteViews(rv)
+
+        assertIs<TextView>(view)
+        assertThat(view.textSize).isEqualTo(spToPixel(12.sp))
+        val content = view.text as SpannedString
+        assertThat(content.toString()).isEqualTo("test")
+        if (Build.VERSION.SDK_INT >= 28) {
+            content.checkSingleSpan<TypefaceSpan> {
+                assertThat(it.typeface).isEqualTo(Typeface.create(Typeface.DEFAULT, 500, false))
+            }
+        } else {
+            content.checkSingleSpan<StyleSpan> {
+                assertThat(it.style).isEqualTo(Typeface.BOLD)
+            }
+        }
+    }
+
+    @Test
+    fun canTranslateText_withStyleStrikeThrough() = fakeCoroutineScope.runBlockingTest {
+        val rv = runAndTranslate {
+            Text("test", style = TextStyle(textDecoration = TextDecoration.LineThrough))
+        }
+        val view = context.applyRemoteViews(rv)
+
+        assertIs<TextView>(view)
+        val content = view.text as SpannedString
+        assertThat(content.toString()).isEqualTo("test")
+        content.checkSingleSpan<StrikethroughSpan> { }
+    }
+
+    @Test
+    fun canTranslateText_withStyleUnderline() = fakeCoroutineScope.runBlockingTest {
+        val rv = runAndTranslate {
+            Text("test", style = TextStyle(textDecoration = TextDecoration.Underline))
+        }
+        val view = context.applyRemoteViews(rv)
+
+        assertIs<TextView>(view)
+        val content = view.text as SpannedString
+        assertThat(content.toString()).isEqualTo("test")
+        content.checkSingleSpan<UnderlineSpan> { }
+    }
+
+    @Test
+    @Config(sdk = [23, 29])
+    fun canTranslateText_withStyleItalic() = fakeCoroutineScope.runBlockingTest {
+        val rv = runAndTranslate {
+            Text("test", style = TextStyle(fontStyle = FontStyle.Italic))
+        }
+        val view = context.applyRemoteViews(rv)
+
+        assertIs<TextView>(view)
+        val content = view.text as SpannedString
+        assertThat(content.toString()).isEqualTo("test")
+        if (Build.VERSION.SDK_INT >= 28) {
+            content.checkSingleSpan<TypefaceSpan> {
+                assertThat(it.typeface).isEqualTo(Typeface.create(Typeface.DEFAULT, 400, true))
+            }
+        } else {
+            content.checkSingleSpan<StyleSpan> {
+                assertThat(it.style).isEqualTo(Typeface.ITALIC)
+            }
+        }
+    }
+
+    @Test
+    @Config(sdk = [23, 29])
+    fun canTranslateText_withComplexStyle() = fakeCoroutineScope.runBlockingTest {
+        val rv = runAndTranslate {
+            Text(
+                "test",
+                style = TextStyle(
+                    textDecoration = TextDecoration.Underline + TextDecoration.LineThrough,
+                    fontStyle = FontStyle.Italic,
+                    fontWeight = FontWeight.Bold,
+                ),
+            )
+        }
+        val view = context.applyRemoteViews(rv)
+
+        assertIs<TextView>(view)
+        val content = view.text as SpannedString
+        assertThat(content.toString()).isEqualTo("test")
+        assertThat(content.getSpans(0, content.length, Any::class.java)).hasLength(3)
+        content.checkHasSingleTypedSpan<UnderlineSpan> { }
+        content.checkHasSingleTypedSpan<StrikethroughSpan> { }
+        if (Build.VERSION.SDK_INT >= 28) {
+            content.checkHasSingleTypedSpan<TypefaceSpan> {
+                assertThat(it.typeface).isEqualTo(Typeface.create(Typeface.DEFAULT, 700, true))
+            }
+        } else {
+            content.checkHasSingleTypedSpan<StyleSpan> {
+                assertThat(it.style).isEqualTo(Typeface.BOLD_ITALIC)
+            }
+        }
+    }
+
+    // Check there is a single span, that it's of the correct type and passes the [check].
+    private inline fun <reified T> SpannedString.checkSingleSpan(check: (T) -> Unit) {
+        val spans = getSpans(0, length, Any::class.java)
+        assertThat(spans).hasLength(1)
+        checkInstance(spans[0], check)
+    }
+
+    // Check there is a single span of the given type and that it passes the [check].
+    private inline fun <reified T> SpannedString.checkHasSingleTypedSpan(check: (T) -> Unit) {
+        val spans = getSpans(0, length, T::class.java)
+        assertThat(spans).hasLength(1)
+        check(spans[0])
+    }
+
+    private inline fun <reified T> checkInstance(obj: Any, check: (T) -> Unit) {
+        assertIs<T>(obj)
+        check(obj)
+    }
+
     private suspend fun runAndTranslate(
         context: Context = this.context,
         content: @Composable () -> Unit
@@ -204,10 +352,11 @@
         return runAndTranslate(rtlContext, content)
     }
 
-    private fun applyRemoteViews(rv: RemoteViews) =
-        rv.apply(context, FrameLayout(context))
-
     private fun dpToPixel(dp: Dp) =
         floor(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp.value, displayMetrics))
             .toInt()
+
+    private fun spToPixel(sp: Sp) =
+        floor(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp.value, displayMetrics))
+            .toInt()
 }
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/SampleAppWidgetProvider.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/SampleAppWidgetProvider.kt
new file mode 100644
index 0000000..ab8c454
--- /dev/null
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/SampleAppWidgetProvider.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.glance.appwidget
+
+import android.appwidget.AppWidgetProvider
+
+class SampleAppWidgetProvider : AppWidgetProvider()
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/Utils.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/Utils.kt
index 3e4cd69..75b622f 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/Utils.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/Utils.kt
@@ -16,6 +16,11 @@
 
 package androidx.glance.appwidget
 
+import android.content.Context
+import android.os.Parcel
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.RemoteViews
 import androidx.compose.runtime.BroadcastFrameClock
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composition
@@ -44,3 +49,16 @@
 
         root
     }
+
+/** Create the view out of a RemoteViews. */
+internal fun Context.applyRemoteViews(rv: RemoteViews): View {
+    val p = Parcel.obtain()
+    return try {
+        rv.writeToParcel(p, 0)
+        p.setDataPosition(0)
+        val parceled = RemoteViews(p)
+        parceled.apply(this, FrameLayout(this))
+    } finally {
+        p.recycle()
+    }
+}
diff --git a/glance/glance-appwidget/src/test/res/layout/empty_layout.xml b/glance/glance-appwidget/src/test/res/layout/empty_layout.xml
new file mode 100644
index 0000000..0376b8a
--- /dev/null
+++ b/glance/glance-appwidget/src/test/res/layout/empty_layout.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/root_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" />
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/test/res/xml/appwidget_info.xml b/glance/glance-appwidget/src/test/res/xml/appwidget_info.xml
new file mode 100644
index 0000000..1f2f53d
--- /dev/null
+++ b/glance/glance-appwidget/src/test/res/xml/appwidget_info.xml
@@ -0,0 +1,23 @@
+<?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.
+  -->
+
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+    android:minWidth="40dp"
+    android:minHeight="40dp"
+    android:initialLayout="@layout/empty_layout"
+    android:resizeMode="horizontal|vertical"
+    android:widgetCategory="home_screen">
+</appwidget-provider>
diff --git a/glance/glance/api/current.txt b/glance/glance/api/current.txt
index 2fd4d39..dc8d168 100644
--- a/glance/glance/api/current.txt
+++ b/glance/glance/api/current.txt
@@ -9,6 +9,11 @@
     method public <R> R! foldOut(R? initial, kotlin.jvm.functions.Function2<? super androidx.glance.Modifier.Element,? super R,? extends R> operation);
   }
 
+  public final class CompositionLocalsKt {
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<android.content.Context> getLocalContext();
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.glance.unit.DpSize> getLocalSize();
+  }
+
   @androidx.compose.runtime.Stable public interface Modifier {
     method public boolean all(kotlin.jvm.functions.Function1<? super androidx.glance.Modifier.Element,java.lang.Boolean> predicate);
     method public boolean any(kotlin.jvm.functions.Function1<? super androidx.glance.Modifier.Element,java.lang.Boolean> predicate);
@@ -140,6 +145,8 @@
 
   public final inline class FontWeight {
     ctor public FontWeight();
+    method public int getValue();
+    property public final int value;
   }
 
   public static final class FontWeight.Companion {
diff --git a/glance/glance/api/public_plus_experimental_current.txt b/glance/glance/api/public_plus_experimental_current.txt
index 88fb690..11c36b9 100644
--- a/glance/glance/api/public_plus_experimental_current.txt
+++ b/glance/glance/api/public_plus_experimental_current.txt
@@ -18,6 +18,11 @@
     method public <R> R! foldOut(R? initial, kotlin.jvm.functions.Function2<? super androidx.glance.Modifier.Element,? super R,? extends R> operation);
   }
 
+  public final class CompositionLocalsKt {
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<android.content.Context> getLocalContext();
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.glance.unit.DpSize> getLocalSize();
+  }
+
   @androidx.glance.GlanceInternalApi public interface Emittable {
     method public androidx.glance.Modifier getModifier();
     method public void setModifier(androidx.glance.Modifier modifier);
@@ -31,7 +36,7 @@
     property public final java.util.List<androidx.glance.Emittable> children;
   }
 
-  @kotlin.RequiresOptIn(message="This API is used for the implementation of androidx.glance, and should " + "not be used by API consumers.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface GlanceInternalApi {
+  @kotlin.RequiresOptIn(message="This API is used for the implementation of androidx.glance, and should " + "not be used by API consumers.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface GlanceInternalApi {
   }
 
   @androidx.compose.runtime.Stable public interface Modifier {
@@ -232,6 +237,8 @@
 
   public final inline class FontWeight {
     ctor public FontWeight();
+    method public int getValue();
+    property public final int value;
   }
 
   public static final class FontWeight.Companion {
diff --git a/glance/glance/api/restricted_current.txt b/glance/glance/api/restricted_current.txt
index 2fd4d39..dc8d168 100644
--- a/glance/glance/api/restricted_current.txt
+++ b/glance/glance/api/restricted_current.txt
@@ -9,6 +9,11 @@
     method public <R> R! foldOut(R? initial, kotlin.jvm.functions.Function2<? super androidx.glance.Modifier.Element,? super R,? extends R> operation);
   }
 
+  public final class CompositionLocalsKt {
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<android.content.Context> getLocalContext();
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.glance.unit.DpSize> getLocalSize();
+  }
+
   @androidx.compose.runtime.Stable public interface Modifier {
     method public boolean all(kotlin.jvm.functions.Function1<? super androidx.glance.Modifier.Element,java.lang.Boolean> predicate);
     method public boolean any(kotlin.jvm.functions.Function1<? super androidx.glance.Modifier.Element,java.lang.Boolean> predicate);
@@ -140,6 +145,8 @@
 
   public final inline class FontWeight {
     ctor public FontWeight();
+    method public int getValue();
+    property public final int value;
   }
 
   public static final class FontWeight.Companion {
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/CompositionLocals.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/CompositionLocals.kt
new file mode 100644
index 0000000..ee7142a
--- /dev/null
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/CompositionLocals.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.glance
+
+import android.content.Context
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.glance.unit.DpSize
+
+/**
+ * Size of the glance view being generated.
+ *
+ * The glance view will have at least that much space to be displayed. The exact meaning may
+ * changed depending on the surface and how it is configured.
+ */
+public val LocalSize = staticCompositionLocalOf<DpSize> { error("No default size") }
+
+/**
+ * Context of application when generating the glance view.
+ */
+public val LocalContext = staticCompositionLocalOf<Context> { error("No default context") }
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/GlanceInternalApi.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/GlanceInternalApi.kt
index 2efdf25..9b73cfd 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/GlanceInternalApi.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/GlanceInternalApi.kt
@@ -10,5 +10,5 @@
         "not be used by API consumers."
 )
 @Retention(AnnotationRetention.BINARY)
-@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
+@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER)
 public annotation class GlanceInternalApi
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/layout/Text.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/layout/Text.kt
index d93cc4cc..86160e2 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/layout/Text.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/layout/Text.kt
@@ -84,7 +84,10 @@
  * Weight of a font.
  */
 @Suppress("INLINE_CLASS_DEPRECATED")
-public inline class FontWeight private constructor(private val weight: Int) {
+public inline class FontWeight private constructor(
+    /** numerical value for the weight (a number from 0 to 1000) **/
+    val value: Int,
+) {
     public companion object {
         public val Normal: FontWeight = FontWeight(400)
         public val Medium: FontWeight = FontWeight(500)
diff --git a/mediarouter/mediarouter/api/current.txt b/mediarouter/mediarouter/api/current.txt
index eac4d53..c47a66d 100644
--- a/mediarouter/mediarouter/api/current.txt
+++ b/mediarouter/mediarouter/api/current.txt
@@ -354,6 +354,7 @@
     method public void removeCallback(androidx.mediarouter.media.MediaRouter.Callback);
     method public void removeProvider(androidx.mediarouter.media.MediaRouteProvider);
     method public void removeRemoteControlClient(Object);
+    method @VisibleForTesting public static void reset();
     method public void selectRoute(androidx.mediarouter.media.MediaRouter.RouteInfo);
     method public void setMediaSession(Object?);
     method public void setMediaSessionCompat(android.support.v4.media.session.MediaSessionCompat?);
diff --git a/mediarouter/mediarouter/api/public_plus_experimental_current.txt b/mediarouter/mediarouter/api/public_plus_experimental_current.txt
index eac4d53..c47a66d 100644
--- a/mediarouter/mediarouter/api/public_plus_experimental_current.txt
+++ b/mediarouter/mediarouter/api/public_plus_experimental_current.txt
@@ -354,6 +354,7 @@
     method public void removeCallback(androidx.mediarouter.media.MediaRouter.Callback);
     method public void removeProvider(androidx.mediarouter.media.MediaRouteProvider);
     method public void removeRemoteControlClient(Object);
+    method @VisibleForTesting public static void reset();
     method public void selectRoute(androidx.mediarouter.media.MediaRouter.RouteInfo);
     method public void setMediaSession(Object?);
     method public void setMediaSessionCompat(android.support.v4.media.session.MediaSessionCompat?);
diff --git a/mediarouter/mediarouter/api/restricted_current.txt b/mediarouter/mediarouter/api/restricted_current.txt
index eac4d53..c47a66d 100644
--- a/mediarouter/mediarouter/api/restricted_current.txt
+++ b/mediarouter/mediarouter/api/restricted_current.txt
@@ -354,6 +354,7 @@
     method public void removeCallback(androidx.mediarouter.media.MediaRouter.Callback);
     method public void removeProvider(androidx.mediarouter.media.MediaRouteProvider);
     method public void removeRemoteControlClient(Object);
+    method @VisibleForTesting public static void reset();
     method public void selectRoute(androidx.mediarouter.media.MediaRouter.RouteInfo);
     method public void setMediaSession(Object?);
     method public void setMediaSessionCompat(android.support.v4.media.session.MediaSessionCompat?);
diff --git a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouter2Test.java b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouter2Test.java
index d18c5e8..dd77f89 100644
--- a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouter2Test.java
+++ b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouter2Test.java
@@ -122,6 +122,7 @@
                 mRouter.removeCallback(callback);
             }
             mCallbacks.clear();
+            MediaRouter.reset();
         });
         MediaRouter2TestActivity.finishActivity();
     }
diff --git a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterTest.java b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterTest.java
index ad9c2b2..9ecd575 100644
--- a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterTest.java
+++ b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterTest.java
@@ -22,6 +22,8 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
@@ -83,6 +85,12 @@
     @After
     public void tearDown() throws Exception {
         mSession.release();
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                MediaRouter.reset();
+            }
+        });
     }
 
     /**
@@ -263,6 +271,20 @@
         assertTrue(mPassiveScanCountDownLatch.await(1000 + TIME_OUT_MS, TimeUnit.MILLISECONDS));
     }
 
+    @Test
+    @UiThreadTest
+    public void testReset() {
+        assertNotNull(mRouter);
+        assertNotNull(MediaRouter.getGlobalRouter());
+
+        MediaRouter.reset();
+        assertNull(MediaRouter.getGlobalRouter());
+
+        MediaRouter newInstance = MediaRouter.getInstance(mContext);
+        assertNotNull(MediaRouter.getGlobalRouter());
+        assertFalse(newInstance.getRoutes().isEmpty());
+    }
+
     /**
      * Asserts that two Bundles are equal.
      */
@@ -311,8 +333,9 @@
 
         @Override
         public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest discoveryRequest) {
-            if (mIsActiveScan != discoveryRequest.isActiveScan()) {
-                mIsActiveScan = discoveryRequest.isActiveScan();
+            boolean isActiveScan = discoveryRequest != null && discoveryRequest.isActiveScan();
+            if (mIsActiveScan != isActiveScan) {
+                mIsActiveScan = isActiveScan;
                 if (mIsActiveScan) {
                     mActiveScanCountDownLatch.countDown();
                 } else {
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
index 9f408c1..617daf6 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
@@ -294,6 +294,32 @@
     }
 
     /**
+     * Resets all internal state for testing.
+     * <p>
+     * After calling this method, the caller should stop using the existing media router instances.
+     * Instead, the caller should create a new media router instance again by calling
+     * {@link #getInstance(Context)}.
+     * <p>
+     * Note that the following classes' instances need to be recreated after calling this method,
+     * as these classes store the media router instance on their constructor:
+     * <ul>
+     *     <li>{@link androidx.mediarouter.app.MediaRouteActionProvider}
+     *     <li>{@link androidx.mediarouter.app.MediaRouteButton}
+     *     <li>{@link androidx.mediarouter.app.MediaRouteChooserDialog}
+     *     <li>{@link androidx.mediarouter.app.MediaRouteControllerDialog}
+     *     <li>{@link androidx.mediarouter.app.MediaRouteDiscoveryFragment}
+     * </ul>
+     */
+    @VisibleForTesting
+    public static void reset() {
+        if (sGlobal == null) {
+            return;
+        }
+        sGlobal.reset();
+        sGlobal = null;
+    }
+
+    /**
      * Gets the initialized global router.
      * Please make sure this is called in the main thread.
      */
@@ -2498,6 +2524,25 @@
             mRegisteredProviderWatcher.start();
         }
 
+        void reset() {
+            if (!mIsInitialized) {
+                return;
+            }
+            mRegisteredProviderWatcher.stop();
+            mActiveScanThrottlingHelper.reset();
+
+            setMediaSessionCompat(null);
+            for (RemoteControlClientRecord record : mRemoteControlClients) {
+                record.disconnect();
+            }
+
+            List<ProviderInfo> providers = new ArrayList<>(mProviders);
+            for (ProviderInfo providerInfo : providers) {
+                removeProvider(providerInfo.mProviderInstance);
+            }
+            mCallbackHandler.removeCallbacksAndMessages(null);
+        }
+
         public MediaRouter getRouter(Context context) {
             MediaRouter router;
             for (int i = mRouters.size(); --i >= 0; ) {
diff --git a/navigation/navigation-common/api/current.txt b/navigation/navigation-common/api/current.txt
index 66b0df7..8c34529 100644
--- a/navigation/navigation-common/api/current.txt
+++ b/navigation/navigation-common/api/current.txt
@@ -486,7 +486,7 @@
     method public final kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.navigation.NavBackStackEntry>> getBackStack();
     method public final kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.navigation.NavBackStackEntry>> getTransitionsInProgress();
     method public void markTransitionComplete(androidx.navigation.NavBackStackEntry entry);
-    method public void onLaunchSingleTop();
+    method @CallSuper public void onLaunchSingleTop(androidx.navigation.NavBackStackEntry backStackEntry);
     method public void pop(androidx.navigation.NavBackStackEntry popUpTo, boolean saveState);
     method public void popWithTransition(androidx.navigation.NavBackStackEntry popUpTo, boolean saveState);
     method public void push(androidx.navigation.NavBackStackEntry backStackEntry);
diff --git a/navigation/navigation-common/api/public_plus_experimental_current.txt b/navigation/navigation-common/api/public_plus_experimental_current.txt
index 66b0df7..8c34529 100644
--- a/navigation/navigation-common/api/public_plus_experimental_current.txt
+++ b/navigation/navigation-common/api/public_plus_experimental_current.txt
@@ -486,7 +486,7 @@
     method public final kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.navigation.NavBackStackEntry>> getBackStack();
     method public final kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.navigation.NavBackStackEntry>> getTransitionsInProgress();
     method public void markTransitionComplete(androidx.navigation.NavBackStackEntry entry);
-    method public void onLaunchSingleTop();
+    method @CallSuper public void onLaunchSingleTop(androidx.navigation.NavBackStackEntry backStackEntry);
     method public void pop(androidx.navigation.NavBackStackEntry popUpTo, boolean saveState);
     method public void popWithTransition(androidx.navigation.NavBackStackEntry popUpTo, boolean saveState);
     method public void push(androidx.navigation.NavBackStackEntry backStackEntry);
diff --git a/navigation/navigation-common/api/restricted_current.txt b/navigation/navigation-common/api/restricted_current.txt
index 66b0df7..8c34529 100644
--- a/navigation/navigation-common/api/restricted_current.txt
+++ b/navigation/navigation-common/api/restricted_current.txt
@@ -486,7 +486,7 @@
     method public final kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.navigation.NavBackStackEntry>> getBackStack();
     method public final kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.navigation.NavBackStackEntry>> getTransitionsInProgress();
     method public void markTransitionComplete(androidx.navigation.NavBackStackEntry entry);
-    method public void onLaunchSingleTop();
+    method @CallSuper public void onLaunchSingleTop(androidx.navigation.NavBackStackEntry backStackEntry);
     method public void pop(androidx.navigation.NavBackStackEntry popUpTo, boolean saveState);
     method public void popWithTransition(androidx.navigation.NavBackStackEntry popUpTo, boolean saveState);
     method public void push(androidx.navigation.NavBackStackEntry backStackEntry);
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavBackStackEntry.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavBackStackEntry.kt
index d7eec40..b6b6415 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavBackStackEntry.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavBackStackEntry.kt
@@ -54,9 +54,8 @@
      * The arguments used for this entry
      * @return The arguments used when this entry was created
      */
-    @set:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public var arguments: Bundle? = null,
-    navControllerLifecycleOwner: LifecycleOwner? = null,
+    public val arguments: Bundle? = null,
+    private val navControllerLifecycleOwner: LifecycleOwner? = null,
     private val viewModelStoreProvider: NavViewModelStoreProvider? = null,
     /**
      * The unique ID that serves as the identity of this entry
@@ -69,6 +68,20 @@
     HasDefaultViewModelProviderFactory,
     SavedStateRegistryOwner {
 
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    constructor(entry: NavBackStackEntry, arguments: Bundle? = entry.arguments) : this(
+        entry.context,
+        entry.destination,
+        arguments,
+        entry.navControllerLifecycleOwner,
+        entry.viewModelStoreProvider,
+        entry.id,
+        entry.savedState
+    ) {
+        hostLifecycleState = entry.hostLifecycleState
+        maxLifecycle = entry.maxLifecycle
+    }
+
     /**
      * @hide
      */
@@ -113,12 +126,6 @@
         ).get(SavedStateViewModel::class.java).handle
     }
 
-    /** @suppress */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public fun replaceArguments(newArgs: Bundle?) {
-        arguments = newArgs
-    }
-
     /**
      * {@inheritDoc}
      *
@@ -203,7 +210,7 @@
             (
                 arguments == other.arguments ||
                     arguments?.keySet()
-                    ?.all { arguments!!.get(it) == other.arguments?.get(it) } == true
+                    ?.all { arguments.get(it) == other.arguments?.get(it) } == true
                 )
     }
 
@@ -211,7 +218,7 @@
         var result = id.hashCode()
         result = 31 * result + destination.hashCode()
         arguments?.keySet()?.forEach {
-            result = 31 * result + arguments!!.get(it).hashCode()
+            result = 31 * result + arguments.get(it).hashCode()
         }
         return result
     }
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/Navigator.kt b/navigation/navigation-common/src/main/java/androidx/navigation/Navigator.kt
index 85733bf..95a9803 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/Navigator.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/Navigator.kt
@@ -122,10 +122,10 @@
                 null -> null
                 destination -> backStackEntry
                 else -> {
-                    backStackEntry.replaceArguments(
+                    state.createBackStackEntry(
+                        navigatedToDestination,
                         navigatedToDestination.addInDefaultArgs(backStackEntry.arguments)
                     )
-                    state.createBackStackEntry(navigatedToDestination, backStackEntry.arguments)
                 }
             }
         }.filterNotNull().forEach { backStackEntry ->
@@ -135,15 +135,15 @@
 
     /**
      * Informational callback indicating that the given [backStackEntry] has been
-     * affected by a [NavOptions.shouldLaunchSingleTop] operation. The entry's state
-     * and arguments have already been updated, but this callback can be used to synchronize
-     * any external state with this operation.
+     * affected by a [NavOptions.shouldLaunchSingleTop] operation. The entry provided is a new
+     * [NavBackStackEntry] instance with all the previous state of the old entry and possibly
+     * new arguments.
      */
     @Suppress("UNCHECKED_CAST")
     public open fun onLaunchSingleTop(backStackEntry: NavBackStackEntry) {
         val destination = backStackEntry.destination as? D ?: return
         navigate(destination, null, navOptions { launchSingleTop = true }, null)
-        state.onLaunchSingleTop()
+        state.onLaunchSingleTop(backStackEntry)
     }
 
     /**
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavigatorState.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavigatorState.kt
index 8267325..2bc7062 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavigatorState.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavigatorState.kt
@@ -17,6 +17,7 @@
 package androidx.navigation
 
 import android.os.Bundle
+import androidx.annotation.CallSuper
 import androidx.annotation.RestrictTo
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -133,15 +134,16 @@
         pop(popUpTo, saveState)
     }
 
-    public open fun onLaunchSingleTop() {
-        // We need to create a new object and change the value of the back stack to something
-        // different so that Kotlin allows it to be a new object with the same values.
-        // TODO: We should change to just assign to itself this once b/196267358 is addressed
-        val updatedBackStack = mutableListOf<NavBackStackEntry>().apply {
-            addAll(_backStack.value)
-        }
+    /**
+     * Informational callback indicating that the given [backStackEntry] has been
+     * affected by a [NavOptions.shouldLaunchSingleTop] operation.
+     */
+    @CallSuper
+    public open fun onLaunchSingleTop(backStackEntry: NavBackStackEntry) {
+        // We update the back stack here because we don't want to leave it to the navigator since
+        // it might be using transitions.
         _backStack.value = _backStack.value - _backStack.value.last()
-        _backStack.value = updatedBackStack
+        _backStack.value = _backStack.value + backStackEntry
     }
 
     /**
diff --git a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostTest.kt b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostTest.kt
index a278702..bcca916 100644
--- a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostTest.kt
+++ b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostTest.kt
@@ -44,12 +44,14 @@
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
+import androidx.lifecycle.viewmodel.compose.viewModel
 import androidx.navigation.NavGraph
 import androidx.navigation.NavGraph.Companion.findStartDestination
 import androidx.navigation.NavHostController
 import androidx.navigation.contains
 import androidx.navigation.createGraph
 import androidx.navigation.navDeepLink
+import androidx.navigation.navigation
 import androidx.navigation.plusAssign
 import androidx.navigation.testing.TestNavHostController
 import androidx.savedstate.SavedStateRegistry
@@ -609,6 +611,38 @@
         composeTestRule.onNodeWithText("test").assertExists()
     }
 
+    @Test
+    fun testGetGraphViewModel() {
+        lateinit var navController: NavHostController
+        lateinit var model: TestViewModel
+
+        composeTestRule.setContent {
+            navController = rememberNavController()
+            NavHost(navController, first) {
+                composable(first) { }
+                navigation(second, "subGraph") {
+                    composable(second) {
+                        model = viewModel(remember { navController.getBackStackEntry("subGraph") })
+                    }
+                }
+            }
+        }
+
+        composeTestRule.runOnIdle {
+            navController.navigate(second)
+        }
+
+        composeTestRule.runOnIdle {
+            navController.popBackStack()
+        }
+
+        assertThat(model.wasCleared).isFalse()
+
+        composeTestRule.waitForIdle()
+
+        assertThat(model.wasCleared).isTrue()
+    }
+
     private fun createNavController(context: Context): TestNavHostController {
         val navController = TestNavHostController(context)
         val navigator = TestNavigator()
@@ -622,4 +656,10 @@
 
 class TestViewModel : ViewModel() {
     var value: String = "nothing"
+    var wasCleared = false
+
+    override fun onCleared() {
+        super.onCleared()
+        wasCleared = true
+    }
 }
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavBackStackEntryLifecycleTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavBackStackEntryLifecycleTest.kt
index f276f45..3010d9a 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavBackStackEntryLifecycleTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavBackStackEntryLifecycleTest.kt
@@ -392,6 +392,41 @@
         )
 
         inOrder.verifyNoMoreInteractions()
+
+        navController.popBackStack()
+
+        inOrder.verify(nestedObserver).onStateChanged(
+            nestedBackStackEntry, Lifecycle.Event.ON_PAUSE
+        )
+        inOrder.verify(nestedObserver).onStateChanged(
+            nestedBackStackEntry, Lifecycle.Event.ON_STOP
+        )
+        inOrder.verify(nestedObserver).onStateChanged(
+            nestedBackStackEntry, Lifecycle.Event.ON_DESTROY
+        )
+
+        inOrder.verify(nestedGraphObserver).onStateChanged(
+            nestedGraphBackStackEntry, Lifecycle.Event.ON_PAUSE
+        )
+        inOrder.verify(nestedGraphObserver).onStateChanged(
+            nestedGraphBackStackEntry, Lifecycle.Event.ON_STOP
+        )
+
+        inOrder.verify(nestedGraphObserver).onStateChanged(
+            nestedGraphBackStackEntry, Lifecycle.Event.ON_DESTROY
+        )
+
+        inOrder.verify(graphObserver).onStateChanged(
+            graphBackStackEntry, Lifecycle.Event.ON_PAUSE
+        )
+        inOrder.verify(graphObserver).onStateChanged(
+            graphBackStackEntry, Lifecycle.Event.ON_STOP
+        )
+        inOrder.verify(graphObserver).onStateChanged(
+            graphBackStackEntry, Lifecycle.Event.ON_DESTROY
+        )
+
+        inOrder.verifyNoMoreInteractions()
     }
 
     /**
@@ -594,6 +629,60 @@
             .isEqualTo(Lifecycle.State.RESUMED)
     }
 
+    @Suppress("DEPRECATION")
+    @UiThreadTest
+    @Test
+    fun testLifecyclePoppedGraph() {
+        val navController = createNavController()
+        val navGraph = navController.navigatorProvider.navigation(
+            id = 1,
+            startDestination = R.id.nested
+        ) {
+            navigation(id = R.id.nested, startDestination = R.id.nested_test) {
+                test(R.id.nested_test)
+            }
+            test(R.id.second_test)
+        }
+        navController.graph = navGraph
+
+        val graphBackStackEntry = navController.getBackStackEntry(navGraph.id)
+        assertWithMessage("The parent graph should be resumed when its child is resumed")
+            .that(graphBackStackEntry.lifecycle.currentState)
+            .isEqualTo(Lifecycle.State.RESUMED)
+        val nestedGraphBackStackEntry = navController.getBackStackEntry(R.id.nested)
+        assertWithMessage("The nested graph should be resumed when its child is resumed")
+            .that(nestedGraphBackStackEntry.lifecycle.currentState)
+            .isEqualTo(Lifecycle.State.RESUMED)
+        val nestedBackStackEntry = navController.getBackStackEntry(R.id.nested_test)
+        assertWithMessage("The nested start destination should be resumed")
+            .that(nestedBackStackEntry.lifecycle.currentState)
+            .isEqualTo(Lifecycle.State.RESUMED)
+
+        navController.navigate(
+            R.id.second_test,
+            null,
+            navOptions {
+                popUpTo(R.id.nested) {
+                    inclusive = true
+                }
+            }
+        )
+
+        assertWithMessage("The parent graph should be resumed when its child is resumed")
+            .that(graphBackStackEntry.lifecycle.currentState)
+            .isEqualTo(Lifecycle.State.RESUMED)
+        assertWithMessage("The nested graph should be destroyed when its children are destroyed")
+            .that(nestedGraphBackStackEntry.lifecycle.currentState)
+            .isEqualTo(Lifecycle.State.DESTROYED)
+        assertWithMessage("The nested start destination should be destroyed after being popped")
+            .that(nestedBackStackEntry.lifecycle.currentState)
+            .isEqualTo(Lifecycle.State.DESTROYED)
+        val secondBackStackEntry = navController.getBackStackEntry(R.id.second_test)
+        assertWithMessage("The new destination should be resumed")
+            .that(secondBackStackEntry.lifecycle.currentState)
+            .isEqualTo(Lifecycle.State.RESUMED)
+    }
+
     /**
      * Test that navigating to a new instance of a graph leaves the previous instance in its
      * current state.
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
index c24cf4c..00920d9 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
@@ -1765,6 +1765,7 @@
         args.putString(testKey, testValue)
 
         var destinationListenerExecuted = false
+        val currentBackStackEntry = navController.currentBackStackEntry
 
         navController.navigate(R.id.self, args)
 
@@ -1780,6 +1781,9 @@
         val returnedArgs = navigator.current.arguments
         assertThat(returnedArgs?.getString(testKey)).isEqualTo(testValue)
         assertThat(destinationListenerExecuted).isTrue()
+        assertThat(navController.currentBackStackEntry).isNotSameInstanceAs(
+            currentBackStackEntry
+        )
     }
 
     @UiThreadTest
@@ -1798,6 +1802,7 @@
         args.putString(testKey, testValue)
 
         var destinationListenerExecuted = false
+        val currentBackStackEntry = navController.currentBackStackEntry
 
         navController.navigate(
             R.id.start_test, args,
@@ -1818,6 +1823,9 @@
         val returnedArgs = navigator.current.arguments
         assertThat(returnedArgs?.getString(testKey)).isEqualTo(testValue)
         assertThat(destinationListenerExecuted).isTrue()
+        assertThat(navController.currentBackStackEntry).isNotSameInstanceAs(
+            currentBackStackEntry
+        )
     }
 
     @UiThreadTest
@@ -1839,6 +1847,7 @@
         args.putString(testKey, testValue)
 
         var destinationListenerExecuted = false
+        val currentBackStackEntry = navController.currentBackStackEntry
 
         navController.navigate(
             R.id.start_test_with_default_arg, args,
@@ -1862,6 +1871,9 @@
         assertThat(returnedArgs?.getString(testKey)).isEqualTo(testValue)
         assertThat(returnedArgs?.getBoolean("defaultArg", false)).isTrue()
         assertThat(destinationListenerExecuted).isTrue()
+        assertThat(navController.currentBackStackEntry).isNotSameInstanceAs(
+            currentBackStackEntry
+        )
     }
 
     @UiThreadTest
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
index bef31737..61f01537 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
@@ -40,12 +40,14 @@
 import androidx.lifecycle.ViewModelStore
 import androidx.lifecycle.ViewModelStoreOwner
 import androidx.navigation.NavDestination.Companion.createRoute
+import androidx.navigation.NavDestination.Companion.hierarchy
 import androidx.navigation.NavGraph.Companion.findStartDestination
 import kotlinx.coroutines.channels.BufferOverflow
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.asSharedFlow
 import java.util.concurrent.CopyOnWriteArrayList
+import java.util.concurrent.atomic.AtomicInteger
 
 /**
  * NavController manages app navigation within a [NavHost].
@@ -107,6 +109,29 @@
      */
     @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public open val backQueue: ArrayDeque<NavBackStackEntry> = ArrayDeque()
+    private val childToParentEntries = mutableMapOf<NavBackStackEntry, NavBackStackEntry>()
+    private val parentToChildCount = mutableMapOf<NavBackStackEntry, AtomicInteger>()
+
+    private fun linkChildToParent(child: NavBackStackEntry, parent: NavBackStackEntry) {
+        childToParentEntries[child] = parent
+        if (parentToChildCount[parent] == null) {
+            parentToChildCount[parent] = AtomicInteger(0)
+        }
+        parentToChildCount[parent]!!.incrementAndGet()
+    }
+
+    internal fun unlinkChildFromParent(child: NavBackStackEntry): NavBackStackEntry? {
+        val parent = childToParentEntries.remove(child) ?: return null
+        val count = parentToChildCount[parent]?.decrementAndGet()
+        if (count == 0) {
+            val navGraphNavigator: Navigator<out NavGraph> =
+                _navigatorProvider[parent.destination.navigatorName]
+            navigatorState[navGraphNavigator]?.markTransitionComplete(parent)
+            parentToChildCount.remove(parent)
+        }
+        return parent
+    }
+
     private val backStackMap = mutableMapOf<Int, String?>()
     private val backStackStates = mutableMapOf<String, ArrayDeque<NavBackStackEntryState>>()
     private var lifecycleOwner: LifecycleOwner? = null
@@ -280,9 +305,12 @@
             super.markTransitionComplete(entry)
             entrySavedState.remove(entry)
             if (!backQueue.contains(entry)) {
+                unlinkChildFromParent(entry)
                 // If the entry is no longer part of the backStack, we need to manually move
                 // it to DESTROYED, and clear its view model
-                entry.maxLifecycle = Lifecycle.State.DESTROYED
+                if (entry.lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
+                    entry.maxLifecycle = Lifecycle.State.DESTROYED
+                }
                 if (!savedState) {
                     viewModel?.clear(entry.id)
                 }
@@ -591,7 +619,11 @@
         val navigator = navigatorProvider
             .getNavigator<Navigator<NavDestination>>(entry.destination.navigatorName)
         val state = navigatorState[navigator]
-        val transitioning = state?.transitionsInProgress?.value?.contains(entry)
+        // If we pop an entry with transitions, but not the graph, we will not make a call to
+        // popBackStackInternal, so the graph entry will not be marked as transitioning so we
+        // need to check if it still has children.
+        val transitioning = state?.transitionsInProgress?.value?.contains(entry) == true ||
+            parentToChildCount.containsKey(entry)
         if (entry.lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
             if (saveState) {
                 // Move the state through STOPPED
@@ -599,13 +631,14 @@
                 // Then save the state of the NavBackStackEntry
                 savedState.addFirst(NavBackStackEntryState(entry))
             }
-            if (transitioning != true) {
+            if (!transitioning) {
                 entry.maxLifecycle = Lifecycle.State.DESTROYED
+                unlinkChildFromParent(entry)
             } else {
                 entry.maxLifecycle = Lifecycle.State.CREATED
             }
         }
-        if (!saveState && transitioning != true) {
+        if (!saveState && !transitioning) {
             viewModel?.clear(entry.id)
         }
     }
@@ -761,11 +794,8 @@
      */
     private fun dispatchOnDestinationChanged(): Boolean {
         // We never want to leave NavGraphs on the top of the stack
-        while (!backQueue.isEmpty() &&
-            backQueue.last().destination is NavGraph &&
-            popBackStackInternal(backQueue.last().destination.id, true)
-        ) {
-            // Keep popping
+        while (!backQueue.isEmpty() && backQueue.last().destination is NavGraph) {
+            popEntryFromBackStack(backQueue.last())
         }
         val lastBackStackEntry = backQueue.lastOrNull()
         if (lastBackStackEntry != null) {
@@ -840,7 +870,7 @@
                         .getNavigator<Navigator<*>>(entry.destination.navigatorName)
                     val state = navigatorState[navigator]
                     val transitioning = state?.transitionsInProgress?.value?.contains(entry)
-                    if (transitioning != true) {
+                    if (transitioning != true && parentToChildCount[entry]?.get() != 0) {
                         upwardStateTransitions[entry] = Lifecycle.State.RESUMED
                     } else {
                         upwardStateTransitions[entry] = Lifecycle.State.STARTED
@@ -1527,18 +1557,6 @@
             }
         }
         val finalArgs = node.addInDefaultArgs(args)
-        val currentBackStackEntry = currentBackStackEntry
-        if (navOptions?.shouldLaunchSingleTop() == true &&
-            node.id == currentBackStackEntry?.destination?.id
-        ) {
-            // Single top operations don't change the back stack, they just update arguments
-            launchSingleTop = true
-            currentBackStackEntry.replaceArguments(finalArgs)
-            val navigator = _navigatorProvider.getNavigator<Navigator<NavDestination>>(
-                node.navigatorName
-            )
-            navigator.onLaunchSingleTop(currentBackStackEntry)
-        }
         // Now determine what new destinations we need to add to the back stack
         if (navOptions?.shouldRestoreState() == true && backStackMap.containsKey(node.id)) {
             val backStackId = backStackMap[node.id]
@@ -1587,17 +1605,27 @@
                     addEntryToBackStack(lastDestination, finalArgs, entry, restoredEntries)
                 }
             }
-        } else if (!launchSingleTop) {
-            // Not a single top operation, so we're looking to add the node to the back stack
-            val backStackEntry = NavBackStackEntry.create(
-                context, node, finalArgs, lifecycleOwner, viewModel
-            )
+        } else {
+            val currentBackStackEntry = currentBackStackEntry
             val navigator = _navigatorProvider.getNavigator<Navigator<NavDestination>>(
                 node.navigatorName
             )
-            navigator.navigateInternal(listOf(backStackEntry), navOptions, navigatorExtras) {
-                navigated = true
-                addEntryToBackStack(node, finalArgs, it)
+            if (navOptions?.shouldLaunchSingleTop() == true &&
+                node.id == currentBackStackEntry?.destination?.id
+            ) {
+                backQueue.removeLast()
+                val newEntry = NavBackStackEntry(currentBackStackEntry, finalArgs)
+                backQueue.addLast(newEntry)
+                navigator.onLaunchSingleTop(newEntry)
+            } else {
+                // Not a single top operation, so we're looking to add the node to the back stack
+                val backStackEntry = NavBackStackEntry.create(
+                    context, node, finalArgs, lifecycleOwner, viewModel
+                )
+                navigator.navigateInternal(listOf(backStackEntry), navOptions, navigatorExtras) {
+                    navigated = true
+                    addEntryToBackStack(node, finalArgs, it)
+                }
             }
         }
         updateOnBackPressedCallbackEnabled()
@@ -1667,7 +1695,7 @@
                     hierarchy.addFirst(entry)
                     // Pop any orphaned copy of that navigation graph off the back stack
                     if (backQueue.isNotEmpty() && backQueue.last().destination === parent) {
-                        popBackStackInternal(parent.id, true)
+                        popEntryFromBackStack(backQueue.last())
                     }
                 }
                 destination = parent
@@ -1698,9 +1726,9 @@
         while (!backQueue.isEmpty() && backQueue.last().destination is NavGraph &&
             (backQueue.last().destination as NavGraph).findNode(
                     overlappingDestination.id, false
-                ) == null && popBackStackInternal(backQueue.last().destination.id, true)
+                ) == null
         ) {
-            // Keep popping
+            popEntryFromBackStack(backQueue.last())
         }
 
         // The _graph should always be on the top of the back stack after you navigate()
@@ -1728,6 +1756,15 @@
 
         // And finally, add the new destination
         backQueue.add(backStackEntry)
+
+        // Link the newly added hierarchy and entry with the parent NavBackStackEntry
+        // so that we can track how many destinations are associated with each NavGraph
+        (hierarchy + backStackEntry).forEach {
+            val parent = it.destination.parent
+            if (parent != null) {
+                linkChildToParent(it, getBackStackEntry(parent.id))
+            }
+        }
     }
 
     /**
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 590e787..1f308a1 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
@@ -1880,7 +1880,6 @@
         scrollInOtherOrientationTest(FLAG_VERTICAL | FLAG_FLING);
     }
 
-    @FlakyTest(bugId = 195936088)
     @SuppressWarnings("WrongConstant")
     @Test
     public void nestedDragVertical() throws Throwable {
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/TouchUtils.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/TouchUtils.java
index eaead8f..8ba182c 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/TouchUtils.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/TouchUtils.java
@@ -305,15 +305,15 @@
         MotionEvent event = MotionEvent.obtain(downTime, eventTime,
                 MotionEvent.ACTION_DOWN, x, y, 0);
         inst.sendPointerSync(event);
+
         for (int i = 0; i < stepCount; ++i) {
             y += yStep;
             x += xStep;
-            eventTime = SystemClock.uptimeMillis();
+            eventTime++;
             event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
             inst.sendPointerSync(event);
         }
-
-        eventTime = SystemClock.uptimeMillis();
+        eventTime++;
         event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
         inst.sendPointerSync(event);
         if (waitForIdleSync) {
diff --git a/slidingpanelayout/slidingpanelayout/api/1.2.0-beta01.txt b/slidingpanelayout/slidingpanelayout/api/1.2.0-beta01.txt
new file mode 100644
index 0000000..076425d
--- /dev/null
+++ b/slidingpanelayout/slidingpanelayout/api/1.2.0-beta01.txt
@@ -0,0 +1,65 @@
+// Signature format: 4.0
+package androidx.slidingpanelayout.widget {
+
+  public class SlidingPaneLayout extends android.view.ViewGroup implements androidx.customview.widget.Openable {
+    ctor public SlidingPaneLayout(android.content.Context);
+    ctor public SlidingPaneLayout(android.content.Context, android.util.AttributeSet?);
+    ctor public SlidingPaneLayout(android.content.Context, android.util.AttributeSet?, int);
+    method public void addPanelSlideListener(androidx.slidingpanelayout.widget.SlidingPaneLayout.PanelSlideListener);
+    method protected boolean canScroll(android.view.View!, boolean, int, int, int);
+    method @Deprecated public boolean canSlide();
+    method public void close();
+    method public boolean closePane();
+    method @Deprecated @ColorInt public int getCoveredFadeColor();
+    method public final int getLockMode();
+    method @Px public int getParallaxDistance();
+    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 removePanelSlideListener(androidx.slidingpanelayout.widget.SlidingPaneLayout.PanelSlideListener);
+    method @Deprecated public void setCoveredFadeColor(@ColorInt int);
+    method public final void setLockMode(int);
+    method @Deprecated public void setPanelSlideListener(androidx.slidingpanelayout.widget.SlidingPaneLayout.PanelSlideListener?);
+    method public void setParallaxDistance(@Px int);
+    method @Deprecated public void setShadowDrawable(android.graphics.drawable.Drawable!);
+    method public void setShadowDrawableLeft(android.graphics.drawable.Drawable?);
+    method public void setShadowDrawableRight(android.graphics.drawable.Drawable?);
+    method @Deprecated public void setShadowResource(@DrawableRes int);
+    method public void setShadowResourceLeft(int);
+    method public void setShadowResourceRight(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 = 3; // 0x3
+    field public static final int LOCK_MODE_LOCKED_CLOSED = 2; // 0x2
+    field public static final int LOCK_MODE_LOCKED_OPEN = 1; // 0x1
+    field public static final int LOCK_MODE_UNLOCKED = 0; // 0x0
+  }
+
+  public static class SlidingPaneLayout.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
+    ctor public SlidingPaneLayout.LayoutParams();
+    ctor public SlidingPaneLayout.LayoutParams(int, int);
+    ctor public SlidingPaneLayout.LayoutParams(android.view.ViewGroup.LayoutParams);
+    ctor public SlidingPaneLayout.LayoutParams(android.view.ViewGroup.MarginLayoutParams);
+    ctor public SlidingPaneLayout.LayoutParams(androidx.slidingpanelayout.widget.SlidingPaneLayout.LayoutParams);
+    ctor public SlidingPaneLayout.LayoutParams(android.content.Context, android.util.AttributeSet?);
+    field public float weight;
+  }
+
+  public static interface SlidingPaneLayout.PanelSlideListener {
+    method public void onPanelClosed(android.view.View);
+    method public void onPanelOpened(android.view.View);
+    method public void onPanelSlide(android.view.View, float);
+  }
+
+  public static class SlidingPaneLayout.SimplePanelSlideListener implements androidx.slidingpanelayout.widget.SlidingPaneLayout.PanelSlideListener {
+    ctor public SlidingPaneLayout.SimplePanelSlideListener();
+    method public void onPanelClosed(android.view.View);
+    method public void onPanelOpened(android.view.View);
+    method public void onPanelSlide(android.view.View, float);
+  }
+
+}
+
diff --git a/slidingpanelayout/slidingpanelayout/api/public_plus_experimental_1.2.0-beta01.txt b/slidingpanelayout/slidingpanelayout/api/public_plus_experimental_1.2.0-beta01.txt
new file mode 100644
index 0000000..076425d
--- /dev/null
+++ b/slidingpanelayout/slidingpanelayout/api/public_plus_experimental_1.2.0-beta01.txt
@@ -0,0 +1,65 @@
+// Signature format: 4.0
+package androidx.slidingpanelayout.widget {
+
+  public class SlidingPaneLayout extends android.view.ViewGroup implements androidx.customview.widget.Openable {
+    ctor public SlidingPaneLayout(android.content.Context);
+    ctor public SlidingPaneLayout(android.content.Context, android.util.AttributeSet?);
+    ctor public SlidingPaneLayout(android.content.Context, android.util.AttributeSet?, int);
+    method public void addPanelSlideListener(androidx.slidingpanelayout.widget.SlidingPaneLayout.PanelSlideListener);
+    method protected boolean canScroll(android.view.View!, boolean, int, int, int);
+    method @Deprecated public boolean canSlide();
+    method public void close();
+    method public boolean closePane();
+    method @Deprecated @ColorInt public int getCoveredFadeColor();
+    method public final int getLockMode();
+    method @Px public int getParallaxDistance();
+    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 removePanelSlideListener(androidx.slidingpanelayout.widget.SlidingPaneLayout.PanelSlideListener);
+    method @Deprecated public void setCoveredFadeColor(@ColorInt int);
+    method public final void setLockMode(int);
+    method @Deprecated public void setPanelSlideListener(androidx.slidingpanelayout.widget.SlidingPaneLayout.PanelSlideListener?);
+    method public void setParallaxDistance(@Px int);
+    method @Deprecated public void setShadowDrawable(android.graphics.drawable.Drawable!);
+    method public void setShadowDrawableLeft(android.graphics.drawable.Drawable?);
+    method public void setShadowDrawableRight(android.graphics.drawable.Drawable?);
+    method @Deprecated public void setShadowResource(@DrawableRes int);
+    method public void setShadowResourceLeft(int);
+    method public void setShadowResourceRight(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 = 3; // 0x3
+    field public static final int LOCK_MODE_LOCKED_CLOSED = 2; // 0x2
+    field public static final int LOCK_MODE_LOCKED_OPEN = 1; // 0x1
+    field public static final int LOCK_MODE_UNLOCKED = 0; // 0x0
+  }
+
+  public static class SlidingPaneLayout.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
+    ctor public SlidingPaneLayout.LayoutParams();
+    ctor public SlidingPaneLayout.LayoutParams(int, int);
+    ctor public SlidingPaneLayout.LayoutParams(android.view.ViewGroup.LayoutParams);
+    ctor public SlidingPaneLayout.LayoutParams(android.view.ViewGroup.MarginLayoutParams);
+    ctor public SlidingPaneLayout.LayoutParams(androidx.slidingpanelayout.widget.SlidingPaneLayout.LayoutParams);
+    ctor public SlidingPaneLayout.LayoutParams(android.content.Context, android.util.AttributeSet?);
+    field public float weight;
+  }
+
+  public static interface SlidingPaneLayout.PanelSlideListener {
+    method public void onPanelClosed(android.view.View);
+    method public void onPanelOpened(android.view.View);
+    method public void onPanelSlide(android.view.View, float);
+  }
+
+  public static class SlidingPaneLayout.SimplePanelSlideListener implements androidx.slidingpanelayout.widget.SlidingPaneLayout.PanelSlideListener {
+    ctor public SlidingPaneLayout.SimplePanelSlideListener();
+    method public void onPanelClosed(android.view.View);
+    method public void onPanelOpened(android.view.View);
+    method public void onPanelSlide(android.view.View, float);
+  }
+
+}
+
diff --git a/slidingpanelayout/slidingpanelayout/api/res-1.2.0-beta01.txt b/slidingpanelayout/slidingpanelayout/api/res-1.2.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/slidingpanelayout/slidingpanelayout/api/res-1.2.0-beta01.txt
diff --git a/slidingpanelayout/slidingpanelayout/api/restricted_1.2.0-beta01.txt b/slidingpanelayout/slidingpanelayout/api/restricted_1.2.0-beta01.txt
new file mode 100644
index 0000000..076425d
--- /dev/null
+++ b/slidingpanelayout/slidingpanelayout/api/restricted_1.2.0-beta01.txt
@@ -0,0 +1,65 @@
+// Signature format: 4.0
+package androidx.slidingpanelayout.widget {
+
+  public class SlidingPaneLayout extends android.view.ViewGroup implements androidx.customview.widget.Openable {
+    ctor public SlidingPaneLayout(android.content.Context);
+    ctor public SlidingPaneLayout(android.content.Context, android.util.AttributeSet?);
+    ctor public SlidingPaneLayout(android.content.Context, android.util.AttributeSet?, int);
+    method public void addPanelSlideListener(androidx.slidingpanelayout.widget.SlidingPaneLayout.PanelSlideListener);
+    method protected boolean canScroll(android.view.View!, boolean, int, int, int);
+    method @Deprecated public boolean canSlide();
+    method public void close();
+    method public boolean closePane();
+    method @Deprecated @ColorInt public int getCoveredFadeColor();
+    method public final int getLockMode();
+    method @Px public int getParallaxDistance();
+    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 removePanelSlideListener(androidx.slidingpanelayout.widget.SlidingPaneLayout.PanelSlideListener);
+    method @Deprecated public void setCoveredFadeColor(@ColorInt int);
+    method public final void setLockMode(int);
+    method @Deprecated public void setPanelSlideListener(androidx.slidingpanelayout.widget.SlidingPaneLayout.PanelSlideListener?);
+    method public void setParallaxDistance(@Px int);
+    method @Deprecated public void setShadowDrawable(android.graphics.drawable.Drawable!);
+    method public void setShadowDrawableLeft(android.graphics.drawable.Drawable?);
+    method public void setShadowDrawableRight(android.graphics.drawable.Drawable?);
+    method @Deprecated public void setShadowResource(@DrawableRes int);
+    method public void setShadowResourceLeft(int);
+    method public void setShadowResourceRight(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 = 3; // 0x3
+    field public static final int LOCK_MODE_LOCKED_CLOSED = 2; // 0x2
+    field public static final int LOCK_MODE_LOCKED_OPEN = 1; // 0x1
+    field public static final int LOCK_MODE_UNLOCKED = 0; // 0x0
+  }
+
+  public static class SlidingPaneLayout.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
+    ctor public SlidingPaneLayout.LayoutParams();
+    ctor public SlidingPaneLayout.LayoutParams(int, int);
+    ctor public SlidingPaneLayout.LayoutParams(android.view.ViewGroup.LayoutParams);
+    ctor public SlidingPaneLayout.LayoutParams(android.view.ViewGroup.MarginLayoutParams);
+    ctor public SlidingPaneLayout.LayoutParams(androidx.slidingpanelayout.widget.SlidingPaneLayout.LayoutParams);
+    ctor public SlidingPaneLayout.LayoutParams(android.content.Context, android.util.AttributeSet?);
+    field public float weight;
+  }
+
+  public static interface SlidingPaneLayout.PanelSlideListener {
+    method public void onPanelClosed(android.view.View);
+    method public void onPanelOpened(android.view.View);
+    method public void onPanelSlide(android.view.View, float);
+  }
+
+  public static class SlidingPaneLayout.SimplePanelSlideListener implements androidx.slidingpanelayout.widget.SlidingPaneLayout.PanelSlideListener {
+    ctor public SlidingPaneLayout.SimplePanelSlideListener();
+    method public void onPanelClosed(android.view.View);
+    method public void onPanelOpened(android.view.View);
+    method public void onPanelSlide(android.view.View, float);
+  }
+
+}
+
diff --git a/startup/startup-runtime/src/main/java/androidx/startup/AppInitializer.java b/startup/startup-runtime/src/main/java/androidx/startup/AppInitializer.java
index eda1040..1d23647 100644
--- a/startup/startup-runtime/src/main/java/androidx/startup/AppInitializer.java
+++ b/startup/startup-runtime/src/main/java/androidx/startup/AppInitializer.java
@@ -69,10 +69,7 @@
      * @param context The application context
      */
     AppInitializer(@NonNull Context context) {
-        // We cannot always rely on getApplicationContext()
-        // More context: b/196959015
-        Context applicationContext = context.getApplicationContext();
-        mContext = applicationContext == null ? context : applicationContext;
+        mContext = context.getApplicationContext();
         mDiscovered = new HashSet<>();
         mInitialized = new HashMap<>();
     }
diff --git a/startup/startup-runtime/src/main/java/androidx/startup/InitializationProvider.java b/startup/startup-runtime/src/main/java/androidx/startup/InitializationProvider.java
index a14ce54..764e278 100644
--- a/startup/startup-runtime/src/main/java/androidx/startup/InitializationProvider.java
+++ b/startup/startup-runtime/src/main/java/androidx/startup/InitializationProvider.java
@@ -31,11 +31,21 @@
  * initializes them before {@link Application#onCreate()}.
  */
 public class InitializationProvider extends ContentProvider {
+
     @Override
     public final boolean onCreate() {
         Context context = getContext();
         if (context != null) {
-            AppInitializer.getInstance(context).discoverAndInitialize();
+            // Many Initializer's expect the `applicationContext` to be non-null. This
+            // typically happens when `android:sharedUid` is used. In such cases, we postpone
+            // initialization altogether, and rely on lazy init.
+            // More context: b/196959015
+            Context applicationContext = context.getApplicationContext();
+            if (applicationContext != null) {
+                AppInitializer.getInstance(context).discoverAndInitialize();
+            } else {
+                StartupLogger.w("Deferring initialization because `applicationContext` is null.");
+            }
         } else {
             throw new StartupException("Context cannot be null");
         }
diff --git a/startup/startup-runtime/src/main/java/androidx/startup/StartupLogger.java b/startup/startup-runtime/src/main/java/androidx/startup/StartupLogger.java
index a2465b4..8034b2a 100644
--- a/startup/startup-runtime/src/main/java/androidx/startup/StartupLogger.java
+++ b/startup/startup-runtime/src/main/java/androidx/startup/StartupLogger.java
@@ -53,6 +53,15 @@
     }
 
     /**
+     * Warning level logging.
+     *
+     * @param message The message being logged
+     */
+    public static void w(@NonNull String message) {
+        Log.w(TAG, message);
+    }
+
+    /**
      * Error level logging
      *
      * @param message   The message being logged
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyColumn.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyColumn.kt
index d1d0f97..913a8d5 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyColumn.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyColumn.kt
@@ -151,32 +151,88 @@
      * Creates a [ScalingParams] that represents the scaling and alpha properties for a
      * [ScalingLazyColumn].
      *
+     * Items in the ScalingLazyColumn have scaling and alpha effects applied to them depending on
+     * their position in the viewport. The closer to the edge (top or bottom) of the viewport that
+     * they are the greater the down scaling and transparency that is applied. Note that scaling and
+     * transparency effects are applied from the center of the viewport (full size and normal
+     * transparency) towards the edge (items can be smaller and more transparent).
+     *
+     * Deciding how much scaling and alpha to apply is based on the position and size of the item
+     * and on a series of properties that are used to determine the transition area for each item.
+     *
+     * The transition area is defined by the edge of the screen and a transition line which is
+     * calculated for each item in the list. The items transition line is based upon its size with
+     * the potential for larger list items to start their transition earlier (closer to the center)
+     * than smaller items.
+     *
+     * [minTransitionArea] and [maxTransitionArea] are both in the range [0f..1f] and are
+     * the fraction of the distance between the edge of the viewport and the center of
+     * the viewport. E.g. a value of 0.2f for minTransitionArea and 0.75f for maxTransitionArea
+     * determines that all transition lines will fall between 1/5th (20%) and 3/4s (75%) of the
+     * distance between the viewport edge and center.
+     *
+     * The size of the each item is used to determine where within the transition area range
+     * minTransitionArea..maxTransitionArea the actual transition line will be. [minElementHeight]
+     * and [maxElementHeight] are used along with the item height (as a fraction of the viewport
+     * height in the range [0f..1f]) to find the transition line. So if the items size is 0.25f
+     * (25%) of way between minElementSize..maxElementSize then the transition line will be 0.25f
+     * (25%) of the way between minTransitionArea..maxTransitionArea.
+     *
+     * A list item smaller than minElementHeight is rounded up to minElementHeight and larger than
+     * maxElementHeight is rounded down to maxElementHeight. Whereabouts the items height sits
+     * between minElementHeight..maxElementHeight is then used to determine where the transition
+     * line sits between minTransitionArea..maxTransition area.
+     *
+     * If an item is smaller than or equal to minElementSize its transition line with be at
+     * minTransitionArea and if it is larger than or equal to maxElementSize its transition line
+     * will be  at maxTransitionArea.
+     *
+     * For example, if we take the default values for minTransitionArea = 0.2f and
+     * maxTransitionArea = 0.6f and minElementSize = 0.2f and maxElementSize= 0.8f then an item
+     * with a height of 0.4f (40%) of the viewport height is one third of way between
+     * minElementSize and maxElementSize, (0.4f - 0.2f) / (0.8f - 0.2f) = 0.33f. So its transition
+     * line would be one third of way between 0.2f and 0.6f, transition line = 0.2f + (0.6f -
+     * 0.2f) * 0.33f = 0.33f.
+     *
+     * Once the position of the transition line is established we now have a transition area
+     * for the item, e.g. in the example above the item will start/finish its transitions when it
+     * is 0.33f (33%) of the distance from the edge of the viewport and will start/finish its
+     * transitions at the viewport edge.
+     *
+     * The scaleInterpolator is used to determine how much of the scaling and alpha to apply
+     * as the item transits through the transition area.
+     *
+     * The edge of the item furthest from the edge of the screen is used as a scaling trigger
+     * point for each item.
+     *
      * @param edgeScale What fraction of the full size of the item to scale it by when most
-     * scaled, e.g. at the [minTransitionArea] line. A value between [0.0,1.0], so a value of 0.2f
+     * scaled, e.g. at the edge of the viewport. A value between [0f,1f], so a value of 0.2f
      * means to scale an item to 20% of its normal size.
      *
-     * @param edgeAlpha What fraction of the full transparency of the item to scale it by when
-     * most scaled, e.g. at the [minTransitionArea] line. A value between [0.0,1.0], so a value of
+     * @param edgeAlpha What fraction of the full transparency of the item to draw it with
+     * when closest to the edge of the screen. A value between [0f,1f], so a value of
      * 0.2f means to set the alpha of an item to 20% of its normal value.
      *
      * @param minElementHeight The minimum element height as a ratio of the viewport size to use
      * for determining the transition point within ([minTransitionArea], [maxTransitionArea])
-     * that a given content item will start to be scaled. Items smaller than [minElementHeight]
-     * will be treated as if [minElementHeight]. Must be less than or equal to [maxElementHeight].
+     * that a given content item will start to be transitioned. Items smaller than
+     * [minElementHeight] will be treated as if [minElementHeight]. Must be less than or equal to
+     * [maxElementHeight].
      *
      * @param maxElementHeight The maximum element height as a ratio of the viewport size to use
      * for determining the transition point within ([minTransitionArea], [maxTransitionArea])
-     * that a given content item will start to be scaled. Items larger than [maxElementHeight]
+     * that a given content item will start to be transitioned. Items larger than [maxElementHeight]
      * will be treated as if [maxElementHeight]. Must be greater than or equal to
      * [minElementHeight].
      *
-     * @param minTransitionArea The lower bound of the scaling transition area, closest to the
-     * edge of the component. Defined as a ratio of the distance between the viewport center line
-     * and viewport edge of the list component. Must be less than or equal to [maxTransitionArea].
+     * @param minTransitionArea The lower bound of the transition line area, closest to the
+     * edge of the viewport. Defined as a fraction (value between 0f..1f) of the distance between
+     * the viewport edge and viewport center line. Must be less than or equal to
+     * [maxTransitionArea].
      *
-     * @param maxTransitionArea The upper bound of the scaling transition area, closest to the
-     * center of the component. The is a ratio of the distance between the viewport center line and
-     * viewport edge of the list component. Must be greater
+     * @param maxTransitionArea The upper bound of the transition line area, closest to the
+     * center of the viewport. The fraction (value between 0f..1f) of the distance
+     * between the viewport edge and viewport center line. Must be greater
      * than or equal to [minTransitionArea].
      *
      * @param scaleInterpolator An interpolator to use to determine how to apply scaling as a
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyColumnMeasure.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyColumnMeasure.kt
index 9994b74..a871340 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyColumnMeasure.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyColumnMeasure.kt
@@ -25,63 +25,67 @@
 import kotlin.math.roundToInt
 
 /**
- * Parameters to control the scaling of the contents of a [ScalingLazyColumn]. The contents
- * of a [ScalingLazyColumn] are scaled down (made smaller) as they move further from the center
- * of the viewport. This scaling gives a "fisheye" effect with the contents in the center being
- * larger and therefore more prominent.
+ * Parameters to control the scaling of the contents of a [ScalingLazyColumn].
  *
- * Items in the center of the component's viewport will be full sized and with normal transparency.
- * As items move further from the center of the viewport they get smaller and become transparent.
+ * Items in the ScalingLazyColumn have scaling and alpha effects applied to them depending on
+ * their position in the viewport. The closer to the edge (top or bottom) of the viewport that
+ * they are the greater the down scaling and transparency that is applied. Note that scaling and
+ * transparency effects are applied from the center of the viewport (full size and normal
+ * transparency) towards the edge (items can be smaller and more transparent).
  *
- * The scaling parameters allow for larger items to start being scaled closer to the center than
- * smaller items. This allows for larger items to scale over a bigger transition area giving a more
- * visually pleasing effect.
+ * Deciding how much scaling and alpha to apply is based on the position and size of the item
+ * and on a series of properties that are used to determine the transition area for each item.
  *
- * Scaling transitions take place between a transition line and the edge of the screen. The trigger
- * for an item to start being scaled is when its most central edge, the item's edge that is furthest
- * from the screen edge, passing across the item's transition line. The amount of scaling to apply is
- * a function of the how far the item has moved through its transition area. An interpolator is
- * applied to allow for the scaling to follow a bezier curve.
+ * The transition area is defined by the edge of the screen and a transition line which is
+ * calculated for each item in the list. The items transition line is based upon its size with
+ * the potential for larger list items to start their transition earlier (closer to the center)
+ * than smaller items.
  *
- * There are 4 properties that are used to determine an item's scaling transition point.
+ * [minTransitionArea] and [maxTransitionArea] are both in the range [0f..1f] and are
+ * the fraction of the distance between the edge of the viewport and the center of
+ * the viewport. E.g. a value of 0.2f for minTransitionArea and 0.75f for maxTransitionArea
+ * determines that all transition lines will fall between 1/5th (20%) and 3/4s (75%) of the
+ * distance between the viewport edge and center.
  *
- * [maxTransitionArea] and [minTransitionArea] define the range in which all item scaling lines sit.
- * The largest items will start to scale at the [maxTransitionArea] and the smallest items will
- * start to scale at the [minTransitionArea].
+ * The size of the each item is used to determine where within the transition area range
+ * minTransitionArea..maxTransitionArea the actual transition line will be. [minElementHeight]
+ * and [maxElementHeight] are used along with the item height (as a fraction of the viewport
+ * height in the range [0f..1f]) to find the transition line. So if the items size is 0.25f
+ * (25%) of way between minElementSize..maxElementSize then the transition line will be 0.25f
+ * (25%) of the way between minTransitionArea..maxTransitionArea.
  *
- * The [minTransitionArea] and [maxTransitionArea] apply either side of the center line of the
- * viewport creating 2 transition areas one at the top/start the other at the bottom/end.
- * So a [maxTransitionArea] value of 0.6f on a Viewport of size 320 dp will give start transition
- * line for scaling at (320 / 2) * 0.6 = 96 dp from the top/start and bottom/end edges of the
- * viewport. Similarly [minTransitionArea] gives the point at which the scaling transition area
- * ends, e.g. a value of 0.2 with the same 320 dp screen gives an min scaling transition area line
- * of (320 / 2) * 0.2 = 32 dp from top/start and bottom/end. So in this example we have two
- * transition areas in the ranges [32.dp,96.dp] and [224.dp (320.dp-96.d),288.dp (320.dp-32.dp)].
+ * A list item smaller than minElementHeight is rounded up to minElementHeight and larger than
+ * maxElementHeight is rounded down to maxElementHeight. Whereabouts the items height sits
+ * between minElementHeight..maxElementHeight is then used to determine where the transition
+ * line sits between minTransitionArea..maxTransition area.
  *
- * Deciding for a specific content item exactly where its transition line will be within the
- * ([minTransitionArea], [maxTransitionArea]) transition area is determined by its height as a
- * fraction of the viewport height and the properties [minElementHeight] and [maxElementHeight],
- * also defined as a fraction of the viewport height.
+ * If an item is smaller than or equal to minElementSize its transition line with be at
+ * minTransitionArea and if it is larger than or equal to maxElementSize its transition line
+ * will be  at maxTransitionArea.
  *
- * If an item is smaller than [minElementHeight] it is treated as is [minElementHeight] and if
- * larger than [maxElementHeight] then it is treated as if [maxElementHeight].
+ * For example, if we take the default values for minTransitionArea = 0.2f and
+ * maxTransitionArea = 0.6f and minElementSize = 0.2f and maxElementSize= 0.8f then an item
+ * with a height of 0.4f (40%) of the viewport height is one third of way between
+ * minElementSize and maxElementSize, (0.4f - 0.2f) / (0.8f - 0.2f) = 0.33f. So its transition
+ * line would be one third of way between 0.2f and 0.6f, transition line = 0.2f + (0.6f -
+ * 0.2f) * 0.33f = 0.33f.
  *
- * Given the size of an item where it sits between [minElementHeight] and [maxElementHeight] is used
- * to determine what fraction of the transition area to use. For example if [minElementHeight] is
- * 0.2 and [maxElementHeight] is 0.8 then a component item that is 0.4 (40%) of the size of the
- * viewport would start to scale when it was 0.333 (33.3%) of the way through the transition area,
- * (0.4 - 0.2) / (0.8 - 0.2) = 0.2 / 0.6 = 0.333.
+ * Once the position of the transition line is established we now have a transition area
+ * for the item, e.g. in the example above the item will start/finish its transitions when it
+ * is 0.33f (33%) of the distance from the edge of the viewport and will start/finish its
+ * transitions at the viewport edge.
  *
- * Taking the example transition area above that means that the scaling line for the item would be a
- * third of the way between 32.dp and 96.dp. 32.dp + ((96.dp-32.dp) * 0.333) = 53.dp. So this item
- * would start to scale when it moved from the center across the 53.dp line and its scaling would be
- * between 53.dp and 0.dp.
+ * The scaleInterpolator is used to determine how much of the scaling and alpha to apply
+ * as the item transits through the transition area.
+ *
+ * The edge of the item furthest from the edge of the screen is used as a scaling trigger
+ * point for each item.
  */
 public interface ScalingParams {
     /**
      * What fraction of the full size of the item to scale it by when most
-     * scaled, e.g. at the viewport edge. A value between [0.0,1.0], so a value of 0.2f means
-     * to scale an item to 20% of its normal size.
+     * scaled, e.g. at the edge of the viewport. A value between [0f,1f], so a value of 0.2f
+     * means to scale an item to 20% of its normal size.
      */
 //    @FloatRange(
 //        fromInclusive = true, from = 0.0, toInclusive = true, to = 1.0
@@ -89,9 +93,9 @@
     val edgeScale: Float
 
     /**
-     * What fraction of the full transparency of the item to scale it by when
-     * most scaled, e.g. at the viewport edge. A value between [0.0,1.0], so a value of 0.2f
-     * means to set the alpha of an item to 20% of its normal value.
+     * What fraction of the full transparency of the item to draw it with
+     * when closest to the edge of the screen. A value between [0f,1f], so a value of
+     * 0.2f means to set the alpha of an item to 20% of its normal value.
      */
 //    @FloatRange(
 //        fromInclusive = true, from = 0.0, toInclusive = true, to = 1.0
@@ -99,10 +103,11 @@
     val edgeAlpha: Float
 
     /**
-     * The minimum element height as a fraction of the viewport size to use
-     * for determining the transition point within ([minTransitionArea], [maxTransitionArea]) that a
-     * given content item will start to be scaled. Items smaller than [minElementHeight] will be
-     * treated as if [minElementHeight]. Must be less than or equal to [maxElementHeight].
+     * The maximum element height as a ratio of the viewport size to use
+     * for determining the transition point within ([minTransitionArea], [maxTransitionArea])
+     * that a given content item will start to be transitioned. Items larger than [maxElementHeight]
+     * will be treated as if [maxElementHeight]. Must be greater than or equal to
+     * [minElementHeight].
      */
 //    @FloatRange(
 //        fromInclusive = true, from = 0.0, toInclusive = true, to = 1.0
@@ -110,10 +115,11 @@
     val minElementHeight: Float
 
     /**
-     * The minimum element height as a fraction of the viewport size to use
-     * for determining the transition point within ([minTransitionArea], [maxTransitionArea]) that a
-     * given content item will start to be scaled. Items smaller than [minElementHeight] will be
-     * treated as if [minElementHeight]. Must be less than or equal to [maxElementHeight].
+     * The maximum element height as a ratio of the viewport size to use
+     * for determining the transition point within ([minTransitionArea], [maxTransitionArea])
+     * that a given content item will start to be transitioned. Items larger than [maxElementHeight]
+     * will be treated as if [maxElementHeight]. Must be greater than or equal to
+     * [minElementHeight].
      */
 //    @FloatRange(
 //        fromInclusive = true, from = 0.0, toInclusive = true, to = 1.0
@@ -121,9 +127,10 @@
     val maxElementHeight: Float
 
     /**
-     * The lower bound of the scaling transition area, closest to the edge
-     * of the component. Defined as a fraction of the distance between the viewport center line and
-     * viewport edge of the component. Must be less than or equal to [maxTransitionArea].
+     * The lower bound of the transition line area, closest to the
+     * edge of the viewport. Defined as a fraction (value between 0f..1f) of the distance between
+     * the viewport edge and viewport center line. Must be less than or equal to
+     * [maxTransitionArea].
      */
 //    @FloatRange(
 //        fromInclusive = true, from = 0.0, toInclusive = true, to = 1.0
@@ -131,9 +138,9 @@
     val minTransitionArea: Float
 
     /**
-     * The upper bound of the scaling transition area, closest to the center
-     * of the component. Defined as a fraction of the distance between the viewport center line and
-     * viewport edge of the component. Must be greater
+     * The upper bound of the transition line area, closest to the
+     * center of the viewport. The fraction (value between 0f..1f) of the distance
+     * between the viewport edge and viewport center line. Must be greater
      * than or equal to [minTransitionArea].
      */
 //    @FloatRange(
@@ -148,13 +155,18 @@
     val scaleInterpolator: Easing
 
     /**
-     * Determine the offset/extra padding (in pixels) that is used to define a space for additional
-     * items that should be considered for drawing on the screen as the scaling of the visible
-     * items in viewport is calculated. This additional padding area means that more items will
-     * materialized and therefore be in scope for being drawn in the viewport if the scaling of
-     * other elements means that there is additional space at the top and bottom of the viewport
-     * that can be used. The default value is a fifth of the viewport height allowing an
-     * additional 20% of the viewport height above and below the viewport.
+     * The additional padding to consider above and below the
+     * viewport of a [ScalingLazyColumn] when considering which items to draw in the viewport. If
+     * set to 0 then no additional padding will be provided and only the items which would appear
+     * in the viewport before any scaling is applied will be considered for drawing, this may
+     * leave blank space at the top and bottom of the viewport where the next available item
+     * could have been drawn once other items have been scaled down in size. The larger this
+     * value is set to will allow for more content items to be considered for drawing in the
+     * viewport, however there is a performance cost associated with materializing items that are
+     * subsequently not drawn. The higher/more extreme the scaling parameters that are applied to
+     * the [ScalingLazyColumn] the more padding may be needed to ensure there are always enough
+     * content items available to be rendered. By default will be 20% of the maxHeight of the
+     * viewport above and below the content.
      *
      * @param viewportConstraints the viewports constraints
      */
diff --git a/wear/wear-phone-interactions/api/current.txt b/wear/wear-phone-interactions/api/current.txt
index bff73eca..4ee7415 100644
--- a/wear/wear-phone-interactions/api/current.txt
+++ b/wear/wear-phone-interactions/api/current.txt
@@ -34,6 +34,7 @@
   public final class OAuthRequest {
     method public String getPackageName();
     method public android.net.Uri getRequestUrl();
+    method public String redirectUrl();
     property public final String packageName;
     property public final android.net.Uri requestUrl;
     field public static final androidx.wear.phone.interactions.authentication.OAuthRequest.Companion Companion;
diff --git a/wear/wear-phone-interactions/api/public_plus_experimental_current.txt b/wear/wear-phone-interactions/api/public_plus_experimental_current.txt
index bff73eca..4ee7415 100644
--- a/wear/wear-phone-interactions/api/public_plus_experimental_current.txt
+++ b/wear/wear-phone-interactions/api/public_plus_experimental_current.txt
@@ -34,6 +34,7 @@
   public final class OAuthRequest {
     method public String getPackageName();
     method public android.net.Uri getRequestUrl();
+    method public String redirectUrl();
     property public final String packageName;
     property public final android.net.Uri requestUrl;
     field public static final androidx.wear.phone.interactions.authentication.OAuthRequest.Companion Companion;
diff --git a/wear/wear-phone-interactions/api/restricted_current.txt b/wear/wear-phone-interactions/api/restricted_current.txt
index bff73eca..4ee7415 100644
--- a/wear/wear-phone-interactions/api/restricted_current.txt
+++ b/wear/wear-phone-interactions/api/restricted_current.txt
@@ -34,6 +34,7 @@
   public final class OAuthRequest {
     method public String getPackageName();
     method public android.net.Uri getRequestUrl();
+    method public String redirectUrl();
     property public final String packageName;
     property public final android.net.Uri requestUrl;
     field public static final androidx.wear.phone.interactions.authentication.OAuthRequest.Companion Companion;
diff --git a/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/OAuthRequest.kt b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/OAuthRequest.kt
index 2590363..33aa598 100644
--- a/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/OAuthRequest.kt
+++ b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/OAuthRequest.kt
@@ -61,6 +61,8 @@
          */
         public const val WEAR_REDIRECT_URL_PREFIX_CN: String =
             "https://wear.googleapis-cn.com/3p_auth/"
+
+        internal const val REDIRECT_URI_KEY: String = "redirect_uri"
     }
 
     /**
@@ -145,7 +147,7 @@
              */
             appendQueryParameter(
                 requestUriBuilder,
-                "redirect_uri",
+                REDIRECT_URI_KEY,
                 Uri.withAppendedPath(
                     if (redirectUrl == null) {
                         if (WearTypeHelper.isChinaBuild(context)) {
@@ -231,6 +233,12 @@
         }
     }
 
+    /**
+     * The redirect url the companion app is registered to.
+     */
+    // It is save to put non-null check here as it is always set in the builder.
+    public fun redirectUrl(): String = requestUrl.getQueryParameter(REDIRECT_URI_KEY)!!
+
     internal fun toBundle(): Bundle = Bundle().apply {
         putParcelable(RemoteAuthClient.KEY_REQUEST_URL, requestUrl)
         putString(RemoteAuthClient.KEY_PACKAGE_NAME, packageName)
diff --git a/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/OAuthRequestResponseTest.kt b/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/OAuthRequestResponseTest.kt
index 3568a44..80b453b 100644
--- a/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/OAuthRequestResponseTest.kt
+++ b/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/OAuthRequestResponseTest.kt
@@ -23,6 +23,7 @@
 import androidx.test.core.app.ApplicationProvider
 import androidx.wear.phone.interactions.WearPhoneInteractionsTestRunner
 import com.google.common.truth.Truth.assertThat
+import org.junit.Assert
 import org.junit.Assert.fail
 import org.junit.Before
 import org.junit.Test
@@ -101,7 +102,7 @@
         setSystemFeatureChina(false)
         val codeChallenge = CodeChallenge(CodeVerifier())
         val requestBuilder = OAuthRequest.Builder(context)
-            .setAuthProviderUrl(authProviderUrl = Uri.parse(authProviderUrl))
+            .setAuthProviderUrl(Uri.parse(authProviderUrl))
             .setClientId(clientId)
             .setCodeChallenge(codeChallenge)
 
@@ -119,7 +120,7 @@
         setSystemFeatureChina(true)
         val codeChallenge = CodeChallenge(CodeVerifier())
         val requestBuilder = OAuthRequest.Builder(context)
-            .setAuthProviderUrl(authProviderUrl = Uri.parse(authProviderUrl))
+            .setAuthProviderUrl(Uri.parse(authProviderUrl))
             .setClientId(clientId)
             .setCodeChallenge(codeChallenge)
 
@@ -137,7 +138,7 @@
         setSystemFeatureChina(false)
         val codeChallenge = CodeChallenge(CodeVerifier())
         val requestBuilder = OAuthRequest.Builder(context)
-            .setAuthProviderUrl(authProviderUrl = Uri.parse(authProviderUrl))
+            .setAuthProviderUrl(Uri.parse(authProviderUrl))
             .setClientId(clientId)
             .setRedirectUrl(Uri.parse(customRedirectUrl))
             .setCodeChallenge(codeChallenge)
@@ -156,7 +157,7 @@
         setSystemFeatureChina(true)
         val codeChallenge = CodeChallenge(CodeVerifier())
         val requestBuilder = OAuthRequest.Builder(context)
-            .setAuthProviderUrl(authProviderUrl = Uri.parse(authProviderUrl))
+            .setAuthProviderUrl(Uri.parse(authProviderUrl))
             .setClientId(clientId)
             .setRedirectUrl(Uri.parse(customRedirectUrl))
             .setCodeChallenge(codeChallenge)
@@ -176,7 +177,7 @@
         val codeChallenge = CodeChallenge(CodeVerifier())
         val requestBuilder = OAuthRequest.Builder(context)
             .setAuthProviderUrl(
-                authProviderUrl = Uri.parse(
+                Uri.parse(
                     "$authProviderUrl?client_id=$clientId" +
                         "&redirect_uri=$redirectUrlWithPackageName" +
                         "&response_type=code" +
@@ -211,7 +212,7 @@
     public fun testRequestBuildFailureWithoutClientId() {
         setSystemFeatureChina(false)
         val builder = OAuthRequest.Builder(context)
-            .setAuthProviderUrl(authProviderUrl = Uri.parse(authProviderUrl))
+            .setAuthProviderUrl(Uri.parse(authProviderUrl))
             .setCodeChallenge(CodeChallenge(CodeVerifier()))
 
         checkBuildFailure(
@@ -264,7 +265,7 @@
     public fun testRequestBuildFailureWithoutCodeChallenge() {
         setSystemFeatureChina(false)
         val builder = OAuthRequest.Builder(context)
-            .setAuthProviderUrl(authProviderUrl = Uri.parse(authProviderUrl))
+            .setAuthProviderUrl(Uri.parse(authProviderUrl))
             .setClientId(clientId)
 
         checkBuildFailure(
@@ -437,4 +438,69 @@
         assertThat(response2.errorCode).isEqualTo(RemoteAuthClient.ERROR_PHONE_UNAVAILABLE)
         assertThat(response2.responseUrl).isNull()
     }
+
+    @Test
+    public fun testGetRedirectUrl() {
+        setSystemFeatureChina(false)
+        val codeChallenge = CodeChallenge(CodeVerifier())
+        val builder = OAuthRequest.Builder(context)
+            .setAuthProviderUrl(Uri.parse(authProviderUrl))
+            .setClientId(clientId)
+            .setCodeChallenge(codeChallenge)
+
+        // Building should always be successful and it's already tested in the previous tests, so
+        // no need for try-catch block as in #checkBuildFailure.
+        val request = builder.build()
+        assertThat(request.redirectUrl()).isEqualTo(redirectUrlWithPackageName)
+    }
+
+    @Test
+    public fun testGetRedirectUrl_cn() {
+        setSystemFeatureChina(true)
+        val codeChallenge = CodeChallenge(CodeVerifier())
+        val builder = OAuthRequest.Builder(context)
+            .setAuthProviderUrl(Uri.parse(authProviderUrl))
+            .setClientId(clientId)
+            .setCodeChallenge(codeChallenge)
+
+        // Building should always be successful and it's already tested in the previous tests, so
+        // no need for try-catch block as in #checkBuildFailure.
+        val request = builder.build()
+        assertThat(request.redirectUrl()).isEqualTo(redirectUrlWithPackageName_cn)
+    }
+
+    @Test
+    public fun testGetRedirectUrlWithCustomRedirectUri() {
+        setSystemFeatureChina(false)
+        val codeChallenge = CodeChallenge(CodeVerifier())
+        val builder = OAuthRequest.Builder(context)
+            .setAuthProviderUrl(Uri.parse(authProviderUrl))
+            .setClientId(clientId)
+            .setRedirectUrl(Uri.parse(customRedirectUrl))
+            .setCodeChallenge(codeChallenge)
+
+        // Building should always be successful and it's already tested in the previous tests, so
+        // no need for try-catch block as in #checkBuildFailure.
+        val request = builder.build()
+        assertThat(request.redirectUrl()).isEqualTo(customRedirectUrlWithPackageName)
+    }
+
+    @Test
+    public fun testGetRedirectUrl_builtWithConstructor() {
+        val requestUrl = Uri.parse(authProviderUrl).buildUpon()
+            .appendQueryParameter(OAuthRequest.REDIRECT_URI_KEY, customRedirectUrl).build()
+        val request = OAuthRequest(appPackageName, requestUrl)
+
+        assertThat(request.redirectUrl()).isEqualTo(customRedirectUrl)
+    }
+
+    @Test
+    public fun testGetRedirectUrl_builtWithConstructor_failure() {
+        val requestUrl = Uri.parse(authProviderUrl)
+        val request = OAuthRequest(appPackageName, requestUrl)
+
+        Assert.assertThrows(
+            NullPointerException::class.java
+        ) { request.redirectUrl() }
+    }
 }
\ No newline at end of file
diff --git a/window/window-java/api/1.0.0-beta02.txt b/window/window-java/api/1.0.0-beta02.txt
new file mode 100644
index 0000000..8ea20c0
--- /dev/null
+++ b/window/window-java/api/1.0.0-beta02.txt
@@ -0,0 +1,13 @@
+// Signature format: 4.0
+package androidx.window.java.layout {
+
+  public final class WindowInfoRepositoryCallbackAdapter implements androidx.window.layout.WindowInfoRepository {
+    ctor public WindowInfoRepositoryCallbackAdapter(androidx.window.layout.WindowInfoRepository repository);
+    method public void addCurrentWindowMetricsListener(java.util.concurrent.Executor executor, androidx.core.util.Consumer<androidx.window.layout.WindowMetrics> consumer);
+    method public void addWindowLayoutInfoListener(java.util.concurrent.Executor executor, androidx.core.util.Consumer<androidx.window.layout.WindowLayoutInfo> consumer);
+    method public void removeCurrentWindowMetricsListener(androidx.core.util.Consumer<androidx.window.layout.WindowMetrics> consumer);
+    method public void removeWindowLayoutInfoListener(androidx.core.util.Consumer<androidx.window.layout.WindowLayoutInfo> consumer);
+  }
+
+}
+
diff --git a/window/window-java/api/public_plus_experimental_1.0.0-beta02.txt b/window/window-java/api/public_plus_experimental_1.0.0-beta02.txt
new file mode 100644
index 0000000..8ea20c0
--- /dev/null
+++ b/window/window-java/api/public_plus_experimental_1.0.0-beta02.txt
@@ -0,0 +1,13 @@
+// Signature format: 4.0
+package androidx.window.java.layout {
+
+  public final class WindowInfoRepositoryCallbackAdapter implements androidx.window.layout.WindowInfoRepository {
+    ctor public WindowInfoRepositoryCallbackAdapter(androidx.window.layout.WindowInfoRepository repository);
+    method public void addCurrentWindowMetricsListener(java.util.concurrent.Executor executor, androidx.core.util.Consumer<androidx.window.layout.WindowMetrics> consumer);
+    method public void addWindowLayoutInfoListener(java.util.concurrent.Executor executor, androidx.core.util.Consumer<androidx.window.layout.WindowLayoutInfo> consumer);
+    method public void removeCurrentWindowMetricsListener(androidx.core.util.Consumer<androidx.window.layout.WindowMetrics> consumer);
+    method public void removeWindowLayoutInfoListener(androidx.core.util.Consumer<androidx.window.layout.WindowLayoutInfo> consumer);
+  }
+
+}
+
diff --git a/window/window-java/api/res-1.0.0-beta02.txt b/window/window-java/api/res-1.0.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/window/window-java/api/res-1.0.0-beta02.txt
diff --git a/window/window-java/api/restricted_1.0.0-beta02.txt b/window/window-java/api/restricted_1.0.0-beta02.txt
new file mode 100644
index 0000000..8ea20c0
--- /dev/null
+++ b/window/window-java/api/restricted_1.0.0-beta02.txt
@@ -0,0 +1,13 @@
+// Signature format: 4.0
+package androidx.window.java.layout {
+
+  public final class WindowInfoRepositoryCallbackAdapter implements androidx.window.layout.WindowInfoRepository {
+    ctor public WindowInfoRepositoryCallbackAdapter(androidx.window.layout.WindowInfoRepository repository);
+    method public void addCurrentWindowMetricsListener(java.util.concurrent.Executor executor, androidx.core.util.Consumer<androidx.window.layout.WindowMetrics> consumer);
+    method public void addWindowLayoutInfoListener(java.util.concurrent.Executor executor, androidx.core.util.Consumer<androidx.window.layout.WindowLayoutInfo> consumer);
+    method public void removeCurrentWindowMetricsListener(androidx.core.util.Consumer<androidx.window.layout.WindowMetrics> consumer);
+    method public void removeWindowLayoutInfoListener(androidx.core.util.Consumer<androidx.window.layout.WindowLayoutInfo> consumer);
+  }
+
+}
+
diff --git a/window/window-rxjava2/api/1.0.0-beta02.txt b/window/window-rxjava2/api/1.0.0-beta02.txt
new file mode 100644
index 0000000..ba57afd
--- /dev/null
+++ b/window/window-rxjava2/api/1.0.0-beta02.txt
@@ -0,0 +1,12 @@
+// Signature format: 4.0
+package androidx.window.rxjava2.layout {
+
+  public final class WindowInfoRepositoryRx {
+    method public static io.reactivex.Flowable<androidx.window.layout.WindowMetrics> currentWindowMetricsFlowable(androidx.window.layout.WindowInfoRepository);
+    method public static io.reactivex.Observable<androidx.window.layout.WindowMetrics> currentWindowMetricsObservable(androidx.window.layout.WindowInfoRepository);
+    method public static io.reactivex.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoRepository);
+    method public static io.reactivex.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoRepository);
+  }
+
+}
+
diff --git a/window/window-rxjava2/api/public_plus_experimental_1.0.0-beta02.txt b/window/window-rxjava2/api/public_plus_experimental_1.0.0-beta02.txt
new file mode 100644
index 0000000..ba57afd
--- /dev/null
+++ b/window/window-rxjava2/api/public_plus_experimental_1.0.0-beta02.txt
@@ -0,0 +1,12 @@
+// Signature format: 4.0
+package androidx.window.rxjava2.layout {
+
+  public final class WindowInfoRepositoryRx {
+    method public static io.reactivex.Flowable<androidx.window.layout.WindowMetrics> currentWindowMetricsFlowable(androidx.window.layout.WindowInfoRepository);
+    method public static io.reactivex.Observable<androidx.window.layout.WindowMetrics> currentWindowMetricsObservable(androidx.window.layout.WindowInfoRepository);
+    method public static io.reactivex.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoRepository);
+    method public static io.reactivex.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoRepository);
+  }
+
+}
+
diff --git a/window/window-rxjava2/api/res-1.0.0-beta02.txt b/window/window-rxjava2/api/res-1.0.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/window/window-rxjava2/api/res-1.0.0-beta02.txt
diff --git a/window/window-rxjava2/api/restricted_1.0.0-beta02.txt b/window/window-rxjava2/api/restricted_1.0.0-beta02.txt
new file mode 100644
index 0000000..ba57afd
--- /dev/null
+++ b/window/window-rxjava2/api/restricted_1.0.0-beta02.txt
@@ -0,0 +1,12 @@
+// Signature format: 4.0
+package androidx.window.rxjava2.layout {
+
+  public final class WindowInfoRepositoryRx {
+    method public static io.reactivex.Flowable<androidx.window.layout.WindowMetrics> currentWindowMetricsFlowable(androidx.window.layout.WindowInfoRepository);
+    method public static io.reactivex.Observable<androidx.window.layout.WindowMetrics> currentWindowMetricsObservable(androidx.window.layout.WindowInfoRepository);
+    method public static io.reactivex.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoRepository);
+    method public static io.reactivex.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoRepository);
+  }
+
+}
+
diff --git a/window/window-rxjava3/api/1.0.0-beta02.txt b/window/window-rxjava3/api/1.0.0-beta02.txt
new file mode 100644
index 0000000..f4a0b0e
--- /dev/null
+++ b/window/window-rxjava3/api/1.0.0-beta02.txt
@@ -0,0 +1,12 @@
+// Signature format: 4.0
+package androidx.window.rxjava3.layout {
+
+  public final class WindowInfoRepositoryRx {
+    method public static io.reactivex.rxjava3.core.Flowable<androidx.window.layout.WindowMetrics> currentWindowMetricsFlowable(androidx.window.layout.WindowInfoRepository);
+    method public static io.reactivex.rxjava3.core.Observable<androidx.window.layout.WindowMetrics> currentWindowMetricsObservable(androidx.window.layout.WindowInfoRepository);
+    method public static io.reactivex.rxjava3.core.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoRepository);
+    method public static io.reactivex.rxjava3.core.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoRepository);
+  }
+
+}
+
diff --git a/window/window-rxjava3/api/public_plus_experimental_1.0.0-beta02.txt b/window/window-rxjava3/api/public_plus_experimental_1.0.0-beta02.txt
new file mode 100644
index 0000000..f4a0b0e
--- /dev/null
+++ b/window/window-rxjava3/api/public_plus_experimental_1.0.0-beta02.txt
@@ -0,0 +1,12 @@
+// Signature format: 4.0
+package androidx.window.rxjava3.layout {
+
+  public final class WindowInfoRepositoryRx {
+    method public static io.reactivex.rxjava3.core.Flowable<androidx.window.layout.WindowMetrics> currentWindowMetricsFlowable(androidx.window.layout.WindowInfoRepository);
+    method public static io.reactivex.rxjava3.core.Observable<androidx.window.layout.WindowMetrics> currentWindowMetricsObservable(androidx.window.layout.WindowInfoRepository);
+    method public static io.reactivex.rxjava3.core.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoRepository);
+    method public static io.reactivex.rxjava3.core.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoRepository);
+  }
+
+}
+
diff --git a/window/window-rxjava3/api/res-1.0.0-beta02.txt b/window/window-rxjava3/api/res-1.0.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/window/window-rxjava3/api/res-1.0.0-beta02.txt
diff --git a/window/window-rxjava3/api/restricted_1.0.0-beta02.txt b/window/window-rxjava3/api/restricted_1.0.0-beta02.txt
new file mode 100644
index 0000000..f4a0b0e
--- /dev/null
+++ b/window/window-rxjava3/api/restricted_1.0.0-beta02.txt
@@ -0,0 +1,12 @@
+// Signature format: 4.0
+package androidx.window.rxjava3.layout {
+
+  public final class WindowInfoRepositoryRx {
+    method public static io.reactivex.rxjava3.core.Flowable<androidx.window.layout.WindowMetrics> currentWindowMetricsFlowable(androidx.window.layout.WindowInfoRepository);
+    method public static io.reactivex.rxjava3.core.Observable<androidx.window.layout.WindowMetrics> currentWindowMetricsObservable(androidx.window.layout.WindowInfoRepository);
+    method public static io.reactivex.rxjava3.core.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoRepository);
+    method public static io.reactivex.rxjava3.core.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoRepository);
+  }
+
+}
+
diff --git a/window/window-testing/api/1.0.0-beta02.txt b/window/window-testing/api/1.0.0-beta02.txt
new file mode 100644
index 0000000..f47d528
--- /dev/null
+++ b/window/window-testing/api/1.0.0-beta02.txt
@@ -0,0 +1,19 @@
+// Signature format: 4.0
+package androidx.window.testing.layout {
+
+  public final class DisplayFeatureTesting {
+    method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.app.Activity activity, optional int center, optional int size, optional androidx.window.layout.FoldingFeature.State state, optional androidx.window.layout.FoldingFeature.Orientation orientation);
+    method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.app.Activity activity, optional int center, optional int size, optional androidx.window.layout.FoldingFeature.State state);
+    method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.app.Activity activity, optional int center, optional int size);
+    method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.app.Activity activity, optional int center);
+    method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.app.Activity activity);
+  }
+
+  public final class WindowLayoutInfoPublisherRule implements org.junit.rules.TestRule {
+    ctor public WindowLayoutInfoPublisherRule();
+    method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
+    method public void overrideWindowLayoutInfo(androidx.window.layout.WindowLayoutInfo info);
+  }
+
+}
+
diff --git a/window/window-testing/api/public_plus_experimental_1.0.0-beta02.txt b/window/window-testing/api/public_plus_experimental_1.0.0-beta02.txt
new file mode 100644
index 0000000..0ebaf19
--- /dev/null
+++ b/window/window-testing/api/public_plus_experimental_1.0.0-beta02.txt
@@ -0,0 +1,24 @@
+// Signature format: 4.0
+package androidx.window.testing.layout {
+
+  public final class DisplayFeatureTesting {
+    method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.app.Activity activity, optional int center, optional int size, optional androidx.window.layout.FoldingFeature.State state, optional androidx.window.layout.FoldingFeature.Orientation orientation);
+    method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.app.Activity activity, optional int center, optional int size, optional androidx.window.layout.FoldingFeature.State state);
+    method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.app.Activity activity, optional int center, optional int size);
+    method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.app.Activity activity, optional int center);
+    method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.app.Activity activity);
+    method @androidx.window.core.ExperimentalWindowApi public static androidx.window.layout.FoldingFeature createFoldingFeature(android.graphics.Rect windowBounds, optional int center, optional int size, optional androidx.window.layout.FoldingFeature.State state, optional androidx.window.layout.FoldingFeature.Orientation orientation);
+    method @androidx.window.core.ExperimentalWindowApi public static androidx.window.layout.FoldingFeature createFoldingFeature(android.graphics.Rect windowBounds, optional int center, optional int size, optional androidx.window.layout.FoldingFeature.State state);
+    method @androidx.window.core.ExperimentalWindowApi public static androidx.window.layout.FoldingFeature createFoldingFeature(android.graphics.Rect windowBounds, optional int center, optional int size);
+    method @androidx.window.core.ExperimentalWindowApi public static androidx.window.layout.FoldingFeature createFoldingFeature(android.graphics.Rect windowBounds, optional int center);
+    method @androidx.window.core.ExperimentalWindowApi public static androidx.window.layout.FoldingFeature createFoldingFeature(android.graphics.Rect windowBounds);
+  }
+
+  public final class WindowLayoutInfoPublisherRule implements org.junit.rules.TestRule {
+    ctor public WindowLayoutInfoPublisherRule();
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
+    method public void overrideWindowLayoutInfo(androidx.window.layout.WindowLayoutInfo info);
+  }
+
+}
+
diff --git a/window/window-testing/api/res-1.0.0-beta02.txt b/window/window-testing/api/res-1.0.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/window/window-testing/api/res-1.0.0-beta02.txt
diff --git a/window/window-testing/api/restricted_1.0.0-beta02.txt b/window/window-testing/api/restricted_1.0.0-beta02.txt
new file mode 100644
index 0000000..f47d528
--- /dev/null
+++ b/window/window-testing/api/restricted_1.0.0-beta02.txt
@@ -0,0 +1,19 @@
+// Signature format: 4.0
+package androidx.window.testing.layout {
+
+  public final class DisplayFeatureTesting {
+    method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.app.Activity activity, optional int center, optional int size, optional androidx.window.layout.FoldingFeature.State state, optional androidx.window.layout.FoldingFeature.Orientation orientation);
+    method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.app.Activity activity, optional int center, optional int size, optional androidx.window.layout.FoldingFeature.State state);
+    method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.app.Activity activity, optional int center, optional int size);
+    method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.app.Activity activity, optional int center);
+    method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.app.Activity activity);
+  }
+
+  public final class WindowLayoutInfoPublisherRule implements org.junit.rules.TestRule {
+    ctor public WindowLayoutInfoPublisherRule();
+    method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
+    method public void overrideWindowLayoutInfo(androidx.window.layout.WindowLayoutInfo info);
+  }
+
+}
+
diff --git a/window/window/api/1.0.0-beta02.txt b/window/window/api/1.0.0-beta02.txt
new file mode 100644
index 0000000..f87dfd1
--- /dev/null
+++ b/window/window/api/1.0.0-beta02.txt
@@ -0,0 +1,89 @@
+// Signature format: 4.0
+package androidx.window.layout {
+
+  public interface DisplayFeature {
+    method public android.graphics.Rect getBounds();
+    property public abstract android.graphics.Rect bounds;
+  }
+
+  public interface FoldingFeature extends androidx.window.layout.DisplayFeature {
+    method public androidx.window.layout.FoldingFeature.OcclusionType getOcclusionType();
+    method public androidx.window.layout.FoldingFeature.Orientation getOrientation();
+    method public androidx.window.layout.FoldingFeature.State getState();
+    method public boolean isSeparating();
+    property public abstract boolean isSeparating;
+    property public abstract androidx.window.layout.FoldingFeature.OcclusionType occlusionType;
+    property public abstract androidx.window.layout.FoldingFeature.Orientation orientation;
+    property public abstract androidx.window.layout.FoldingFeature.State state;
+  }
+
+  public static final class FoldingFeature.OcclusionType {
+    field public static final androidx.window.layout.FoldingFeature.OcclusionType.Companion Companion;
+    field public static final androidx.window.layout.FoldingFeature.OcclusionType FULL;
+    field public static final androidx.window.layout.FoldingFeature.OcclusionType NONE;
+  }
+
+  public static final class FoldingFeature.OcclusionType.Companion {
+  }
+
+  public static final class FoldingFeature.Orientation {
+    field public static final androidx.window.layout.FoldingFeature.Orientation.Companion Companion;
+    field public static final androidx.window.layout.FoldingFeature.Orientation HORIZONTAL;
+    field public static final androidx.window.layout.FoldingFeature.Orientation VERTICAL;
+  }
+
+  public static final class FoldingFeature.Orientation.Companion {
+  }
+
+  public static final class FoldingFeature.State {
+    field public static final androidx.window.layout.FoldingFeature.State.Companion Companion;
+    field public static final androidx.window.layout.FoldingFeature.State FLAT;
+    field public static final androidx.window.layout.FoldingFeature.State HALF_OPENED;
+  }
+
+  public static final class FoldingFeature.State.Companion {
+  }
+
+  public interface WindowInfoRepository {
+    method public kotlinx.coroutines.flow.Flow<androidx.window.layout.WindowMetrics> getCurrentWindowMetrics();
+    method public default static androidx.window.layout.WindowInfoRepository getOrCreate(android.app.Activity);
+    method public kotlinx.coroutines.flow.Flow<androidx.window.layout.WindowLayoutInfo> getWindowLayoutInfo();
+    property public abstract kotlinx.coroutines.flow.Flow<androidx.window.layout.WindowMetrics> currentWindowMetrics;
+    property public abstract kotlinx.coroutines.flow.Flow<androidx.window.layout.WindowLayoutInfo> windowLayoutInfo;
+    field public static final androidx.window.layout.WindowInfoRepository.Companion Companion;
+  }
+
+  public static final class WindowInfoRepository.Companion {
+    method public androidx.window.layout.WindowInfoRepository getOrCreate(android.app.Activity);
+  }
+
+  public final class WindowLayoutInfo {
+    method public java.util.List<androidx.window.layout.DisplayFeature> getDisplayFeatures();
+    property public final java.util.List<androidx.window.layout.DisplayFeature> displayFeatures;
+  }
+
+  public static final class WindowLayoutInfo.Builder {
+    ctor public WindowLayoutInfo.Builder();
+    method public androidx.window.layout.WindowLayoutInfo build();
+    method public androidx.window.layout.WindowLayoutInfo.Builder setDisplayFeatures(java.util.List<? extends androidx.window.layout.DisplayFeature> displayFeatures);
+  }
+
+  public final class WindowMetrics {
+    ctor public WindowMetrics(android.graphics.Rect bounds);
+    method public android.graphics.Rect getBounds();
+    property public final android.graphics.Rect bounds;
+  }
+
+  public interface WindowMetricsCalculator {
+    method public androidx.window.layout.WindowMetrics computeCurrentWindowMetrics(android.app.Activity activity);
+    method public androidx.window.layout.WindowMetrics computeMaximumWindowMetrics(android.app.Activity activity);
+    method public default static androidx.window.layout.WindowMetricsCalculator getOrCreate();
+    field public static final androidx.window.layout.WindowMetricsCalculator.Companion Companion;
+  }
+
+  public static final class WindowMetricsCalculator.Companion {
+    method public androidx.window.layout.WindowMetricsCalculator getOrCreate();
+  }
+
+}
+
diff --git a/window/window/api/public_plus_experimental_1.0.0-beta02.txt b/window/window/api/public_plus_experimental_1.0.0-beta02.txt
new file mode 100644
index 0000000..125d6da
--- /dev/null
+++ b/window/window/api/public_plus_experimental_1.0.0-beta02.txt
@@ -0,0 +1,96 @@
+// Signature format: 4.0
+package androidx.window.core {
+
+  @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level) @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) public @interface ExperimentalWindowApi {
+  }
+
+}
+
+package androidx.window.layout {
+
+  public interface DisplayFeature {
+    method public android.graphics.Rect getBounds();
+    property public abstract android.graphics.Rect bounds;
+  }
+
+  public interface FoldingFeature extends androidx.window.layout.DisplayFeature {
+    method public androidx.window.layout.FoldingFeature.OcclusionType getOcclusionType();
+    method public androidx.window.layout.FoldingFeature.Orientation getOrientation();
+    method public androidx.window.layout.FoldingFeature.State getState();
+    method public boolean isSeparating();
+    property public abstract boolean isSeparating;
+    property public abstract androidx.window.layout.FoldingFeature.OcclusionType occlusionType;
+    property public abstract androidx.window.layout.FoldingFeature.Orientation orientation;
+    property public abstract androidx.window.layout.FoldingFeature.State state;
+  }
+
+  public static final class FoldingFeature.OcclusionType {
+    field public static final androidx.window.layout.FoldingFeature.OcclusionType.Companion Companion;
+    field public static final androidx.window.layout.FoldingFeature.OcclusionType FULL;
+    field public static final androidx.window.layout.FoldingFeature.OcclusionType NONE;
+  }
+
+  public static final class FoldingFeature.OcclusionType.Companion {
+  }
+
+  public static final class FoldingFeature.Orientation {
+    field public static final androidx.window.layout.FoldingFeature.Orientation.Companion Companion;
+    field public static final androidx.window.layout.FoldingFeature.Orientation HORIZONTAL;
+    field public static final androidx.window.layout.FoldingFeature.Orientation VERTICAL;
+  }
+
+  public static final class FoldingFeature.Orientation.Companion {
+  }
+
+  public static final class FoldingFeature.State {
+    field public static final androidx.window.layout.FoldingFeature.State.Companion Companion;
+    field public static final androidx.window.layout.FoldingFeature.State FLAT;
+    field public static final androidx.window.layout.FoldingFeature.State HALF_OPENED;
+  }
+
+  public static final class FoldingFeature.State.Companion {
+  }
+
+  public interface WindowInfoRepository {
+    method public kotlinx.coroutines.flow.Flow<androidx.window.layout.WindowMetrics> getCurrentWindowMetrics();
+    method public default static androidx.window.layout.WindowInfoRepository getOrCreate(android.app.Activity);
+    method public kotlinx.coroutines.flow.Flow<androidx.window.layout.WindowLayoutInfo> getWindowLayoutInfo();
+    property public abstract kotlinx.coroutines.flow.Flow<androidx.window.layout.WindowMetrics> currentWindowMetrics;
+    property public abstract kotlinx.coroutines.flow.Flow<androidx.window.layout.WindowLayoutInfo> windowLayoutInfo;
+    field public static final androidx.window.layout.WindowInfoRepository.Companion Companion;
+  }
+
+  public static final class WindowInfoRepository.Companion {
+    method public androidx.window.layout.WindowInfoRepository getOrCreate(android.app.Activity);
+  }
+
+  public final class WindowLayoutInfo {
+    method public java.util.List<androidx.window.layout.DisplayFeature> getDisplayFeatures();
+    property public final java.util.List<androidx.window.layout.DisplayFeature> displayFeatures;
+  }
+
+  public static final class WindowLayoutInfo.Builder {
+    ctor public WindowLayoutInfo.Builder();
+    method public androidx.window.layout.WindowLayoutInfo build();
+    method public androidx.window.layout.WindowLayoutInfo.Builder setDisplayFeatures(java.util.List<? extends androidx.window.layout.DisplayFeature> displayFeatures);
+  }
+
+  public final class WindowMetrics {
+    ctor public WindowMetrics(android.graphics.Rect bounds);
+    method public android.graphics.Rect getBounds();
+    property public final android.graphics.Rect bounds;
+  }
+
+  public interface WindowMetricsCalculator {
+    method public androidx.window.layout.WindowMetrics computeCurrentWindowMetrics(android.app.Activity activity);
+    method public androidx.window.layout.WindowMetrics computeMaximumWindowMetrics(android.app.Activity activity);
+    method public default static androidx.window.layout.WindowMetricsCalculator getOrCreate();
+    field public static final androidx.window.layout.WindowMetricsCalculator.Companion Companion;
+  }
+
+  public static final class WindowMetricsCalculator.Companion {
+    method public androidx.window.layout.WindowMetricsCalculator getOrCreate();
+  }
+
+}
+
diff --git a/window/window/api/res-1.0.0-beta02.txt b/window/window/api/res-1.0.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/window/window/api/res-1.0.0-beta02.txt
diff --git a/window/window/api/restricted_1.0.0-beta02.txt b/window/window/api/restricted_1.0.0-beta02.txt
new file mode 100644
index 0000000..082d83a
--- /dev/null
+++ b/window/window/api/restricted_1.0.0-beta02.txt
@@ -0,0 +1,97 @@
+// Signature format: 4.0
+package androidx.window.layout {
+
+  public interface DisplayFeature {
+    method public android.graphics.Rect getBounds();
+    property public abstract android.graphics.Rect bounds;
+  }
+
+  public interface FoldingFeature extends androidx.window.layout.DisplayFeature {
+    method public androidx.window.layout.FoldingFeature.OcclusionType getOcclusionType();
+    method public androidx.window.layout.FoldingFeature.Orientation getOrientation();
+    method public androidx.window.layout.FoldingFeature.State getState();
+    method public boolean isSeparating();
+    property public abstract boolean isSeparating;
+    property public abstract androidx.window.layout.FoldingFeature.OcclusionType occlusionType;
+    property public abstract androidx.window.layout.FoldingFeature.Orientation orientation;
+    property public abstract androidx.window.layout.FoldingFeature.State state;
+  }
+
+  public static final class FoldingFeature.OcclusionType {
+    field public static final androidx.window.layout.FoldingFeature.OcclusionType.Companion Companion;
+    field public static final androidx.window.layout.FoldingFeature.OcclusionType FULL;
+    field public static final androidx.window.layout.FoldingFeature.OcclusionType NONE;
+  }
+
+  public static final class FoldingFeature.OcclusionType.Companion {
+  }
+
+  public static final class FoldingFeature.Orientation {
+    field public static final androidx.window.layout.FoldingFeature.Orientation.Companion Companion;
+    field public static final androidx.window.layout.FoldingFeature.Orientation HORIZONTAL;
+    field public static final androidx.window.layout.FoldingFeature.Orientation VERTICAL;
+  }
+
+  public static final class FoldingFeature.Orientation.Companion {
+  }
+
+  public static final class FoldingFeature.State {
+    field public static final androidx.window.layout.FoldingFeature.State.Companion Companion;
+    field public static final androidx.window.layout.FoldingFeature.State FLAT;
+    field public static final androidx.window.layout.FoldingFeature.State HALF_OPENED;
+  }
+
+  public static final class FoldingFeature.State.Companion {
+  }
+
+  public interface WindowInfoRepository {
+    method public kotlinx.coroutines.flow.Flow<androidx.window.layout.WindowMetrics> getCurrentWindowMetrics();
+    method public default static androidx.window.layout.WindowInfoRepository getOrCreate(android.app.Activity);
+    method public kotlinx.coroutines.flow.Flow<androidx.window.layout.WindowLayoutInfo> getWindowLayoutInfo();
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public default static void overrideDecorator(androidx.window.layout.WindowInfoRepositoryDecorator overridingDecorator);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public default static void reset();
+    property public abstract kotlinx.coroutines.flow.Flow<androidx.window.layout.WindowMetrics> currentWindowMetrics;
+    property public abstract kotlinx.coroutines.flow.Flow<androidx.window.layout.WindowLayoutInfo> windowLayoutInfo;
+    field public static final androidx.window.layout.WindowInfoRepository.Companion Companion;
+  }
+
+  public static final class WindowInfoRepository.Companion {
+    method public androidx.window.layout.WindowInfoRepository getOrCreate(android.app.Activity);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public void overrideDecorator(androidx.window.layout.WindowInfoRepositoryDecorator overridingDecorator);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public void reset();
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public interface WindowInfoRepositoryDecorator {
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public androidx.window.layout.WindowInfoRepository decorate(androidx.window.layout.WindowInfoRepository repository);
+  }
+
+  public final class WindowLayoutInfo {
+    method public java.util.List<androidx.window.layout.DisplayFeature> getDisplayFeatures();
+    property public final java.util.List<androidx.window.layout.DisplayFeature> displayFeatures;
+  }
+
+  public static final class WindowLayoutInfo.Builder {
+    ctor public WindowLayoutInfo.Builder();
+    method public androidx.window.layout.WindowLayoutInfo build();
+    method public androidx.window.layout.WindowLayoutInfo.Builder setDisplayFeatures(java.util.List<? extends androidx.window.layout.DisplayFeature> displayFeatures);
+  }
+
+  public final class WindowMetrics {
+    ctor public WindowMetrics(android.graphics.Rect bounds);
+    method public android.graphics.Rect getBounds();
+    property public final android.graphics.Rect bounds;
+  }
+
+  public interface WindowMetricsCalculator {
+    method public androidx.window.layout.WindowMetrics computeCurrentWindowMetrics(android.app.Activity activity);
+    method public androidx.window.layout.WindowMetrics computeMaximumWindowMetrics(android.app.Activity activity);
+    method public default static androidx.window.layout.WindowMetricsCalculator getOrCreate();
+    field public static final androidx.window.layout.WindowMetricsCalculator.Companion Companion;
+  }
+
+  public static final class WindowMetricsCalculator.Companion {
+    method public androidx.window.layout.WindowMetricsCalculator getOrCreate();
+  }
+
+}
+