Merge "Add alternative TypeConverterStore for KSP" into androidx-main
diff --git a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/perfetto/PerfettoConfigTest.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/perfetto/PerfettoConfigTest.kt
index 45f983d..f1815b3 100644
--- a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/perfetto/PerfettoConfigTest.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/perfetto/PerfettoConfigTest.kt
@@ -119,4 +119,31 @@
             actual = exception.message
         )
     }
+
+    @SdkSuppress(minSdkVersion = 21, maxSdkVersion = 23)
+    @Test
+    fun validateAndEncode_invalidLength() {
+        val invalidConfig = perfettoConfig(
+            listOf(
+                "0123456789",
+                "0123456789",
+                "0123456789",
+                "0123456789",
+                "0123456789",
+                "0123456789",
+                "0123456789",
+                "0123456789",
+                "0123456789",
+            )
+        )
+        val exception = assertFailsWith<IllegalStateException> {
+            invalidConfig.validateAndEncode()
+        }
+        assertEquals(
+            expected = "Unable to trace package list (\"0123456789,0123456789,0123456789," +
+                "0123456789,0123456789,0123456789,0123456789,0123456789,0123456789\").length" +
+                " = 98 > 91 chars, which is the limit before API 24",
+            actual = exception.message
+        )
+    }
 }
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt
index c3cbefe..c8fe215 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt
@@ -59,7 +59,12 @@
         val argumentName = "profiling.mode"
         val argumentValue = getBenchmarkArgument(argumentName, "")
         val profiler = Profiler.getByName(argumentValue)
-        if (profiler == null && argumentValue.isNotEmpty()) {
+        if (profiler == null &&
+            argumentValue.isNotEmpty() &&
+            // 'none' is documented as noop (and works better in gradle than
+            // an empty string, if a value must be specified)
+            argumentValue.trim().lowercase() != "none"
+        ) {
             error = "Could not parse $prefix$argumentName=$argumentValue"
             return null
         }
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 1e3b309..8e7258a6 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Shell.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Shell.kt
@@ -24,6 +24,7 @@
 import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
 import androidx.test.platform.app.InstrumentationRegistry
+import androidx.tracing.trace
 import java.io.File
 import java.io.InputStream
 import java.nio.charset.Charset
@@ -298,6 +299,11 @@
         }
         throw IllegalStateException("Failed to stop $runningProcesses")
     }
+
+    @RequiresApi(21)
+    fun pathExists(absoluteFilePath: String): Boolean {
+        return executeCommand("ls $absoluteFilePath").trim() == absoluteFilePath
+    }
 }
 
 @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
@@ -362,7 +368,7 @@
             executeCommand("cp ${writableScriptFile.absolutePath} $runnableScriptPath")
             Shell.chmodExecutable(runnableScriptPath)
 
-            val stdout = executeCommand(runnableScriptPath)
+            val stdout = trace("executeCommand") { executeCommand(runnableScriptPath) }
             val stderr = stderrPath?.run { executeCommand("cat $stderrPath") }
 
             return Pair(stdout, stderr)
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoConfig.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoConfig.kt
index e7412a0..26a45c3 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoConfig.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoConfig.kt
@@ -95,7 +95,8 @@
         name = "linux.process_stats",
         target_buffer = 1,
         process_stats_config = ProcessStatsConfig(
-            proc_stats_poll_ms = 10000
+            proc_stats_poll_ms = 10000,
+            scan_all_processes_on_start = true
         )
     )
 )
@@ -177,5 +178,13 @@
             "Support for wildcard (*) app matching in atrace added in API 28"
         }
     }
+
+    if (Build.VERSION.SDK_INT < 24) {
+        val packageList = ftraceConfig.atrace_apps.joinToString(",")
+        check(packageList.length <= 91) {
+            "Unable to trace package list (\"$packageList\").length = " +
+                "${packageList.length} > 91 chars, which is the limit before API 24"
+        }
+    }
     return encode()
 }
\ No newline at end of file
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 5d5e266a..2b0cf5d 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
@@ -114,6 +114,58 @@
             throw perfettoStartupException("Perfetto tracing failed to start.", null)
         }
         Log.i(LOG_TAG, "Perfetto tracing started successfully with pid $perfettoPid.")
+
+        checkTracingOn()
+    }
+
+    /**
+     * Poll for tracing_on to be set to 1.
+     *
+     * This is a good indicator that tracing is actually enabled (including the app atrace tag), and
+     * that content will be captured in the trace buffer
+     */
+    private fun checkTracingOn(): Unit = userspaceTrace("poll tracing_on") {
+        val path: String = when {
+            Shell.pathExists(TRACING_ON_PATH) -> {
+                TRACING_ON_PATH
+            }
+            Shell.pathExists(TRACING_ON_FALLBACK_PATH) -> {
+                TRACING_ON_FALLBACK_PATH
+            }
+            else -> {
+                throw perfettoStartupException(
+                    "Unable to find path to tracing_on (e.g. $TRACING_ON_PATH)",
+                    null
+                )
+            }
+        }
+
+        val pollTracingOnMaxCount = 50
+        val pollTracingOnMs = 100L
+
+        repeat(pollTracingOnMaxCount) {
+            when (val output = Shell.executeCommand("cat $path").trim()) {
+                "0" -> {
+                    userspaceTrace("wait for trace to start (tracing_on == 1)") {
+                        SystemClock.sleep(pollTracingOnMs)
+                    }
+                }
+                "1" -> {
+                    // success!
+                    Log.i(LOG_TAG, "$path = 1, polled $it times, capture fully started")
+                    return@checkTracingOn
+                }
+                else -> {
+                    throw perfettoStartupException(
+                        "Saw unexpected tracing_on contents: $output",
+                        null
+                    )
+                }
+            }
+        }
+
+        val duration = pollTracingOnMs * pollTracingOnMaxCount
+        throw perfettoStartupException("Error: did not detect tracing on after $duration ms", null)
     }
 
     /**
@@ -294,6 +346,10 @@
         private val SUPPORTED_64_ABIS = setOf("arm64-v8a", "x86_64")
         private val SUPPORTED_32_ABIS = setOf("armeabi")
 
+        // potential paths that tracing_on may reside in
+        private const val TRACING_ON_PATH = "/sys/kernel/tracing/tracing_on"
+        private const val TRACING_ON_FALLBACK_PATH = "/sys/kernel/debug/tracing/tracing_on"
+
         @TestOnly
         fun isAbiSupported(): Boolean {
             Log.d(LOG_TAG, "Supported ABIs: ${Build.SUPPORTED_ABIS.joinToString()}")
diff --git a/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/PerfettoRule.kt b/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/PerfettoRule.kt
index 4862143..7cf92c9 100644
--- a/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/PerfettoRule.kt
+++ b/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/PerfettoRule.kt
@@ -81,11 +81,8 @@
 internal fun PerfettoCapture.recordAndReportFile(traceName: String, block: () -> Unit) {
     try {
         Log.d(PerfettoRule.TAG, "Recording perfetto trace $traceName")
-        val targetPackage = InstrumentationRegistry
-            .getInstrumentation()
-            .targetContext
-            .packageName
-        start(packages = listOf(targetPackage))
+        val inst = InstrumentationRegistry.getInstrumentation()
+        start(packages = listOf(inst.targetContext.packageName, inst.context.packageName))
         block()
         Outputs.writeFile(fileName = traceName, reportKey = "perfetto_trace") {
             val destinationPath = it.absolutePath
diff --git a/benchmark/benchmark-macro/api/1.1.0-beta01.txt b/benchmark/benchmark-macro/api/1.1.0-beta01.txt
index 8ebc594..b5ba91e 100644
--- a/benchmark/benchmark-macro/api/1.1.0-beta01.txt
+++ b/benchmark/benchmark-macro/api/1.1.0-beta01.txt
@@ -74,3 +74,10 @@
 
 }
 
+package androidx.benchmark.macro.perfetto {
+
+  public final class ForceTracingKt {
+  }
+
+}
+
diff --git a/benchmark/benchmark-macro/api/current.txt b/benchmark/benchmark-macro/api/current.txt
index 8ebc594..b5ba91e 100644
--- a/benchmark/benchmark-macro/api/current.txt
+++ b/benchmark/benchmark-macro/api/current.txt
@@ -74,3 +74,10 @@
 
 }
 
+package androidx.benchmark.macro.perfetto {
+
+  public final class ForceTracingKt {
+  }
+
+}
+
diff --git a/benchmark/benchmark-macro/api/public_plus_experimental_1.1.0-beta01.txt b/benchmark/benchmark-macro/api/public_plus_experimental_1.1.0-beta01.txt
index 8ebc594..b5ba91e 100644
--- a/benchmark/benchmark-macro/api/public_plus_experimental_1.1.0-beta01.txt
+++ b/benchmark/benchmark-macro/api/public_plus_experimental_1.1.0-beta01.txt
@@ -74,3 +74,10 @@
 
 }
 
+package androidx.benchmark.macro.perfetto {
+
+  public final class ForceTracingKt {
+  }
+
+}
+
diff --git a/benchmark/benchmark-macro/api/public_plus_experimental_current.txt b/benchmark/benchmark-macro/api/public_plus_experimental_current.txt
index 8ebc594..b5ba91e 100644
--- a/benchmark/benchmark-macro/api/public_plus_experimental_current.txt
+++ b/benchmark/benchmark-macro/api/public_plus_experimental_current.txt
@@ -74,3 +74,10 @@
 
 }
 
+package androidx.benchmark.macro.perfetto {
+
+  public final class ForceTracingKt {
+  }
+
+}
+
diff --git a/benchmark/benchmark-macro/api/restricted_1.1.0-beta01.txt b/benchmark/benchmark-macro/api/restricted_1.1.0-beta01.txt
index 4a59db7..f7121a1e 100644
--- a/benchmark/benchmark-macro/api/restricted_1.1.0-beta01.txt
+++ b/benchmark/benchmark-macro/api/restricted_1.1.0-beta01.txt
@@ -83,3 +83,10 @@
 
 }
 
+package androidx.benchmark.macro.perfetto {
+
+  public final class ForceTracingKt {
+  }
+
+}
+
diff --git a/benchmark/benchmark-macro/api/restricted_current.txt b/benchmark/benchmark-macro/api/restricted_current.txt
index 4a59db7..f7121a1e 100644
--- a/benchmark/benchmark-macro/api/restricted_current.txt
+++ b/benchmark/benchmark-macro/api/restricted_current.txt
@@ -83,3 +83,10 @@
 
 }
 
+package androidx.benchmark.macro.perfetto {
+
+  public final class ForceTracingKt {
+  }
+
+}
+
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 4cf3f2b..e9996c5 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
@@ -38,6 +38,7 @@
 import org.junit.runner.RunWith
 import kotlin.test.assertEquals
 import kotlin.test.assertFailsWith
+import kotlin.test.assertTrue
 
 /**
  * Tests for PerfettoCapture
@@ -90,39 +91,43 @@
 
         perfettoCapture.start(listOf(Packages.TEST))
 
-        verifyTraceEnable(true)
+        assertTrue(
+            Trace.isEnabled(),
+            "In-process tracing should be enabled immediately after trace capture is started"
+        )
 
-        // TODO: figure out why this sleep (200ms+) is needed - possibly related to b/194105203
-        Thread.sleep(500)
-
-        // 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) }
+        /**
+         * Trace section labels, in order
+         *
+         * We trace for non-trivial duration both to enable easier manual debugging, but also to
+         * help clarify problems in front/back trace truncation, with indication of severity.
+         *
+         * We use unique, app tag names to avoid conflicting with other legitimate platform tracing.
+         */
+        val traceSectionLabels = List(20) {
+            "PerfettoCaptureTest_$it".also { label ->
+                trace(label) { Thread.sleep(50) }
+            }
+        }
 
         perfettoCapture.stop(traceFilePath)
 
         val matchingSlices = PerfettoTraceProcessor.querySlices(
             absoluteTracePath = traceFilePath,
-            CUSTOM_TRACE_SECTION_LABEL_1,
-            CUSTOM_TRACE_SECTION_LABEL_2
+            "PerfettoCaptureTest_%"
         )
 
         // Note: this test avoids validating platform-triggered trace sections, to avoid flakes
         // from legitimate (and coincidental) platform use during test.
         assertEquals(
-            listOf(CUSTOM_TRACE_SECTION_LABEL_1, CUSTOM_TRACE_SECTION_LABEL_2),
+            traceSectionLabels,
             matchingSlices.sortedBy { it.ts }.map { it.name }
         )
         matchingSlices
             .forEach {
-                assertTrue(it.dur > 15_000_000) // should be at least 15ms
+                assertTrue(it.dur > 30_000_000) // should be at least 30ms
             }
     }
-
-    companion object {
-        const val CUSTOM_TRACE_SECTION_LABEL_1 = "PerfettoCaptureTest_1"
-        const val CUSTOM_TRACE_SECTION_LABEL_2 = "PerfettoCaptureTest_2"
-    }
 }
 
 fun verifyTraceEnable(enabled: Boolean) {
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/StartupTimingQueryTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/StartupTimingQueryTest.kt
new file mode 100644
index 0000000..54dd8a0
--- /dev/null
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/StartupTimingQueryTest.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.
+ */
+
+package androidx.benchmark.macro.perfetto
+
+import androidx.benchmark.macro.StartupMode
+import androidx.benchmark.macro.createTempFileFromAsset
+import androidx.benchmark.perfetto.PerfettoHelper.Companion.isAbiSupported
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Assume.assumeTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.Locale
+import kotlin.test.assertEquals
+
+@RunWith(AndroidJUnit4::class)
+class StartupTimingQueryTest {
+    private fun validateFixedTrace(
+        @Suppress("SameParameterValue") api: Int,
+        startupMode: StartupMode,
+        expectedMetrics: StartupTimingQuery.SubMetrics
+    ) {
+        assumeTrue(isAbiSupported())
+        val traceFile = createTempFileFromAsset(
+            prefix = "api${api}_startup_${startupMode.name.lowercase(Locale.getDefault())}",
+            suffix = ".perfetto-trace"
+        )
+
+        val startupSubMetrics = StartupTimingQuery.getFrameSubMetrics(
+            absoluteTracePath = traceFile.absolutePath,
+            captureApiLevel = api,
+            targetPackageName = "androidx.benchmark.integration.macrobenchmark.target",
+            testPackageName = "androidx.benchmark.integration.macrobenchmark.test",
+            startupMode = startupMode
+        )
+
+        assertEquals(expected = expectedMetrics, actual = startupSubMetrics)
+    }
+
+    @Test
+    fun fixedApi24Cold() = validateFixedTrace(
+        api = 24,
+        startupMode = StartupMode.COLD,
+        StartupTimingQuery.SubMetrics(
+            timeToInitialDisplayNs = 358237760,
+            timeToFullDisplayNs = 784769167,
+            timelineRange = 269543917431669..269544702200836
+        )
+    )
+
+    @Test
+    fun fixedApi24Warm() = validateFixedTrace(
+        api = 24,
+        startupMode = StartupMode.WARM,
+        StartupTimingQuery.SubMetrics(
+            timeToInitialDisplayNs = 135008333,
+            timeToFullDisplayNs = 598500833,
+            timelineRange = 268757401479247..268757999980080
+        )
+    )
+
+    @Test
+    fun fixedApi24Hot() = validateFixedTrace(
+        api = 24,
+        startupMode = StartupMode.HOT,
+        StartupTimingQuery.SubMetrics(
+            timeToInitialDisplayNs = 54248802,
+            timeToFullDisplayNs = 529336511,
+            timelineRange = 268727533977218..268728063313729
+        )
+    )
+
+    @Test
+    fun fixedApi31Cold() = validateFixedTrace(
+        api = 31,
+        startupMode = StartupMode.COLD,
+        StartupTimingQuery.SubMetrics(
+            timeToInitialDisplayNs = 137401159,
+            timeToFullDisplayNs = 612424592,
+            timelineRange = 186974946587883..186975559012475
+        )
+    )
+
+    @Test
+    fun fixedApi31Warm() = validateFixedTrace(
+        api = 31,
+        startupMode = StartupMode.WARM,
+        StartupTimingQuery.SubMetrics(
+            timeToInitialDisplayNs = 55378859,
+            timeToFullDisplayNs = 546599533,
+            timelineRange = 186982060149946..186982606749479
+        )
+    )
+
+    @Test
+    fun fixedApi31Hot() = validateFixedTrace(
+        api = 31,
+        startupMode = StartupMode.HOT,
+        StartupTimingQuery.SubMetrics(
+            timeToInitialDisplayNs = 42757609,
+            timeToFullDisplayNs = 537651148,
+            timelineRange = 186969446545095..186969984196243
+        )
+    )
+}
\ No newline at end of file
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/CompilationMode.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/CompilationMode.kt
index 79cbffc..fe8e04e 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/CompilationMode.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/CompilationMode.kt
@@ -16,6 +16,7 @@
 
 package androidx.benchmark.macro
 
+import android.os.Build
 import android.util.Log
 import androidx.annotation.RestrictTo
 import androidx.benchmark.DeviceInfo
@@ -95,6 +96,12 @@
  * For more information: https://source.android.com/devices/tech/dalvik/jit-compiler
  */
 internal fun CompilationMode.compile(packageName: String, block: () -> Unit) {
+    if (Build.VERSION.SDK_INT < 24) {
+        // All supported versions prior to 24 were full AOT
+        // TODO: clarify this with CompilationMode errors on these versions
+        return
+    }
+
     // Clear profile between runs.
     Log.d(TAG, "Clearing profiles for $packageName")
     Shell.executeCommand("cmd package compile --reset $packageName")
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
index a0451ae..34b557d 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
@@ -139,6 +139,9 @@
         }
     }
 
+    // package name for macrobench process, so it's captured as well
+    val macrobenchPackageName = InstrumentationRegistry.getInstrumentation().context.packageName
+
     // Perfetto collector is separate from metrics, so we can control file
     // output, and give it different (test-wide) lifecycle
     val perfettoCollector = PerfettoCaptureWrapper()
@@ -157,7 +160,21 @@
             val tracePath = perfettoCollector.record(
                 benchmarkName = uniqueName,
                 iteration = iteration,
-                packages = listOf(packageName)
+
+                /**
+                 * Prior to API 24, every package name was joined into a single setprop which can
+                 * overflow, and disable *ALL* app level tracing.
+                 *
+                 * For safety here, we only trace the macrobench package on newer platforms, and use
+                 * reflection in the macrobench test process to trace important sections
+                 *
+                 * @see androidx.benchmark.macro.perfetto.ForceTracing
+                 */
+                packages = if (Build.VERSION.SDK_INT >= 24) {
+                    listOf(packageName, macrobenchPackageName)
+                } else {
+                    listOf(packageName)
+                }
             ) {
                 try {
                     userspaceTrace("start metrics") {
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt
index 7a639fc..e15d0cd 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt
@@ -22,9 +22,9 @@
 import androidx.annotation.RequiresApi
 import androidx.benchmark.DeviceInfo
 import androidx.benchmark.Shell
+import androidx.benchmark.macro.perfetto.forceTrace
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
-import androidx.tracing.trace
 
 /**
  * Provides access to common operations in app automation, such as killing the app,
@@ -73,7 +73,7 @@
      *
      * @param intent Specifies which app/Activity should be launched.
      */
-    public fun startActivityAndWait(intent: Intent): Unit = trace("startActivityAndWait") {
+    public fun startActivityAndWait(intent: Intent): Unit = forceTrace("startActivityAndWait") {
         // Must launch with new task, as we're not launching from an existing task
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
         if (launchWithClearTask) {
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/ForceTracing.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/ForceTracing.kt
new file mode 100644
index 0000000..2f0c372
--- /dev/null
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/ForceTracing.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark.macro.perfetto
+
+import android.os.Build
+import android.os.Trace
+
+/**
+ * Wrapper that uses standard app tracing above API 24, and forces trace sections via reflection
+ * on 23 and below.
+ *
+ * Prior to API 24, all apps with tracing enabled had to be listed in a single setprop, fully
+ * enumerated by package name. As a setprop can only be 91 chars long, it's easy to hit cases where
+ * a macrobenchmark and its target can't fit into this single setprop. For this reason, prior to 24,
+ * the macrobenchmark process doesn't rely on the app tracing tag, and instead forces trace sections
+ * via reflection, and the TRACE_TAG_ALWAYS tag.
+ */
+internal object ForceTracing {
+    private val traceBeginMethod = if (Build.VERSION.SDK_INT < 24) {
+        android.os.Trace::class.java.getMethod(
+            "traceBegin",
+            Long::class.javaPrimitiveType,
+            String::class.javaPrimitiveType
+        )
+    } else null
+
+    private val traceEndMethod = if (Build.VERSION.SDK_INT < 24) {
+        android.os.Trace::class.java.getMethod(
+            "traceEnd",
+            Long::class.javaPrimitiveType,
+            String::class.javaPrimitiveType
+        )
+    } else null
+
+    private const val TRACE_TAG_ALWAYS = 1L shl 0 // copied from android.os.Trace
+
+    fun begin(label: String) {
+        if (Build.VERSION.SDK_INT < 24) {
+            traceBeginMethod!!.invoke(null, TRACE_TAG_ALWAYS, label)
+        } else {
+            Trace.beginSection(label)
+        }
+    }
+
+    fun end() {
+        if (Build.VERSION.SDK_INT < 24) {
+            traceEndMethod!!.invoke(null, TRACE_TAG_ALWAYS)
+        } else {
+            Trace.endSection()
+        }
+    }
+}
+
+internal inline fun <T> forceTrace(label: String, block: () -> T): T {
+    ForceTracing.begin(label)
+    return try {
+        block()
+    } finally {
+        ForceTracing.end()
+    }
+}
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/PerfettoTraceProcessor.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/PerfettoTraceProcessor.kt
index 68803a4c..8bed227 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/PerfettoTraceProcessor.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/PerfettoTraceProcessor.kt
@@ -72,6 +72,8 @@
 
     /**
      * Query a trace for a list of slices - name, timestamp, and duration.
+     *
+     * Note that sliceNames may include wildcard matches, such as `foo%`
      */
     fun querySlices(
         absoluteTracePath: String,
@@ -79,7 +81,7 @@
     ): List<Slice> {
         val whereClause = sliceNames
             .joinToString(separator = " OR ") {
-                "slice.name = '$it'"
+                "slice.name LIKE \"$it\""
             }
 
         return Slice.parseListFromQueryResult(
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/StartupTimingQuery.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/StartupTimingQuery.kt
new file mode 100644
index 0000000..91d194b
--- /dev/null
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/StartupTimingQuery.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark.macro.perfetto
+
+import androidx.benchmark.macro.StartupMode
+
+internal object StartupTimingQuery {
+
+    /**
+     * On older platforms, process name may be truncated, especially in cold startup traces,
+     * when the process name dump at trace begin happens _before_ app process is created.
+     *
+     * @see perfetto.protos.ProcessStatsConfig.scan_all_processes_on_start
+     */
+    private fun String.truncatedProcessName() = takeLast(15)
+
+    private fun getFullQuery(testProcessName: String, targetProcessName: String) = """
+        ------ Select all startup-relevant slices from slice table
+        SELECT
+            slice.name as name,
+            slice.ts as ts,
+            slice.dur as dur
+        FROM slice
+            INNER JOIN thread_track on slice.track_id = thread_track.id
+            INNER JOIN thread USING(utid)
+            INNER JOIN process USING(upid)
+        WHERE (
+            -- Test process starts before tracing, so it shouldn't have truncation problem
+            (process.name LIKE "$testProcessName" AND slice.name LIKE "startActivityAndWait") OR
+            (
+                (
+                    -- check for full or truncated process name (can happen on older platforms)
+                    process.name LIKE "$targetProcessName" OR
+                    process.name LIKE "${targetProcessName.truncatedProcessName()}"
+                ) AND (
+                    (slice.name LIKE "activityResume" AND process.pid LIKE thread.tid) OR
+                    (slice.name LIKE "Choreographer#doFrame%" AND process.pid LIKE thread.tid) OR
+                    (slice.name LIKE "reportFullyDrawn() for %" AND process.pid LIKE thread.tid) OR
+                    (slice.name LIKE "DrawFrame%" AND thread.name LIKE "RenderThread")
+                )
+            )
+        )
+        ------ Add in async slices
+        UNION
+        SELECT
+            slice.name as name,
+            slice.ts as ts,
+            slice.dur as dur
+        FROM slice
+            INNER JOIN process_track on slice.track_id = process_track.id
+            INNER JOIN process USING(upid)
+        WHERE (
+            -- API 23+:   "launching: <target>"
+            -- API 19-22: "launching"
+            slice.name LIKE "launching%" AND process.name LIKE "system_server"
+        )
+        ORDER BY ts ASC
+    """.trimIndent()
+
+    enum class StartupSliceType {
+        StartActivityAndWait,
+        Launching,
+        ReportFullyDrawn,
+        FrameUiThread,
+        FrameRenderThread,
+        ActivityResume
+    }
+
+    data class SubMetrics(
+        val timeToInitialDisplayNs: Long,
+        val timeToFullDisplayNs: Long?,
+        val timelineRange: LongRange
+    ) {
+        constructor(
+            startTs: Long,
+            initialDisplayTs: Long,
+            fullDisplayTs: Long?
+        ) : this(
+            timeToInitialDisplayNs = initialDisplayTs - startTs,
+            timeToFullDisplayNs = fullDisplayTs?.let { it - startTs },
+            timelineRange = startTs..(fullDisplayTs ?: initialDisplayTs),
+        )
+    }
+
+    private fun findEndRenderTimeForUiFrame(
+        uiSlices: List<Slice>,
+        rtSlices: List<Slice>,
+        predicate: (Slice) -> Boolean
+    ): Long {
+        // find first UI slice that corresponds with the predicate
+        val uiSlice = uiSlices.first(predicate)
+
+        // find corresponding rt slice
+        val rtSlice = rtSlices.first { rtSlice ->
+            rtSlice.ts > uiSlice.ts
+        }
+        return rtSlice.endTs
+    }
+
+    fun getFrameSubMetrics(
+        absoluteTracePath: String,
+        captureApiLevel: Int,
+        targetPackageName: String,
+        testPackageName: String,
+        startupMode: StartupMode
+    ): SubMetrics? {
+        val queryResult = PerfettoTraceProcessor.rawQuery(
+            absoluteTracePath = absoluteTracePath,
+            query = getFullQuery(
+                testProcessName = testPackageName,
+                targetProcessName = targetPackageName
+            )
+        )
+        val slices = Slice.parseListFromQueryResult(queryResult)
+
+        val groupedData = slices
+            .filter { it.dur > 0 } // drop non-terminated slices
+            .groupBy {
+                when {
+                    // note: we use "startsWith" as many of these have more details
+                    // appended to the slice name in more recent platform versions
+                    it.name.startsWith("Choreographer#doFrame") -> StartupSliceType.FrameUiThread
+                    it.name.startsWith("DrawFrame") -> StartupSliceType.FrameRenderThread
+                    it.name.startsWith("launching") -> StartupSliceType.Launching
+                    it.name.startsWith("reportFullyDrawn") -> StartupSliceType.ReportFullyDrawn
+                    it.name == "activityResume" -> StartupSliceType.ActivityResume
+                    it.name == "startActivityAndWait" -> StartupSliceType.StartActivityAndWait
+                    else -> throw IllegalStateException("Unexpected slice $it")
+                }
+            }
+
+        val startActivityAndWaitSlice = groupedData[StartupSliceType.StartActivityAndWait]?.first()
+            ?: return null
+
+        val uiSlices = groupedData.getOrElse(StartupSliceType.FrameUiThread) { listOf() }
+        val rtSlices = groupedData.getOrElse(StartupSliceType.FrameRenderThread) { listOf() }
+
+        val startTs: Long
+        val initialDisplayTs: Long
+        if (captureApiLevel >= 29 || startupMode != StartupMode.HOT) {
+            val launchingSlice = groupedData[StartupSliceType.Launching]?.firstOrNull {
+                // find first "launching" slice that starts within startActivityAndWait
+                // verify full name only on API 23+, since before package name not specified
+                startActivityAndWaitSlice.contains(it.ts) &&
+                    (captureApiLevel < 23 || it.name == "launching: $targetPackageName")
+            } ?: return null
+            startTs = launchingSlice.ts
+            initialDisplayTs = launchingSlice.endTs
+        } else {
+            // Prior to API 29, hot starts weren't traced with the launching slice, so we do a best
+            // guess - the time taken to Activity#onResume, and then produce the next frame.
+            startTs = groupedData[StartupSliceType.ActivityResume]?.first()?.ts
+                ?: return null
+            initialDisplayTs = findEndRenderTimeForUiFrame(uiSlices, rtSlices) { uiSlice ->
+                uiSlice.ts > startTs
+            }
+        }
+
+        val reportFullyDrawnSlice = groupedData[StartupSliceType.ReportFullyDrawn]?.firstOrNull()
+
+        val reportFullyDrawnEndTs: Long? = reportFullyDrawnSlice?.let {
+            // find first uiSlice with end after reportFullyDrawn (reportFullyDrawn may happen
+            // during or before a given frame)
+            findEndRenderTimeForUiFrame(uiSlices, rtSlices) { uiSlice ->
+                uiSlice.endTs > reportFullyDrawnSlice.ts
+            }
+        }
+
+        return SubMetrics(
+            startTs = startTs,
+            initialDisplayTs = initialDisplayTs,
+            fullDisplayTs = reportFullyDrawnEndTs,
+        )
+    }
+}
\ No newline at end of file
diff --git a/benchmark/integration-tests/dry-run-benchmark/src/androidTest/java/androidx/benchmark/integration/dryrun/benchmark/ArgumentInjectingApplication.kt b/benchmark/integration-tests/dry-run-benchmark/src/androidTest/java/androidx/benchmark/integration/dryrun/benchmark/ArgumentInjectingApplication.kt
index dc94441..6059c07 100644
--- a/benchmark/integration-tests/dry-run-benchmark/src/androidTest/java/androidx/benchmark/integration/dryrun/benchmark/ArgumentInjectingApplication.kt
+++ b/benchmark/integration-tests/dry-run-benchmark/src/androidTest/java/androidx/benchmark/integration/dryrun/benchmark/ArgumentInjectingApplication.kt
@@ -41,9 +41,9 @@
         super.onCreate()
 
         argumentSource = Bundle().apply {
-            putString("androidx.benchmark.output.enable", "true")
             putString("androidx.benchmark.startupMode.enable", "true") // this should be ignored
             putString("androidx.benchmark.dryRunMode.enable", "true")
+            putString("androidx.benchmark.profiling.mode", "none") // noop, tests "none" arg parsing
         }
     }
 }
\ No newline at end of file
diff --git a/benchmark/integration-tests/macrobenchmark-target/build.gradle b/benchmark/integration-tests/macrobenchmark-target/build.gradle
index 6dae47c..7ad8481 100644
--- a/benchmark/integration-tests/macrobenchmark-target/build.gradle
+++ b/benchmark/integration-tests/macrobenchmark-target/build.gradle
@@ -33,8 +33,8 @@
 dependencies {
     implementation(libs.kotlinStdlib)
     implementation(libs.constraintLayout)
-    implementation("androidx.arch.core:core-runtime:2.1.0")
     implementation("androidx.appcompat:appcompat:1.2.0")
+    implementation("androidx.activity:activity:1.3.0")
     implementation("androidx.recyclerview:recyclerview:1.1.0")
     implementation(libs.material)
 }
diff --git a/benchmark/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml b/benchmark/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
index 3126a21..80943c0 100644
--- a/benchmark/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
+++ b/benchmark/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
@@ -29,8 +29,7 @@
         <profileable android:shell="true"/>
 
         <!--
-        Activities need to be exported so the macrobenchmark can discover them
-        under the new package visibility changes for Android 11.
+        Activities need to be exported so the macrobenchmark can discover them.
          -->
         <activity
             android:name=".TrivialStartupActivity"
@@ -46,6 +45,15 @@
         </activity>
 
         <activity
+            android:name=".TrivialStartupFullyDrawnActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="androidx.benchmark.integration.macrobenchmark.target.TRIVIAL_STARTUP_FULLY_DRAWN_ACTIVITY" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <activity
             android:name=".RecyclerViewActivity"
             android:exported="true">
             <intent-filter>
@@ -54,10 +62,8 @@
             </intent-filter>
         </activity>
 
-
         <!--
         Activities need to be exported so the macrobenchmark can discover them
-        under the new package visibility changes for Android 11.
          -->
         <activity
             android:name=".NotExportedActivity"
diff --git a/benchmark/integration-tests/macrobenchmark-target/src/main/java/androidx/benchmark/integration/macrobenchmark/target/TrivialStartupFullyDrawnActivity.kt b/benchmark/integration-tests/macrobenchmark-target/src/main/java/androidx/benchmark/integration/macrobenchmark/target/TrivialStartupFullyDrawnActivity.kt
new file mode 100644
index 0000000..bae9765
--- /dev/null
+++ b/benchmark/integration-tests/macrobenchmark-target/src/main/java/androidx/benchmark/integration/macrobenchmark/target/TrivialStartupFullyDrawnActivity.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark.integration.macrobenchmark.target
+
+import android.annotation.SuppressLint
+import android.os.Build
+import android.os.Bundle
+import android.os.Trace
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+
+/**
+ * Trivial activity which triggers reportFullyDrawn ~500ms after resume
+ */
+@SuppressLint("SyntheticAccessor")
+class TrivialStartupFullyDrawnActivity : AppCompatActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_main)
+
+        forceEnablePlatformTracing() // ensure reportFullyDrawn will be traced
+    }
+
+    override fun onResume() {
+        super.onResume()
+
+        val notice = findViewById<TextView>(R.id.txtNotice)
+        notice.text = "INITIAL DISPLAY"
+
+        // report delayed, modify text
+        val runnable = {
+            notice.text = "FULL DISPLAY"
+            reportFullyDrawn()
+        }
+        notice.postDelayed(runnable, 500 /* ms */)
+    }
+}
+
+/**
+ * Force-enables platform tracing on older API levels, where it's disabled for non-debuggable apps.
+ *
+ * TODO: move this to an androidx.tracing API
+ */
+@SuppressLint("BanUncheckedReflection") // b/202759865
+private fun forceEnablePlatformTracing() {
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) return
+    if (Build.VERSION.SDK_INT >= 29) return
+
+    val method = android.os.Trace::class.java.getMethod(
+        "setAppTracingAllowed",
+        Boolean::class.javaPrimitiveType
+    )
+    method.invoke(null, true)
+}
diff --git a/benchmark/integration-tests/macrobenchmark-target/src/main/res/values/donottranslate-strings.xml b/benchmark/integration-tests/macrobenchmark-target/src/main/res/values/donottranslate-strings.xml
index c59add0..f8753b2 100644
--- a/benchmark/integration-tests/macrobenchmark-target/src/main/res/values/donottranslate-strings.xml
+++ b/benchmark/integration-tests/macrobenchmark-target/src/main/res/values/donottranslate-strings.xml
@@ -15,6 +15,6 @@
   -->
 
 <resources>
-    <string name="app_notice">Macro Benchmark Integration Test App.</string>
+    <string name="app_notice">Macrobenchmark Integration Test App.</string>
     <string name="recyclerDescription">A list of items</string>
 </resources>
diff --git a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/TrivialStartupFullyDrawnBenchmark.kt b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/TrivialStartupFullyDrawnBenchmark.kt
new file mode 100644
index 0000000..22a5431
--- /dev/null
+++ b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/TrivialStartupFullyDrawnBenchmark.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark.integration.macrobenchmark
+
+import android.content.Intent
+import androidx.benchmark.macro.CompilationMode
+import androidx.benchmark.macro.StartupMode
+import androidx.benchmark.macro.StartupTimingMetric
+import androidx.benchmark.macro.junit4.MacrobenchmarkRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Fully drawn benchmark, used to create sample startup
+ * traces with reportFullyDrawn ~500ms after resume
+ *
+ * As this is just used to provide sample / test traces, it's only ever one iteration with no
+ * parameterization beyond startup mode
+ */
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = 29)
+class TrivialStartupFullyDrawnBenchmark {
+    @get:Rule
+    val benchmarkRule = MacrobenchmarkRule()
+
+    fun startup(startupMode: StartupMode) = benchmarkRule.measureRepeated(
+        compilationMode = CompilationMode.None,
+        packageName = TARGET_PACKAGE_NAME,
+        metrics = listOf(StartupTimingMetric()),
+        startupMode = startupMode,
+        iterations = 1
+    ) {
+        pressHome()
+        startActivityAndWait(Intent().apply {
+            setPackage(TARGET_PACKAGE_NAME)
+            action = "androidx.benchmark.integration.macrobenchmark.target" +
+                ".TRIVIAL_STARTUP_FULLY_DRAWN_ACTIVITY"
+        })
+        val fullDisplayComplete = UiDevice
+            .getInstance(InstrumentationRegistry.getInstrumentation())
+            .wait(Until.findObject(By.text("FULL DISPLAY")), 3000) != null
+        check(fullDisplayComplete)
+    }
+
+    @Test
+    fun hot() = startup(StartupMode.HOT)
+
+    @Test
+    fun warm() = startup(StartupMode.WARM)
+
+    @Test
+    fun cold() = startup(StartupMode.COLD)
+
+    companion object {
+        const val TARGET_PACKAGE_NAME = "androidx.benchmark.integration.macrobenchmark.target"
+    }
+}
diff --git a/biometric/biometric/api/current.txt b/biometric/biometric/api/current.txt
index 5ff779e0..6d8f18f 100644
--- a/biometric/biometric/api/current.txt
+++ b/biometric/biometric/api/current.txt
@@ -5,6 +5,7 @@
     method @Deprecated public int canAuthenticate();
     method public int canAuthenticate(int);
     method public static androidx.biometric.BiometricManager from(android.content.Context);
+    method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public androidx.biometric.BiometricManager.Strings? getStrings(int);
     field public static final int BIOMETRIC_ERROR_HW_UNAVAILABLE = 1; // 0x1
     field public static final int BIOMETRIC_ERROR_NONE_ENROLLED = 11; // 0xb
     field public static final int BIOMETRIC_ERROR_NO_HARDWARE = 12; // 0xc
@@ -20,6 +21,12 @@
     field public static final int DEVICE_CREDENTIAL = 32768; // 0x8000
   }
 
+  public static class BiometricManager.Strings {
+    method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public CharSequence? getButtonLabel();
+    method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public CharSequence? getPromptMessage();
+    method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public CharSequence? getSettingName();
+  }
+
   public class BiometricPrompt {
     ctor public BiometricPrompt(androidx.fragment.app.FragmentActivity, androidx.biometric.BiometricPrompt.AuthenticationCallback);
     ctor public BiometricPrompt(androidx.fragment.app.Fragment, androidx.biometric.BiometricPrompt.AuthenticationCallback);
diff --git a/biometric/biometric/api/public_plus_experimental_current.txt b/biometric/biometric/api/public_plus_experimental_current.txt
index 5ff779e0..6d8f18f 100644
--- a/biometric/biometric/api/public_plus_experimental_current.txt
+++ b/biometric/biometric/api/public_plus_experimental_current.txt
@@ -5,6 +5,7 @@
     method @Deprecated public int canAuthenticate();
     method public int canAuthenticate(int);
     method public static androidx.biometric.BiometricManager from(android.content.Context);
+    method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public androidx.biometric.BiometricManager.Strings? getStrings(int);
     field public static final int BIOMETRIC_ERROR_HW_UNAVAILABLE = 1; // 0x1
     field public static final int BIOMETRIC_ERROR_NONE_ENROLLED = 11; // 0xb
     field public static final int BIOMETRIC_ERROR_NO_HARDWARE = 12; // 0xc
@@ -20,6 +21,12 @@
     field public static final int DEVICE_CREDENTIAL = 32768; // 0x8000
   }
 
+  public static class BiometricManager.Strings {
+    method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public CharSequence? getButtonLabel();
+    method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public CharSequence? getPromptMessage();
+    method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public CharSequence? getSettingName();
+  }
+
   public class BiometricPrompt {
     ctor public BiometricPrompt(androidx.fragment.app.FragmentActivity, androidx.biometric.BiometricPrompt.AuthenticationCallback);
     ctor public BiometricPrompt(androidx.fragment.app.Fragment, androidx.biometric.BiometricPrompt.AuthenticationCallback);
diff --git a/biometric/biometric/api/restricted_current.txt b/biometric/biometric/api/restricted_current.txt
index 5ff779e0..6d8f18f 100644
--- a/biometric/biometric/api/restricted_current.txt
+++ b/biometric/biometric/api/restricted_current.txt
@@ -5,6 +5,7 @@
     method @Deprecated public int canAuthenticate();
     method public int canAuthenticate(int);
     method public static androidx.biometric.BiometricManager from(android.content.Context);
+    method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public androidx.biometric.BiometricManager.Strings? getStrings(int);
     field public static final int BIOMETRIC_ERROR_HW_UNAVAILABLE = 1; // 0x1
     field public static final int BIOMETRIC_ERROR_NONE_ENROLLED = 11; // 0xb
     field public static final int BIOMETRIC_ERROR_NO_HARDWARE = 12; // 0xc
@@ -20,6 +21,12 @@
     field public static final int DEVICE_CREDENTIAL = 32768; // 0x8000
   }
 
+  public static class BiometricManager.Strings {
+    method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public CharSequence? getButtonLabel();
+    method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public CharSequence? getPromptMessage();
+    method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public CharSequence? getSettingName();
+  }
+
   public class BiometricPrompt {
     ctor public BiometricPrompt(androidx.fragment.app.FragmentActivity, androidx.biometric.BiometricPrompt.AuthenticationCallback);
     ctor public BiometricPrompt(androidx.fragment.app.Fragment, androidx.biometric.BiometricPrompt.AuthenticationCallback);
diff --git a/biometric/biometric/src/main/java/androidx/biometric/AuthenticatorUtils.java b/biometric/biometric/src/main/java/androidx/biometric/AuthenticatorUtils.java
index 180a8ca..bce39f5 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/AuthenticatorUtils.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/AuthenticatorUtils.java
@@ -60,6 +60,17 @@
     }
 
     /**
+     * Removes non-biometric authenticator types from the given set of allowed authenticators.
+     *
+     * @param authenticators A bit field representing a set of allowed authenticator types.
+     * @return A bit field representing the allowed biometric authenticator types.
+     */
+    @BiometricManager.AuthenticatorTypes
+    static int getBiometricAuthenticators(@BiometricManager.AuthenticatorTypes int authenticators) {
+        return authenticators & BIOMETRIC_CLASS_MASK;
+    }
+
+    /**
      * Combines relevant information from the given {@link BiometricPrompt.PromptInfo} and
      * {@link BiometricPrompt.CryptoObject} to determine which type(s) of authenticators should be
      * allowed for a given authentication session.
@@ -140,7 +151,7 @@
      * @return Whether the allowed authenticator types include one or more biometric classes.
      */
     static boolean isSomeBiometricAllowed(@BiometricManager.AuthenticatorTypes int authenticators) {
-        return (authenticators & BIOMETRIC_CLASS_MASK) != 0;
+        return getBiometricAuthenticators(authenticators) != 0;
     }
 
     /**
diff --git a/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java b/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java
index 9c1fc3e..ea9fc39 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java
@@ -38,7 +38,6 @@
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentActivity;
 import androidx.fragment.app.FragmentManager;
-import androidx.lifecycle.Observer;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -196,14 +195,96 @@
     }
 
     /**
-     * A handler used to post delayed events.
+     * An injector for various class and method dependencies. Used for testing.
      */
-    @VisibleForTesting Handler mHandler = new Handler(Looper.getMainLooper());
+    @VisibleForTesting
+    interface Injector {
+        /**
+         * Provides a handler that will be used to post callbacks and messages.
+         *
+         * @return The handler for this fragment.
+         */
+        @NonNull
+        Handler getHandler();
+
+        /**
+         * Provides a view model that will be used to persist state for this fragment.
+         *
+         * @param hostContext The host activity or fragment hostContext.
+         * @return The {@link BiometricViewModel} tied to the host lifecycle.
+         */
+        @Nullable
+        BiometricViewModel getViewModel(@Nullable Context hostContext);
+
+        /**
+         * Checks if the current device has hardware sensor support for fingerprint authentication.
+         *
+         * @param context The application or host context.
+         * @return Whether this device supports fingerprint authentication.
+         */
+        boolean isFingerprintHardwarePresent(@Nullable Context context);
+
+        /**
+         * Checks if the current device has hardware sensor support for face authentication.
+         *
+         * @param context The application or host context.
+         * @return Whether this device supports face authentication.
+         */
+        boolean isFaceHardwarePresent(@Nullable Context context);
+
+        /**
+         * Checks if the current device has hardware sensor support for iris authentication.
+         *
+         * @param context The application or host context.
+         * @return Whether this device supports iris authentication.
+         */
+        boolean isIrisHardwarePresent(@Nullable Context context);
+    }
+
+    /**
+     * Provides the default class and method dependencies that will be used in production.
+     */
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    static class DefaultInjector implements Injector {
+        private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+        @Override
+        @NonNull
+        public Handler getHandler() {
+            return mHandler;
+        }
+
+        @Override
+        @Nullable
+        public BiometricViewModel getViewModel(@Nullable Context hostContext) {
+            return BiometricPrompt.getViewModel(hostContext);
+        }
+
+        @Override
+        public boolean isFingerprintHardwarePresent(@Nullable Context context) {
+            return PackageUtils.hasSystemFeatureFingerprint(context);
+        }
+
+        @Override
+        public boolean isFaceHardwarePresent(@Nullable Context context) {
+            return PackageUtils.hasSystemFeatureFace(context);
+        }
+
+        @Override
+        public boolean isIrisHardwarePresent(@Nullable Context context) {
+            return PackageUtils.hasSystemFeatureIris(context);
+        }
+    }
+
+    /**
+     * The injector for class and method dependencies used by this manager.
+     */
+    private Injector mInjector = new DefaultInjector();
 
     /**
      * The view model for the ongoing authentication session.
      */
-    @VisibleForTesting BiometricViewModel mViewModel;
+    @Nullable private BiometricViewModel mViewModel;
 
     /**
      * Creates a new instance of {@link BiometricFragment}.
@@ -214,6 +295,18 @@
         return new BiometricFragment();
     }
 
+    /**
+     * Creates a new instance of {@link BiometricFragment}.
+     *
+     * @return A {@link BiometricFragment}.
+     */
+    @VisibleForTesting
+    static BiometricFragment newInstance(@NonNull Injector injector) {
+        final BiometricFragment fragment = new BiometricFragment();
+        fragment.mInjector = injector;
+        return fragment;
+    }
+
     @Override
     public void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -226,19 +319,23 @@
 
         // Some device credential implementations in API 29 cause the prompt to receive a cancel
         // signal immediately after it's shown (b/162022588).
+        final BiometricViewModel viewModel = getViewModel();
         if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q
+                && viewModel != null
                 && AuthenticatorUtils.isDeviceCredentialAllowed(
-                        mViewModel.getAllowedAuthenticators())) {
-            mViewModel.setIgnoringCancel(true);
-            mHandler.postDelayed(new StopIgnoringCancelRunnable(mViewModel), 250L);
+                        viewModel.getAllowedAuthenticators())) {
+            viewModel.setIgnoringCancel(true);
+            mInjector.getHandler().postDelayed(new StopIgnoringCancelRunnable(viewModel), 250L);
         }
     }
 
     @Override
     public void onStop() {
         super.onStop();
+        final BiometricViewModel viewModel = getViewModel();
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q
-                && !mViewModel.isConfirmingDeviceCredential()
+                && viewModel != null
+                && !viewModel.isConfirmingDeviceCredential()
                 && !isChangingConfigurations()) {
             cancelAuthentication(BiometricFragment.CANCELED_FROM_INTERNAL);
         }
@@ -248,101 +345,90 @@
     public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
         if (requestCode == REQUEST_CONFIRM_CREDENTIAL) {
-            mViewModel.setConfirmingDeviceCredential(false);
+            final BiometricViewModel viewModel = getViewModel();
+            if (viewModel != null) {
+                viewModel.setConfirmingDeviceCredential(false);
+            }
             handleConfirmCredentialResult(resultCode);
         }
     }
 
     /**
+     * @return The {@link BiometricViewModel} for the ongoing authentication session, injecting it
+     * if necessary.
+     */
+    @Nullable
+    private BiometricViewModel getViewModel() {
+        if (mViewModel == null) {
+            mViewModel = mInjector.getViewModel(BiometricPrompt.getHostActivityOrContext(this));
+        }
+        return mViewModel;
+    }
+
+    /**
      * Connects the {@link BiometricViewModel} for the ongoing authentication session to this
      * fragment.
      */
     private void connectViewModel() {
-        final Context host = BiometricPrompt.getHostActivityOrContext(this);
-        if (host == null) {
-            return;
-        }
-        mViewModel = BiometricPrompt.getViewModel(host);
-        mViewModel.setClientActivity(getActivity());
-        mViewModel.getAuthenticationResult().observe(
-                this,
-                new Observer<BiometricPrompt.AuthenticationResult>() {
-                    @Override
-                    public void onChanged(
-                            BiometricPrompt.AuthenticationResult authenticationResult) {
+        final BiometricViewModel viewModel = getViewModel();
+        if (viewModel != null) {
+            viewModel.setClientActivity(getActivity());
+
+            viewModel.getAuthenticationResult().observe(this,
+                    authenticationResult -> {
                         if (authenticationResult != null) {
                             onAuthenticationSucceeded(authenticationResult);
-                            mViewModel.setAuthenticationResult(null);
+                            viewModel.setAuthenticationResult(null);
                         }
-                    }
-                });
+                    });
 
-        mViewModel.getAuthenticationError().observe(
-                this,
-                new Observer<BiometricErrorData>() {
-                    @Override
-                    public void onChanged(BiometricErrorData authenticationError) {
+            viewModel.getAuthenticationError().observe(this,
+                    authenticationError -> {
                         if (authenticationError != null) {
                             onAuthenticationError(
                                     authenticationError.getErrorCode(),
                                     authenticationError.getErrorMessage());
-                            mViewModel.setAuthenticationError(null);
+                            viewModel.setAuthenticationError(null);
                         }
-                    }
-                });
+                    });
 
-        mViewModel.getAuthenticationHelpMessage().observe(
-                this,
-                new Observer<CharSequence>() {
-                    @Override
-                    public void onChanged(CharSequence authenticationHelpMessage) {
+            viewModel.getAuthenticationHelpMessage().observe(this,
+                    authenticationHelpMessage -> {
                         if (authenticationHelpMessage != null) {
                             onAuthenticationHelp(authenticationHelpMessage);
-                            mViewModel.setAuthenticationError(null);
+                            viewModel.setAuthenticationError(null);
                         }
-                    }
-                });
+                    });
 
-        mViewModel.isAuthenticationFailurePending().observe(
-                this,
-                new Observer<Boolean>() {
-                    @Override
-                    public void onChanged(Boolean authenticationFailurePending) {
+            viewModel.isAuthenticationFailurePending().observe(this,
+                    authenticationFailurePending -> {
                         if (authenticationFailurePending) {
                             onAuthenticationFailed();
-                            mViewModel.setAuthenticationFailurePending(false);
+                            viewModel.setAuthenticationFailurePending(false);
                         }
-                    }
-                });
+                    });
 
-        mViewModel.isNegativeButtonPressPending().observe(
-                this,
-                new Observer<Boolean>() {
-                    @Override
-                    public void onChanged(Boolean negativeButtonPressPending) {
+            viewModel.isNegativeButtonPressPending().observe(this,
+                    negativeButtonPressPending -> {
                         if (negativeButtonPressPending) {
                             if (isManagingDeviceCredentialButton()) {
                                 onDeviceCredentialButtonPressed();
                             } else {
                                 onCancelButtonPressed();
                             }
-                            mViewModel.setNegativeButtonPressPending(false);
+                            viewModel.setNegativeButtonPressPending(false);
                         }
-                    }
-                });
+                    });
 
-        mViewModel.isFingerprintDialogCancelPending().observe(
-                this,
-                new Observer<Boolean>() {
-                    @Override
-                    public void onChanged(Boolean fingerprintDialogCancelPending) {
+            viewModel.isFingerprintDialogCancelPending().observe(this,
+                    fingerprintDialogCancelPending -> {
                         if (fingerprintDialogCancelPending) {
                             cancelAuthentication(BiometricFragment.CANCELED_FROM_USER);
                             dismiss();
-                            mViewModel.setFingerprintDialogCancelPending(false);
+                            viewModel.setFingerprintDialogCancelPending(false);
                         }
-                    }
-                });
+                    });
+        }
     }
 
     /**
@@ -361,7 +447,13 @@
             return;
         }
 
-        mViewModel.setPromptInfo(info);
+        final BiometricViewModel viewModel = getViewModel();
+        if (viewModel == null) {
+            Log.e(TAG, "Not launching prompt. View model was null.");
+            return;
+        }
+
+        viewModel.setPromptInfo(info);
 
         // Use a fake crypto object to force Strong biometric auth prior to Android 11 (API 30).
         @BiometricManager.AuthenticatorTypes final int authenticators =
@@ -370,17 +462,17 @@
                 && Build.VERSION.SDK_INT < Build.VERSION_CODES.R
                 && authenticators == Authenticators.BIOMETRIC_STRONG
                 && crypto == null) {
-            mViewModel.setCryptoObject(CryptoObjectUtils.createFakeCryptoObject());
+            viewModel.setCryptoObject(CryptoObjectUtils.createFakeCryptoObject());
         } else {
-            mViewModel.setCryptoObject(crypto);
+            viewModel.setCryptoObject(crypto);
         }
 
         if (isManagingDeviceCredentialButton()) {
-            mViewModel.setNegativeButtonTextOverride(
+            viewModel.setNegativeButtonTextOverride(
                     getString(R.string.confirm_device_credential_password));
         } else {
             // Don't override the negative button text from the client.
-            mViewModel.setNegativeButtonTextOverride(null);
+            viewModel.setNegativeButtonTextOverride(null);
         }
 
         // Fall back to device credential immediately if no known biometrics are available.
@@ -388,14 +480,14 @@
                 && isManagingDeviceCredentialButton()
                 && BiometricManager.from(host).canAuthenticate(Authenticators.BIOMETRIC_WEAK)
                         != BiometricManager.BIOMETRIC_SUCCESS) {
-            mViewModel.setAwaitingResult(true);
+            viewModel.setAwaitingResult(true);
             launchConfirmCredentialActivity();
             return;
         }
 
         // Check if we should delay showing the authentication prompt.
-        if (mViewModel.isDelayingPrompt()) {
-            mHandler.postDelayed(
+        if (viewModel.isDelayingPrompt()) {
+            mInjector.getHandler().postDelayed(
                     new ShowPromptForAuthenticationRunnable(this), SHOW_PROMPT_DELAY_MS);
         } else {
             showPromptForAuthentication();
@@ -408,15 +500,19 @@
      */
     @SuppressWarnings("WeakerAccess") /* synthetic access */
     void showPromptForAuthentication() {
-        if (!mViewModel.isPromptShowing()) {
+        final BiometricViewModel viewModel = getViewModel();
+        if (viewModel != null && !viewModel.isPromptShowing()) {
             if (getContext() == null) {
                 Log.w(TAG, "Not showing biometric prompt. Context is null.");
                 return;
             }
 
-            mViewModel.setPromptShowing(true);
-            mViewModel.setAwaitingResult(true);
-            if (isUsingFingerprintDialog()) {
+            viewModel.setPromptShowing(true);
+            viewModel.setAwaitingResult(true);
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
+                    && isKeyguardManagerNeededForAuthentication()) {
+                launchConfirmCredentialActivity();
+            } else if (isUsingFingerprintDialog()) {
                 showFingerprintDialogForAuthentication();
             } else {
                 showBiometricPromptForAuthentication();
@@ -444,23 +540,19 @@
             return;
         }
 
-        if (isAdded()) {
-            mViewModel.setFingerprintDialogDismissedInstantly(true);
+        final BiometricViewModel viewModel = getViewModel();
+        if (viewModel != null && isAdded()) {
+            viewModel.setFingerprintDialogDismissedInstantly(true);
             if (!DeviceUtils.shouldHideFingerprintDialog(context, Build.MODEL)) {
-                mHandler.postDelayed(
-                        new Runnable() {
-                            @Override
-                            public void run() {
-                                mViewModel.setFingerprintDialogDismissedInstantly(false);
-                            }
-                        },
+                mInjector.getHandler().postDelayed(
+                        () -> viewModel.setFingerprintDialogDismissedInstantly(false),
                         DISMISS_INSTANTLY_DELAY_MS);
 
                 final FingerprintDialogFragment dialog = FingerprintDialogFragment.newInstance();
                 dialog.show(getParentFragmentManager(), FINGERPRINT_DIALOG_FRAGMENT_TAG);
             }
 
-            mViewModel.setCanceledFrom(CANCELED_FROM_INTERNAL);
+            viewModel.setCanceledFrom(CANCELED_FROM_INTERNAL);
 
             authenticateWithFingerprint(fingerprintManagerCompat, context);
         }
@@ -475,9 +567,15 @@
         final android.hardware.biometrics.BiometricPrompt.Builder builder =
                 Api28Impl.createPromptBuilder(requireContext().getApplicationContext());
 
-        final CharSequence title = mViewModel.getTitle();
-        final CharSequence subtitle = mViewModel.getSubtitle();
-        final CharSequence description = mViewModel.getDescription();
+        final BiometricViewModel viewModel = getViewModel();
+        if (viewModel == null) {
+            Log.e(TAG, "Not showing biometric prompt. View model was null.");
+            return;
+        }
+
+        final CharSequence title = viewModel.getTitle();
+        final CharSequence subtitle = viewModel.getSubtitle();
+        final CharSequence description = viewModel.getDescription();
         if (title != null) {
             Api28Impl.setTitle(builder, title);
         }
@@ -488,23 +586,23 @@
             Api28Impl.setDescription(builder, description);
         }
 
-        final CharSequence negativeButtonText = mViewModel.getNegativeButtonText();
+        final CharSequence negativeButtonText = viewModel.getNegativeButtonText();
         if (!TextUtils.isEmpty(negativeButtonText)) {
             Api28Impl.setNegativeButton(
                     builder,
                     negativeButtonText,
-                    mViewModel.getClientExecutor(),
-                    mViewModel.getNegativeButtonListener());
+                    viewModel.getClientExecutor(),
+                    viewModel.getNegativeButtonListener());
         }
 
         // Set the confirmation required option introduced in Android 10 (API 29).
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
-            Api29Impl.setConfirmationRequired(builder, mViewModel.isConfirmationRequired());
+            Api29Impl.setConfirmationRequired(builder, viewModel.isConfirmationRequired());
         }
 
         // Set or emulate the allowed authenticators option introduced in Android 11 (API 30).
         @BiometricManager.AuthenticatorTypes final int authenticators =
-                mViewModel.getAllowedAuthenticators();
+                viewModel.getAllowedAuthenticators();
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
             Api30Impl.setAllowedAuthenticators(builder, authenticators);
         } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
@@ -527,12 +625,18 @@
             @NonNull androidx.core.hardware.fingerprint.FingerprintManagerCompat fingerprintManager,
             @NonNull Context context) {
 
+        final BiometricViewModel viewModel = getViewModel();
+        if (viewModel == null) {
+            Log.e(TAG, "Not showing fingerprint dialog. View model was null.");
+            return;
+        }
+
         final androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject crypto =
-                CryptoObjectUtils.wrapForFingerprintManager(mViewModel.getCryptoObject());
+                CryptoObjectUtils.wrapForFingerprintManager(viewModel.getCryptoObject());
         final androidx.core.os.CancellationSignal cancellationSignal =
-                mViewModel.getCancellationSignalProvider().getFingerprintCancellationSignal();
+                viewModel.getCancellationSignalProvider().getFingerprintCancellationSignal();
         final androidx.core.hardware.fingerprint.FingerprintManagerCompat.AuthenticationCallback
-                callback = mViewModel.getAuthenticationCallbackProvider()
+                callback = viewModel.getAuthenticationCallbackProvider()
                 .getFingerprintCallback();
 
         try {
@@ -559,13 +663,19 @@
             @NonNull android.hardware.biometrics.BiometricPrompt biometricPrompt,
             @Nullable Context context) {
 
+        final BiometricViewModel viewModel = getViewModel();
+        if (viewModel == null) {
+            Log.e(TAG, "Not authenticating with biometric prompt. View model was null.");
+            return;
+        }
+
         final android.hardware.biometrics.BiometricPrompt.CryptoObject cryptoObject =
-                CryptoObjectUtils.wrapForBiometricPrompt(mViewModel.getCryptoObject());
+                CryptoObjectUtils.wrapForBiometricPrompt(viewModel.getCryptoObject());
         final android.os.CancellationSignal cancellationSignal =
-                mViewModel.getCancellationSignalProvider().getBiometricCancellationSignal();
+                viewModel.getCancellationSignalProvider().getBiometricCancellationSignal();
         final Executor executor = new PromptExecutor();
         final android.hardware.biometrics.BiometricPrompt.AuthenticationCallback callback =
-                mViewModel.getAuthenticationCallbackProvider().getBiometricCallback();
+                viewModel.getAuthenticationCallbackProvider().getBiometricCallback();
 
         try {
             if (cryptoObject == null) {
@@ -591,12 +701,18 @@
      * @param canceledFrom Where authentication was canceled from.
      */
     void cancelAuthentication(@CanceledFrom int canceledFrom) {
-        if (canceledFrom != CANCELED_FROM_CLIENT && mViewModel.isIgnoringCancel()) {
+        final BiometricViewModel viewModel = getViewModel();
+        if (viewModel == null) {
+            Log.e(TAG, "Unable to cancel authentication. View model was null.");
+            return;
+        }
+
+        if (canceledFrom != CANCELED_FROM_CLIENT && viewModel.isIgnoringCancel()) {
             return;
         }
 
         if (isUsingFingerprintDialog()) {
-            mViewModel.setCanceledFrom(canceledFrom);
+            viewModel.setCanceledFrom(canceledFrom);
             if (canceledFrom == CANCELED_FROM_USER) {
                 final int errorCode = BiometricPrompt.ERROR_USER_CANCELED;
                 sendErrorToClient(
@@ -604,7 +720,7 @@
             }
         }
 
-        mViewModel.getCancellationSignalProvider().cancel();
+        viewModel.getCancellationSignalProvider().cancel();
     }
 
     /**
@@ -612,17 +728,26 @@
      */
     @SuppressWarnings("WeakerAccess") /* synthetic access */
     void dismiss() {
-        mViewModel.setPromptShowing(false);
         dismissFingerprintDialog();
-        if (!mViewModel.isConfirmingDeviceCredential() && isAdded()) {
+
+        final BiometricViewModel viewModel = getViewModel();
+        if (viewModel != null) {
+            viewModel.setPromptShowing(false);
+        }
+
+        if (viewModel == null || (!viewModel.isConfirmingDeviceCredential() && isAdded())) {
             getParentFragmentManager().beginTransaction().remove(this).commitAllowingStateLoss();
         }
 
         // Wait before showing again to work around a dismissal logic issue on API 29 (b/157783075).
         final Context context = getContext();
         if (context != null && DeviceUtils.shouldDelayShowingPrompt(context, Build.MODEL)) {
-            mViewModel.setDelayingPrompt(true);
-            mHandler.postDelayed(new StopDelayingPromptRunnable(mViewModel), SHOW_PROMPT_DELAY_MS);
+            if (viewModel != null) {
+                viewModel.setDelayingPrompt(true);
+            }
+
+            mInjector.getHandler().postDelayed(
+                    new StopDelayingPromptRunnable(mViewModel), SHOW_PROMPT_DELAY_MS);
         }
     }
 
@@ -630,7 +755,11 @@
      * Removes the fingerprint dialog UI from the client activity/fragment.
      */
     private void dismissFingerprintDialog() {
-        mViewModel.setPromptShowing(false);
+        final BiometricViewModel viewModel = getViewModel();
+        if (viewModel != null) {
+            viewModel.setPromptShowing(false);
+        }
+
         if (isAdded()) {
             final FragmentManager fragmentManager = getParentFragmentManager();
             final FingerprintDialogFragment fingerprintDialog =
@@ -672,6 +801,12 @@
                 ? errorCode
                 : BiometricPrompt.ERROR_VENDOR;
 
+        final BiometricViewModel viewModel = getViewModel();
+        if (viewModel == null) {
+            Log.e(TAG, "Unable to handle authentication error. View model was null.");
+            return;
+        }
+
         final Context context = getContext();
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
                 && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q
@@ -679,7 +814,7 @@
                 && context != null
                 && KeyguardUtils.isDeviceSecuredWithCredential(context)
                 && AuthenticatorUtils.isDeviceCredentialAllowed(
-                        mViewModel.getAllowedAuthenticators())) {
+                        viewModel.getAllowedAuthenticators())) {
             launchConfirmCredentialActivity();
             return;
         }
@@ -692,7 +827,7 @@
 
             if (knownErrorCode == BiometricPrompt.ERROR_CANCELED) {
                 // User-initiated cancellation errors should already be handled.
-                @CanceledFrom final int canceledFrom = mViewModel.getCanceledFrom();
+                @CanceledFrom final int canceledFrom = viewModel.getCanceledFrom();
                 if (canceledFrom == CANCELED_FROM_INTERNAL
                         || canceledFrom == CANCELED_FROM_CLIENT) {
                     sendErrorToClient(knownErrorCode, errorString);
@@ -700,23 +835,18 @@
 
                 dismiss();
             } else {
-                if (mViewModel.isFingerprintDialogDismissedInstantly()) {
+                if (viewModel.isFingerprintDialogDismissedInstantly()) {
                     sendErrorAndDismiss(knownErrorCode, errorString);
                 } else {
                     showFingerprintErrorMessage(errorString);
-                    mHandler.postDelayed(
-                            new Runnable() {
-                                @Override
-                                public void run() {
-                                    sendErrorAndDismiss(knownErrorCode, errorString);
-                                }
-                            },
+                    mInjector.getHandler().postDelayed(
+                            () -> sendErrorAndDismiss(knownErrorCode, errorString),
                             getDismissDialogDelay());
                 }
 
                 // Always set this to true. In case the user tries to authenticate again
                 // the UI will not be shown.
-                mViewModel.setFingerprintDialogDismissedInstantly(true);
+                viewModel.setFingerprintDialogDismissedInstantly(true);
             }
         } else {
             final CharSequence errorString = errorMessage != null
@@ -768,12 +898,17 @@
      */
     @SuppressWarnings("WeakerAccess") /* synthetic access */
     void onCancelButtonPressed() {
-        final CharSequence negativeButtonText = mViewModel.getNegativeButtonText();
+        final BiometricViewModel viewModel = getViewModel();
+        final CharSequence negativeButtonText = viewModel != null
+                ? viewModel.getNegativeButtonText()
+                : null;
+
         sendErrorAndDismiss(
                 BiometricPrompt.ERROR_NEGATIVE_BUTTON,
                 negativeButtonText != null
                         ? negativeButtonText
                         : getString(R.string.default_error_msg));
+
         cancelAuthentication(BiometricFragment.CANCELED_FROM_NEGATIVE_BUTTON);
     }
 
@@ -785,7 +920,13 @@
     private void launchConfirmCredentialActivity() {
         final Context host = BiometricPrompt.getHostActivityOrContext(this);
         if (host == null) {
-            Log.e(TAG, "Failed to check device credential. Client Context not found.");
+            Log.e(TAG, "Failed to check device credential. Client context not found.");
+            return;
+        }
+
+        final BiometricViewModel viewModel = getViewModel();
+        if (viewModel == null) {
+            Log.e(TAG, "Failed to check device credential. View model was null.");
             return;
         }
 
@@ -799,9 +940,9 @@
         }
 
         // Pass along the title and subtitle/description from the biometric prompt.
-        final CharSequence title = mViewModel.getTitle();
-        final CharSequence subtitle = mViewModel.getSubtitle();
-        final CharSequence description = mViewModel.getDescription();
+        final CharSequence title = viewModel.getTitle();
+        final CharSequence subtitle = viewModel.getSubtitle();
+        final CharSequence description = viewModel.getDescription();
         final CharSequence credentialDescription = subtitle != null ? subtitle : description;
 
         final Intent intent = Api21Impl.createConfirmDeviceCredentialIntent(
@@ -815,7 +956,7 @@
             return;
         }
 
-        mViewModel.setConfirmingDeviceCredential(true);
+        viewModel.setConfirmingDeviceCredential(true);
 
         // Dismiss the fingerprint dialog before launching the activity.
         if (isUsingFingerprintDialog()) {
@@ -853,11 +994,14 @@
      * @param errorMessage The error message to show on the dialog.
      */
     private void showFingerprintErrorMessage(@Nullable CharSequence errorMessage) {
-        final CharSequence helpMessage = errorMessage != null
-                ? errorMessage
-                : getString(R.string.default_error_msg);
-        mViewModel.setFingerprintDialogState(FingerprintDialogFragment.STATE_FINGERPRINT_ERROR);
-        mViewModel.setFingerprintDialogHelpMessage(helpMessage);
+        final BiometricViewModel viewModel = getViewModel();
+        if (viewModel != null) {
+            final CharSequence helpMessage = errorMessage != null
+                    ? errorMessage
+                    : getString(R.string.default_error_msg);
+            viewModel.setFingerprintDialogState(FingerprintDialogFragment.STATE_FINGERPRINT_ERROR);
+            viewModel.setFingerprintDialogHelpMessage(helpMessage);
+        }
     }
 
     /**
@@ -897,17 +1041,23 @@
      *      BiometricPrompt.AuthenticationResult)
      */
     private void sendSuccessToClient(@NonNull final BiometricPrompt.AuthenticationResult result) {
-        if (!mViewModel.isAwaitingResult()) {
+        final BiometricViewModel viewModel = getViewModel();
+        if (viewModel == null) {
+            Log.e(TAG, "Unable to send success to client. View model was null.");
+            return;
+        }
+
+        if (!viewModel.isAwaitingResult()) {
             Log.w(TAG, "Success not sent to client. Client is not awaiting a result.");
             return;
         }
 
-        mViewModel.setAwaitingResult(false);
-        mViewModel.getClientExecutor().execute(
+        viewModel.setAwaitingResult(false);
+        viewModel.getClientExecutor().execute(
                 new Runnable() {
                     @Override
                     public void run() {
-                        mViewModel.getClientCallback().onAuthenticationSucceeded(result);
+                        viewModel.getClientCallback().onAuthenticationSucceeded(result);
                     }
                 });
     }
@@ -922,21 +1072,27 @@
      * @see BiometricPrompt.AuthenticationCallback#onAuthenticationError(int, CharSequence)
      */
     private void sendErrorToClient(final int errorCode, @NonNull final CharSequence errorString) {
-        if (mViewModel.isConfirmingDeviceCredential()) {
+        final BiometricViewModel viewModel = getViewModel();
+        if (viewModel == null) {
+            Log.e(TAG, "Unable to send error to client. View model was null.");
+            return;
+        }
+
+        if (viewModel.isConfirmingDeviceCredential()) {
             Log.v(TAG, "Error not sent to client. User is confirming their device credential.");
             return;
         }
 
-        if (!mViewModel.isAwaitingResult()) {
+        if (!viewModel.isAwaitingResult()) {
             Log.w(TAG, "Error not sent to client. Client is not awaiting a result.");
             return;
         }
 
-        mViewModel.setAwaitingResult(false);
-        mViewModel.getClientExecutor().execute(new Runnable() {
+        viewModel.setAwaitingResult(false);
+        viewModel.getClientExecutor().execute(new Runnable() {
             @Override
             public void run() {
-                mViewModel.getClientCallback().onAuthenticationError(errorCode, errorString);
+                viewModel.getClientCallback().onAuthenticationError(errorCode, errorString);
             }
         });
     }
@@ -947,15 +1103,21 @@
      * @see BiometricPrompt.AuthenticationCallback#onAuthenticationFailed()
      */
     private void sendFailureToClient() {
-        if (!mViewModel.isAwaitingResult()) {
+        final BiometricViewModel viewModel = getViewModel();
+        if (viewModel == null) {
+            Log.e(TAG, "Unable to send failure to client. View model was null.");
+            return;
+        }
+
+        if (!viewModel.isAwaitingResult()) {
             Log.w(TAG, "Failure not sent to client. Client is not awaiting a result.");
             return;
         }
 
-        mViewModel.getClientExecutor().execute(new Runnable() {
+        viewModel.getClientExecutor().execute(new Runnable() {
             @Override
             public void run() {
-                mViewModel.getClientCallback().onAuthenticationFailed();
+                viewModel.getClientCallback().onAuthenticationFailed();
             }
         });
     }
@@ -984,9 +1146,11 @@
      */
     @SuppressWarnings("WeakerAccess") /* synthetic access */
     boolean isManagingDeviceCredentialButton() {
+        final BiometricViewModel viewModel = getViewModel();
         return Build.VERSION.SDK_INT <= Build.VERSION_CODES.P
+                && viewModel != null
                 && AuthenticatorUtils.isDeviceCredentialAllowed(
-                        mViewModel.getAllowedAuthenticators());
+                        viewModel.getAllowedAuthenticators());
     }
 
     /**
@@ -1011,8 +1175,10 @@
      */
     private boolean isFingerprintDialogNeededForCrypto() {
         final Context host = BiometricPrompt.getHostActivityOrContext(this);
+        final BiometricViewModel viewModel = getViewModel();
         return host != null
-                && mViewModel.getCryptoObject() != null
+                && viewModel != null
+                && viewModel.getCryptoObject() != null
                 && DeviceUtils.shouldUseFingerprintForCrypto(
                         host, Build.MANUFACTURER, Build.MODEL);
     }
@@ -1022,14 +1188,29 @@
      * biometric prompt, to handle an authentication error.
      *
      * @return Whether this fragment should invoke the fingerprint dialog.
-     *
-     * @see DeviceUtils#shouldUseFingerprintForCrypto(Context, String, String)
      */
     private boolean isFingerprintDialogNeededForErrorHandling() {
         // On API 28, BiometricPrompt internally calls FingerprintManager#getErrorString(), which
         // requires fingerprint hardware to be present (b/151443237).
         return Build.VERSION.SDK_INT == Build.VERSION_CODES.P
-                && !PackageUtils.hasSystemFeatureFingerprint(getContext());
+                && !mInjector.isFingerprintHardwarePresent(getContext());
+    }
+
+    /**
+     * Checks if this fragment should invoke {@link
+     * KeyguardManager#createConfirmDeviceCredentialIntent(CharSequence, CharSequence)} directly to
+     * start authentication, rather than explicitly showing a dialog.
+     *
+     * @return Whether this fragment should use {@link KeyguardManager} directly.
+     */
+    private boolean isKeyguardManagerNeededForAuthentication() {
+        // On API 29, BiometricPrompt fails to launch the confirm device credential Settings
+        // activity if no biometric hardware is present.
+        final Context context = getContext();
+        return Build.VERSION.SDK_INT == Build.VERSION_CODES.Q
+                && !mInjector.isFingerprintHardwarePresent(context)
+                && !mInjector.isFaceHardwarePresent(context)
+                && !mInjector.isIrisHardwarePresent(context);
     }
 
     /**
diff --git a/biometric/biometric/src/main/java/androidx/biometric/BiometricManager.java b/biometric/biometric/src/main/java/androidx/biometric/BiometricManager.java
index c126640..48cc44c 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/BiometricManager.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/BiometricManager.java
@@ -16,8 +16,10 @@
 
 package androidx.biometric;
 
+import android.Manifest;
 import android.app.KeyguardManager;
 import android.content.Context;
+import android.content.res.Resources;
 import android.os.Build;
 import android.util.Log;
 
@@ -25,6 +27,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
+import androidx.annotation.RequiresPermission;
+import androidx.annotation.StringRes;
 import androidx.annotation.VisibleForTesting;
 
 import java.lang.annotation.Retention;
@@ -151,12 +155,364 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface AuthenticatorTypes {}
 
+    private static final int AUTH_MODALITY_NONE = 0;
+    private static final int AUTH_MODALITY_CREDENTIAL = 1;
+    private static final int AUTH_MODALITY_UNKNOWN_BIOMETRIC = 1 << 1;
+    private static final int AUTH_MODALITY_FINGERPRINT = 1 << 2;
+    private static final int AUTH_MODALITY_FACE = 1 << 3;
+
+    @IntDef(flag = true, value = {
+        AUTH_MODALITY_NONE,
+        AUTH_MODALITY_CREDENTIAL,
+        AUTH_MODALITY_UNKNOWN_BIOMETRIC,
+        AUTH_MODALITY_FINGERPRINT,
+        AUTH_MODALITY_FACE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface AuthModalities {}
+
+    /**
+     * Provides localized strings for an application that uses {@link BiometricPrompt} to
+     * authenticate the user.
+     */
+    public static class Strings {
+        /**
+         * The framework strings instance. Non-null on Android 12 (API 31) and above.
+         */
+        @Nullable private final android.hardware.biometrics.BiometricManager.Strings mStrings;
+
+        /**
+         * The compatibility strings instance. Non-null on Android 11 (API 30) and below.
+         */
+        @Nullable private final StringsCompat mStringsCompat;
+
+        @RequiresApi(Build.VERSION_CODES.S)
+        Strings(@NonNull android.hardware.biometrics.BiometricManager.Strings strings) {
+            mStrings = strings;
+            mStringsCompat = null;
+        }
+
+        Strings(@NonNull StringsCompat stringsCompat) {
+            mStrings = null;
+            mStringsCompat = stringsCompat;
+        }
+
+        /**
+         * Provides a localized string that can be used as the label for a button that invokes
+         * {@link BiometricPrompt}.
+         *
+         * <p>When possible, this method should use the given authenticator requirements to more
+         * precisely specify the authentication type that will be used. For example, if
+         * <strong>Class 3</strong> biometric authentication is requested on a device with a
+         * <strong>Class 3</strong> fingerprint sensor and a <strong>Class 2</strong> face sensor,
+         * the returned string should indicate that fingerprint authentication will be used.
+         *
+         * <p>This method should also try to specify which authentication method(s) will be used in
+         * practice when multiple authenticators meet the given requirements. For example, if
+         * biometric authentication is requested on a device with both face and fingerprint sensors
+         * but the user has selected face as their preferred method, the returned string should
+         * indicate that face authentication will be used.
+         *
+         * <p>This method may return {@code null} if none of the requested authenticator types are
+         * available, but this should <em>not</em> be relied upon for checking the status of
+         * authenticators. Instead, use {@link #canAuthenticate(int)}.
+         *
+         * @return The label for a button that invokes {@link BiometricPrompt} for authentication.
+         */
+        @RequiresPermission(Manifest.permission.USE_BIOMETRIC)
+        @Nullable
+        public CharSequence getButtonLabel() {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && mStrings != null) {
+                return Api31Impl.getButtonLabel(mStrings);
+            } else if (mStringsCompat != null) {
+                return mStringsCompat.getButtonLabel();
+            } else {
+                Log.e(TAG, "Failure in Strings.getButtonLabel(). No available string provider.");
+                return null;
+            }
+        }
+
+        /**
+         * Provides a localized string that can be shown while the user is authenticating with
+         * {@link BiometricPrompt}.
+         *
+         * <p>When possible, this method should use the given authenticator requirements to more
+         * precisely specify the authentication type that will be used. For example, if
+         * <strong>Class 3</strong> biometric authentication is requested on a device with a
+         * <strong>Class 3</strong> fingerprint sensor and a <strong>Class 2</strong> face sensor,
+         * the returned string should indicate that fingerprint authentication will be used.
+         *
+         * <p>This method should also try to specify which authentication method(s) will be used in
+         * practice when multiple authenticators meet the given requirements. For example, if
+         * biometric authentication is requested on a device with both face and fingerprint sensors
+         * but the user has selected face as their preferred method, the returned string should
+         * indicate that face authentication will be used.
+         *
+         * <p>This method may return {@code null} if none of the requested authenticator types are
+         * available, but this should <em>not</em> be relied upon for checking the status of
+         * authenticators. Instead, use {@link #canAuthenticate(int)}.
+         *
+         * @return A message to be shown on {@link BiometricPrompt} during authentication.
+         */
+        @RequiresPermission(Manifest.permission.USE_BIOMETRIC)
+        @Nullable
+        public CharSequence getPromptMessage() {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && mStrings != null) {
+                return Api31Impl.getPromptMessage(mStrings);
+            } else if (mStringsCompat != null) {
+                return mStringsCompat.getPromptMessage();
+            } else {
+                Log.e(TAG, "Failure in Strings.getPromptMessage(). No available string provider.");
+                return null;
+            }
+        }
+
+        /**
+         * Provides a localized string that can be shown as the title for an app setting that
+         * allows authentication with {@link BiometricPrompt}.
+         *
+         * <p>When possible, this method should use the given authenticator requirements to more
+         * precisely specify the authentication type that will be used. For example, if
+         * <strong>Class 3</strong> biometric authentication is requested on a device with a
+         * <strong>Class 3</strong> fingerprint sensor and a <strong>Class 2</strong> face sensor,
+         * the returned string should indicate that fingerprint authentication will be used.
+         *
+         * <p>This method should <em>not</em> try to specify which authentication method(s) will be
+         * used in practice when multiple authenticators meet the given requirements. For example,
+         * if biometric authentication is requested on a device with both face and fingerprint
+         * sensors, the returned string should indicate that either face or fingerprint
+         * authentication may be used, regardless of whether the user has enrolled or selected
+         * either as their preferred method.
+         *
+         * <p>This method may return {@code null} if none of the requested authenticator types are
+         * supported by the system, but this should <em>not</em> be relied upon for checking the
+         * status of authenticators. Instead, use {@link #canAuthenticate(int)} or
+         * {@link android.content.pm.PackageManager#hasSystemFeature(String)}.
+         *
+         * @return The name for a setting that allows authentication with {@link BiometricPrompt}.
+         */
+        @RequiresPermission(Manifest.permission.USE_BIOMETRIC)
+        @Nullable
+        public CharSequence getSettingName() {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && mStrings != null) {
+                return Api31Impl.getSettingName(mStrings);
+            } else if (mStringsCompat != null) {
+                return mStringsCompat.getSettingName();
+            } else {
+                Log.e(TAG, "Failure in Strings.getSettingName(). No available string provider.");
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Compatibility wrapper for the {@link Strings} class on Android 11 (API 30) and below.
+     */
+    private class StringsCompat {
+        @NonNull private final Resources mResources;
+        @AuthenticatorTypes private final int mAuthenticators;
+        @AuthModalities private final int mPossibleModalities;
+
+        StringsCompat(
+                @NonNull Resources resources,
+                @AuthenticatorTypes int authenticators,
+                boolean isFingerprintSupported,
+                boolean isFaceSupported,
+                boolean isIrisSupported,
+                boolean isDeviceSecured) {
+
+            mResources = resources;
+            mAuthenticators = authenticators;
+
+            @AuthModalities int possibleModalities =
+                    isDeviceSecured && AuthenticatorUtils.isDeviceCredentialAllowed(authenticators)
+                            ? AUTH_MODALITY_CREDENTIAL
+                            : AUTH_MODALITY_NONE;
+
+            if (AuthenticatorUtils.isSomeBiometricAllowed(authenticators)) {
+                if (isFingerprintSupported) {
+                    possibleModalities |= AUTH_MODALITY_FINGERPRINT;
+                }
+                if (isFaceSupported) {
+                    possibleModalities |= AUTH_MODALITY_FACE;
+                }
+                if (isIrisSupported) {
+                    possibleModalities |= AUTH_MODALITY_UNKNOWN_BIOMETRIC;
+                }
+            }
+            mPossibleModalities = possibleModalities;
+        }
+
+        /**
+         * Provides a localized string that can be used as the label for a button that invokes
+         * {@link BiometricPrompt}.
+         *
+         * This is a backwards-compatible implementation of the {@link Strings#getButtonLabel()}
+         * method for Android 11 (API 30) and below.
+         *
+         * @return The label for a button that invokes {@link BiometricPrompt} for authentication.
+         */
+        @Nullable
+        CharSequence getButtonLabel() {
+            @AuthenticatorTypes final int biometricAuthenticators =
+                    AuthenticatorUtils.getBiometricAuthenticators(mAuthenticators);
+            if (canAuthenticate(biometricAuthenticators) == BIOMETRIC_SUCCESS) {
+                switch (mPossibleModalities & ~AUTH_MODALITY_CREDENTIAL) {
+                    case AUTH_MODALITY_FINGERPRINT:
+                        // Fingerprint is the only supported and available biometric.
+                        return mResources.getString(R.string.use_fingerprint_label);
+                    case AUTH_MODALITY_FACE:
+                        // Face is the only supported and available biometric.
+                        return mResources.getString(R.string.use_face_label);
+                    default:
+                        // 1+ biometric types are supported and available.
+                        return mResources.getString(R.string.use_biometric_label);
+                }
+            }
+
+            if ((mPossibleModalities & AUTH_MODALITY_CREDENTIAL) != 0) {
+                // Only screen lock is supported and available.
+                return mResources.getString(R.string.use_screen_lock_label);
+            }
+
+            // Authentication is not supported or not available.
+            return null;
+        }
+
+        /**
+         * Provides a localized string that can be shown while the user is authenticating with
+         * {@link BiometricPrompt}.
+         *
+         * This is a backwards-compatible implementation of the {@link Strings#getPromptMessage()}
+         * method for Android 11 (API 30) and below.
+         *
+         * @return A message to be shown on {@link BiometricPrompt} during authentication.
+         */
+        @Nullable
+        CharSequence getPromptMessage() {
+            @AuthenticatorTypes final int biometricAuthenticators =
+                    AuthenticatorUtils.getBiometricAuthenticators(mAuthenticators);
+
+            if (canAuthenticate(biometricAuthenticators) == BIOMETRIC_SUCCESS) {
+                @StringRes final int messageRes;
+                switch (mPossibleModalities & ~AUTH_MODALITY_CREDENTIAL) {
+                    case AUTH_MODALITY_FINGERPRINT:
+                        // Fingerprint is the only supported and available biometric.
+                        messageRes = AuthenticatorUtils.isDeviceCredentialAllowed(mAuthenticators)
+                                ? R.string.fingerprint_or_screen_lock_prompt_message
+                                : R.string.fingerprint_prompt_message;
+                        break;
+
+                    case AUTH_MODALITY_FACE:
+                        // Face is the only supported and available biometric.
+                        messageRes = AuthenticatorUtils.isDeviceCredentialAllowed(mAuthenticators)
+                                ? R.string.face_or_screen_lock_prompt_message
+                                : R.string.face_prompt_message;
+                        break;
+
+                    default:
+                        // 1+ biometric types are supported and available.
+                        messageRes = AuthenticatorUtils.isDeviceCredentialAllowed(mAuthenticators)
+                                ? R.string.biometric_or_screen_lock_prompt_message
+                                : R.string.biometric_prompt_message;
+                        break;
+                }
+
+                return mResources.getString(messageRes);
+            }
+
+            if ((mPossibleModalities & AUTH_MODALITY_CREDENTIAL) != 0) {
+                // Only screen lock is supported and available.
+                return mResources.getString(R.string.screen_lock_prompt_message);
+            }
+
+            // Authentication is not supported or not available.
+            return null;
+        }
+
+        /**
+         * Provides a localized string that can be shown as the title for an app setting that
+         * allows authentication with {@link BiometricPrompt}.
+         *
+         * This is a backwards-compatible implementation of the {@link Strings#getSettingName()}
+         * method for Android 11 (API 30) and below.
+         *
+         * @return The name for a setting that allows authentication with {@link BiometricPrompt}.
+         */
+        @Nullable
+        CharSequence getSettingName() {
+            CharSequence settingName;
+            switch (mPossibleModalities) {
+                case AUTH_MODALITY_NONE:
+                    // Authentication is not supported.
+                    settingName = null;
+                    break;
+
+                case AUTH_MODALITY_CREDENTIAL:
+                    // Only screen lock is supported.
+                    settingName = mResources.getString(R.string.use_screen_lock_label);
+                    break;
+
+                case AUTH_MODALITY_UNKNOWN_BIOMETRIC:
+                    // Only an unknown biometric type(s) is supported.
+                    settingName = mResources.getString(R.string.use_biometric_label);
+                    break;
+
+                case AUTH_MODALITY_FINGERPRINT:
+                    // Only fingerprint is supported.
+                    settingName = mResources.getString(R.string.use_fingerprint_label);
+                    break;
+
+                case AUTH_MODALITY_FACE:
+                    // Only face is supported.
+                    settingName = mResources.getString(R.string.use_face_label);
+                    break;
+
+                default:
+                    if ((mPossibleModalities & AUTH_MODALITY_CREDENTIAL) == 0) {
+                        // 2+ biometric types are supported (but not screen lock).
+                        settingName = mResources.getString(R.string.use_biometric_label);
+                    } else {
+                        switch (mPossibleModalities & ~AUTH_MODALITY_CREDENTIAL) {
+                            case AUTH_MODALITY_FINGERPRINT:
+                                // Only fingerprint and screen lock are supported.
+                                settingName = mResources.getString(
+                                        R.string.use_fingerprint_or_screen_lock_label);
+                                break;
+
+                            case AUTH_MODALITY_FACE:
+                                // Only face and screen lock are supported.
+                                settingName = mResources.getString(
+                                        R.string.use_face_or_screen_lock_label);
+                                break;
+
+                            default:
+                                // 1+ biometric types and screen lock are supported.
+                                settingName = mResources.getString(
+                                        R.string.use_biometric_or_screen_lock_label);
+                                break;
+                        }
+                    }
+                    break;
+            }
+            return settingName;
+        }
+    }
+
     /**
      * An injector for various class and method dependencies. Used for testing.
      */
     @VisibleForTesting
     interface Injector {
         /**
+         * Provides the application {@link Resources} object.
+         *
+         * @return An instance of {@link Resources}.
+         */
+        @NonNull
+        Resources getResources();
+
+        /**
          * Provides the framework biometric manager that may be used on Android 10 (API 29) and
          * above.
          *
@@ -198,6 +554,22 @@
         boolean isFingerprintHardwarePresent();
 
         /**
+         * Checks if the current device has a hardware sensor that may be used for face
+         * authentication.
+         *
+         * @return Whether the device has a face sensor.
+         */
+        boolean isFaceHardwarePresent();
+
+        /**
+         * Checks if the current device has a hardware sensor that may be used for iris
+         * authentication.
+         *
+         * @return Whether the device has an iris sensor.
+         */
+        boolean isIrisHardwarePresent();
+
+        /**
          * Checks if all biometric sensors on the device are known to meet or exceed the security
          * requirements for <strong>Class 3</strong> (formerly <strong>Strong</strong>).
          *
@@ -222,6 +594,12 @@
         }
 
         @Override
+        @NonNull
+        public Resources getResources() {
+            return mContext.getResources();
+        }
+
+        @Override
         @RequiresApi(Build.VERSION_CODES.Q)
         @Nullable
         public android.hardware.biometrics.BiometricManager getBiometricManager() {
@@ -250,6 +628,16 @@
         }
 
         @Override
+        public boolean isFaceHardwarePresent() {
+            return PackageUtils.hasSystemFeatureFace(mContext);
+        }
+
+        @Override
+        public boolean isIrisHardwarePresent() {
+            return PackageUtils.hasSystemFeatureIris(mContext);
+        }
+
+        @Override
         public boolean isStrongBiometricGuaranteed() {
             return DeviceUtils.canAssumeStrongBiometrics(mContext, Build.MODEL);
         }
@@ -507,6 +895,112 @@
     }
 
     /**
+     * Produces an instance of the {@link Strings} class, which provides localized strings for an
+     * application, given a set of allowed authenticator types.
+     *
+     * @param authenticators A bit field representing the types of {@link Authenticators} that may
+     *                       be used for authentication.
+     * @return A {@link Strings} collection for the given allowed authenticator types.
+     */
+    @RequiresPermission(Manifest.permission.USE_BIOMETRIC)
+    @Nullable
+    public Strings getStrings(@AuthenticatorTypes int authenticators) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+            if (mBiometricManager == null) {
+                Log.e(TAG, "Failure in getStrings(). BiometricManager was null.");
+                return null;
+            }
+            return new Strings(Api31Impl.getStrings(mBiometricManager, authenticators));
+        }
+
+        final StringsCompat stringsCompat = new StringsCompat(
+                mInjector.getResources(),
+                authenticators,
+                mInjector.isFingerprintHardwarePresent(),
+                mInjector.isFaceHardwarePresent(),
+                mInjector.isIrisHardwarePresent(),
+                mInjector.isDeviceSecuredWithCredential());
+
+        return new Strings(stringsCompat);
+    }
+
+
+    /**
+     * Nested class to avoid verification errors for methods introduced in Android 12 (API 31).
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    private static class Api31Impl {
+        // Prevent instantiation.
+        private Api31Impl() {}
+
+        /**
+         * Gets an instance of the framework
+         * {@link android.hardware.biometrics.BiometricManager.Strings} class.
+         *
+         * @param biometricManager An instance of
+         *                         {@link android.hardware.biometrics.BiometricManager}.
+         * @param authenticators   A bit field representing the types of {@link Authenticators} that
+         *                         may be used for authentication.
+         * @return An instance of {@link android.hardware.biometrics.BiometricManager.Strings}.
+         */
+        @RequiresPermission(Manifest.permission.USE_BIOMETRIC)
+        @NonNull
+        static android.hardware.biometrics.BiometricManager.Strings getStrings(
+                @NonNull android.hardware.biometrics.BiometricManager biometricManager,
+                @AuthenticatorTypes int authenticators) {
+            return biometricManager.getStrings(authenticators);
+        }
+
+        /**
+         * Calls {@link android.hardware.biometrics.BiometricManager.Strings#getButtonLabel()} for
+         * the given framework strings instance.
+         *
+         * @param strings An instance of
+         *                {@link android.hardware.biometrics.BiometricManager.Strings}.
+         * @return The result of
+         * {@link android.hardware.biometrics.BiometricManager.Strings#getButtonLabel()}.
+         */
+        @RequiresPermission(Manifest.permission.USE_BIOMETRIC)
+        @Nullable
+        static CharSequence getButtonLabel(
+                @NonNull android.hardware.biometrics.BiometricManager.Strings strings) {
+            return strings.getButtonLabel();
+        }
+
+        /**
+         * Calls {@link android.hardware.biometrics.BiometricManager.Strings#getPromptMessage()} for
+         * the given framework strings instance.
+         *
+         * @param strings An instance of
+         *                {@link android.hardware.biometrics.BiometricManager.Strings}.
+         * @return The result of
+         * {@link android.hardware.biometrics.BiometricManager.Strings#getPromptMessage()}.
+         */
+        @RequiresPermission(Manifest.permission.USE_BIOMETRIC)
+        @Nullable
+        static CharSequence getPromptMessage(
+                @NonNull android.hardware.biometrics.BiometricManager.Strings strings) {
+            return strings.getPromptMessage();
+        }
+
+        /**
+         * Calls {@link android.hardware.biometrics.BiometricManager.Strings#getSettingName()} for
+         * the given framework strings instance.
+         *
+         * @param strings An instance of
+         *                {@link android.hardware.biometrics.BiometricManager.Strings}.
+         * @return The result of
+         * {@link android.hardware.biometrics.BiometricManager.Strings#getSettingName()}.
+         */
+        @RequiresPermission(Manifest.permission.USE_BIOMETRIC)
+        @Nullable
+        static CharSequence getSettingName(
+                @NonNull android.hardware.biometrics.BiometricManager.Strings strings) {
+            return strings.getSettingName();
+        }
+    }
+
+    /**
      * Nested class to avoid verification errors for methods introduced in Android 11 (API 30).
      */
     @RequiresApi(Build.VERSION_CODES.R)
diff --git a/biometric/biometric/src/main/java/androidx/biometric/BiometricPrompt.java b/biometric/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
index 5dc7c27..3ebe8a6 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
@@ -1025,7 +1025,7 @@
      */
     @Nullable
     static BiometricViewModel getViewModel(@Nullable Context context) {
-        return  context instanceof ViewModelStoreOwner
+        return context instanceof ViewModelStoreOwner
                 ? new ViewModelProvider((ViewModelStoreOwner) context).get(BiometricViewModel.class)
                 : null;
     }
diff --git a/biometric/biometric/src/main/java/androidx/biometric/PackageUtils.java b/biometric/biometric/src/main/java/androidx/biometric/PackageUtils.java
index 9dc2f51..6e73c27 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/PackageUtils.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/PackageUtils.java
@@ -45,6 +45,32 @@
     }
 
     /**
+     * Checks if the current device supports fingerprint authentication.
+     *
+     * @param context The application or activity context.
+     * @return Whether fingerprint is supported.
+     */
+    static boolean hasSystemFeatureFace(@Nullable Context context) {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
+                && context != null
+                && context.getPackageManager() != null
+                && Api29Impl.hasSystemFeatureFace(context.getPackageManager());
+    }
+
+    /**
+     * Checks if the current device supports fingerprint authentication.
+     *
+     * @param context The application or activity context.
+     * @return Whether fingerprint is supported.
+     */
+    static boolean hasSystemFeatureIris(@Nullable Context context) {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
+                && context != null
+                && context.getPackageManager() != null
+                && Api29Impl.hasSystemFeatureIris(context.getPackageManager());
+    }
+
+    /**
      * Nested class to avoid verification errors for methods introduced in Android 6.0 (API 23).
      */
     @RequiresApi(Build.VERSION_CODES.M)
@@ -62,4 +88,33 @@
             return packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
         }
     }
+
+    /**
+     * Nested class to avoid verification errors for methods introduced in Android 6.0 (API 23).
+     */
+    @RequiresApi(Build.VERSION_CODES.Q)
+    private static class Api29Impl {
+        // Prevent instantiation.
+        private Api29Impl() {}
+
+        /**
+         * Checks if the given package manager has support for the face system feature.
+         *
+         * @param packageManager The system package manager.
+         * @return Whether face is supported.
+         */
+        static boolean hasSystemFeatureFace(@NonNull PackageManager packageManager) {
+            return packageManager.hasSystemFeature(PackageManager.FEATURE_FACE);
+        }
+
+        /**
+         * Checks if the given package manager has support for the iris system feature.
+         *
+         * @param packageManager The system package manager.
+         * @return Whether iris is supported.
+         */
+        static boolean hasSystemFeatureIris(@NonNull PackageManager packageManager) {
+            return packageManager.hasSystemFeature(PackageManager.FEATURE_IRIS);
+        }
+    }
 }
diff --git a/biometric/biometric/src/main/res/values/strings.xml b/biometric/biometric/src/main/res/values/strings.xml
index 605afc7..5e23122 100644
--- a/biometric/biometric/src/main/res/values/strings.xml
+++ b/biometric/biometric/src/main/res/values/strings.xml
@@ -48,4 +48,50 @@
     <!-- Content description for the icon shown on the fingerprint dialog during authentication.
     This is not shown but may be read aloud to the user for accessibility. [CHAR LIMIT=NONE] -->
     <string name="fingerprint_dialog_icon_description">Fingerprint icon</string>
+
+    <!-- Label for a button or in-app setting that allows the user to authenticate with their
+    fingerprint. [CHAR LIMIT=70] -->
+    <string name="use_fingerprint_label">Use fingerprint</string>
+    <!-- Label for a button or in-app setting that allows the user to authenticate with their
+    face. [CHAR LIMIT=70] -->
+    <string name="use_face_label">Use face</string>
+    <!-- Label for a button or in-app setting that allows the user to authenticate with their
+    biometric credentials (e.g. face or fingerprint). [CHAR LIMIT=70] -->
+    <string name="use_biometric_label">Use biometrics</string>
+    <!-- Label for a button or in-app setting that allows the user to authenticate with their
+    device screen lock (i.e. PIN, pattern, or password). [CHAR LIMIT=70] -->
+    <string name="use_screen_lock_label">Use screen lock</string>
+    <!-- Label for a button or in-app setting that allows the user to authenticate with their
+    fingerprint or device screen lock (i.e. PIN, pattern, or password). [CHAR LIMIT=70] -->
+    <string name="use_fingerprint_or_screen_lock_label">Use fingerprint or screen lock</string>
+    <!-- Label for a button or in-app setting that allows the user to authenticate with their
+    face or device screen lock (i.e. PIN, pattern, or password). [CHAR LIMIT=70] -->
+    <string name="use_face_or_screen_lock_label">Use face or screen lock</string>
+    <!-- Label for a button or in-app setting that allows the user to authenticate with their
+    biometric credentials (e.g. face or fingerprint) or their device screen lock (i.e. PIN, pattern,
+    or password). [CHAR LIMIT=70] -->
+    <string name="use_biometric_or_screen_lock_label">Use biometrics or screen lock</string>
+
+    <!-- Message shown on the biometric authentication dialog, asking the user to authenticate with
+    their fingerprint. [CHAR LIMIT=90] -->
+    <string name="fingerprint_prompt_message">Use your fingerprint to continue</string>
+    <!-- Message shown on the biometric authentication dialog, asking the user to authenticate with
+    their face. [CHAR LIMIT=90] -->
+    <string name="face_prompt_message">Use your face to continue</string>
+    <!-- Message shown on the biometric authentication dialog, asking the user to authenticate with
+    their biometric credential (e.g. face or fingerprint). [CHAR LIMIT=90] -->
+    <string name="biometric_prompt_message">Use your biometric to continue</string>
+    <!-- Message shown on the biometric authentication dialog, asking the user to authenticate with
+    their device screen lock (i.e. PIN, pattern, or password). [CHAR LIMIT=90] -->
+    <string name="screen_lock_prompt_message">Enter your screen lock to continue</string>
+    <!-- Message shown on the biometric authentication dialog, asking the user to authenticate with
+    their fingerprint or device screen lock (i.e. PIN, pattern, or password). [CHAR LIMIT=90] -->
+    <string name="fingerprint_or_screen_lock_prompt_message">Use your fingerprint or screen lock to continue</string>
+    <!-- Message shown on the biometric authentication dialog, asking the user to authenticate with
+    their fingerprint or device screen lock (i.e. PIN, pattern, or password). [CHAR LIMIT=90] -->
+    <string name="face_or_screen_lock_prompt_message">Use your face or screen lock to continue</string>
+    <!-- Message shown on the biometric authentication dialog, asking the user to authenticate with
+    their biometric credential (e.g. face or fingerprint) or their device screen lock (i.e. PIN,
+    pattern, or password). [CHAR LIMIT=90] -->
+    <string name="biometric_or_screen_lock_prompt_message">Use your biometric or screen lock to continue</string>
 </resources>
diff --git a/biometric/biometric/src/test/java/androidx/biometric/BiometricFragmentTest.java b/biometric/biometric/src/test/java/androidx/biometric/BiometricFragmentTest.java
index d0bc9b4..a46337f 100644
--- a/biometric/biometric/src/test/java/androidx/biometric/BiometricFragmentTest.java
+++ b/biometric/biometric/src/test/java/androidx/biometric/BiometricFragmentTest.java
@@ -34,6 +34,8 @@
 import android.os.Handler;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -42,7 +44,6 @@
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.Config;
@@ -54,12 +55,7 @@
 @DoNotInstrument
 @SuppressWarnings("deprecation")
 public class BiometricFragmentTest {
-    private static final Executor EXECUTOR = new Executor() {
-        @Override
-        public void execute(@NonNull Runnable runnable) {
-            runnable.run();
-        }
-    };
+    private static final Executor EXECUTOR = Runnable::run;
 
     @Mock private BiometricPrompt.AuthenticationCallback mAuthenticationCallback;
     @Mock private Context mContext;
@@ -68,22 +64,21 @@
 
     @Captor private ArgumentCaptor<BiometricPrompt.AuthenticationResult> mResultCaptor;
 
-    private BiometricFragment mFragment;
     private BiometricViewModel mViewModel;
+    private TestInjector.Builder mInjectorBuilder;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         prepareMockHandler(mHandler);
-        mFragment = BiometricFragment.newInstance();
         mViewModel = new BiometricViewModel();
-        mFragment.mHandler = mHandler;
-        mFragment.mViewModel = mViewModel;
+        mInjectorBuilder = new TestInjector.Builder(mHandler).setViewModel(mViewModel);
     }
 
     @Test
     public void testCancel_DoesNotCrash_WhenNotAssociatedWithFragmentManager() {
-        mFragment.cancelAuthentication(BiometricFragment.CANCELED_FROM_INTERNAL);
+        final BiometricFragment fragment = BiometricFragment.newInstance(mInjectorBuilder.build());
+        fragment.cancelAuthentication(BiometricFragment.CANCELED_FROM_INTERNAL);
     }
 
     @Test
@@ -92,7 +87,8 @@
         mViewModel.setClientCallback(mAuthenticationCallback);
         mViewModel.setAwaitingResult(true);
 
-        mFragment.onAuthenticationSucceeded(
+        final BiometricFragment fragment = BiometricFragment.newInstance(mInjectorBuilder.build());
+        fragment.onAuthenticationSucceeded(
                 new BiometricPrompt.AuthenticationResult(
                         null /* crypto */, BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC));
 
@@ -112,7 +108,8 @@
         mViewModel.setAwaitingResult(true);
         mViewModel.setFingerprintDialogDismissedInstantly(false);
 
-        mFragment.onAuthenticationError(errMsgId, errString);
+        final BiometricFragment fragment = BiometricFragment.newInstance(mInjectorBuilder.build());
+        fragment.onAuthenticationError(errMsgId, errString);
 
         assertThat(mViewModel.getFingerprintDialogState().getValue())
                 .isEqualTo(FingerprintDialogFragment.STATE_FINGERPRINT_ERROR);
@@ -123,7 +120,8 @@
 
     @Test
     public void testAuthenticate_ReturnsWithoutError_WhenDetached() {
-        mFragment.authenticate(
+        final BiometricFragment fragment = BiometricFragment.newInstance(mInjectorBuilder.build());
+        fragment.authenticate(
                 new BiometricPrompt.PromptInfo.Builder()
                         .setTitle("Title")
                         .setNegativeButtonText("Cancel")
@@ -150,13 +148,15 @@
                 nullable(Handler.class));
         when(mContext.getString(anyInt())).thenReturn(errString);
 
-        mFragment.authenticateWithFingerprint(mFingerprintManager, mContext);
+        final BiometricFragment fragment = BiometricFragment.newInstance(mInjectorBuilder.build());
+        fragment.authenticateWithFingerprint(mFingerprintManager, mContext);
 
         verify(mAuthenticationCallback).onAuthenticationError(eq(errMsgId), anyString());
     }
 
     @Test
     @Config(minSdk = Build.VERSION_CODES.P)
+    @RequiresApi(Build.VERSION_CODES.P)
     public void testAuthenticateWithBiometricPrompt_DoesShowErrorAndDismiss_WhenNPEThrown() {
         final int errMsgId = BiometricPrompt.ERROR_HW_UNAVAILABLE;
         final String errString = "test string";
@@ -173,7 +173,8 @@
                 any(android.hardware.biometrics.BiometricPrompt.AuthenticationCallback.class));
         when(mContext.getString(anyInt())).thenReturn(errString);
 
-        mFragment.authenticateWithBiometricPrompt(biometricPrompt, mContext);
+        final BiometricFragment fragment = BiometricFragment.newInstance(mInjectorBuilder.build());
+        fragment.authenticateWithBiometricPrompt(biometricPrompt, mContext);
 
         verify(mAuthenticationCallback).onAuthenticationError(eq(errMsgId), anyString());
     }
@@ -181,15 +182,102 @@
     private static void prepareMockHandler(Handler mockHandler) {
         // Immediately invoke any scheduled callbacks.
         when(mockHandler.postDelayed(any(Runnable.class), anyLong()))
-                .thenAnswer(new Answer<Boolean>() {
-                    @Override
-                    public Boolean answer(InvocationOnMock invocation) {
-                        final Runnable runnable = invocation.getArgument(0);
-                        if (runnable != null) {
-                            runnable.run();
-                        }
-                        return true;
+                .thenAnswer((Answer<Boolean>) invocation -> {
+                    final Runnable runnable = invocation.getArgument(0);
+                    if (runnable != null) {
+                        runnable.run();
                     }
+                    return true;
                 });
     }
+
+    private static class TestInjector implements BiometricFragment.Injector {
+        static class Builder {
+            @NonNull private final Handler mHandler;
+
+            @Nullable private BiometricViewModel mViewModel = null;
+            private boolean mIsFingerprintHardwarePresent = false;
+            private boolean mIsFaceHardwarePresent = false;
+            private boolean mIsIrisHardwarePresent = false;
+
+            Builder(@NonNull Handler handler) {
+                mHandler = handler;
+            }
+
+            Builder setViewModel(@Nullable BiometricViewModel viewModel) {
+                mViewModel = viewModel;
+                return this;
+            }
+
+            Builder setFingerprintHardwarePresent(boolean fingerprintHardwarePresent) {
+                mIsFingerprintHardwarePresent = fingerprintHardwarePresent;
+                return this;
+            }
+
+            Builder setFaceHardwarePresent(boolean faceHardwarePresent) {
+                mIsFaceHardwarePresent = faceHardwarePresent;
+                return this;
+            }
+
+            Builder setIrisHardwarePresent(boolean irisHardwarePresent) {
+                mIsIrisHardwarePresent = irisHardwarePresent;
+                return this;
+            }
+
+            TestInjector build() {
+                return new TestInjector(
+                        mHandler,
+                        mViewModel,
+                        mIsFingerprintHardwarePresent,
+                        mIsFaceHardwarePresent,
+                        mIsIrisHardwarePresent);
+            }
+        }
+
+        @NonNull private final Handler mHandler;
+        @Nullable private final BiometricViewModel mViewModel;
+        private final boolean mIsFingerprintHardwarePresent;
+        private final boolean mIsFaceHardwarePresent;
+        private final boolean mIsIrisHardwarePresent;
+
+        private TestInjector(
+                @NonNull Handler handler,
+                @Nullable BiometricViewModel viewModel,
+                boolean isFingerprintHardwarePresent,
+                boolean isFaceHardwarePresent,
+                boolean isIrisHardwarePresent) {
+            mHandler = handler;
+            mViewModel = viewModel;
+            mIsFingerprintHardwarePresent = isFingerprintHardwarePresent;
+            mIsFaceHardwarePresent = isFaceHardwarePresent;
+            mIsIrisHardwarePresent = isIrisHardwarePresent;
+        }
+
+        @Override
+        @NonNull
+        public Handler getHandler() {
+            return mHandler;
+        }
+
+        @Override
+        @Nullable
+        public BiometricViewModel getViewModel(@Nullable Context hostContext) {
+            return mViewModel;
+        }
+
+        @Override
+        public boolean isFingerprintHardwarePresent(@Nullable Context context) {
+            return mIsFingerprintHardwarePresent;
+        }
+
+        @Override
+        public boolean isFaceHardwarePresent(@Nullable Context context) {
+            return mIsFaceHardwarePresent;
+        }
+
+        @Override
+        public boolean isIrisHardwarePresent(@Nullable Context context) {
+            return  mIsIrisHardwarePresent;
+        }
+    }
 }
diff --git a/biometric/biometric/src/test/java/androidx/biometric/BiometricManagerTest.java b/biometric/biometric/src/test/java/androidx/biometric/BiometricManagerTest.java
index 7afcdea..5f22b16 100644
--- a/biometric/biometric/src/test/java/androidx/biometric/BiometricManagerTest.java
+++ b/biometric/biometric/src/test/java/androidx/biometric/BiometricManagerTest.java
@@ -27,10 +27,16 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.content.Context;
+import android.content.res.Resources;
 import android.os.Build;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.biometric.BiometricManager.AuthenticatorTypes;
 import androidx.biometric.BiometricManager.Authenticators;
+import androidx.test.core.app.ApplicationProvider;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -47,13 +53,17 @@
 public class BiometricManagerTest {
     @Mock private androidx.core.hardware.fingerprint.FingerprintManagerCompat mFingerprintManager;
 
+    private Context mContext;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mContext = ApplicationProvider.getApplicationContext();
     }
 
     @Test
     @Config(minSdk = Build.VERSION_CODES.Q)
+    @RequiresApi(Build.VERSION_CODES.Q)
     public void testCanAuthenticate_ReturnsSuccess_WhenManagerReturnsSuccess_OnApi29AndAbove() {
         final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
                 mock(android.hardware.biometrics.BiometricManager.class);
@@ -61,21 +71,22 @@
         when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
         when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
 
-        final BiometricManager.Injector injector = new TestInjector.Builder()
-                .setBiometricManager(frameworkBiometricManager)
-                .setFingerprintManager(mFingerprintManager)
-                .setDeviceSecurable(true)
-                .setDeviceSecuredWithCredential(true)
-                .setFingerprintHardwarePresent(true)
-                .build();
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(true)
+                        .build());
 
-        final BiometricManager biometricManager = new BiometricManager(injector);
         final int authenticators = Authenticators.BIOMETRIC_WEAK;
         assertThat(biometricManager.canAuthenticate(authenticators)).isEqualTo(BIOMETRIC_SUCCESS);
     }
 
     @Test
     @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = 29)
+    @RequiresApi(Build.VERSION_CODES.Q)
     public void testCanAuthenticate_ReturnsError_WhenManagerReturnsNoneEnrolled_OnApi29AndAbove() {
         final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
                 mock(android.hardware.biometrics.BiometricManager.class);
@@ -83,15 +94,15 @@
         when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
         when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(true);
 
-        final BiometricManager.Injector injector = new TestInjector.Builder()
-                .setBiometricManager(frameworkBiometricManager)
-                .setFingerprintManager(mFingerprintManager)
-                .setDeviceSecurable(true)
-                .setDeviceSecuredWithCredential(true)
-                .setFingerprintHardwarePresent(true)
-                .build();
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(true)
+                        .build());
 
-        final BiometricManager biometricManager = new BiometricManager(injector);
         final int authenticators = Authenticators.BIOMETRIC_WEAK;
         assertThat(biometricManager.canAuthenticate(authenticators))
                 .isEqualTo(BIOMETRIC_ERROR_NONE_ENROLLED);
@@ -99,6 +110,7 @@
 
     @Test
     @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = 29)
+    @RequiresApi(Build.VERSION_CODES.Q)
     public void testCanAuthenticate_ReturnsError_WhenManagerReturnsNoHardware_OnApi29AndAbove() {
         final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
                 mock(android.hardware.biometrics.BiometricManager.class);
@@ -106,15 +118,15 @@
         when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
         when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(true);
 
-        final BiometricManager.Injector injector = new TestInjector.Builder()
-                .setBiometricManager(frameworkBiometricManager)
-                .setFingerprintManager(mFingerprintManager)
-                .setDeviceSecurable(true)
-                .setDeviceSecuredWithCredential(true)
-                .setFingerprintHardwarePresent(true)
-                .build();
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(true)
+                        .build());
 
-        final BiometricManager biometricManager = new BiometricManager(injector);
         final int authenticators = Authenticators.BIOMETRIC_WEAK;
         assertThat(biometricManager.canAuthenticate(authenticators))
                 .isEqualTo(BIOMETRIC_ERROR_NO_HARDWARE);
@@ -122,6 +134,7 @@
 
     @Test
     @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+    @RequiresApi(Build.VERSION_CODES.Q)
     public void testCanAuthenticate_ReturnsError_WhenCombinationNotSupported_OnApi29() {
         final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
                 mock(android.hardware.biometrics.BiometricManager.class);
@@ -129,15 +142,15 @@
         when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
         when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(true);
 
-        final BiometricManager.Injector injector = new TestInjector.Builder()
-                .setBiometricManager(frameworkBiometricManager)
-                .setFingerprintManager(mFingerprintManager)
-                .setDeviceSecurable(true)
-                .setDeviceSecuredWithCredential(true)
-                .setFingerprintHardwarePresent(true)
-                .build();
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(true)
+                        .build());
 
-        final BiometricManager biometricManager = new BiometricManager(injector);
         final int authenticators = Authenticators.DEVICE_CREDENTIAL;
         assertThat(biometricManager.canAuthenticate(authenticators))
                 .isEqualTo(BIOMETRIC_ERROR_UNSUPPORTED);
@@ -149,14 +162,14 @@
         when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
         when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(true);
 
-        final BiometricManager.Injector injector = new TestInjector.Builder()
-                .setFingerprintManager(mFingerprintManager)
-                .setDeviceSecurable(true)
-                .setDeviceSecuredWithCredential(true)
-                .setFingerprintHardwarePresent(true)
-                .build();
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(true)
+                        .build());
 
-        final BiometricManager biometricManager = new BiometricManager(injector);
         final int authenticators = Authenticators.DEVICE_CREDENTIAL;
         assertThat(biometricManager.canAuthenticate(authenticators))
                 .isEqualTo(BIOMETRIC_ERROR_UNSUPPORTED);
@@ -164,6 +177,7 @@
 
     @Test
     @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+    @RequiresApi(Build.VERSION_CODES.Q)
     public void testCanAuthenticate_ReturnsError_WhenNoAuthenticatorsAllowed_OnApi29() {
         final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
                 mock(android.hardware.biometrics.BiometricManager.class);
@@ -171,15 +185,15 @@
         when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
         when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(true);
 
-        final BiometricManager.Injector injector = new TestInjector.Builder()
-                .setBiometricManager(frameworkBiometricManager)
-                .setFingerprintManager(mFingerprintManager)
-                .setDeviceSecurable(true)
-                .setDeviceSecuredWithCredential(true)
-                .setFingerprintHardwarePresent(true)
-                .build();
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(true)
+                        .build());
 
-        final BiometricManager biometricManager = new BiometricManager(injector);
         assertThat(biometricManager.canAuthenticate(0)).isEqualTo(BIOMETRIC_ERROR_NO_HARDWARE);
     }
 
@@ -189,19 +203,20 @@
         when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
         when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(true);
 
-        final BiometricManager.Injector injector = new TestInjector.Builder()
-                .setFingerprintManager(mFingerprintManager)
-                .setDeviceSecurable(true)
-                .setDeviceSecuredWithCredential(true)
-                .setFingerprintHardwarePresent(true)
-                .build();
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(true)
+                        .build());
 
-        final BiometricManager biometricManager = new BiometricManager(injector);
         assertThat(biometricManager.canAuthenticate(0)).isEqualTo(BIOMETRIC_ERROR_NO_HARDWARE);
     }
 
     @Test
     @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+    @RequiresApi(Build.VERSION_CODES.Q)
     public void testCanAuthenticate_ReturnsError_WhenDeviceNotSecurable_OnApi29() {
         final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
                 mock(android.hardware.biometrics.BiometricManager.class);
@@ -209,13 +224,13 @@
         when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
         when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
 
-        final BiometricManager.Injector injector = new TestInjector.Builder()
-                .setBiometricManager(frameworkBiometricManager)
-                .setFingerprintManager(mFingerprintManager)
-                .setFingerprintHardwarePresent(true)
-                .build();
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setFingerprintHardwarePresent(true)
+                        .build());
 
-        final BiometricManager biometricManager = new BiometricManager(injector);
         final int authenticators = Authenticators.BIOMETRIC_WEAK;
         assertThat(biometricManager.canAuthenticate(authenticators))
                 .isEqualTo(BIOMETRIC_ERROR_NO_HARDWARE);
@@ -227,12 +242,12 @@
         when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
         when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(true);
 
-        final BiometricManager.Injector injector = new TestInjector.Builder()
-                .setFingerprintManager(mFingerprintManager)
-                .setFingerprintHardwarePresent(true)
-                .build();
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setFingerprintHardwarePresent(true)
+                        .build());
 
-        final BiometricManager biometricManager = new BiometricManager(injector);
         final int authenticators = Authenticators.BIOMETRIC_WEAK;
         assertThat(biometricManager.canAuthenticate(authenticators))
                 .isEqualTo(BIOMETRIC_ERROR_NO_HARDWARE);
@@ -240,6 +255,7 @@
 
     @Test
     @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+    @RequiresApi(Build.VERSION_CODES.Q)
     public void testCanAuthenticate_ReturnsError_WhenUnsecured_BiometricOnly_OnApi29() {
         final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
                 mock(android.hardware.biometrics.BiometricManager.class);
@@ -247,13 +263,13 @@
         when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
         when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
 
-        final BiometricManager.Injector injector = new TestInjector.Builder()
-                .setBiometricManager(frameworkBiometricManager)
-                .setDeviceSecurable(true)
-                .setFingerprintHardwarePresent(false)
-                .build();
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .setDeviceSecurable(true)
+                        .setFingerprintHardwarePresent(false)
+                        .build());
 
-        final BiometricManager biometricManager = new BiometricManager(injector);
         final int authenticators = Authenticators.BIOMETRIC_WEAK;
         assertThat(biometricManager.canAuthenticate(authenticators))
                 .isEqualTo(BIOMETRIC_ERROR_NO_HARDWARE);
@@ -265,13 +281,13 @@
         when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
         when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
 
-        final BiometricManager.Injector injector = new TestInjector.Builder()
-                .setFingerprintManager(mFingerprintManager)
-                .setDeviceSecurable(true)
-                .setFingerprintHardwarePresent(false)
-                .build();
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setFingerprintHardwarePresent(false)
+                        .build());
 
-        final BiometricManager biometricManager = new BiometricManager(injector);
         final int authenticators = Authenticators.BIOMETRIC_WEAK;
         assertThat(biometricManager.canAuthenticate(authenticators))
                 .isEqualTo(BIOMETRIC_ERROR_NO_HARDWARE);
@@ -279,6 +295,7 @@
 
     @Test
     @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+    @RequiresApi(Build.VERSION_CODES.Q)
     public void testCanAuthenticate_ReturnsError_WhenUnsecured_CredentialAllowed_OnApi29() {
         final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
                 mock(android.hardware.biometrics.BiometricManager.class);
@@ -286,13 +303,13 @@
         when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
         when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
 
-        final BiometricManager.Injector injector = new TestInjector.Builder()
-                .setBiometricManager(frameworkBiometricManager)
-                .setDeviceSecurable(true)
-                .setFingerprintHardwarePresent(false)
-                .build();
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .setDeviceSecurable(true)
+                        .setFingerprintHardwarePresent(false)
+                        .build());
 
-        final BiometricManager biometricManager = new BiometricManager(injector);
         final int authenticators = Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL;
         assertThat(biometricManager.canAuthenticate(authenticators))
                 .isEqualTo(BIOMETRIC_ERROR_NONE_ENROLLED);
@@ -304,13 +321,13 @@
         when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
         when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
 
-        final BiometricManager.Injector injector = new TestInjector.Builder()
-                .setFingerprintManager(mFingerprintManager)
-                .setDeviceSecurable(true)
-                .setFingerprintHardwarePresent(false)
-                .build();
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setFingerprintHardwarePresent(false)
+                        .build());
 
-        final BiometricManager biometricManager = new BiometricManager(injector);
         final int authenticators = Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL;
         assertThat(biometricManager.canAuthenticate(authenticators))
                 .isEqualTo(BIOMETRIC_ERROR_NONE_ENROLLED);
@@ -318,18 +335,19 @@
 
     @Test
     @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+    @RequiresApi(Build.VERSION_CODES.Q)
     public void testCanAuthenticate_ReturnsSuccess_WhenDeviceCredentialAvailable_OnApi29() {
         final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
                 mock(android.hardware.biometrics.BiometricManager.class);
         when(frameworkBiometricManager.canAuthenticate()).thenReturn(BIOMETRIC_ERROR_NONE_ENROLLED);
 
-        final BiometricManager.Injector injector = new TestInjector.Builder()
-                .setBiometricManager(frameworkBiometricManager)
-                .setDeviceSecurable(true)
-                .setDeviceSecuredWithCredential(true)
-                .build();
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .build());
 
-        final BiometricManager biometricManager = new BiometricManager(injector);
         final int authenticators = Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL;
         assertThat(biometricManager.canAuthenticate(authenticators)).isEqualTo(BIOMETRIC_SUCCESS);
     }
@@ -340,19 +358,20 @@
         when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
         when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
 
-        final BiometricManager.Injector injector = new TestInjector.Builder()
-                .setFingerprintManager(mFingerprintManager)
-                .setDeviceSecurable(true)
-                .setDeviceSecuredWithCredential(true)
-                .build();
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .build());
 
-        final BiometricManager biometricManager = new BiometricManager(injector);
         final int authenticators = Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL;
         assertThat(biometricManager.canAuthenticate(authenticators)).isEqualTo(BIOMETRIC_SUCCESS);
     }
 
     @Test
     @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+    @RequiresApi(Build.VERSION_CODES.Q)
     public void testCanAuthenticateStrong_ReturnsSuccess_WhenStrongBiometricGuaranteed_OnApi29() {
         final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
                 mock(android.hardware.biometrics.BiometricManager.class);
@@ -360,22 +379,23 @@
         when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
         when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
 
-        final BiometricManager.Injector injector = new TestInjector.Builder()
-                .setBiometricManager(frameworkBiometricManager)
-                .setFingerprintManager(mFingerprintManager)
-                .setDeviceSecurable(true)
-                .setDeviceSecuredWithCredential(true)
-                .setFingerprintHardwarePresent(true)
-                .setStrongBiometricGuaranteed(true)
-                .build();
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(true)
+                        .setStrongBiometricGuaranteed(true)
+                        .build());
 
-        final BiometricManager biometricManager = new BiometricManager(injector);
         final int authenticators = Authenticators.BIOMETRIC_STRONG;
         assertThat(biometricManager.canAuthenticate(authenticators)).isEqualTo(BIOMETRIC_SUCCESS);
     }
 
     @Test
     @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+    @RequiresApi(Build.VERSION_CODES.Q)
     public void testCanAuthenticateStrong_ReturnsError_WhenWeakUnavailable_OnApi29() {
         final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
                 mock(android.hardware.biometrics.BiometricManager.class);
@@ -383,15 +403,15 @@
         when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
         when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
 
-        final BiometricManager.Injector injector = new TestInjector.Builder()
-                .setBiometricManager(frameworkBiometricManager)
-                .setFingerprintManager(mFingerprintManager)
-                .setDeviceSecurable(true)
-                .setDeviceSecuredWithCredential(true)
-                .setFingerprintHardwarePresent(true)
-                .build();
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(true)
+                        .build());
 
-        final BiometricManager biometricManager = new BiometricManager(injector);
         final int authenticators = Authenticators.BIOMETRIC_STRONG;
         assertThat(biometricManager.canAuthenticate(authenticators))
                 .isEqualTo(BIOMETRIC_ERROR_NONE_ENROLLED);
@@ -399,6 +419,7 @@
 
     @Test
     @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+    @RequiresApi(Build.VERSION_CODES.Q)
     public void testCanAuthenticateStrong_ReturnsSuccess_WhenFingerprintAvailable_OnApi29() {
         final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
                 mock(android.hardware.biometrics.BiometricManager.class);
@@ -406,15 +427,15 @@
         when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
         when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(true);
 
-        final BiometricManager.Injector injector = new TestInjector.Builder()
-                .setBiometricManager(frameworkBiometricManager)
-                .setFingerprintManager(mFingerprintManager)
-                .setDeviceSecurable(true)
-                .setDeviceSecuredWithCredential(true)
-                .setFingerprintHardwarePresent(true)
-                .build();
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(true)
+                        .build());
 
-        final BiometricManager biometricManager = new BiometricManager(injector);
         final int authenticators = Authenticators.BIOMETRIC_STRONG;
         assertThat(biometricManager.canAuthenticate(authenticators)).isEqualTo(BIOMETRIC_SUCCESS);
     }
@@ -425,20 +446,21 @@
         when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
         when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(true);
 
-        final BiometricManager.Injector injector = new TestInjector.Builder()
-                .setFingerprintManager(mFingerprintManager)
-                .setDeviceSecurable(true)
-                .setDeviceSecuredWithCredential(true)
-                .setFingerprintHardwarePresent(true)
-                .build();
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(true)
+                        .build());
 
-        final BiometricManager biometricManager = new BiometricManager(injector);
         final int authenticators = Authenticators.BIOMETRIC_STRONG;
         assertThat(biometricManager.canAuthenticate(authenticators)).isEqualTo(BIOMETRIC_SUCCESS);
     }
 
     @Test
     @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+    @RequiresApi(Build.VERSION_CODES.Q)
     public void testCanAuthenticate_ReturnsUnknown_WhenFingerprintUnavailable_OnApi29() {
         final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
                 mock(android.hardware.biometrics.BiometricManager.class);
@@ -446,15 +468,15 @@
         when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
         when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
 
-        final BiometricManager.Injector injector = new TestInjector.Builder()
-                .setBiometricManager(frameworkBiometricManager)
-                .setFingerprintManager(mFingerprintManager)
-                .setDeviceSecurable(true)
-                .setDeviceSecuredWithCredential(true)
-                .setFingerprintHardwarePresent(true)
-                .build();
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(true)
+                        .build());
 
-        final BiometricManager biometricManager = new BiometricManager(injector);
         final int authenticators = Authenticators.BIOMETRIC_STRONG;
         assertThat(biometricManager.canAuthenticate(authenticators))
                 .isEqualTo(BIOMETRIC_STATUS_UNKNOWN);
@@ -467,14 +489,14 @@
         when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
         when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
 
-        final BiometricManager.Injector injector = new TestInjector.Builder()
-                .setFingerprintManager(mFingerprintManager)
-                .setDeviceSecurable(true)
-                .setDeviceSecuredWithCredential(true)
-                .setFingerprintHardwarePresent(true)
-                .build();
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(true)
+                        .build());
 
-        final BiometricManager biometricManager = new BiometricManager(injector);
         final int authenticators = Authenticators.BIOMETRIC_STRONG;
         assertThat(biometricManager.canAuthenticate(authenticators))
                 .isEqualTo(BIOMETRIC_STATUS_UNKNOWN);
@@ -486,13 +508,13 @@
         when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
         when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
 
-        final BiometricManager.Injector injector = new TestInjector.Builder()
-                .setFingerprintManager(mFingerprintManager)
-                .setDeviceSecurable(true)
-                .setDeviceSecuredWithCredential(true)
-                .build();
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .build());
 
-        final BiometricManager biometricManager = new BiometricManager(injector);
         final int authenticators = Authenticators.BIOMETRIC_STRONG;
         assertThat(biometricManager.canAuthenticate(authenticators))
                 .isEqualTo(BIOMETRIC_ERROR_NO_HARDWARE);
@@ -504,49 +526,753 @@
         when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
         when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
 
-        final BiometricManager.Injector injector = new TestInjector.Builder()
-                .setFingerprintManager(mFingerprintManager)
-                .setDeviceSecurable(true)
-                .setDeviceSecuredWithCredential(true)
-                .setFingerprintHardwarePresent(true)
-                .build();
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(true)
+                        .build());
 
-        final BiometricManager biometricManager = new BiometricManager(injector);
         final int authenticators = Authenticators.BIOMETRIC_STRONG;
         assertThat(biometricManager.canAuthenticate(authenticators))
                 .isEqualTo(BIOMETRIC_ERROR_NONE_ENROLLED);
     }
 
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.S)
+    @RequiresApi(Build.VERSION_CODES.S)
+    public void testGetStrings_ReturnsFrameworkStrings_OnApi31AndAbove() {
+        @AuthenticatorTypes final int authenticators =
+                Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL;
+        final String buttonLabel = "buttonLabel";
+        final String promptMessage = "promptMessage";
+        final String settingName = "settingName";
+
+        final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
+                mock(android.hardware.biometrics.BiometricManager.class);
+        final android.hardware.biometrics.BiometricManager.Strings frameworkStrings =
+                mock(android.hardware.biometrics.BiometricManager.Strings.class);
+        when(frameworkBiometricManager.getStrings(authenticators)).thenReturn(frameworkStrings);
+        when(frameworkStrings.getButtonLabel()).thenReturn(buttonLabel);
+        when(frameworkStrings.getPromptMessage()).thenReturn(promptMessage);
+        when(frameworkStrings.getSettingName()).thenReturn(settingName);
+
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .build());
+
+        final BiometricManager.Strings strings = biometricManager.getStrings(authenticators);
+        assertThat(strings).isNotNull();
+        assertThat(strings.getButtonLabel()).isEqualTo(buttonLabel);
+        assertThat(strings.getPromptMessage()).isEqualTo(promptMessage);
+        assertThat(strings.getSettingName()).isEqualTo(settingName);
+    }
+
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.R, maxSdk = Build.VERSION_CODES.R)
+    @RequiresApi(Build.VERSION_CODES.R)
+    public void testGetStrings_WhenFingerprintAvailable_OnApi30() {
+        final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
+                mock(android.hardware.biometrics.BiometricManager.class);
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+        when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(true);
+        when(frameworkBiometricManager.canAuthenticate(0)).thenReturn(BIOMETRIC_ERROR_NO_HARDWARE);
+        when(frameworkBiometricManager.canAuthenticate(Authenticators.BIOMETRIC_WEAK))
+                .thenReturn(BIOMETRIC_SUCCESS);
+        when(frameworkBiometricManager.canAuthenticate(Authenticators.DEVICE_CREDENTIAL))
+                .thenReturn(BIOMETRIC_SUCCESS);
+        when(frameworkBiometricManager.canAuthenticate(
+                Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL))
+                .thenReturn(BIOMETRIC_SUCCESS);
+
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(true)
+                        .setFaceHardwarePresent(false)
+                        .setIrisHardwarePresent(false)
+                        .build());
+
+        final BiometricManager.Strings biometricStrings =
+                biometricManager.getStrings(Authenticators.BIOMETRIC_WEAK);
+        assertThat(biometricStrings).isNotNull();
+        assertThat(biometricStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_fingerprint_label));
+        assertThat(biometricStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.fingerprint_prompt_message));
+        assertThat(biometricStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_fingerprint_label));
+
+        final BiometricManager.Strings biometricOrCredentialStrings =
+                biometricManager.getStrings(
+                        Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
+        assertThat(biometricOrCredentialStrings).isNotNull();
+        assertThat(biometricOrCredentialStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_fingerprint_label));
+        assertThat(biometricOrCredentialStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.fingerprint_or_screen_lock_prompt_message));
+        assertThat(biometricOrCredentialStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_fingerprint_or_screen_lock_label));
+
+        final BiometricManager.Strings credentialStrings =
+                biometricManager.getStrings(Authenticators.DEVICE_CREDENTIAL);
+        assertThat(credentialStrings).isNotNull();
+        assertThat(credentialStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_screen_lock_label));
+        assertThat(credentialStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.screen_lock_prompt_message));
+        assertThat(credentialStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_screen_lock_label));
+    }
+
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.R, maxSdk = Build.VERSION_CODES.R)
+    @RequiresApi(Build.VERSION_CODES.R)
+    public void testGetStrings_WhenFaceAvailable_OnApi30() {
+        final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
+                mock(android.hardware.biometrics.BiometricManager.class);
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
+        when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
+        when(frameworkBiometricManager.canAuthenticate(0)).thenReturn(BIOMETRIC_ERROR_NO_HARDWARE);
+        when(frameworkBiometricManager.canAuthenticate(Authenticators.BIOMETRIC_WEAK))
+                .thenReturn(BIOMETRIC_SUCCESS);
+        when(frameworkBiometricManager.canAuthenticate(Authenticators.DEVICE_CREDENTIAL))
+                .thenReturn(BIOMETRIC_SUCCESS);
+        when(frameworkBiometricManager.canAuthenticate(
+                Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL))
+                .thenReturn(BIOMETRIC_SUCCESS);
+
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(false)
+                        .setFaceHardwarePresent(true)
+                        .setIrisHardwarePresent(false)
+                        .build());
+
+        final BiometricManager.Strings biometricStrings =
+                biometricManager.getStrings(Authenticators.BIOMETRIC_WEAK);
+        assertThat(biometricStrings).isNotNull();
+        assertThat(biometricStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_face_label));
+        assertThat(biometricStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.face_prompt_message));
+        assertThat(biometricStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_face_label));
+
+        final BiometricManager.Strings biometricOrCredentialStrings =
+                biometricManager.getStrings(
+                        Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
+        assertThat(biometricOrCredentialStrings).isNotNull();
+        assertThat(biometricOrCredentialStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_face_label));
+        assertThat(biometricOrCredentialStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.face_or_screen_lock_prompt_message));
+        assertThat(biometricOrCredentialStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_face_or_screen_lock_label));
+
+        final BiometricManager.Strings credentialStrings =
+                biometricManager.getStrings(Authenticators.DEVICE_CREDENTIAL);
+        assertThat(credentialStrings).isNotNull();
+        assertThat(credentialStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_screen_lock_label));
+        assertThat(credentialStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.screen_lock_prompt_message));
+        assertThat(credentialStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_screen_lock_label));
+    }
+
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.R, maxSdk = Build.VERSION_CODES.R)
+    @RequiresApi(Build.VERSION_CODES.R)
+    public void testGetStrings_WhenIrisAvailable_OnApi30() {
+        final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
+                mock(android.hardware.biometrics.BiometricManager.class);
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
+        when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
+        when(frameworkBiometricManager.canAuthenticate(0)).thenReturn(BIOMETRIC_ERROR_NO_HARDWARE);
+        when(frameworkBiometricManager.canAuthenticate(Authenticators.BIOMETRIC_WEAK))
+                .thenReturn(BIOMETRIC_SUCCESS);
+        when(frameworkBiometricManager.canAuthenticate(Authenticators.DEVICE_CREDENTIAL))
+                .thenReturn(BIOMETRIC_SUCCESS);
+        when(frameworkBiometricManager.canAuthenticate(
+                Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL))
+                .thenReturn(BIOMETRIC_SUCCESS);
+
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(false)
+                        .setFaceHardwarePresent(false)
+                        .setIrisHardwarePresent(true)
+                        .build());
+
+        final BiometricManager.Strings biometricStrings =
+                biometricManager.getStrings(Authenticators.BIOMETRIC_WEAK);
+        assertThat(biometricStrings).isNotNull();
+        assertThat(biometricStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_biometric_label));
+        assertThat(biometricStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.biometric_prompt_message));
+        assertThat(biometricStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_biometric_label));
+
+        final BiometricManager.Strings biometricOrCredentialStrings =
+                biometricManager.getStrings(
+                        Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
+        assertThat(biometricOrCredentialStrings).isNotNull();
+        assertThat(biometricOrCredentialStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_biometric_label));
+        assertThat(biometricOrCredentialStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.biometric_or_screen_lock_prompt_message));
+        assertThat(biometricOrCredentialStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_biometric_or_screen_lock_label));
+
+        final BiometricManager.Strings credentialStrings =
+                biometricManager.getStrings(Authenticators.DEVICE_CREDENTIAL);
+        assertThat(credentialStrings).isNotNull();
+        assertThat(credentialStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_screen_lock_label));
+        assertThat(credentialStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.screen_lock_prompt_message));
+        assertThat(credentialStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_screen_lock_label));
+    }
+
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.R, maxSdk = Build.VERSION_CODES.R)
+    @RequiresApi(Build.VERSION_CODES.R)
+    public void testGetStrings_WhenFingerprintAndFaceAvailable_OnApi30() {
+        final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
+                mock(android.hardware.biometrics.BiometricManager.class);
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+        when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(true);
+        when(frameworkBiometricManager.canAuthenticate(0)).thenReturn(BIOMETRIC_ERROR_NO_HARDWARE);
+        when(frameworkBiometricManager.canAuthenticate(Authenticators.BIOMETRIC_WEAK))
+                .thenReturn(BIOMETRIC_SUCCESS);
+        when(frameworkBiometricManager.canAuthenticate(Authenticators.DEVICE_CREDENTIAL))
+                .thenReturn(BIOMETRIC_SUCCESS);
+        when(frameworkBiometricManager.canAuthenticate(
+                Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL))
+                .thenReturn(BIOMETRIC_SUCCESS);
+
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(true)
+                        .setFaceHardwarePresent(true)
+                        .setIrisHardwarePresent(false)
+                        .build());
+
+        final BiometricManager.Strings biometricStrings =
+                biometricManager.getStrings(Authenticators.BIOMETRIC_WEAK);
+        assertThat(biometricStrings).isNotNull();
+        assertThat(biometricStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_biometric_label));
+        assertThat(biometricStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.biometric_prompt_message));
+        assertThat(biometricStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_biometric_label));
+
+        final BiometricManager.Strings biometricOrCredentialStrings =
+                biometricManager.getStrings(
+                        Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
+        assertThat(biometricOrCredentialStrings).isNotNull();
+        assertThat(biometricOrCredentialStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_biometric_label));
+        assertThat(biometricOrCredentialStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.biometric_or_screen_lock_prompt_message));
+        assertThat(biometricOrCredentialStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_biometric_or_screen_lock_label));
+
+        final BiometricManager.Strings credentialStrings =
+                biometricManager.getStrings(Authenticators.DEVICE_CREDENTIAL);
+        assertThat(credentialStrings).isNotNull();
+        assertThat(credentialStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_screen_lock_label));
+        assertThat(credentialStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.screen_lock_prompt_message));
+        assertThat(credentialStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_screen_lock_label));
+    }
+
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.R, maxSdk = Build.VERSION_CODES.R)
+    @RequiresApi(Build.VERSION_CODES.R)
+    public void testGetStrings_WhenBiometricsPresentButNotAvailable_OnApi30() {
+        final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
+                mock(android.hardware.biometrics.BiometricManager.class);
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+        when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
+        when(frameworkBiometricManager.canAuthenticate(0)).thenReturn(BIOMETRIC_ERROR_NO_HARDWARE);
+        when(frameworkBiometricManager.canAuthenticate(Authenticators.BIOMETRIC_WEAK))
+                .thenReturn(BIOMETRIC_ERROR_NONE_ENROLLED);
+        when(frameworkBiometricManager.canAuthenticate(Authenticators.DEVICE_CREDENTIAL))
+                .thenReturn(BIOMETRIC_SUCCESS);
+        when(frameworkBiometricManager.canAuthenticate(
+                Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL))
+                .thenReturn(BIOMETRIC_SUCCESS);
+
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(true)
+                        .setFaceHardwarePresent(true)
+                        .setIrisHardwarePresent(true)
+                        .build());
+
+        final BiometricManager.Strings biometricOrCredentialStrings =
+                biometricManager.getStrings(
+                        Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
+        assertThat(biometricOrCredentialStrings).isNotNull();
+        assertThat(biometricOrCredentialStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_screen_lock_label));
+        assertThat(biometricOrCredentialStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.screen_lock_prompt_message));
+        assertThat(biometricOrCredentialStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_biometric_or_screen_lock_label));
+
+        final BiometricManager.Strings credentialStrings =
+                biometricManager.getStrings(Authenticators.DEVICE_CREDENTIAL);
+        assertThat(credentialStrings).isNotNull();
+        assertThat(credentialStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_screen_lock_label));
+        assertThat(credentialStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.screen_lock_prompt_message));
+        assertThat(credentialStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_screen_lock_label));
+    }
+
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.R, maxSdk = Build.VERSION_CODES.R)
+    @RequiresApi(Build.VERSION_CODES.R)
+    public void testGetStrings_WhenOnlyScreenLockAvailable_OnApi30() {
+        final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
+                mock(android.hardware.biometrics.BiometricManager.class);
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
+        when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
+        when(frameworkBiometricManager.canAuthenticate(0)).thenReturn(BIOMETRIC_ERROR_NO_HARDWARE);
+        when(frameworkBiometricManager.canAuthenticate(Authenticators.BIOMETRIC_WEAK))
+                .thenReturn(BIOMETRIC_ERROR_NO_HARDWARE);
+        when(frameworkBiometricManager.canAuthenticate(Authenticators.DEVICE_CREDENTIAL))
+                .thenReturn(BIOMETRIC_SUCCESS);
+        when(frameworkBiometricManager.canAuthenticate(
+                Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL))
+                .thenReturn(BIOMETRIC_SUCCESS);
+
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(false)
+                        .setFaceHardwarePresent(false)
+                        .setIrisHardwarePresent(false)
+                        .build());
+
+        final BiometricManager.Strings biometricOrCredentialStrings =
+                biometricManager.getStrings(
+                        Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
+        assertThat(biometricOrCredentialStrings).isNotNull();
+        assertThat(biometricOrCredentialStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_screen_lock_label));
+        assertThat(biometricOrCredentialStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.screen_lock_prompt_message));
+        assertThat(biometricOrCredentialStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_screen_lock_label));
+
+        final BiometricManager.Strings credentialStrings =
+                biometricManager.getStrings(Authenticators.DEVICE_CREDENTIAL);
+        assertThat(credentialStrings).isNotNull();
+        assertThat(credentialStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_screen_lock_label));
+        assertThat(credentialStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.screen_lock_prompt_message));
+        assertThat(credentialStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_screen_lock_label));
+    }
+
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+    @RequiresApi(Build.VERSION_CODES.Q)
+    public void testGetStrings_WhenFingerprintAvailable_OnApi29() {
+        final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
+                mock(android.hardware.biometrics.BiometricManager.class);
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+        when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(true);
+        when(frameworkBiometricManager.canAuthenticate()).thenReturn(BIOMETRIC_SUCCESS);
+
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(true)
+                        .build());
+
+        final BiometricManager.Strings biometricStrings =
+                biometricManager.getStrings(Authenticators.BIOMETRIC_WEAK);
+        assertThat(biometricStrings).isNotNull();
+        assertThat(biometricStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_fingerprint_label));
+        assertThat(biometricStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.fingerprint_prompt_message));
+        assertThat(biometricStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_fingerprint_label));
+
+        final BiometricManager.Strings biometricOrCredentialStrings =
+                biometricManager.getStrings(
+                        Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
+        assertThat(biometricOrCredentialStrings).isNotNull();
+        assertThat(biometricOrCredentialStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_fingerprint_label));
+        assertThat(biometricOrCredentialStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.fingerprint_or_screen_lock_prompt_message));
+        assertThat(biometricOrCredentialStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_fingerprint_or_screen_lock_label));
+    }
+
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+    @RequiresApi(Build.VERSION_CODES.Q)
+    public void testGetStrings_WhenFaceAvailable_OnApi29() {
+        final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
+                mock(android.hardware.biometrics.BiometricManager.class);
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
+        when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
+        when(frameworkBiometricManager.canAuthenticate()).thenReturn(BIOMETRIC_SUCCESS);
+
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(false)
+                        .setFaceHardwarePresent(true)
+                        .setIrisHardwarePresent(false)
+                        .build());
+
+        final BiometricManager.Strings biometricStrings =
+                biometricManager.getStrings(Authenticators.BIOMETRIC_WEAK);
+        assertThat(biometricStrings).isNotNull();
+        assertThat(biometricStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_face_label));
+        assertThat(biometricStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.face_prompt_message));
+        assertThat(biometricStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_face_label));
+
+        final BiometricManager.Strings biometricOrCredentialStrings =
+                biometricManager.getStrings(
+                        Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
+        assertThat(biometricOrCredentialStrings).isNotNull();
+        assertThat(biometricOrCredentialStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_face_label));
+        assertThat(biometricOrCredentialStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.face_or_screen_lock_prompt_message));
+        assertThat(biometricOrCredentialStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_face_or_screen_lock_label));
+    }
+
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+    @RequiresApi(Build.VERSION_CODES.Q)
+    public void testGetStrings_WhenIrisAvailable_OnApi29() {
+        final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
+                mock(android.hardware.biometrics.BiometricManager.class);
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
+        when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
+        when(frameworkBiometricManager.canAuthenticate()).thenReturn(BIOMETRIC_SUCCESS);
+
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(false)
+                        .setFaceHardwarePresent(false)
+                        .setIrisHardwarePresent(true)
+                        .build());
+
+        final BiometricManager.Strings biometricStrings =
+                biometricManager.getStrings(Authenticators.BIOMETRIC_WEAK);
+        assertThat(biometricStrings).isNotNull();
+        assertThat(biometricStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_biometric_label));
+        assertThat(biometricStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.biometric_prompt_message));
+        assertThat(biometricStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_biometric_label));
+
+        final BiometricManager.Strings biometricOrCredentialStrings =
+                biometricManager.getStrings(
+                        Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
+        assertThat(biometricOrCredentialStrings).isNotNull();
+        assertThat(biometricOrCredentialStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_biometric_label));
+        assertThat(biometricOrCredentialStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.biometric_or_screen_lock_prompt_message));
+        assertThat(biometricOrCredentialStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_biometric_or_screen_lock_label));
+    }
+
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+    @RequiresApi(Build.VERSION_CODES.Q)
+    public void testGetStrings_WhenFingerprintAndFaceAvailable_OnApi29() {
+        final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
+                mock(android.hardware.biometrics.BiometricManager.class);
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+        when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(true);
+        when(frameworkBiometricManager.canAuthenticate()).thenReturn(BIOMETRIC_SUCCESS);
+
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(true)
+                        .setFaceHardwarePresent(true)
+                        .setIrisHardwarePresent(false)
+                        .build());
+
+        final BiometricManager.Strings biometricStrings =
+                biometricManager.getStrings(Authenticators.BIOMETRIC_WEAK);
+        assertThat(biometricStrings).isNotNull();
+        assertThat(biometricStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_biometric_label));
+        assertThat(biometricStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.biometric_prompt_message));
+        assertThat(biometricStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_biometric_label));
+
+        final BiometricManager.Strings biometricOrCredentialStrings =
+                biometricManager.getStrings(
+                        Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
+        assertThat(biometricOrCredentialStrings).isNotNull();
+        assertThat(biometricOrCredentialStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_biometric_label));
+        assertThat(biometricOrCredentialStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.biometric_or_screen_lock_prompt_message));
+        assertThat(biometricOrCredentialStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_biometric_or_screen_lock_label));
+    }
+
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+    @RequiresApi(Build.VERSION_CODES.Q)
+    public void testGetStrings_WhenBiometricsPresentButNotAvailable_OnApi29() {
+        final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
+                mock(android.hardware.biometrics.BiometricManager.class);
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+        when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
+        when(frameworkBiometricManager.canAuthenticate()).thenReturn(BIOMETRIC_ERROR_NONE_ENROLLED);
+
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(true)
+                        .setFaceHardwarePresent(true)
+                        .setIrisHardwarePresent(true)
+                        .build());
+
+        final BiometricManager.Strings biometricOrCredentialStrings =
+                biometricManager.getStrings(
+                        Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
+        assertThat(biometricOrCredentialStrings).isNotNull();
+        assertThat(biometricOrCredentialStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_screen_lock_label));
+        assertThat(biometricOrCredentialStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.screen_lock_prompt_message));
+        assertThat(biometricOrCredentialStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_biometric_or_screen_lock_label));
+    }
+
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.Q)
+    @RequiresApi(Build.VERSION_CODES.Q)
+    public void testGetStrings_WhenOnlyScreenLockAvailable_OnApi29() {
+        final android.hardware.biometrics.BiometricManager frameworkBiometricManager =
+                mock(android.hardware.biometrics.BiometricManager.class);
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
+        when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
+        when(frameworkBiometricManager.canAuthenticate()).thenReturn(BIOMETRIC_ERROR_NO_HARDWARE);
+
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setBiometricManager(frameworkBiometricManager)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(false)
+                        .setFaceHardwarePresent(false)
+                        .setIrisHardwarePresent(false)
+                        .build());
+
+        final BiometricManager.Strings biometricOrCredentialStrings =
+                biometricManager.getStrings(
+                        Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
+        assertThat(biometricOrCredentialStrings).isNotNull();
+        assertThat(biometricOrCredentialStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_screen_lock_label));
+        assertThat(biometricOrCredentialStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.screen_lock_prompt_message));
+        assertThat(biometricOrCredentialStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_screen_lock_label));
+    }
+
+    @Test
+    @Config(maxSdk = Build.VERSION_CODES.P)
+    public void testGetStrings_WhenFingerprintAvailable_OnApi28AndBelow() {
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+        when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(true);
+
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(true)
+                        .build());
+
+        final BiometricManager.Strings biometricStrings =
+                biometricManager.getStrings(Authenticators.BIOMETRIC_WEAK);
+        assertThat(biometricStrings).isNotNull();
+        assertThat(biometricStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_fingerprint_label));
+        assertThat(biometricStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.fingerprint_prompt_message));
+        assertThat(biometricStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_fingerprint_label));
+
+        final BiometricManager.Strings biometricOrCredentialStrings =
+                biometricManager.getStrings(
+                        Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
+        assertThat(biometricOrCredentialStrings).isNotNull();
+        assertThat(biometricOrCredentialStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_fingerprint_label));
+        assertThat(biometricOrCredentialStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.fingerprint_or_screen_lock_prompt_message));
+        assertThat(biometricOrCredentialStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_fingerprint_or_screen_lock_label));
+    }
+
+    @Test
+    @Config(maxSdk = Build.VERSION_CODES.P)
+    public void testGetStrings_WhenFingerprintPresentButNotAvailable_OnApi28AndBelow() {
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+        when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
+
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(true)
+                        .build());
+
+        final BiometricManager.Strings biometricOrCredentialStrings =
+                biometricManager.getStrings(
+                        Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
+        assertThat(biometricOrCredentialStrings).isNotNull();
+        assertThat(biometricOrCredentialStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_screen_lock_label));
+        assertThat(biometricOrCredentialStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.screen_lock_prompt_message));
+        assertThat(biometricOrCredentialStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_fingerprint_or_screen_lock_label));
+    }
+
+    @Test
+    @Config(maxSdk = Build.VERSION_CODES.P)
+    public void testGetStrings_WhenOnlyScreenLockAvailable_OnApi28AndBelow() {
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
+        when(mFingerprintManager.hasEnrolledFingerprints()).thenReturn(false);
+
+        final BiometricManager biometricManager = new BiometricManager(
+                new TestInjector.Builder(mContext)
+                        .setFingerprintManager(mFingerprintManager)
+                        .setDeviceSecurable(true)
+                        .setDeviceSecuredWithCredential(true)
+                        .setFingerprintHardwarePresent(false)
+                        .build());
+
+        final BiometricManager.Strings biometricOrCredentialStrings =
+                biometricManager.getStrings(
+                        Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
+        assertThat(biometricOrCredentialStrings).isNotNull();
+        assertThat(biometricOrCredentialStrings.getButtonLabel())
+                .isEqualTo(mContext.getString(R.string.use_screen_lock_label));
+        assertThat(biometricOrCredentialStrings.getPromptMessage())
+                .isEqualTo(mContext.getString(R.string.screen_lock_prompt_message));
+        assertThat(biometricOrCredentialStrings.getSettingName())
+                .isEqualTo(mContext.getString(R.string.use_screen_lock_label));
+    }
+
     /**
      * A configurable injector to be used for testing.
      */
     private static class TestInjector implements BiometricManager.Injector {
+        @NonNull private final Context mContext;
         @Nullable private final android.hardware.biometrics.BiometricManager mBiometricManager;
         @Nullable private final androidx.core.hardware.fingerprint.FingerprintManagerCompat
                 mFingerprintManager;
         private final boolean mIsDeviceSecurable;
         private final boolean mIsDeviceSecuredWithCredential;
         private final boolean mIsFingerprintHardwarePresent;
+        private final boolean mIsFaceHardwarePresent;
+        private final boolean mIsIrisHardwarePresent;
         private final boolean mIsStrongBiometricGuaranteed;
 
         private TestInjector(
+                @NonNull Context context,
                 @Nullable android.hardware.biometrics.BiometricManager biometricManager,
                 @Nullable androidx.core.hardware.fingerprint.FingerprintManagerCompat
                         fingerprintManager,
                 boolean isDeviceSecurable,
                 boolean isDeviceSecuredWithCredential,
                 boolean isFingerprintHardwarePresent,
+                boolean isFaceHardwarePresent,
+                boolean isIrisHardwarePresent,
                 boolean isStrongBiometricGuaranteed) {
+            mContext = context;
             mBiometricManager = biometricManager;
             mFingerprintManager = fingerprintManager;
             mIsDeviceSecurable = isDeviceSecurable;
             mIsDeviceSecuredWithCredential = isDeviceSecuredWithCredential;
             mIsFingerprintHardwarePresent = isFingerprintHardwarePresent;
+            mIsFaceHardwarePresent = isFaceHardwarePresent;
+            mIsIrisHardwarePresent = isIrisHardwarePresent;
             mIsStrongBiometricGuaranteed = isStrongBiometricGuaranteed;
         }
 
-        @Nullable
         @Override
+        @NonNull
+        public Resources getResources() {
+            return mContext.getResources();
+        }
+
+        @Override
+        @Nullable
         public android.hardware.biometrics.BiometricManager getBiometricManager() {
             return mBiometricManager;
         }
@@ -573,19 +1299,37 @@
         }
 
         @Override
+        public boolean isFaceHardwarePresent() {
+            return mIsFaceHardwarePresent;
+        }
+
+        @Override
+        public boolean isIrisHardwarePresent() {
+            return mIsIrisHardwarePresent;
+        }
+
+        @Override
         public boolean isStrongBiometricGuaranteed() {
             return mIsStrongBiometricGuaranteed;
         }
 
         static final class Builder {
+            @NonNull private final Context mContext;
+
             @Nullable private android.hardware.biometrics.BiometricManager mBiometricManager = null;
             @Nullable private androidx.core.hardware.fingerprint.FingerprintManagerCompat
                     mFingerprintManager = null;
             private boolean mIsDeviceSecurable = false;
             private boolean mIsDeviceSecuredWithCredential = false;
             private boolean mIsFingerprintHardwarePresent = false;
+            private boolean mIsFaceHardwarePresent = false;
+            private boolean mIsIrisHardwarePresent = false;
             private boolean mIsStrongBiometricGuaranteed = false;
 
+            Builder(@NonNull Context context) {
+                mContext = context;
+            }
+
             Builder setBiometricManager(
                     @Nullable android.hardware.biometrics.BiometricManager biometricManager) {
                 mBiometricManager = biometricManager;
@@ -614,6 +1358,16 @@
                 return this;
             }
 
+            Builder setFaceHardwarePresent(boolean faceHardwarePresent) {
+                mIsFaceHardwarePresent = faceHardwarePresent;
+                return this;
+            }
+
+            Builder setIrisHardwarePresent(boolean irisHardwarePresent) {
+                mIsIrisHardwarePresent = irisHardwarePresent;
+                return this;
+            }
+
             Builder setStrongBiometricGuaranteed(boolean strongBiometricGuaranteed) {
                 mIsStrongBiometricGuaranteed = strongBiometricGuaranteed;
                 return this;
@@ -621,11 +1375,14 @@
 
             TestInjector build() {
                 return new TestInjector(
+                        mContext,
                         mBiometricManager,
                         mFingerprintManager,
                         mIsDeviceSecurable,
                         mIsDeviceSecuredWithCredential,
                         mIsFingerprintHardwarePresent,
+                        mIsFaceHardwarePresent,
+                        mIsIrisHardwarePresent,
                         mIsStrongBiometricGuaranteed);
             }
         }
diff --git a/biometric/biometric/src/test/java/androidx/biometric/PackageUtilsTest.java b/biometric/biometric/src/test/java/androidx/biometric/PackageUtilsTest.java
index 18d3157..de5cebc 100644
--- a/biometric/biometric/src/test/java/androidx/biometric/PackageUtilsTest.java
+++ b/biometric/biometric/src/test/java/androidx/biometric/PackageUtilsTest.java
@@ -82,4 +82,78 @@
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true);
         assertThat(PackageUtils.hasSystemFeatureFingerprint(mContext)).isTrue();
     }
+
+    @Test
+    @Config(maxSdk = Build.VERSION_CODES.P)
+    public void testHasSystemFeatureFace_ReturnsFalse_OnApi28AndBelow() {
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.hasSystemFeature(anyString())).thenReturn(true);
+        assertThat(PackageUtils.hasSystemFeatureFace(mContext)).isFalse();
+    }
+
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.Q)
+    public void testHasSystemFeatureFace_ReturnsFalse_WhenContextIsNull() {
+        assertThat(PackageUtils.hasSystemFeatureFace(null)).isFalse();
+    }
+
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.Q)
+    public void testHasSystemFeatureFace_ReturnsFalse_WhenPackageManagerIsNull() {
+        when(mContext.getPackageManager()).thenReturn(null);
+        assertThat(PackageUtils.hasSystemFeatureFace(mContext)).isFalse();
+    }
+
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.Q)
+    public void testHasSystemFeatureFace_ReturnsFalse_WhenPackageManagerReturnsFalse() {
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(false);
+        assertThat(PackageUtils.hasSystemFeatureFace(mContext)).isFalse();
+    }
+
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.Q)
+    public void testHasSystemFeatureFace_ReturnsTrue_WhenPackageManagerReturnsTrue() {
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
+        assertThat(PackageUtils.hasSystemFeatureFace(mContext)).isTrue();
+    }
+
+    @Test
+    @Config(maxSdk = Build.VERSION_CODES.P)
+    public void testHasSystemFeatureIris_ReturnsFalse_OnApi28AndBelow() {
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.hasSystemFeature(anyString())).thenReturn(true);
+        assertThat(PackageUtils.hasSystemFeatureIris(mContext)).isFalse();
+    }
+
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.Q)
+    public void testHasSystemFeatureIris_ReturnsFalse_WhenContextIsNull() {
+        assertThat(PackageUtils.hasSystemFeatureIris(null)).isFalse();
+    }
+
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.Q)
+    public void testHasSystemFeatureIris_ReturnsFalse_WhenPackageManagerIsNull() {
+        when(mContext.getPackageManager()).thenReturn(null);
+        assertThat(PackageUtils.hasSystemFeatureIris(mContext)).isFalse();
+    }
+
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.Q)
+    public void testHasSystemFeatureIris_ReturnsFalse_WhenPackageManagerReturnsFalse() {
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IRIS)).thenReturn(false);
+        assertThat(PackageUtils.hasSystemFeatureIris(mContext)).isFalse();
+    }
+
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.Q)
+    public void testHasSystemFeatureIris_ReturnsTrue_WhenPackageManagerReturnsTrue() {
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IRIS)).thenReturn(true);
+        assertThat(PackageUtils.hasSystemFeatureIris(mContext)).isTrue();
+    }
 }
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/public/src/main/kotlin/androidx/build/LibraryVersions.kt
index 726ade6..06ff33b 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -65,7 +65,7 @@
     val EMOJI2 = Version("1.0.0-rc01")
     val ENTERPRISE = Version("1.1.0-rc01")
     val EXIFINTERFACE = Version("1.4.0-alpha01")
-    val FRAGMENT = Version("1.4.0-alpha11")
+    val FRAGMENT = Version("1.4.0-beta01")
     val FUTURES = Version("1.2.0-alpha01")
     val GLANCE = Version("1.0.0-alpha01")
     val GRIDLAYOUT = Version("1.1.0-alpha01")
@@ -90,7 +90,7 @@
     val MEDIA2 = Version("1.3.0-alpha01")
     val MEDIAROUTER = Version("1.3.0-alpha01")
     val METRICS = Version("1.0.0-alpha01")
-    val NAVIGATION = Version("2.4.0-alpha11")
+    val NAVIGATION = Version("2.4.0-beta01")
     val PAGING = Version("3.1.0-beta01")
     val PAGING_COMPOSE = Version("1.0.0-alpha14")
     val PALETTE = Version("1.1.0-alpha01")
@@ -133,7 +133,7 @@
     val VIEWPAGER = Version("1.1.0-alpha02")
     val VIEWPAGER2 = Version("1.1.0-beta02")
     val WEAR = Version("1.3.0-alpha02")
-    val WEAR_COMPOSE = Version("1.0.0-alpha08")
+    val WEAR_COMPOSE = Version("1.0.0-alpha09")
     val WEAR_INPUT = Version("1.2.0-alpha03")
     val WEAR_INPUT_TESTING = WEAR_INPUT
     val WEAR_ONGOING = Version("1.1.0-alpha01")
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/RobolectricCameraPipeTestRunner.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/RobolectricCameraPipeTestRunner.kt
index 0debeec..978178d 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/RobolectricCameraPipeTestRunner.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/RobolectricCameraPipeTestRunner.kt
@@ -47,8 +47,8 @@
     }
 }
 
-@Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
-public inline class TestValue(public val value: String)
+@JvmInline
+public value class TestValue(public val value: String)
 
 public data class TestData(
     val value1: TestValue,
diff --git a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunner.kt b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunner.kt
index 03cbedb..d1439db 100644
--- a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunner.kt
+++ b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunner.kt
@@ -47,8 +47,8 @@
     }
 }
 
-@Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
-public inline class TestValue(public val value: String)
+@JvmInline
+public value class TestValue(public val value: String)
 
 public data class TestData(
     val value1: TestValue,
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraDevices.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraDevices.kt
index c45f0d4..b686291 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraDevices.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraDevices.kt
@@ -60,8 +60,8 @@
     public fun awaitMetadata(camera: CameraId): CameraMetadata
 }
 
-@Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
-public inline class CameraId(public val value: String) {
+@JvmInline
+public value class CameraId(public val value: String) {
     public companion object {
         public inline fun fromCamera2Id(value: String): CameraId = CameraId(value)
         public inline fun fromCamera1Id(value: Int): CameraId = CameraId("$value")
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Metadata.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Metadata.kt
index 7099325..5692a9c 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Metadata.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Metadata.kt
@@ -177,23 +177,23 @@
  * A [RequestTemplate] indicates which preset set list of parameters will be applied to a request by
  * default. These values are defined by camera2.
  */
-@Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
-public inline class RequestTemplate(public val value: Int)
+@JvmInline
+public value class RequestTemplate(public val value: Int)
 
 /**
  * A [RequestNumber] is an artificial identifier that is created for each request that is submitted
  * to the Camera.
  */
-@Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
-public inline class RequestNumber(public val value: Long)
+@JvmInline
+public value class RequestNumber(public val value: Long)
 
 /**
  * A [FrameNumber] is the identifier that represents a specific exposure by the Camera. FrameNumbers
  * increase within a specific CameraCaptureSession, and are not created until the HAL begins
  * processing a request.
  */
-@Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
-public inline class FrameNumber(public val value: Long)
+@JvmInline
+public value class FrameNumber(public val value: Long)
 
 /**
  * This is a timestamp from the Camera, and corresponds to the nanosecond exposure time of a Frame.
@@ -206,8 +206,8 @@
  * operate based on a real-time clock, while audio/visual systems commonly operate based on a
  * monotonic clock.
  */
-@Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
-public inline class CameraTimestamp(public val value: Long)
+@JvmInline
+public value class CameraTimestamp(public val value: Long)
 
 /**
  * Utility function to help deal with the unsafe nature of the typed Key/Value pairs.
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/StreamFormat.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/StreamFormat.kt
index 9d8ae50..ea9dc79 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/StreamFormat.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/StreamFormat.kt
@@ -25,9 +25,9 @@
  * or not listed.
  * // TODO: Consider adding data-space as a separate property, or finding a way to work it in.
  */
-@Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-public inline class StreamFormat(public val value: Int) {
+@JvmInline
+public value class StreamFormat(public val value: Int) {
     public companion object {
         public val UNKNOWN: StreamFormat = StreamFormat(0)
         public val PRIVATE: StreamFormat = StreamFormat(0x22)
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Streams.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Streams.kt
index 19ef4a3..bed13a6 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Streams.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Streams.kt
@@ -99,8 +99,8 @@
 /**
  * This identifies a single surface that is used to tell the camera to produce one or more outputs.
  */
-@Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
-public inline class StreamId(public val value: Int) {
+@JvmInline
+public value class StreamId(public val value: Int) {
     override fun toString(): String = "Stream-$value"
 }
 
@@ -207,8 +207,8 @@
 /**
  * This identifies a single output.
  */
-@Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
-public inline class OutputId(public val value: Int) {
+@JvmInline
+public value class OutputId(public val value: Int) {
     override fun toString(): String = "Output-$value"
 }
 
@@ -227,7 +227,7 @@
 /**
  * This identifies a single input.
  */
-@Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
-public inline class InputId(public val value: Int) {
+@JvmInline
+public value class InputId(public val value: Int) {
     override fun toString(): String = "Input-$value"
 }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2StreamGraph.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2StreamGraph.kt
index 98ccd0c..ad4c0d2 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2StreamGraph.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2StreamGraph.kt
@@ -56,8 +56,8 @@
 private val groupIds = atomic(0)
 internal fun nextGroupId(): Int = groupIds.incrementAndGet()
 
-@Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
-internal inline class CameraConfigId(val value: Int) {
+@JvmInline
+public value class CameraConfigId(val value: Int) {
     override fun toString(): String = "OutputConfig-$value"
 }
 
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Timestamps.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Timestamps.kt
index 916c74d..2b46b26 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Timestamps.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Timestamps.kt
@@ -25,8 +25,8 @@
 /**
  * A nanosecond timestamp
  */
-@Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
-public inline class TimestampNs constructor(public val value: Long) {
+@JvmInline
+public value class TimestampNs constructor(public val value: Long) {
     public inline operator fun minus(other: TimestampNs): DurationNs =
         DurationNs(value - other.value)
 
@@ -34,8 +34,8 @@
         TimestampNs(value + other.value)
 }
 
-@Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
-public inline class DurationNs(public val value: Long) {
+@JvmInline
+public value class DurationNs(public val value: Long) {
     public inline operator fun minus(other: DurationNs): DurationNs =
         DurationNs(value - other.value)
 
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraDeviceWrapper.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraDeviceWrapper.kt
index 8707eb49..660b69e 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraDeviceWrapper.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraDeviceWrapper.kt
@@ -20,10 +20,12 @@
 import android.hardware.camera2.CaptureRequest
 import android.hardware.camera2.TotalCaptureResult
 import android.hardware.camera2.params.InputConfiguration
+import android.os.Build
 import android.os.Handler
 import android.view.Surface
 import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.RequestTemplate
+import androidx.camera.camera2.pipe.compat.Api23Compat
 import androidx.camera.camera2.pipe.compat.CameraCaptureSessionWrapper
 import androidx.camera.camera2.pipe.compat.CameraDeviceWrapper
 import androidx.camera.camera2.pipe.compat.InputConfigData
@@ -48,7 +50,12 @@
     override fun createReprocessCaptureRequest(
         inputResult: TotalCaptureResult
     ): CaptureRequest.Builder {
-        return fakeCamera.cameraDevice.createReprocessCaptureRequest(inputResult)
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            return Api23Compat.createReprocessCaptureRequest(fakeCamera.cameraDevice, inputResult)
+        }
+        throw UnsupportedOperationException(
+            "createReprocessCaptureRequest is not supported below API 23"
+        )
     }
 
     override fun createCaptureSession(
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunner.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunner.kt
index 03cbedb..d1439db 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunner.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunner.kt
@@ -47,8 +47,8 @@
     }
 }
 
-@Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
-public inline class TestValue(public val value: String)
+@JvmInline
+public value class TestValue(public val value: String)
 
 public data class TestData(
     val value1: TestValue,
diff --git a/camera/camera-core/api/current.txt b/camera/camera-core/api/current.txt
index 9dbca99..bd13c62 100644
--- a/camera/camera-core/api/current.txt
+++ b/camera/camera-core/api/current.txt
@@ -208,6 +208,7 @@
   @RequiresApi(21) public final class ImageCapture extends androidx.camera.core.UseCase {
     method public int getCaptureMode();
     method public int getFlashMode();
+    method @IntRange(from=1, to=100) public int getJpegQuality();
     method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
     method public int getTargetRotation();
     method public void setCropAspectRatio(android.util.Rational);
@@ -233,6 +234,7 @@
     method public androidx.camera.core.ImageCapture.Builder setCaptureMode(int);
     method public androidx.camera.core.ImageCapture.Builder setFlashMode(int);
     method public androidx.camera.core.ImageCapture.Builder setIoExecutor(java.util.concurrent.Executor);
+    method public androidx.camera.core.ImageCapture.Builder setJpegQuality(@IntRange(from=1, to=100) int);
     method public androidx.camera.core.ImageCapture.Builder setTargetAspectRatio(int);
     method public androidx.camera.core.ImageCapture.Builder setTargetName(String);
     method public androidx.camera.core.ImageCapture.Builder setTargetResolution(android.util.Size);
diff --git a/camera/camera-core/api/public_plus_experimental_current.txt b/camera/camera-core/api/public_plus_experimental_current.txt
index 4a7ffd0..ffaebcf 100644
--- a/camera/camera-core/api/public_plus_experimental_current.txt
+++ b/camera/camera-core/api/public_plus_experimental_current.txt
@@ -211,6 +211,7 @@
   @RequiresApi(21) public final class ImageCapture extends androidx.camera.core.UseCase {
     method public int getCaptureMode();
     method public int getFlashMode();
+    method @IntRange(from=1, to=100) public int getJpegQuality();
     method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
     method public int getTargetRotation();
     method public void setCropAspectRatio(android.util.Rational);
@@ -236,6 +237,7 @@
     method public androidx.camera.core.ImageCapture.Builder setCaptureMode(int);
     method public androidx.camera.core.ImageCapture.Builder setFlashMode(int);
     method public androidx.camera.core.ImageCapture.Builder setIoExecutor(java.util.concurrent.Executor);
+    method public androidx.camera.core.ImageCapture.Builder setJpegQuality(@IntRange(from=1, to=100) int);
     method public androidx.camera.core.ImageCapture.Builder setTargetAspectRatio(int);
     method public androidx.camera.core.ImageCapture.Builder setTargetName(String);
     method public androidx.camera.core.ImageCapture.Builder setTargetResolution(android.util.Size);
diff --git a/camera/camera-core/api/restricted_current.txt b/camera/camera-core/api/restricted_current.txt
index 9dbca99..bd13c62 100644
--- a/camera/camera-core/api/restricted_current.txt
+++ b/camera/camera-core/api/restricted_current.txt
@@ -208,6 +208,7 @@
   @RequiresApi(21) public final class ImageCapture extends androidx.camera.core.UseCase {
     method public int getCaptureMode();
     method public int getFlashMode();
+    method @IntRange(from=1, to=100) public int getJpegQuality();
     method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
     method public int getTargetRotation();
     method public void setCropAspectRatio(android.util.Rational);
@@ -233,6 +234,7 @@
     method public androidx.camera.core.ImageCapture.Builder setCaptureMode(int);
     method public androidx.camera.core.ImageCapture.Builder setFlashMode(int);
     method public androidx.camera.core.ImageCapture.Builder setIoExecutor(java.util.concurrent.Executor);
+    method public androidx.camera.core.ImageCapture.Builder setJpegQuality(@IntRange(from=1, to=100) int);
     method public androidx.camera.core.ImageCapture.Builder setTargetAspectRatio(int);
     method public androidx.camera.core.ImageCapture.Builder setTargetName(String);
     method public androidx.camera.core.ImageCapture.Builder setTargetResolution(android.util.Size);
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageCaptureTest.java b/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageCaptureTest.java
index 4ad0e86..0a56bfc 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageCaptureTest.java
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageCaptureTest.java
@@ -24,11 +24,15 @@
 import static org.mockito.Mockito.verify;
 
 import android.app.Instrumentation;
+import android.content.ContentResolver;
+import android.content.ContentValues;
 import android.graphics.Bitmap;
 import android.graphics.ImageFormat;
 import android.graphics.Rect;
+import android.provider.MediaStore;
 import android.util.Rational;
 import android.util.Size;
+import android.view.Surface;
 
 import androidx.annotation.NonNull;
 import androidx.camera.core.impl.CameraCaptureCallback;
@@ -46,6 +50,7 @@
 import androidx.camera.testing.fakes.FakeImageProxy;
 import androidx.camera.testing.fakes.FakeUseCaseConfigFactory;
 import androidx.exifinterface.media.ExifInterface;
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
 import androidx.test.filters.SdkSuppress;
@@ -101,44 +106,8 @@
     }
 
     @Test
-    public void getDispatchCropRect_dispatchBufferRotated90() {
-        assertGetDispatchCropRect(90, new Size(4, 6), new Rect(3, 0, 4, 1));
-    }
-
-    @Test
-    public void getDispatchCropRect_dispatchBufferRotated180() {
-        assertGetDispatchCropRect(180, new Size(6, 4), new Rect(5, 3, 6, 4));
-    }
-
-    @Test
-    public void getDispatchCropRect_dispatchBufferRotated270() {
-        assertGetDispatchCropRect(270, new Size(4, 6), new Rect(0, 5, 1, 6));
-    }
-
-    @Test
-    public void getDispatchCropRect_dispatchBufferRotated0() {
-        assertGetDispatchCropRect(0, new Size(6, 4), new Rect(0, 0, 1, 1));
-    }
-
-    private void assertGetDispatchCropRect(int outputDegrees, Size dispatchResolution,
-            Rect dispatchRect) {
-        // Arrange:
-        // Surface crop rect stays the same regardless of HAL rotations.
-        Rect surfaceCropRect = new Rect(0, 0, 1, 1);
-        // Exif degrees being 0 means HAL consumed the target rotation.
-        int exifRotationDegrees = 0;
-
-        // Act.
-        Rect dispatchCropRect = ImageCapture.ImageCaptureRequest.getDispatchCropRect(
-                surfaceCropRect, outputDegrees, dispatchResolution, exifRotationDegrees);
-
-        // Assert.
-        assertThat(dispatchCropRect).isEqualTo(dispatchRect);
-    }
-
-    @Test
     public void onCaptureCancelled_onErrorCAMERA_CLOSED() {
-        ImageCapture imageCapture = createImageCapture();
+        ImageCapture imageCapture = new ImageCapture.Builder().build();
 
         mInstrumentation.runOnMainSync(() -> {
             try {
@@ -169,7 +138,7 @@
 
     @Test
     public void onRequestFailed_OnErrorCAPTURE_FAILED() {
-        ImageCapture imageCapture = createImageCapture();
+        ImageCapture imageCapture = new ImageCapture.Builder().build();
 
         mInstrumentation.runOnMainSync(() -> {
             try {
@@ -200,27 +169,98 @@
     }
 
     @Test
-    public void captureWithMinLatency_jpegQualityIs95() {
-        List<CaptureConfig> captureConfigs =
-                captureWithCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY);
+    public void captureWithMinLatencyByImageCapturedCallback_jpegQualityIs95() {
+        ImageCapture imageCapture = new ImageCapture.Builder().setCaptureMode(
+                ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY).build();
+        List<CaptureConfig> captureConfigs = captureImage(imageCapture,
+                ImageCapture.OnImageCapturedCallback.class);
         assertThat(hasJpegQuality(captureConfigs, 95)).isTrue();
     }
 
     @Test
-    public void captureWithMaxQuality_jpegQualityIs100() {
-        List<CaptureConfig> captureConfigs =
-                captureWithCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY);
+    public void captureWithMaxQualityByImageCapturedCallback_jpegQualityIs100() {
+        ImageCapture imageCapture = new ImageCapture.Builder().setCaptureMode(
+                ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY).build();
+        List<CaptureConfig> captureConfigs = captureImage(imageCapture,
+                ImageCapture.OnImageCapturedCallback.class);
         assertThat(hasJpegQuality(captureConfigs, 100)).isTrue();
     }
 
-    @NonNull
-    private List<CaptureConfig> captureWithCaptureMode(
-            @ImageCapture.CaptureMode int captureMode) {
-        // Arrange.
-        ImageCapture imageCapture = new ImageCapture.Builder()
-                .setCaptureMode(captureMode)
-                .build();
+    @Test
+    public void captureWithMinLatencyByImageCapturedCallback_jpegQualityOverwrittenBy100() {
+        int jpegQuality = 100;
+        ImageCapture imageCapture = new ImageCapture.Builder().setCaptureMode(
+                ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY).setJpegQuality(jpegQuality).build();
+        List<CaptureConfig> captureConfigs = captureImage(imageCapture,
+                ImageCapture.OnImageCapturedCallback.class);
+        assertThat(hasJpegQuality(captureConfigs, jpegQuality)).isTrue();
+    }
 
+    @Test
+    public void captureWithMaxQualityByImageCapturedCallback_jpegQualityOverwrittenBy1() {
+        int jpegQuality = 1;
+        ImageCapture imageCapture = new ImageCapture.Builder().setCaptureMode(
+                ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY).setJpegQuality(jpegQuality).build();
+        List<CaptureConfig> captureConfigs = captureImage(imageCapture,
+                ImageCapture.OnImageCapturedCallback.class);
+        assertThat(hasJpegQuality(captureConfigs, jpegQuality)).isTrue();
+    }
+
+    @Test
+    public void captureWithoutCropSettingByImageSavedCallback_jpegQualitySameAsSettingValue() {
+        int jpegQuality = 50;
+        ImageCapture imageCapture = new ImageCapture.Builder().setJpegQuality(jpegQuality).build();
+        List<CaptureConfig> captureConfigs = captureImage(imageCapture,
+                ImageCapture.OnImageSavedCallback.class);
+        assertThat(hasJpegQuality(captureConfigs, jpegQuality)).isTrue();
+    }
+
+    @Test
+    public void captureWithCropAspectRatioByImageSavedCallback_jpegQualityIs100() {
+        int jpegQuality = 50;
+        ImageCapture imageCapture = new ImageCapture.Builder().setJpegQuality(jpegQuality).build();
+        imageCapture.setCropAspectRatio(new Rational(1, 1));
+        List<CaptureConfig> captureConfigs = captureImage(imageCapture,
+                ImageCapture.OnImageSavedCallback.class);
+        assertThat(hasJpegQuality(captureConfigs, 100)).isTrue();
+    }
+
+    @Test
+    public void captureWithCropAspectRatioByImageCapturedCallback_jpegQualitySameAsSettingValue() {
+        int jpegQuality = 50;
+        ImageCapture imageCapture = new ImageCapture.Builder().setJpegQuality(jpegQuality).build();
+        imageCapture.setCropAspectRatio(new Rational(1, 1));
+        List<CaptureConfig> captureConfigs = captureImage(imageCapture,
+                ImageCapture.OnImageCapturedCallback.class);
+        assertThat(hasJpegQuality(captureConfigs, jpegQuality)).isTrue();
+    }
+
+    @Test
+    public void captureWithViewPortByImageSavedCallback_jpegQualityIs100() {
+        mCameraUseCaseAdapter.setViewPort(new ViewPort.Builder(new Rational(1, 1),
+                Surface.ROTATION_0).build());
+        int jpegQuality = 50;
+        ImageCapture imageCapture = new ImageCapture.Builder().setJpegQuality(jpegQuality).build();
+        List<CaptureConfig> captureConfigs = captureImage(imageCapture,
+                ImageCapture.OnImageSavedCallback.class);
+        assertThat(hasJpegQuality(captureConfigs, 100)).isTrue();
+    }
+
+    @Test
+    public void captureWithViewPortByImageCapturedCallback_jpegQualitySameAsSettingValue() {
+        mCameraUseCaseAdapter.setViewPort(new ViewPort.Builder(new Rational(1, 1),
+                Surface.ROTATION_0).build());
+        int jpegQuality = 50;
+        ImageCapture imageCapture = new ImageCapture.Builder().setJpegQuality(jpegQuality).build();
+        List<CaptureConfig> captureConfigs = captureImage(imageCapture,
+                ImageCapture.OnImageCapturedCallback.class);
+        assertThat(hasJpegQuality(captureConfigs, jpegQuality)).isTrue();
+    }
+
+    @NonNull
+    private List<CaptureConfig> captureImage(@NonNull ImageCapture imageCapture,
+            @NonNull Class<?> callbackClass) {
+        // Arrange.
         mInstrumentation.runOnMainSync(() -> {
             try {
                 mCameraUseCaseAdapter.addUseCases(Collections.singleton(imageCapture));
@@ -233,7 +273,7 @@
         // Sets repeating capture result to the imageCapture's session config repeating capture
         // callbacks to make ImageCapture#preTakePicture can be completed when capture mode is
         // set as CAPTURE_MODE_MAXIMIZE_QUALITY.
-        if (captureMode == ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY) {
+        if (imageCapture.getCaptureMode() == ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY) {
             FakeCameraCaptureResult fakeCameraCaptureResult = new FakeCameraCaptureResult();
             fakeCameraCaptureResult.setAfState(CameraCaptureMetaData.AfState.LOCKED_FOCUSED);
             fakeCameraCaptureResult.setAeState(CameraCaptureMetaData.AeState.CONVERGED);
@@ -257,8 +297,26 @@
 
         // Act.
         mInstrumentation.runOnMainSync(
-                () -> imageCapture.takePicture(CameraXExecutors.mainThreadExecutor(),
-                        mock(ImageCapture.OnImageCapturedCallback.class)));
+                () -> {
+                    if (callbackClass == ImageCapture.OnImageCapturedCallback.class) {
+                        imageCapture.takePicture(CameraXExecutors.mainThreadExecutor(),
+                                mock(ImageCapture.OnImageCapturedCallback.class));
+                    } else if (callbackClass == ImageCapture.OnImageSavedCallback.class) {
+                        ContentResolver contentResolver =
+                                ApplicationProvider.getApplicationContext().getContentResolver();
+                        ContentValues contentValues = new ContentValues();
+                        contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg");
+                        imageCapture.takePicture(new ImageCapture.OutputFileOptions.Builder(
+                                        contentResolver,
+                                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+                                        contentValues).build(),
+                                CameraXExecutors.mainThreadExecutor(),
+                                mock(ImageCapture.OnImageSavedCallback.class));
+                    } else {
+                        throw new IllegalArgumentException("Unexpected callback type for taking "
+                                + "picture!");
+                    }
+                });
 
         // Assert.
         @SuppressWarnings("unchecked")
@@ -275,6 +333,16 @@
         return captureConfigs;
     }
 
+    @Test(expected = IllegalArgumentException.class)
+    public void throwIllegalArgumentException_setInvalidJpegQuality0() {
+        new ImageCapture.Builder().setJpegQuality(0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void throwIllegalArgumentException_setInvalidJpegQuality101() {
+        new ImageCapture.Builder().setJpegQuality(101);
+    }
+
     @Test
     public void dispatchImage_cropRectIsUpdatedBasedOnExifOrientation()
             throws InterruptedException, IOException {
@@ -373,7 +441,8 @@
     @Test
     public void setFlashModeDuringPictureTaken() throws InterruptedException {
         // Arrange.
-        ImageCapture imageCapture = createImageCapture();
+        ImageCapture imageCapture =
+                new ImageCapture.Builder().setFlashMode(ImageCapture.FLASH_MODE_OFF).build();
 
         mInstrumentation.runOnMainSync(() -> {
             try {
@@ -440,15 +509,4 @@
         }
         return false;
     }
-
-    private ImageCapture createImageCapture() {
-        return new ImageCapture.Builder()
-                .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
-                .setFlashMode(ImageCapture.FLASH_MODE_OFF)
-                .setCaptureOptionUnpacker((config, builder) -> {
-                })
-                .setSessionOptionUnpacker((config, builder) -> {
-                })
-                .build();
-    }
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
index 55d1539..2f09aff 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
@@ -27,6 +27,7 @@
 import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_IMAGE_CAPTURE_MODE;
 import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_IMAGE_READER_PROXY_PROVIDER;
 import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_IO_EXECUTOR;
+import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_JPEG_COMPRESSION_QUALITY;
 import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_MAX_CAPTURE_STAGES;
 import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_MAX_RESOLUTION;
 import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_SESSION_CONFIG_UNPACKER;
@@ -41,15 +42,11 @@
 import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_USE_SOFTWARE_JPEG_ENCODER;
 import static androidx.camera.core.impl.ImageInputConfig.OPTION_INPUT_FORMAT;
 import static androidx.camera.core.impl.UseCaseConfig.OPTION_CAMERA_SELECTOR;
-import static androidx.camera.core.internal.utils.ImageUtil.min;
-import static androidx.camera.core.internal.utils.ImageUtil.sizeToVertexes;
 
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.graphics.ImageFormat;
-import android.graphics.Matrix;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.location.Location;
 import android.media.Image;
 import android.media.ImageReader;
@@ -382,6 +379,7 @@
         Threads.checkMainThread();
         SessionConfig.Builder sessionConfigBuilder = SessionConfig.Builder.createFrom(config);
         sessionConfigBuilder.addRepeatingCameraCaptureCallback(mSessionCallbackChecker);
+        YuvToJpegProcessor softwareJpegProcessor = null;
 
         // Setup the ImageReader to do processing
         if (config.getImageReaderProxyProvider() != null) {
@@ -393,7 +391,6 @@
             };
         } else if (mCaptureProcessor != null || mUseSoftwareJpeg) {
             // Capture processor set from configuration takes precedence over software JPEG.
-            YuvToJpegProcessor softwareJpegProcessor = null;
             CaptureProcessorPipeline captureProcessorPipeline = null;
             CaptureProcessor captureProcessor = mCaptureProcessor;
             int inputFormat = getImageFormat();
@@ -404,14 +401,14 @@
                     Logger.i(TAG, "Using software JPEG encoder.");
 
                     if (mCaptureProcessor != null) {
-                        softwareJpegProcessor = new YuvToJpegProcessor(getJpegQuality(),
+                        softwareJpegProcessor = new YuvToJpegProcessor(getJpegQualityInternal(),
                                 mMaxCaptureStages);
                         captureProcessor = captureProcessorPipeline = new CaptureProcessorPipeline(
                                 mCaptureProcessor, mMaxCaptureStages, softwareJpegProcessor,
                                 mExecutor);
                     } else {
                         captureProcessor = softwareJpegProcessor =
-                                new YuvToJpegProcessor(getJpegQuality(), mMaxCaptureStages);
+                                new YuvToJpegProcessor(getJpegQualityInternal(), mMaxCaptureStages);
                     }
 
                     outputFormat = ImageFormat.JPEG;
@@ -454,8 +451,24 @@
             mImageCaptureRequestProcessor.cancelRequests(
                     new CancellationException("Request is canceled."));
         }
+
+        final YuvToJpegProcessor finalSoftwareJpegProcessor = softwareJpegProcessor;
         mImageCaptureRequestProcessor = new ImageCaptureRequestProcessor(MAX_IMAGES,
-                request -> takePictureInternal(request));
+                request -> takePictureInternal(request), softwareJpegProcessor == null ? null :
+                new ImageCaptureRequestProcessor.RequestProcessCallback() {
+                    @Override
+                    public void onPreProcessRequest(
+                            @NonNull ImageCaptureRequest imageCaptureRequest) {
+                        if (Build.VERSION.SDK_INT >= 26) {
+                            // Updates output JPEG compression quality of YuvToJpegProcessor
+                            // according to current request. This was determined by whether the
+                            // final output image needs to be cropped (uncompress and recompress)
+                            // again when the capture request was created.
+                            finalSoftwareJpegProcessor.setJpegQuality(
+                                    imageCaptureRequest.mJpegQuality);
+                        }
+                    }
+                });
 
         // By default close images that come from the listener.
         mImageReader.setOnImageAvailableListener(mClosingListener,
@@ -790,6 +803,20 @@
     }
 
     /**
+     * Returns the JPEG quality setting.
+     *
+     * <p>This is set when constructing an ImageCapture using
+     * {@link ImageCapture.Builder#setJpegQuality(int)}. If not set, a default value will be set
+     * according to the capture mode setting. JPEG compression quality 95 is set for
+     * {@link #CAPTURE_MODE_MINIMIZE_LATENCY} and 100 is set for
+     * {@link #CAPTURE_MODE_MAXIMIZE_QUALITY}. This is static for an instance of ImageCapture.
+     */
+    @IntRange(from = 1, to = 100)
+    public int getJpegQuality() {
+        return getJpegQualityInternal();
+    }
+
+    /**
      * Gets selected resolution information of the {@link ImageCapture}.
      *
      * <p>The returned {@link ResolutionInfo} will be expressed in the coordinates of the camera
@@ -862,7 +889,11 @@
             return;
         }
 
-        sendImageCaptureRequest(executor, callback);
+        // The captured image will be directly provided to the app via the OnImageCapturedCallback
+        // callback. It won't be uncompressed and compressed again after the image is captured.
+        // The JPEG quality setting will be directly provided to the HAL to compress the output
+        // JPEG image.
+        sendImageCaptureRequest(executor, callback, getJpegQualityInternal());
     }
 
     /**
@@ -935,7 +966,7 @@
                     }
                 };
 
-        int jpegQuality = getJpegQuality();
+        int outputJpegQuality = getJpegQualityInternal();
 
         // Wrap the ImageCapture.OnImageSavedCallback with an OnImageCapturedCallback so it can
         // be put into the capture request queue
@@ -948,7 +979,7 @@
                                         image,
                                         outputFileOptions,
                                         image.getImageInfo().getRotationDegrees(),
-                                        jpegQuality,
+                                        outputJpegQuality,
                                         executor,
                                         mSequentialIoExecutor,
                                         imageSavedCallbackWrapper));
@@ -960,9 +991,51 @@
                     }
                 };
 
+        // If the final output image needs to be cropped, setting the JPEG quality as 100 when
+        // capturing the image. So that the image quality won't be lost when uncompressing and
+        // compressing the image again in the cropping process.
+        int rotationDegrees = getRelativeRotation(getCamera());
+        Size dispatchResolution = getAttachedSurfaceResolution();
+        // At this point, we can't know whether HAL will rotate the captured image or not. No
+        // matter HAL will rotate the image byte array or not, it won't affect whether the final
+        // image needs cropping or not. Therefore, we can still use the attached surface
+        // resolution and its relative rotation degrees against to the target rotation setting to
+        // calculate the possible crop rectangle and then use it to determine whether the final
+        // image will need cropping or not.
+        Rect cropRect = computeDispatchCropRect(getViewPortCropRect(), mCropAspectRatio,
+                rotationDegrees, dispatchResolution, rotationDegrees);
+        boolean shouldCropImage = ImageUtil.shouldCropImage(dispatchResolution.getWidth(),
+                dispatchResolution.getHeight(), cropRect.width(), cropRect.height());
+        int capturingJpegQuality = shouldCropImage ? 100 : outputJpegQuality;
+
         // Always use the mainThreadExecutor for the initial callback so we don't need to double
         // post to another thread
-        sendImageCaptureRequest(CameraXExecutors.mainThreadExecutor(), imageCaptureCallbackWrapper);
+        sendImageCaptureRequest(CameraXExecutors.mainThreadExecutor(),
+                imageCaptureCallbackWrapper, capturingJpegQuality);
+    }
+
+    @NonNull
+    static Rect computeDispatchCropRect(@Nullable Rect viewPortCropRect,
+            @Nullable Rational cropAspectRatio, int rotationDegrees,
+            @NonNull Size dispatchResolution, int dispatchRotationDegrees) {
+        if (viewPortCropRect != null) {
+            return ImageUtil.computeCropRectFromDispatchInfo(viewPortCropRect, rotationDegrees,
+                    dispatchResolution, dispatchRotationDegrees);
+        } else if (cropAspectRatio != null) {
+            // Fall back to crop aspect ratio if view port is not available.
+            Rational aspectRatio = cropAspectRatio;
+            if ((dispatchRotationDegrees % 180) != 0) {
+                aspectRatio = new Rational(
+                        /* invert the ratio numerator=*/ cropAspectRatio.getDenominator(),
+                        /* invert the ratio denominator=*/ cropAspectRatio.getNumerator());
+            }
+            if (ImageUtil.isAspectRatioValid(dispatchResolution, aspectRatio)) {
+                return ImageUtil.computeCropRectFromAspectRatio(dispatchResolution,
+                        aspectRatio);
+            }
+        }
+
+        return new Rect(0, 0, dispatchResolution.getWidth(), dispatchResolution.getHeight());
     }
 
     /**
@@ -986,8 +1059,9 @@
     }
 
     @UiThread
-    private void sendImageCaptureRequest(
-            @NonNull Executor callbackExecutor, @NonNull OnImageCapturedCallback callback) {
+    private void sendImageCaptureRequest(@NonNull Executor callbackExecutor,
+            @NonNull OnImageCapturedCallback callback,
+            @IntRange(from = 1, to = 100) int jpegQuality) {
 
         // TODO(b/143734846): From here on, the image capture request should be
         //  self-contained and use this camera for everything. Currently the pre-capture
@@ -1010,7 +1084,7 @@
         }
 
         mImageCaptureRequestProcessor.sendRequest(new ImageCaptureRequest(
-                getRelativeRotation(attachedCamera), getJpegQuality(), mCropAspectRatio,
+                getRelativeRotation(attachedCamera), jpegQuality, mCropAspectRatio,
                 getViewPortCropRect(), callbackExecutor, callback));
     }
 
@@ -1056,7 +1130,13 @@
      * @return Compression quality of the captured JPEG image.
      */
     @IntRange(from = 1, to = 100)
-    private int getJpegQuality() {
+    private int getJpegQualityInternal() {
+        ImageCaptureConfig imageCaptureConfig = (ImageCaptureConfig) getCurrentConfig();
+
+        if (imageCaptureConfig.containsOption(OPTION_JPEG_COMPRESSION_QUALITY)) {
+            return imageCaptureConfig.getJpegQuality();
+        }
+
         switch (mCaptureMode) {
             case CAPTURE_MODE_MAXIMIZE_QUALITY:
                 return JPEG_QUALITY_MAXIMIZE_QUALITY_MODE;
@@ -1159,12 +1239,21 @@
 
         private final int mMaxImages;
 
+        @Nullable
+        private final RequestProcessCallback mRequestProcessCallback;
+
         @SuppressWarnings("WeakerAccess") /* synthetic accessor */
         final Object mLock = new Object();
 
         ImageCaptureRequestProcessor(int maxImages, @NonNull ImageCaptor imageCaptor) {
+            this(maxImages, imageCaptor, null);
+        }
+
+        ImageCaptureRequestProcessor(int maxImages, @NonNull ImageCaptor imageCaptor,
+                @Nullable RequestProcessCallback requestProcessCallback) {
             mMaxImages = maxImages;
             mImageCaptor = imageCaptor;
+            mRequestProcessCallback = requestProcessCallback;
         }
 
         /**
@@ -1234,6 +1323,9 @@
                 }
 
                 mCurrentRequest = imageCaptureRequest;
+                if (mRequestProcessCallback != null) {
+                    mRequestProcessCallback.onPreProcessRequest(mCurrentRequest);
+                }
                 mCurrentRequestFuture = mImageCaptor.capture(imageCaptureRequest);
                 Futures.addCallback(mCurrentRequestFuture, new FutureCallback<ImageProxy>() {
                     @Override
@@ -1284,6 +1376,17 @@
             @NonNull
             ListenableFuture<ImageProxy> capture(@NonNull ImageCaptureRequest imageCaptureRequest);
         }
+
+        /**
+         * An interface to provide callbacks when processing each capture request.
+         */
+        interface RequestProcessCallback {
+            /**
+             * This will be called before starting to process the
+             * ImageCaptureRequest.
+             */
+            void onPreProcessRequest(@NonNull ImageCaptureRequest imageCaptureRequest);
+        }
     }
 
     @NonNull
@@ -2405,31 +2508,13 @@
                     image.getImageInfo().getTimestamp(), dispatchRotationDegrees);
 
             final ImageProxy dispatchedImageProxy = new SettableImageProxy(image,
-                    dispatchResolution,
-                    imageInfo);
+                    dispatchResolution, imageInfo);
 
             // Update the crop rect aspect ratio after it has been rotated into the buffer
             // orientation
-            if (mViewPortCropRect != null) {
-                // If Viewport is present, use the Viewport-based crop rect.
-                dispatchedImageProxy.setCropRect(getDispatchCropRect(mViewPortCropRect,
-                        mRotationDegrees, dispatchResolution, dispatchRotationDegrees));
-            } else if (mTargetRatio != null) {
-                // Fall back to crop aspect ratio if view port is not available.
-                Rational dispatchRatio = mTargetRatio;
-                if ((dispatchRotationDegrees % 180) != 0) {
-                    dispatchRatio = new Rational(
-                            /* invert the ratio numerator=*/ mTargetRatio.getDenominator(),
-                            /* invert the ratio denominator=*/ mTargetRatio.getNumerator());
-                }
-                Size sourceSize = new Size(dispatchedImageProxy.getWidth(),
-                        dispatchedImageProxy.getHeight());
-                if (ImageUtil.isAspectRatioValid(sourceSize, dispatchRatio)) {
-                    dispatchedImageProxy.setCropRect(
-                            ImageUtil.computeCropRectFromAspectRatio(sourceSize,
-                                    dispatchRatio));
-                }
-            }
+            Rect cropRect = computeDispatchCropRect(mViewPortCropRect, mTargetRatio,
+                    mRotationDegrees, dispatchResolution, dispatchRotationDegrees);
+            dispatchedImageProxy.setCropRect(cropRect);
 
             try {
                 mListenerExecutor.execute(() -> {
@@ -2443,50 +2528,6 @@
             }
         }
 
-        /**
-         * Corrects crop rect based on JPEG exif rotation.
-         *
-         * <p> The original crop rect is calculated based on camera sensor buffer. On some devices,
-         * the buffer is rotated before being passed to users, in which case the crop rect also
-         * needs additional transformations.
-         *
-         * <p> There are two most common scenarios: 1) exif rotation is 0, or 2) exif rotation
-         * equals output rotation. 1) means the HAL rotated the buffer based on target
-         * rotation. 2) means HAL no-oped on the rotation. Theoretically only 1) needs
-         * additional transformations, but this method is also generic enough to handle all possible
-         * HAL rotations.
-         */
-        @NonNull
-        static Rect getDispatchCropRect(@NonNull Rect surfaceCropRect, int surfaceToOutputDegrees,
-                @NonNull Size dispatchResolution, int dispatchToOutputDegrees) {
-            // There are 3 coordinate systems: surface, dispatch and output. Surface is where
-            // the original crop rect is defined. We need to figure out what HAL
-            // has done to the buffer (the surface->dispatch mapping) and apply the same
-            // transformation to the crop rect.
-            // The surface->dispatch mapping is calculated by inverting a dispatch->surface mapping.
-
-            Matrix matrix = new Matrix();
-            // Apply the dispatch->surface rotation.
-            matrix.setRotate(dispatchToOutputDegrees - surfaceToOutputDegrees);
-            // Apply the dispatch->surface translation. The translation is calculated by
-            // compensating for the offset caused by the dispatch->surface rotation.
-            float[] vertexes = sizeToVertexes(dispatchResolution);
-            matrix.mapPoints(vertexes);
-            float left = min(vertexes[0], vertexes[2], vertexes[4], vertexes[6]);
-            float top = min(vertexes[1], vertexes[3], vertexes[5], vertexes[7]);
-            matrix.postTranslate(-left, -top);
-            // Inverting the dispatch->surface mapping to get the surface->dispatch mapping.
-            matrix.invert(matrix);
-
-            // Apply the surface->dispatch mapping to surface crop rect.
-            RectF dispatchCropRectF = new RectF();
-            matrix.mapRect(dispatchCropRectF, new RectF(surfaceCropRect));
-            dispatchCropRectF.sort();
-            Rect dispatchCropRect = new Rect();
-            dispatchCropRectF.round(dispatchCropRect);
-            return dispatchCropRect;
-        }
-
         void notifyCallbackError(final @ImageCaptureError int imageCaptureError,
                 final String message, final Throwable cause) {
             // Check to make sure image hasn't been already dispatched or error has been notified
@@ -2969,6 +3010,33 @@
             return this;
         }
 
+        /**
+         * Sets the output JPEG image compression quality.
+         *
+         * <p>This is used for the {@link ImageProxy} which is returned by
+         * {@link #takePicture(Executor, OnImageCapturedCallback)} or the output JPEG image which
+         * is saved by {@link #takePicture(OutputFileOptions, Executor, OnImageSavedCallback)}.
+         * The saved JPEG image might be cropped according to the {@link ViewPort} setting or
+         * the crop aspect ratio set by {@link #setCropAspectRatio(Rational)}. The JPEG quality
+         * setting will also be used to compress the cropped output image.
+         *
+         * <p>If not set, a default value will be used according to the capture mode setting.
+         * JPEG compression quality 95 is used for {@link #CAPTURE_MODE_MINIMIZE_LATENCY} and 100
+         * is used for {@link #CAPTURE_MODE_MAXIMIZE_QUALITY}.
+         *
+         * @param jpegQuality The requested output JPEG image compression quality. The value must
+         *                   be in range [1..100] which larger is higher quality.
+         * @return The current Builder.
+         * @throws IllegalArgumentException if the input value is not in range [1..100].
+         */
+        @NonNull
+        public Builder setJpegQuality(@IntRange(from = 1, to = 100) int jpegQuality) {
+            Preconditions.checkArgumentInRange(jpegQuality, 1, 100, "jpegQuality");
+            getMutableConfig().insertOption(OPTION_JPEG_COMPRESSION_QUALITY,
+                    jpegQuality);
+            return this;
+        }
+
         // Implementations of IoConfig.Builder default methods
 
         /**
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/ImageCaptureConfig.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/ImageCaptureConfig.java
index 9c535eb..0f233fb 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/ImageCaptureConfig.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/ImageCaptureConfig.java
@@ -18,6 +18,7 @@
 
 import android.graphics.ImageFormat;
 
+import androidx.annotation.IntRange;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -59,6 +60,8 @@
             Option.create("camerax.core.imageCapture.useSoftwareJpegEncoder", boolean.class);
     public static final Option<Integer> OPTION_FLASH_TYPE =
             Option.create("camerax.core.imageCapture.flashType", int.class);
+    public static final Option<Integer> OPTION_JPEG_COMPRESSION_QUALITY =
+            Option.create("camerax.core.imageCapture.jpegCompressionQuality", int.class);
 
     // *********************************************************************************************
 
@@ -261,6 +264,30 @@
         return retrieveOption(OPTION_FLASH_TYPE);
     }
 
+    /**
+     * Returns the JPEG compression quality setting.
+     *
+     * @param valueIfMissing The value to return if this configuration option has not been set.
+     * @return The stored value or <code>valueIfMissing</code> if the value does not exist in this
+     * configuration.
+     */
+    @IntRange(from = 1, to = 100)
+    public int getJpegQuality(@IntRange(from = 1, to = 100) int valueIfMissing) {
+        return retrieveOption(OPTION_JPEG_COMPRESSION_QUALITY, valueIfMissing);
+    }
+
+
+    /**
+     * Returns the JPEG compression quality setting.
+     *
+     * @return The stored value, if it exists in this configuration.
+     * @throws IllegalArgumentException if the option does not exist in this configuration.
+     */
+    @IntRange(from = 1, to = 100)
+    public int getJpegQuality() {
+        return retrieveOption(OPTION_JPEG_COMPRESSION_QUALITY);
+    }
+
     // Implementations of IO default methods
 
     /**
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/YuvToJpegProcessor.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/YuvToJpegProcessor.java
index 5cdf970..16ada6c8 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/YuvToJpegProcessor.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/YuvToJpegProcessor.java
@@ -56,7 +56,7 @@
     private static final Rect UNINITIALIZED_RECT = new Rect(0, 0, 0, 0);
 
     @IntRange(from = 0, to = 100)
-    private final int mQuality;
+    private int mQuality;
     private final int mMaxImages;
 
     private final Object mLock = new Object();
@@ -75,6 +75,13 @@
         mMaxImages = maxImages;
     }
 
+    /**
+     * Sets the compression quality for the output JPEG image.
+     */
+    public void setJpegQuality(@IntRange(from = 0, to = 100) int quality) {
+        mQuality = quality;
+    }
+
     @Override
     public void onOutputSurface(@NonNull Surface surface, int imageFormat) {
         Preconditions.checkState(imageFormat == ImageFormat.JPEG, "YuvToJpegProcessor only "
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/utils/ImageUtil.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/utils/ImageUtil.java
index e6ebb17..6a7c9e7 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/utils/ImageUtil.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/utils/ImageUtil.java
@@ -20,7 +20,9 @@
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapRegionDecoder;
 import android.graphics.ImageFormat;
+import android.graphics.Matrix;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.YuvImage;
 import android.util.Rational;
 import android.util.Size;
@@ -273,6 +275,51 @@
         return new Rect(cropLeft, cropTop, cropLeft + outputWidth, cropTop + outputHeight);
     }
 
+    /**
+     * Calculates crop rect based on the dispatch resolution and rotation degrees info.
+     *
+     * <p> The original crop rect is calculated based on camera sensor buffer. On some devices,
+     * the buffer is rotated before being passed to users, in which case the crop rect also
+     * needs additional transformations.
+     *
+     * <p> There are two most common scenarios: 1) exif rotation is 0, or 2) exif rotation
+     * equals output rotation. 1) means the HAL rotated the buffer based on target
+     * rotation. 2) means HAL no-oped on the rotation. Theoretically only 1) needs
+     * additional transformations, but this method is also generic enough to handle all possible
+     * HAL rotations.
+     */
+    @NonNull
+    public static Rect computeCropRectFromDispatchInfo(@NonNull Rect surfaceCropRect,
+            int surfaceToOutputDegrees, @NonNull Size dispatchResolution,
+            int dispatchToOutputDegrees) {
+        // There are 3 coordinate systems: surface, dispatch and output. Surface is where
+        // the original crop rect is defined. We need to figure out what HAL
+        // has done to the buffer (the surface->dispatch mapping) and apply the same
+        // transformation to the crop rect.
+        // The surface->dispatch mapping is calculated by inverting a dispatch->surface mapping.
+
+        Matrix matrix = new Matrix();
+        // Apply the dispatch->surface rotation.
+        matrix.setRotate(dispatchToOutputDegrees - surfaceToOutputDegrees);
+        // Apply the dispatch->surface translation. The translation is calculated by
+        // compensating for the offset caused by the dispatch->surface rotation.
+        float[] vertexes = sizeToVertexes(dispatchResolution);
+        matrix.mapPoints(vertexes);
+        float left = min(vertexes[0], vertexes[2], vertexes[4], vertexes[6]);
+        float top = min(vertexes[1], vertexes[3], vertexes[5], vertexes[7]);
+        matrix.postTranslate(-left, -top);
+        // Inverting the dispatch->surface mapping to get the surface->dispatch mapping.
+        matrix.invert(matrix);
+
+        // Apply the surface->dispatch mapping to surface crop rect.
+        RectF dispatchCropRectF = new RectF();
+        matrix.mapRect(dispatchCropRectF, new RectF(surfaceCropRect));
+        dispatchCropRectF.sort();
+        Rect dispatchCropRect = new Rect();
+        dispatchCropRectF.round(dispatchCropRect);
+        return dispatchCropRect;
+    }
+
     private static byte[] nv21ToJpeg(@NonNull byte[] nv21, int width, int height,
             @Nullable Rect cropRect, @IntRange(from = 1, to = 100) int jpegQuality)
             throws CodecFailedException {
@@ -309,13 +356,19 @@
     }
 
     /**
-     * Checks whether the image's crop rectangle is the same as the image size.
+     * Checks whether the image's crop rectangle is the same as the source image size.
      */
     public static boolean shouldCropImage(@NonNull ImageProxy image) {
-        Size sourceSize = new Size(image.getWidth(), image.getHeight());
-        Size targetSize = new Size(image.getCropRect().width(), image.getCropRect().height());
+        return shouldCropImage(image.getWidth(), image.getHeight(), image.getCropRect().width(),
+                image.getCropRect().height());
+    }
 
-        return !targetSize.equals(sourceSize);
+    /**
+     * Checks whether the image's crop rectangle is the same as the source image size.
+     */
+    public static boolean shouldCropImage(int sourceWidth, int sourceHeight, int cropRectWidth,
+            int cropRectHeight) {
+        return sourceWidth != cropRectWidth || sourceHeight != cropRectHeight;
     }
 
     /** Exception for error during transcoding image. */
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/internal/utils/ImageUtilTest.java b/camera/camera-core/src/test/java/androidx/camera/core/internal/utils/ImageUtilTest.java
index b40fe9d..ab51a62 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/internal/utils/ImageUtilTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/internal/utils/ImageUtilTest.java
@@ -163,4 +163,40 @@
             assertEquals(HEIGHT, resultRect.height());
         }
     }
+
+    @Test
+    public void computeCropRectFromDispatchInfo_dispatchBufferRotated90() {
+        assertComputeCropRectFromDispatchInfo(90, new Size(4, 6), new Rect(3, 0, 4, 1));
+    }
+
+    @Test
+    public void computeCropRectFromDispatchInfo_dispatchBufferRotated180() {
+        assertComputeCropRectFromDispatchInfo(180, new Size(6, 4), new Rect(5, 3, 6, 4));
+    }
+
+    @Test
+    public void computeCropRectFromDispatchInfo_dispatchBufferRotated270() {
+        assertComputeCropRectFromDispatchInfo(270, new Size(4, 6), new Rect(0, 5, 1, 6));
+    }
+
+    @Test
+    public void computeCropRectFromDispatchInfo_dispatchBufferRotated0() {
+        assertComputeCropRectFromDispatchInfo(0, new Size(6, 4), new Rect(0, 0, 1, 1));
+    }
+
+    private void assertComputeCropRectFromDispatchInfo(int outputDegrees, Size dispatchResolution,
+            Rect dispatchRect) {
+        // Arrange:
+        // Surface crop rect stays the same regardless of HAL rotations.
+        Rect surfaceCropRect = new Rect(0, 0, 1, 1);
+        // Exif degrees being 0 means HAL consumed the target rotation.
+        int exifRotationDegrees = 0;
+
+        // Act.
+        Rect dispatchCropRect = ImageUtil.computeCropRectFromDispatchInfo(
+                surfaceCropRect, outputDegrees, dispatchResolution, exifRotationDegrees);
+
+        // Assert.
+        assertThat(dispatchCropRect).isEqualTo(dispatchRect);
+    }
 }
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
index 9f05eb6..29f1803 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
@@ -46,6 +46,7 @@
 import androidx.camera.core.ImageCapture
 import androidx.camera.core.ImageCaptureException
 import androidx.camera.core.ImageProxy
+import androidx.camera.core.ViewPort
 import androidx.camera.core.impl.CaptureBundle
 import androidx.camera.core.impl.CaptureConfig
 import androidx.camera.core.impl.CaptureProcessor
@@ -1014,6 +1015,73 @@
     }
 
     @Test
+    fun capturedImageHasCorrectCroppingSize_viewPortOverwriteCropAspectRatio() = runBlocking {
+        skipTestOnCameraPipeConfig()
+
+        val sensorOrientation = CameraUtil.getSensorOrientation(BACK_LENS_FACING)
+        val isRotateNeeded = sensorOrientation!! % 180 != 0
+
+        val useCase = ImageCapture.Builder()
+            .setTargetRotation(if (isRotateNeeded) Surface.ROTATION_90 else Surface.ROTATION_0)
+            .build()
+
+        // Sets a crop aspect ratio to the use case. This will be overwritten by the view port
+        // setting.
+        val useCaseCroppingAspectRatio = Rational(4, 3)
+        useCase.setCropAspectRatio(useCaseCroppingAspectRatio)
+
+        camera = CameraUtil.createCameraUseCaseAdapter(context, BACK_SELECTOR)
+
+        val viewPortAspectRatio = Rational(2, 1)
+        val viewPort = ViewPort.Builder(
+            viewPortAspectRatio,
+            if (isRotateNeeded) Surface.ROTATION_90 else Surface.ROTATION_0
+        ).build()
+
+        // Sets view port with different aspect ratio and then attach the use case
+        camera.setViewPort(viewPort)
+
+        withContext(Dispatchers.Main) {
+            camera.addUseCases(listOf(useCase))
+        }
+
+        val callback = FakeImageCaptureCallback(capturesCount = 1)
+
+        useCase.takePicture(mainExecutor, callback)
+
+        // Wait for the signal that the image has been captured.
+        callback.awaitCapturesAndAssert(capturedImagesCount = 1)
+
+        // After target rotation is updated, the result cropping aspect ratio should still the
+        // same as original one.
+        val imageProperties = callback.results.first()
+        val cropRect = imageProperties.cropRect
+
+        // Rotate the captured ImageProxy's crop rect into the coordinate space of the final
+        // displayed image
+        val resultCroppingRatio: Rational = if (imageProperties.rotationDegrees % 180 != 0) {
+            Rational(cropRect!!.height(), cropRect.width())
+        } else {
+            Rational(cropRect!!.width(), cropRect.height())
+        }
+
+        if (imageProperties.format == ImageFormat.JPEG) {
+            assertThat(imageProperties.rotationDegrees).isEqualTo(
+                imageProperties.exif!!.rotation
+            )
+        }
+
+        // Compare aspect ratio with a threshold due to floating point rounding. Can't do direct
+        // comparison of height and width, because the target aspect ratio of ImageCapture will
+        // be corrected in API 21 Legacy devices and the captured image will be scaled to fit
+        // within the cropping aspect ratio.
+        val aspectRatioThreshold = 0.01
+        assertThat(
+            abs(resultCroppingRatio.toDouble() - viewPortAspectRatio.toDouble())
+        ).isLessThan(aspectRatioThreshold)
+    }
+
+    @Test
     fun useCaseConfigCanBeReset_afterUnbind() = runBlocking {
         skipTestOnCameraPipeConfig()
 
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeDiagnosticSuppressor.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeDiagnosticSuppressor.kt
index cc26fef..58a9893 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeDiagnosticSuppressor.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeDiagnosticSuppressor.kt
@@ -31,12 +31,12 @@
 
     companion object {
         fun registerExtension(
-            @Suppress("UNUSED_PARAMETER") project: Project,
+            project: Project,
             extension: DiagnosticSuppressor
         ) {
             @Suppress("DEPRECATION")
             Extensions.getRootArea().getExtensionPoint(DiagnosticSuppressor.EP_NAME)
-                .registerExtension(extension)
+                .registerExtension(extension, project)
         }
     }
 
diff --git a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.jvm.kt b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.jvm.kt
index c10893f..87ce6c3 100644
--- a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.jvm.kt
+++ b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.jvm.kt
@@ -58,6 +58,7 @@
 import androidx.compose.material.MaterialTheme
 import androidx.compose.material.Scaffold
 import androidx.compose.material.Slider
+import androidx.compose.material.Switch
 import androidx.compose.material.Text
 import androidx.compose.material.TextField
 import androidx.compose.material.TopAppBar
@@ -403,14 +404,25 @@
             verticalAlignment = Alignment.CenterVertically
         ) {
             Row {
-                Row(modifier = Modifier.padding(4.dp)) {
-                    Checkbox(
+                Column {
+                    Switch(
                         animation.value,
                         onCheckedChange = {
                             animation.value = it
                         }
                     )
-                    Text("Animation")
+                    Row(
+                        modifier = Modifier.padding(4.dp),
+                        verticalAlignment = Alignment.CenterVertically
+                    ) {
+                        Checkbox(
+                            animation.value,
+                            onCheckedChange = {
+                                animation.value = it
+                            }
+                        )
+                        Text("Animation")
+                    }
                 }
 
                 Button(
diff --git a/compose/foundation/foundation-layout/benchmark/src/androidTest/java/androidx/compose/foundation/layout/benchmark/BoxWithConstraintsIntegrationBenchmark.kt b/compose/foundation/foundation-layout/benchmark/src/androidTest/java/androidx/compose/foundation/layout/benchmark/BoxWithConstraintsIntegrationBenchmark.kt
index 17120a3..248e1dc 100644
--- a/compose/foundation/foundation-layout/benchmark/src/androidTest/java/androidx/compose/foundation/layout/benchmark/BoxWithConstraintsIntegrationBenchmark.kt
+++ b/compose/foundation/foundation-layout/benchmark/src/androidTest/java/androidx/compose/foundation/layout/benchmark/BoxWithConstraintsIntegrationBenchmark.kt
@@ -28,7 +28,6 @@
 import androidx.compose.foundation.lazy.LazyVerticalGrid
 import androidx.compose.material.BottomNavigation
 import androidx.compose.material.BottomNavigationItem
-import androidx.compose.material.ExperimentalMaterialApi
 import androidx.compose.material.Icon
 import androidx.compose.material.NavigationRail
 import androidx.compose.material.NavigationRailItem
@@ -187,7 +186,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterialApi::class)
 @Composable
 private fun TabletScreen(gridColumns: Int) {
     Row {
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index ab96527..26fd022 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -51,6 +51,10 @@
     method public static androidx.compose.ui.Modifier focusable(androidx.compose.ui.Modifier, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
   }
 
+  public final class HoverableKt {
+    method public static androidx.compose.ui.Modifier hoverable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional boolean enabled);
+  }
+
   public final class ImageKt {
     method @androidx.compose.runtime.Composable public static void Image(androidx.compose.ui.graphics.ImageBitmap bitmap, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int filterQuality);
     method @androidx.compose.runtime.Composable public static void Image(androidx.compose.ui.graphics.vector.ImageVector imageVector, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
@@ -300,6 +304,23 @@
     method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Boolean> collectIsFocusedAsState(androidx.compose.foundation.interaction.InteractionSource);
   }
 
+  public interface HoverInteraction extends androidx.compose.foundation.interaction.Interaction {
+  }
+
+  public static final class HoverInteraction.Enter implements androidx.compose.foundation.interaction.HoverInteraction {
+    ctor public HoverInteraction.Enter();
+  }
+
+  public static final class HoverInteraction.Exit implements androidx.compose.foundation.interaction.HoverInteraction {
+    ctor public HoverInteraction.Exit(androidx.compose.foundation.interaction.HoverInteraction.Enter enter);
+    method public androidx.compose.foundation.interaction.HoverInteraction.Enter getEnter();
+    property public final androidx.compose.foundation.interaction.HoverInteraction.Enter enter;
+  }
+
+  public final class HoverInteractionKt {
+    method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Boolean> collectIsHoveredAsState(androidx.compose.foundation.interaction.InteractionSource);
+  }
+
   public interface Interaction {
   }
 
@@ -346,6 +367,9 @@
 
 package androidx.compose.foundation.lazy {
 
+  public final class IntervalListKt {
+  }
+
   public final class LazyDslKt {
     method @androidx.compose.runtime.Composable public static void LazyColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyListScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void LazyRow(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyListScope,kotlin.Unit> content);
@@ -378,6 +402,9 @@
     property public abstract int size;
   }
 
+  public final class LazyListItemsProviderImplKt {
+  }
+
   public final class LazyListKt {
   }
 
diff --git a/compose/foundation/foundation/api/public_plus_experimental_current.txt b/compose/foundation/foundation/api/public_plus_experimental_current.txt
index d5b6b12..56f8458 100644
--- a/compose/foundation/foundation/api/public_plus_experimental_current.txt
+++ b/compose/foundation/foundation/api/public_plus_experimental_current.txt
@@ -57,6 +57,10 @@
     method public static androidx.compose.ui.Modifier focusable(androidx.compose.ui.Modifier, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
   }
 
+  public final class HoverableKt {
+    method public static androidx.compose.ui.Modifier hoverable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional boolean enabled);
+  }
+
   public final class ImageKt {
     method @androidx.compose.runtime.Composable public static void Image(androidx.compose.ui.graphics.ImageBitmap bitmap, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int filterQuality);
     method @androidx.compose.runtime.Composable public static void Image(androidx.compose.ui.graphics.vector.ImageVector imageVector, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
@@ -320,6 +324,23 @@
     method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Boolean> collectIsFocusedAsState(androidx.compose.foundation.interaction.InteractionSource);
   }
 
+  public interface HoverInteraction extends androidx.compose.foundation.interaction.Interaction {
+  }
+
+  public static final class HoverInteraction.Enter implements androidx.compose.foundation.interaction.HoverInteraction {
+    ctor public HoverInteraction.Enter();
+  }
+
+  public static final class HoverInteraction.Exit implements androidx.compose.foundation.interaction.HoverInteraction {
+    ctor public HoverInteraction.Exit(androidx.compose.foundation.interaction.HoverInteraction.Enter enter);
+    method public androidx.compose.foundation.interaction.HoverInteraction.Enter getEnter();
+    property public final androidx.compose.foundation.interaction.HoverInteraction.Enter enter;
+  }
+
+  public final class HoverInteractionKt {
+    method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Boolean> collectIsHoveredAsState(androidx.compose.foundation.interaction.InteractionSource);
+  }
+
   public interface Interaction {
   }
 
@@ -381,6 +402,9 @@
     property public final int count;
   }
 
+  public final class IntervalListKt {
+  }
+
   public final class LazyDslKt {
     method @androidx.compose.runtime.Composable public static void LazyColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyListScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void LazyRow(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyListScope,kotlin.Unit> content);
@@ -423,6 +447,9 @@
     property public abstract int size;
   }
 
+  public final class LazyListItemsProviderImplKt {
+  }
+
   public final class LazyListKt {
   }
 
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index ab96527..26fd022 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -51,6 +51,10 @@
     method public static androidx.compose.ui.Modifier focusable(androidx.compose.ui.Modifier, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
   }
 
+  public final class HoverableKt {
+    method public static androidx.compose.ui.Modifier hoverable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional boolean enabled);
+  }
+
   public final class ImageKt {
     method @androidx.compose.runtime.Composable public static void Image(androidx.compose.ui.graphics.ImageBitmap bitmap, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int filterQuality);
     method @androidx.compose.runtime.Composable public static void Image(androidx.compose.ui.graphics.vector.ImageVector imageVector, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
@@ -300,6 +304,23 @@
     method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Boolean> collectIsFocusedAsState(androidx.compose.foundation.interaction.InteractionSource);
   }
 
+  public interface HoverInteraction extends androidx.compose.foundation.interaction.Interaction {
+  }
+
+  public static final class HoverInteraction.Enter implements androidx.compose.foundation.interaction.HoverInteraction {
+    ctor public HoverInteraction.Enter();
+  }
+
+  public static final class HoverInteraction.Exit implements androidx.compose.foundation.interaction.HoverInteraction {
+    ctor public HoverInteraction.Exit(androidx.compose.foundation.interaction.HoverInteraction.Enter enter);
+    method public androidx.compose.foundation.interaction.HoverInteraction.Enter getEnter();
+    property public final androidx.compose.foundation.interaction.HoverInteraction.Enter enter;
+  }
+
+  public final class HoverInteractionKt {
+    method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Boolean> collectIsHoveredAsState(androidx.compose.foundation.interaction.InteractionSource);
+  }
+
   public interface Interaction {
   }
 
@@ -346,6 +367,9 @@
 
 package androidx.compose.foundation.lazy {
 
+  public final class IntervalListKt {
+  }
+
   public final class LazyDslKt {
     method @androidx.compose.runtime.Composable public static void LazyColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyListScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void LazyRow(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyListScope,kotlin.Unit> content);
@@ -378,6 +402,9 @@
     property public abstract int size;
   }
 
+  public final class LazyListItemsProviderImplKt {
+  }
+
   public final class LazyListKt {
   }
 
diff --git a/compose/foundation/foundation/build.gradle b/compose/foundation/foundation/build.gradle
index f31c312..0318f41 100644
--- a/compose/foundation/foundation/build.gradle
+++ b/compose/foundation/foundation/build.gradle
@@ -123,6 +123,7 @@
                 implementation(project(":compose:test-utils"))
                 implementation(project(":compose:ui:ui-test-font"))
                 implementation(project(":test:screenshot:screenshot"))
+                implementation(project(":internal-testutils-runtime"))
                 implementation("androidx.activity:activity-compose:1.3.1")
 
                 implementation(libs.testUiautomator)
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/HighLevelGesturesDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/HighLevelGesturesDemo.kt
index 8d3efe7..05ce920 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/HighLevelGesturesDemo.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/HighLevelGesturesDemo.kt
@@ -19,17 +19,20 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.samples.DraggableSample
 import androidx.compose.foundation.samples.FocusableSample
+import androidx.compose.foundation.samples.HoverableSample
 import androidx.compose.foundation.samples.ScrollableSample
 import androidx.compose.foundation.samples.TransformableSample
+import androidx.compose.foundation.verticalScroll
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
 
 @Composable
 fun HighLevelGesturesDemo() {
-    Column {
+    Column(Modifier.verticalScroll(rememberScrollState())) {
         DraggableSample()
         Spacer(Modifier.height(50.dp))
         ScrollableSample()
@@ -37,5 +40,7 @@
         TransformableSample()
         Spacer(Modifier.height(50.dp))
         FocusableSample()
+        Spacer(Modifier.height(50.dp))
+        HoverableSample()
     }
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeVariousInputField.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeVariousInputField.kt
index ca905b4..7478f64 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeVariousInputField.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeVariousInputField.kt
@@ -111,7 +111,7 @@
  *
  * @see phoneNumberFilter
  */
-private val phoneNumberOffsetTranslator = object : OffsetMapping {
+private fun phoneNumberOffsetTranslator(text: String) = object : OffsetMapping {
     override fun originalToTransformed(offset: Int): Int {
         return when (offset) {
             0 -> 1
@@ -145,7 +145,7 @@
             12 -> 8
             13 -> 9
             else -> 10
-        }
+        }.coerceAtMost(text.length)
     }
 }
 
@@ -160,17 +160,34 @@
     val filled = trimmed + "_".repeat(10 - trimmed.length)
     val res = "(" + filled.substring(0..2) + ") " + filled.substring(3..5) + "-" +
         filled.substring(6..9)
-    TransformedText(AnnotatedString(text = res), phoneNumberOffsetTranslator)
+    TransformedText(AnnotatedString(text = res), phoneNumberOffsetTranslator(text.text))
 }
 
 private val emailFilter = VisualTransformation { text ->
     if (text.text.indexOf("@") == -1) {
-        TransformedText(AnnotatedString(text = text.text + "@gmail.com"), identityTranslator)
+        TransformedText(
+            AnnotatedString(text = text.text + "@gmail.com"),
+            emailOffsetTranslator(text.text)
+        )
     } else {
         TransformedText(text, identityTranslator)
     }
 }
 
+private fun emailOffsetTranslator(text: String) = object : OffsetMapping {
+    override fun originalToTransformed(offset: Int): Int {
+        return (offset).coerceAtMost(text.length + 10)
+    }
+
+    override fun transformedToOriginal(offset: Int): Int {
+        return if (offset <= text.length) {
+            offset
+        } else {
+            (offset - 10).coerceAtMost(text.length).coerceAtLeast(0)
+        }
+    }
+}
+
 @Composable
 fun VariousInputFieldDemo() {
     LazyColumn {
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/HoverableSample.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/HoverableSample.kt
new file mode 100644
index 0000000..411477d
--- /dev/null
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/HoverableSample.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.background
+import androidx.compose.foundation.hoverable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.collectIsHoveredAsState
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+
+@Sampled
+@Composable
+fun HoverableSample() {
+    // MutableInteractionSource to track changes of the component's interactions (like "hovered")
+    val interactionSource = remember { MutableInteractionSource() }
+    val isHovered by interactionSource.collectIsHoveredAsState()
+
+    // the color will change depending on the presence of a hover
+    Box(
+        modifier = Modifier
+            .size(128.dp)
+            .background(if (isHovered) Color.Red else Color.Blue)
+            .hoverable(interactionSource = interactionSource),
+        contentAlignment = Alignment.Center
+    ) {
+        Text(if (isHovered) "Hovered" else "Unhovered")
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ClickableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ClickableTest.kt
index 8a97de9..2f8b9d6 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ClickableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ClickableTest.kt
@@ -20,6 +20,7 @@
 import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.foundation.gestures.draggable
 import androidx.compose.foundation.gestures.rememberDraggableState
+import androidx.compose.foundation.interaction.HoverInteraction
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.interaction.PressInteraction
@@ -42,6 +43,7 @@
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.SemanticsActions
 import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.SemanticsMatcher
 import androidx.compose.ui.test.assert
 import androidx.compose.ui.test.assertHasClickAction
@@ -58,6 +60,7 @@
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performMouseInput
 import androidx.compose.ui.test.performSemanticsAction
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.unit.dp
@@ -820,6 +823,111 @@
         }
     }
 
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun clickableTest_interactionSource_hover() {
+        val interactionSource = MutableInteractionSource()
+
+        var scope: CoroutineScope? = null
+
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            Box {
+                BasicText(
+                    "ClickableText",
+                    modifier = Modifier
+                        .testTag("myClickable")
+                        .combinedClickable(
+                            interactionSource = interactionSource,
+                            indication = null
+                        ) {}
+                )
+            }
+        }
+
+        val interactions = mutableListOf<Interaction>()
+
+        scope!!.launch {
+            interactionSource.interactions.collect { interactions.add(it) }
+        }
+
+        rule.runOnIdle {
+            assertThat(interactions).isEmpty()
+        }
+
+        rule.onNodeWithTag("myClickable")
+            .performMouseInput { enter(center) }
+
+        rule.runOnIdle {
+            assertThat(interactions).hasSize(1)
+            assertThat(interactions.first()).isInstanceOf(HoverInteraction.Enter::class.java)
+        }
+
+        rule.onNodeWithTag("myClickable")
+            .performMouseInput { exit(Offset(-1f, -1f)) }
+
+        rule.runOnIdle {
+            assertThat(interactions).hasSize(2)
+            assertThat(interactions.first()).isInstanceOf(HoverInteraction.Enter::class.java)
+            assertThat(interactions[1])
+                .isInstanceOf(HoverInteraction.Exit::class.java)
+            assertThat((interactions[1] as HoverInteraction.Exit).enter)
+                .isEqualTo(interactions[0])
+        }
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun clickableTest_interactionSource_hover_and_press() {
+        val interactionSource = MutableInteractionSource()
+
+        var scope: CoroutineScope? = null
+
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            Box {
+                BasicText(
+                    "ClickableText",
+                    modifier = Modifier
+                        .testTag("myClickable")
+                        .combinedClickable(
+                            interactionSource = interactionSource,
+                            indication = null
+                        ) {}
+                )
+            }
+        }
+
+        val interactions = mutableListOf<Interaction>()
+
+        scope!!.launch {
+            interactionSource.interactions.collect { interactions.add(it) }
+        }
+
+        rule.runOnIdle {
+            assertThat(interactions).isEmpty()
+        }
+
+        rule.onNodeWithTag("myClickable")
+            .performMouseInput {
+                enter(center)
+                click()
+                exit(Offset(-1f, -1f))
+            }
+
+        rule.runOnIdle {
+            assertThat(interactions).hasSize(4)
+            assertThat(interactions[0]).isInstanceOf(HoverInteraction.Enter::class.java)
+            assertThat(interactions[1]).isInstanceOf(PressInteraction.Press::class.java)
+            assertThat(interactions[2]).isInstanceOf(PressInteraction.Release::class.java)
+            assertThat(interactions[3]).isInstanceOf(HoverInteraction.Exit::class.java)
+            assertThat((interactions[2] as PressInteraction.Release).press)
+                .isEqualTo(interactions[1])
+            assertThat((interactions[3] as HoverInteraction.Exit).enter)
+                .isEqualTo(interactions[0])
+        }
+    }
+
     /**
      * Regression test for b/186223077
      *
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/HoverableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/HoverableTest.kt
new file mode 100644
index 0000000..634b442
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/HoverableTest.kt
@@ -0,0 +1,332 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation
+
+import androidx.compose.foundation.interaction.HoverInteraction
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.collectIsHoveredAsState
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.InspectableValue
+import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performMouseInput
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class HoverableTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    val hoverTag = "myHoverable"
+
+    @Before
+    fun before() {
+        isDebugInspectorInfoEnabled = true
+    }
+
+    @After
+    fun after() {
+        isDebugInspectorInfoEnabled = false
+    }
+
+    @Test
+    fun hoverableText_testInspectorValue() {
+        rule.setContent {
+            val interactionSource = remember { MutableInteractionSource() }
+            val modifier = Modifier.hoverable(interactionSource) as InspectableValue
+            Truth.assertThat(modifier.nameFallback).isEqualTo("hoverable")
+            Truth.assertThat(modifier.valueOverride).isNull()
+            Truth.assertThat(modifier.inspectableElements.map { it.name }.asIterable())
+                .containsExactly(
+                    "interactionSource",
+                    "enabled",
+                )
+        }
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @ExperimentalComposeUiApi
+    @Test
+    fun hoverableTest_hovered() {
+        var isHovered = false
+        val interactionSource = MutableInteractionSource()
+
+        rule.setContent {
+            Box(
+                modifier = Modifier
+                    .size(128.dp)
+                    .testTag(hoverTag)
+                    .hoverable(interactionSource)
+            )
+
+            isHovered = interactionSource.collectIsHoveredAsState().value
+        }
+
+        rule.onNodeWithTag(hoverTag).performMouseInput {
+            enter(Offset(64.dp.toPx(), 64.dp.toPx()))
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(isHovered).isTrue()
+
+        rule.onNodeWithTag(hoverTag).performMouseInput {
+            moveTo(Offset(96.dp.toPx(), 96.dp.toPx()))
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(isHovered).isTrue()
+
+        rule.onNodeWithTag(hoverTag).performMouseInput {
+            moveTo(Offset(129.dp.toPx(), 129.dp.toPx()))
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(isHovered).isFalse()
+
+        rule.onNodeWithTag(hoverTag).performMouseInput {
+            moveTo(Offset(96.dp.toPx(), 96.dp.toPx()))
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(isHovered).isTrue()
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @ExperimentalComposeUiApi
+    @Test
+    fun hoverableTest_interactionSource() {
+        val interactionSource = MutableInteractionSource()
+
+        var scope: CoroutineScope? = null
+
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            Box(
+                modifier = Modifier
+                    .size(128.dp)
+                    .testTag(hoverTag)
+                    .hoverable(interactionSource = interactionSource)
+            )
+        }
+
+        val interactions = mutableListOf<Interaction>()
+
+        scope!!.launch {
+            interactionSource.interactions.collect { interactions.add(it) }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(interactions).isEmpty()
+        }
+
+        rule.onNodeWithTag(hoverTag).performMouseInput {
+            enter(Offset(64.dp.toPx(), 64.dp.toPx()))
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(interactions).hasSize(1)
+            Truth.assertThat(interactions.first()).isInstanceOf(HoverInteraction.Enter::class.java)
+        }
+
+        rule.onNodeWithTag(hoverTag).performMouseInput {
+            moveTo(Offset(129.dp.toPx(), 129.dp.toPx()))
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(interactions).hasSize(2)
+            Truth.assertThat(interactions.first()).isInstanceOf(HoverInteraction.Enter::class.java)
+            Truth.assertThat(interactions[1])
+                .isInstanceOf(HoverInteraction.Exit::class.java)
+            Truth.assertThat((interactions[1] as HoverInteraction.Exit).enter)
+                .isEqualTo(interactions[0])
+        }
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun hoverableTest_interactionSource_resetWhenDisposed() {
+        val interactionSource = MutableInteractionSource()
+        var emitHoverable by mutableStateOf(true)
+
+        var scope: CoroutineScope? = null
+
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            Box {
+                if (emitHoverable) {
+                    Box(
+                        modifier = Modifier
+                            .size(128.dp)
+                            .testTag(hoverTag)
+                            .hoverable(interactionSource = interactionSource)
+                    )
+                }
+            }
+        }
+
+        val interactions = mutableListOf<Interaction>()
+
+        scope!!.launch {
+            interactionSource.interactions.collect { interactions.add(it) }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(interactions).isEmpty()
+        }
+
+        rule.onNodeWithTag(hoverTag).performMouseInput {
+            enter(Offset(64.dp.toPx(), 64.dp.toPx()))
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(interactions).hasSize(1)
+            Truth.assertThat(interactions.first()).isInstanceOf(HoverInteraction.Enter::class.java)
+        }
+
+        // Dispose hoverable, Interaction should be gone
+        rule.runOnIdle {
+            emitHoverable = false
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(interactions).hasSize(2)
+            Truth.assertThat(interactions.first()).isInstanceOf(HoverInteraction.Enter::class.java)
+            Truth.assertThat(interactions[1])
+                .isInstanceOf(HoverInteraction.Exit::class.java)
+            Truth.assertThat((interactions[1] as HoverInteraction.Exit).enter)
+                .isEqualTo(interactions[0])
+        }
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun hoverableTest_interactionSource_dontHoverWhenDisabled() {
+        val interactionSource = MutableInteractionSource()
+
+        var scope: CoroutineScope? = null
+
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            Box {
+                Box(
+                    modifier = Modifier
+                        .size(128.dp)
+                        .testTag(hoverTag)
+                        .hoverable(interactionSource = interactionSource, enabled = false)
+                )
+            }
+        }
+
+        val interactions = mutableListOf<Interaction>()
+
+        scope!!.launch {
+            interactionSource.interactions.collect { interactions.add(it) }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(interactions).isEmpty()
+        }
+
+        rule.onNodeWithTag(hoverTag).performMouseInput {
+            enter(Offset(64.dp.toPx(), 64.dp.toPx()))
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(interactions).isEmpty()
+        }
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun hoverableTest_interactionSource_resetWhenDisabled() {
+        val interactionSource = MutableInteractionSource()
+        var enableHoverable by mutableStateOf(true)
+
+        var scope: CoroutineScope? = null
+
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            Box {
+                Box(
+                    modifier = Modifier
+                        .size(128.dp)
+                        .testTag(hoverTag)
+                        .hoverable(interactionSource = interactionSource, enabled = enableHoverable)
+                )
+            }
+        }
+
+        val interactions = mutableListOf<Interaction>()
+
+        scope!!.launch {
+            interactionSource.interactions.collect { interactions.add(it) }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(interactions).isEmpty()
+        }
+
+        rule.onNodeWithTag(hoverTag).performMouseInput {
+            enter(Offset(64.dp.toPx(), 64.dp.toPx()))
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(interactions).hasSize(1)
+            Truth.assertThat(interactions.first()).isInstanceOf(HoverInteraction.Enter::class.java)
+        }
+
+        // Disable hoverable, Interaction should be gone
+        rule.runOnIdle {
+            enableHoverable = false
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(interactions).hasSize(2)
+            Truth.assertThat(interactions.first()).isInstanceOf(HoverInteraction.Enter::class.java)
+            Truth.assertThat(interactions[1])
+                .isInstanceOf(HoverInteraction.Exit::class.java)
+            Truth.assertThat((interactions[1] as HoverInteraction.Exit).enter)
+                .isEqualTo(interactions[0])
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/SelectableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/SelectableTest.kt
index a18359c..b74d3a8 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/SelectableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/SelectableTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation
 
+import androidx.compose.foundation.interaction.HoverInteraction
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.interaction.PressInteraction
@@ -29,10 +30,12 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.testutils.first
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.platform.InspectableValue
 import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.SemanticsMatcher
 import androidx.compose.ui.test.assert
 import androidx.compose.ui.test.assertCountEquals
@@ -44,6 +47,7 @@
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performMouseInput
 import androidx.compose.ui.test.performTouchInput
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
@@ -293,6 +297,60 @@
         }
     }
 
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun selectableTest_interactionSource_hover() {
+        val interactionSource = MutableInteractionSource()
+
+        var scope: CoroutineScope? = null
+
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            Box {
+                Box(
+                    Modifier.selectable(
+                        selected = true,
+                        interactionSource = interactionSource,
+                        indication = null,
+                        onClick = {}
+                    )
+                ) {
+                    BasicText("SelectableText")
+                }
+            }
+        }
+
+        val interactions = mutableListOf<Interaction>()
+
+        scope!!.launch {
+            interactionSource.interactions.collect { interactions.add(it) }
+        }
+
+        rule.runOnIdle {
+            assertThat(interactions).isEmpty()
+        }
+
+        rule.onNodeWithText("SelectableText")
+            .performMouseInput { enter(center) }
+
+        rule.runOnIdle {
+            assertThat(interactions).hasSize(1)
+            assertThat(interactions.first()).isInstanceOf(HoverInteraction.Enter::class.java)
+        }
+
+        rule.onNodeWithText("SelectableText")
+            .performMouseInput { exit(Offset(-1f, -1f)) }
+
+        rule.runOnIdle {
+            assertThat(interactions).hasSize(2)
+            assertThat(interactions.first()).isInstanceOf(HoverInteraction.Enter::class.java)
+            assertThat(interactions[1])
+                .isInstanceOf(HoverInteraction.Exit::class.java)
+            assertThat((interactions[1] as HoverInteraction.Exit).enter)
+                .isEqualTo(interactions[0])
+        }
+    }
+
     @Test
     fun selectableTest_testInspectorValue_noIndication() {
         rule.setContent {
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ToggleableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ToggleableTest.kt
index a50acae..3de6204 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ToggleableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ToggleableTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation
 
+import androidx.compose.foundation.interaction.HoverInteraction
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.interaction.PressInteraction
@@ -39,6 +40,7 @@
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.SemanticsProperties
 import androidx.compose.ui.state.ToggleableState
+import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.SemanticsMatcher
 import androidx.compose.ui.test.assert
 import androidx.compose.ui.test.assertHasClickAction
@@ -56,6 +58,7 @@
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performMouseInput
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -389,6 +392,60 @@
         }
     }
 
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun toggleableTest_interactionSource_hover() {
+        val interactionSource = MutableInteractionSource()
+
+        var scope: CoroutineScope? = null
+
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            Box {
+                Box(
+                    Modifier.toggleable(
+                        value = true,
+                        interactionSource = interactionSource,
+                        indication = null,
+                        onValueChange = {}
+                    )
+                ) {
+                    BasicText("ToggleableText")
+                }
+            }
+        }
+
+        val interactions = mutableListOf<Interaction>()
+
+        scope!!.launch {
+            interactionSource.interactions.collect { interactions.add(it) }
+        }
+
+        rule.runOnIdle {
+            assertThat(interactions).isEmpty()
+        }
+
+        rule.onNodeWithText("ToggleableText")
+            .performMouseInput { enter(center) }
+
+        rule.runOnIdle {
+            assertThat(interactions).hasSize(1)
+            assertThat(interactions.first()).isInstanceOf(HoverInteraction.Enter::class.java)
+        }
+
+        rule.onNodeWithText("ToggleableText")
+            .performMouseInput { exit(Offset(-1f, -1f)) }
+
+        rule.runOnIdle {
+            assertThat(interactions).hasSize(2)
+            assertThat(interactions.first()).isInstanceOf(HoverInteraction.Enter::class.java)
+            assertThat(interactions[1])
+                .isInstanceOf(HoverInteraction.Exit::class.java)
+            assertThat((interactions[1] as HoverInteraction.Exit).enter)
+                .isEqualTo(interactions[0])
+        }
+    }
+
     @Test
     fun toggleableText_testInspectorValue_noIndication() {
         rule.setContent {
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt
index 8abd74e..5aec42e 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt
@@ -108,11 +108,13 @@
 import androidx.compose.ui.text.input.CommitTextCommand
 import androidx.compose.ui.text.input.EditCommand
 import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.OffsetMapping
 import androidx.compose.ui.text.input.PasswordVisualTransformation
 import androidx.compose.ui.text.input.PlatformTextInputService
 import androidx.compose.ui.text.input.TextFieldValue
 import androidx.compose.ui.text.input.TextFieldValue.Companion.Saver
 import androidx.compose.ui.text.input.TextInputService
+import androidx.compose.ui.text.input.TransformedText
 import androidx.compose.ui.text.intl.Locale
 import androidx.compose.ui.text.intl.LocaleList
 import androidx.compose.ui.text.style.BaselineShift
@@ -121,6 +123,7 @@
 import androidx.compose.ui.text.style.TextDirection
 import androidx.compose.ui.text.style.TextGeometricTransform
 import androidx.compose.ui.text.style.TextIndent
+import androidx.compose.ui.text.toUpperCase
 import androidx.compose.ui.text.withAnnotation
 import androidx.compose.ui.text.withStyle
 import androidx.compose.ui.unit.Density
@@ -720,6 +723,26 @@
             .assert(SemanticsMatcher.keyNotDefined(SemanticsActions.CutText))
     }
 
+    @Test
+    fun semantics_transformedText() {
+        rule.setContent {
+            BasicTextField(
+                modifier = Modifier.testTag(Tag),
+                value = TextFieldValue("Hello"),
+                onValueChange = {},
+                visualTransformation = { text ->
+                    TransformedText(
+                        text.toUpperCase(LocaleList("en_US")),
+                        OffsetMapping.Identity
+                    )
+                }
+            )
+        }
+
+        rule.onNodeWithTag(Tag)
+            .assertTextEquals("HELLO")
+    }
+
     @LargeTest
     @Test
     fun semantics_longClick() {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
index 834b038..e752677 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
@@ -426,5 +426,6 @@
     return this
         .then(semanticModifier)
         .indication(interactionSource, indication)
+        .hoverable(interactionSource = interactionSource)
         .then(gestureModifiers)
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Hoverable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Hoverable.kt
new file mode 100644
index 0000000..788671a
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Hoverable.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation
+
+import androidx.compose.foundation.interaction.HoverInteraction
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.input.pointer.PointerEventType
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.debugInspectorInfo
+import kotlinx.coroutines.currentCoroutineContext
+import kotlinx.coroutines.isActive
+
+/**
+ * Configure component to be hoverable via pointer enter/exit events.
+ *
+ * @sample androidx.compose.foundation.samples.HoverableSample
+ *
+ * @param interactionSource [MutableInteractionSource] that will be used to emit
+ * [HoverInteraction.Enter] when this element is being hovered.
+ * @param enabled Controls the enabled state. When `false`, hover events will be ignored.
+ */
+fun Modifier.hoverable(
+    interactionSource: MutableInteractionSource,
+    enabled: Boolean = true
+): Modifier = composed(
+    inspectorInfo = debugInspectorInfo {
+        name = "hoverable"
+        properties["interactionSource"] = interactionSource
+        properties["enabled"] = enabled
+    }
+) {
+    var hoverInteraction by remember { mutableStateOf<HoverInteraction.Enter?>(null) }
+
+    suspend fun emitEnter() {
+        if (hoverInteraction == null) {
+            val interaction = HoverInteraction.Enter()
+            interactionSource.emit(interaction)
+            hoverInteraction = interaction
+        }
+    }
+
+    suspend fun emitExit() {
+        hoverInteraction?.let { oldValue ->
+            val interaction = HoverInteraction.Exit(oldValue)
+            interactionSource.emit(interaction)
+            hoverInteraction = null
+        }
+    }
+
+    fun tryEmitExit() {
+        hoverInteraction?.let { oldValue ->
+            val interaction = HoverInteraction.Exit(oldValue)
+            interactionSource.tryEmit(interaction)
+            hoverInteraction = null
+        }
+    }
+
+    DisposableEffect(interactionSource) {
+        onDispose { tryEmitExit() }
+    }
+    LaunchedEffect(enabled) {
+        if (!enabled) {
+            emitExit()
+        }
+    }
+
+    if (enabled) {
+        Modifier
+// TODO(b/202505231):
+//  because we only react to input events, and not on layout changes, we can have a situation when
+//  Composable is under the cursor, but not hovered. To fix that, we have two ways:
+//  a. Trigger Enter/Exit on any layout change, inside Owner
+//  b. Manually react on layout changes via Modifier.onGloballyPosition, and check something like
+//  LocalPointerPosition.current
+            .pointerInput(interactionSource) {
+                val currentContext = currentCoroutineContext()
+                while (currentContext.isActive) {
+                    val event = awaitPointerEventScope { awaitPointerEvent() }
+                    when (event.type) {
+                        PointerEventType.Enter -> emitEnter()
+                        PointerEventType.Exit -> emitExit()
+                    }
+                }
+            }
+    } else {
+        Modifier
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Indication.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Indication.kt
index 61f30c1..d85462d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Indication.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Indication.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation
 
 import androidx.compose.foundation.interaction.InteractionSource
+import androidx.compose.foundation.interaction.collectIsHoveredAsState
 import androidx.compose.foundation.interaction.collectIsPressedAsState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
@@ -147,12 +148,15 @@
 private object DefaultDebugIndication : Indication {
 
     private class DefaultDebugIndicationInstance(
-        private val isPressed: State<Boolean>
+        private val isPressed: State<Boolean>,
+        private val isHovered: State<Boolean>
     ) : IndicationInstance {
         override fun ContentDrawScope.drawIndication() {
             drawContent()
             if (isPressed.value) {
                 drawRect(color = Color.Black.copy(alpha = 0.3f), size = size)
+            } else if (isHovered.value) {
+                drawRect(color = Color.Black.copy(alpha = 0.1f), size = size)
             }
         }
     }
@@ -160,8 +164,9 @@
     @Composable
     override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
         val isPressed = interactionSource.collectIsPressedAsState()
+        val isHovered = interactionSource.collectIsHoveredAsState()
         return remember(interactionSource) {
-            DefaultDebugIndicationInstance(isPressed)
+            DefaultDebugIndicationInstance(isPressed, isHovered)
         }
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/interaction/HoverInteraction.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/interaction/HoverInteraction.kt
new file mode 100644
index 0000000..3ae9cec
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/interaction/HoverInteraction.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.interaction
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import kotlinx.coroutines.flow.collect
+
+// An interface, not a sealed class, to allow adding new types here in a safe way (and not break
+// exhaustive when clauses)
+/**
+ * An interaction related to hover events.
+ *
+ * @see androidx.compose.foundation.hoverable
+ * @see Enter
+ * @see Exit
+ */
+interface HoverInteraction : Interaction {
+    /**
+     * An interaction representing a hover event on a component.
+     *
+     * @see androidx.compose.foundation.hoverable
+     * @see Exit
+     */
+    class Enter : HoverInteraction
+
+    /**
+     * An interaction representing a [Enter] event being released on a component.
+     *
+     * @property enter the source [Enter] interaction that is being released
+     *
+     * @see androidx.compose.foundation.hoverable
+     * @see Enter
+     */
+    class Exit(val enter: Enter) : HoverInteraction
+}
+
+/**
+ * Subscribes to this [MutableInteractionSource] and returns a [State] representing whether this
+ * component is hovered or not.
+ *
+ * [HoverInteraction] is typically set by [androidx.compose.foundation.hoverable] and hoverable
+ * components.
+ *
+ * @return [State] representing whether this component is being hovered or not
+ */
+@Composable
+fun InteractionSource.collectIsHoveredAsState(): State<Boolean> {
+    val isHovered = remember { mutableStateOf(false) }
+    LaunchedEffect(this) {
+        val hoverInteractions = mutableListOf<HoverInteraction.Enter>()
+        interactions.collect { interaction ->
+            when (interaction) {
+                is HoverInteraction.Enter -> hoverInteractions.add(interaction)
+                is HoverInteraction.Exit -> hoverInteractions.remove(interaction.enter)
+            }
+            isHovered.value = hoverInteractions.isNotEmpty()
+        }
+    }
+    return isHovered
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/IntervalList.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/IntervalList.kt
index eedce14..8e2f14d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/IntervalList.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/IntervalList.kt
@@ -22,9 +22,15 @@
     val content: T
 )
 
-internal class IntervalList<T> {
-    private val intervals = mutableListOf<IntervalHolder<T>>()
-    internal var totalSize = 0
+internal interface IntervalList<T> {
+    val intervals: List<IntervalHolder<T>>
+    val totalSize: Int
+}
+
+internal class MutableIntervalList<T> : IntervalList<T> {
+    private val _intervals = mutableListOf<IntervalHolder<T>>()
+    override val intervals: List<IntervalHolder<T>> = _intervals
+    override var totalSize = 0
         private set
 
     fun add(size: Int, content: T) {
@@ -38,44 +44,47 @@
             content = content
         )
         totalSize += size
-        intervals.add(interval)
+        _intervals.add(interval)
+    }
+}
+
+internal fun <T> IntervalList<T>.intervalForIndex(index: Int) =
+    intervals[intervalIndexForItemIndex(index)]
+
+internal fun <T> IntervalList<T>.intervalIndexForItemIndex(index: Int) =
+    if (index < 0 || index >= totalSize) {
+        throw IndexOutOfBoundsException("Index $index, size $totalSize")
+    } else {
+        findIndexOfHighestValueLesserThan(intervals, index)
     }
 
-    fun intervalForIndex(index: Int) =
-        if (index < 0 || index >= totalSize) {
-            throw IndexOutOfBoundsException("Index $index, size $totalSize")
-        } else {
-            intervals[findIndexOfHighestValueLesserThan(intervals, index)]
+/**
+ * Finds the index of the [list] which contains the highest value of [IntervalHolder.startIndex]
+ * that is less than or equal to the given [value].
+ */
+private fun <T> findIndexOfHighestValueLesserThan(list: List<IntervalHolder<T>>, value: Int): Int {
+    var left = 0
+    var right = list.lastIndex
+
+    while (left < right) {
+        val middle = left + (right - left) / 2
+
+        val middleValue = list[middle].startIndex
+        if (middleValue == value) {
+            return middle
         }
 
-    /**
-     * Finds the index of the [list] which contains the highest value of [IntervalHolder.startIndex]
-     * that is less than or equal to the given [value].
-     */
-    private fun findIndexOfHighestValueLesserThan(list: List<IntervalHolder<T>>, value: Int): Int {
-        var left = 0
-        var right = list.lastIndex
+        if (middleValue < value) {
+            left = middle + 1
 
-        while (left < right) {
-            val middle = left + (right - left) / 2
-
-            val middleValue = list[middle].startIndex
-            if (middleValue == value) {
+            // Verify that the left will not be bigger than our value
+            if (value < list[left].startIndex) {
                 return middle
             }
-
-            if (middleValue < value) {
-                left = middle + 1
-
-                // Verify that the left will not be bigger than our value
-                if (value < list[left].startIndex) {
-                    return middle
-                }
-            } else {
-                right = middle - 1
-            }
+        } else {
+            right = middle - 1
         }
-
-        return left
     }
+
+    return left
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
index 8603749..b1af308 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
@@ -259,11 +259,3 @@
         content = content
     )
 }
-
-/**
- * This should create an object meeting following requirements:
- * 1) objects created for the same index are equals and never equals for different indexes
- * 2) this class is saveable via a default SaveableStateRegistry on the platform
- * 3) this objects can't be equals to any object which could be provided by a user as a custom key
- */
-internal expect fun getDefaultLazyKeyFor(index: Int): Any
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyGrid.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyGrid.kt
index 4874764..ba75f96 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyGrid.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyGrid.kt
@@ -227,7 +227,7 @@
 
 @ExperimentalFoundationApi
 internal class LazyGridScopeImpl : LazyGridScope {
-    private val intervals = IntervalList<LazyItemScope.(Int) -> (@Composable () -> Unit)>()
+    private val intervals = MutableIntervalList<LazyItemScope.(Int) -> (@Composable () -> Unit)>()
 
     val totalSize get() = intervals.totalSize
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
index 64cccaf..a9faf6f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.foundation.lazy
 
-import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.assertNotNestingScrollableContainers
 import androidx.compose.foundation.clipScrollableContainer
 import androidx.compose.foundation.gestures.FlingBehavior
@@ -35,10 +34,8 @@
 import androidx.compose.foundation.lazy.layout.rememberLazyLayoutState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
-import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Size
@@ -78,7 +75,7 @@
 
     val itemScope: Ref<LazyItemScopeImpl> = remember { Ref() }
 
-    val stateOfItemsProvider = rememberStateOfItemsProvider(content, itemScope)
+    val stateOfItemsProvider = rememberStateOfItemsProvider(state, content, itemScope)
 
     val measurePolicy = rememberLazyListMeasurePolicy(
         stateOfItemsProvider,
@@ -139,78 +136,6 @@
 }
 
 @Composable
-private fun rememberStateOfItemsProvider(
-    content: LazyListScope.() -> Unit,
-    itemScope: Ref<LazyItemScopeImpl>
-): State<LazyListItemsProvider> {
-    val latestContent = rememberUpdatedState(content)
-    return remember {
-        derivedStateOf { LazyListScopeImpl(itemScope).apply(latestContent.value) }
-    }
-}
-
-internal class LazyListScopeImpl(
-    private val itemScope: Ref<LazyItemScopeImpl>
-) : LazyListScope, LazyListItemsProvider {
-    private val intervals = IntervalList<IntervalContent>()
-    override val itemsCount get() = intervals.totalSize
-    private var _headerIndexes: MutableList<Int>? = null
-    override val headerIndexes: List<Int> get() = _headerIndexes ?: emptyList()
-
-    override fun getKey(index: Int): Any {
-        val interval = intervals.intervalForIndex(index)
-        val localIntervalIndex = index - interval.startIndex
-        val key = interval.content.key?.invoke(localIntervalIndex)
-        return key ?: getDefaultLazyKeyFor(index)
-    }
-
-    override fun getContent(index: Int): @Composable () -> Unit {
-        val interval = intervals.intervalForIndex(index)
-        val localIntervalIndex = index - interval.startIndex
-        return interval.content.content.invoke(itemScope.value!!, localIntervalIndex)
-    }
-
-    override fun items(
-        count: Int,
-        key: ((index: Int) -> Any)?,
-        itemContent: @Composable LazyItemScope.(index: Int) -> Unit
-    ) {
-        intervals.add(
-            count,
-            IntervalContent(
-                key = key,
-                content = { index -> @Composable { itemContent(index) } }
-            )
-        )
-    }
-
-    override fun item(key: Any?, content: @Composable LazyItemScope.() -> Unit) {
-        intervals.add(
-            1,
-            IntervalContent(
-                key = if (key != null) { _: Int -> key } else null,
-                content = { @Composable { content() } }
-            )
-        )
-    }
-
-    @ExperimentalFoundationApi
-    override fun stickyHeader(key: Any?, content: @Composable LazyItemScope.() -> Unit) {
-        val headersIndexes = _headerIndexes ?: mutableListOf<Int>().also {
-            _headerIndexes = it
-        }
-        headersIndexes.add(itemsCount)
-
-        item(key, content)
-    }
-}
-
-internal class IntervalContent(
-    val key: ((index: Int) -> Any)?,
-    val content: LazyItemScope.(index: Int) -> @Composable () -> Unit
-)
-
-@Composable
 private fun rememberLazyListMeasurePolicy(
     /** State containing the items provider of the list. */
     stateOfItemsProvider: State<LazyListItemsProvider>,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListHeaders.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListHeaders.kt
index 044999d..2dbeee8 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListHeaders.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListHeaders.kt
@@ -16,7 +16,7 @@
 
 package androidx.compose.foundation.lazy
 
-import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastForEachIndexed
 
 /**
  * This method finds the sticky header in composedItems list or composes the header item if needed.
@@ -28,12 +28,13 @@
  * @param startContentPadding the padding before the first item in the list
  */
 internal fun findOrComposeLazyListHeader(
-    composedVisibleItems: MutableList<LazyMeasuredItem>,
+    composedVisibleItems: MutableList<LazyListPositionedItem>,
     itemProvider: LazyMeasuredItemProvider,
     headerIndexes: List<Int>,
-    startContentPadding: Int
-): LazyMeasuredItem? {
-    var alreadyVisibleHeaderItem: LazyMeasuredItem? = null
+    startContentPadding: Int,
+    layoutWidth: Int,
+    layoutHeight: Int,
+): LazyListPositionedItem? {
     var currentHeaderOffset: Int = Int.MIN_VALUE
     var nextHeaderOffset: Int = Int.MIN_VALUE
 
@@ -52,9 +53,10 @@
         }
     }
 
-    composedVisibleItems.fastForEach { item ->
+    var indexInComposedVisibleItems = -1
+    composedVisibleItems.fastForEachIndexed { index, item ->
         if (item.index == currentHeaderListPosition) {
-            alreadyVisibleHeaderItem = item
+            indexInComposedVisibleItems = index
             currentHeaderOffset = item.offset
         } else {
             if (item.index == nextHeaderListPosition) {
@@ -68,10 +70,7 @@
         return null
     }
 
-    val headerItem = alreadyVisibleHeaderItem
-        ?: itemProvider.getAndMeasure(DataIndex(currentHeaderListPosition)).also {
-            composedVisibleItems.add(0, it)
-        }
+    val measuredHeaderItem = itemProvider.getAndMeasure(DataIndex(currentHeaderListPosition))
 
     var headerOffset = if (currentHeaderOffset != Int.MIN_VALUE) {
         maxOf(-startContentPadding, currentHeaderOffset)
@@ -81,9 +80,14 @@
     // if we have a next header overlapping with the current header, the next one will be
     // pushing the current one away from the viewport.
     if (nextHeaderOffset != Int.MIN_VALUE) {
-        headerOffset = minOf(headerOffset, nextHeaderOffset - headerItem.size)
+        headerOffset = minOf(headerOffset, nextHeaderOffset - measuredHeaderItem.size)
     }
 
-    headerItem.offset = headerOffset
-    return headerItem
+    return measuredHeaderItem.position(headerOffset, layoutWidth, layoutHeight).also {
+        if (indexInComposedVisibleItems != -1) {
+            composedVisibleItems[indexInComposedVisibleItems] = it
+        } else {
+            composedVisibleItems.add(0, it)
+        }
+    }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemsProviderImpl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemsProviderImpl.kt
new file mode 100644
index 0000000..e704e24
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemsProviderImpl.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.node.Ref
+import kotlinx.coroutines.flow.collect
+
+@Composable
+internal fun rememberStateOfItemsProvider(
+    state: LazyListState,
+    content: LazyListScope.() -> Unit,
+    itemScope: Ref<LazyItemScopeImpl>
+): State<LazyListItemsProvider> {
+    val latestContent = rememberUpdatedState(content)
+    val nearestItemsRangeState = remember(state) {
+        mutableStateOf(
+            calculateNearestItemsRange(state.firstVisibleItemIndexNonObservable.value)
+        )
+    }
+    LaunchedEffect(nearestItemsRangeState) {
+        snapshotFlow { calculateNearestItemsRange(state.firstVisibleItemIndex) }
+            // MutableState's SnapshotMutationPolicy will make sure the provider is only
+            // recreated when the state is updated with a new range.
+            .collect { nearestItemsRangeState.value = it }
+    }
+    return remember(nearestItemsRangeState) {
+        derivedStateOf<LazyListItemsProvider> {
+            val listScope = LazyListScopeImpl().apply(latestContent.value)
+            LazyListItemsProviderImpl(
+                itemScope,
+                listScope.intervals,
+                listScope.headerIndexes,
+                nearestItemsRangeState.value
+            )
+        }
+    }
+}
+
+internal class LazyListItemsProviderImpl(
+    private val itemScope: Ref<LazyItemScopeImpl>,
+    private val list: IntervalList<LazyListIntervalContent>,
+    override val headerIndexes: List<Int>,
+    nearestItemsRange: IntRange
+) : LazyListItemsProvider {
+    override val itemsCount get() = list.totalSize
+
+    override fun getKey(index: Int): Any {
+        val interval = list.intervalForIndex(index)
+        val localIntervalIndex = index - interval.startIndex
+        val key = interval.content.key?.invoke(localIntervalIndex)
+        return key ?: getDefaultLazyKeyFor(index)
+    }
+
+    override fun getContent(index: Int): @Composable () -> Unit {
+        val interval = list.intervalForIndex(index)
+        val localIntervalIndex = index - interval.startIndex
+        return interval.content.content.invoke(itemScope.value!!, localIntervalIndex)
+    }
+
+    override val keyToIndexMap: Map<Any, Int> = generateKeyToIndexMap(nearestItemsRange, list)
+}
+
+/**
+ * This should create an object meeting following requirements:
+ * 1) objects created for the same index are equals and never equals for different indexes
+ * 2) this class is saveable via a default SaveableStateRegistry on the platform
+ * 3) this objects can't be equals to any object which could be provided by a user as a custom key
+ */
+internal expect fun getDefaultLazyKeyFor(index: Int): Any
+
+/**
+ * Traverses the interval [list] in order to create a mapping from the key to the index for all
+ * the indexes in the passed [range].
+ * The returned map will not contain the values for intervals with no key mapping provided.
+ */
+private fun generateKeyToIndexMap(
+    range: IntRange,
+    list: IntervalList<LazyListIntervalContent>
+): Map<Any, Int> {
+    val first = range.first
+    check(first >= 0)
+    val last = minOf(range.last, list.totalSize - 1)
+    return if (last < first) {
+        emptyMap()
+    } else {
+        hashMapOf<Any, Int>().also { map ->
+            var intervalIndex = list.intervalIndexForItemIndex(first)
+            var itemIndex = first
+            while (itemIndex <= last) {
+                val interval = list.intervals[intervalIndex]
+                val keyFactory = interval.content.key
+                if (keyFactory != null) {
+                    val localItemIndex = itemIndex - interval.startIndex
+                    if (localItemIndex == interval.size) {
+                        intervalIndex++
+                    } else {
+                        map[keyFactory(localItemIndex)] = itemIndex
+                        itemIndex++
+                    }
+                } else {
+                    intervalIndex++
+                    itemIndex = interval.startIndex + interval.size
+                }
+            }
+        }
+    }
+}
+
+/**
+ * Returns a range of indexes which contains at least [ExtraItemsNearTheSlidingWindow] items near
+ * the first visible item. It is optimized to return the same range for small changes in the
+ * firstVisibleItem value so we do not regenerate the map on each scroll.
+ */
+private fun calculateNearestItemsRange(firstVisibleItem: Int): IntRange {
+    val slidingWindowStart = VisibleItemsSlidingWindowSize *
+        (firstVisibleItem / VisibleItemsSlidingWindowSize)
+
+    val start = maxOf(slidingWindowStart - ExtraItemsNearTheSlidingWindow, 0)
+    val end = slidingWindowStart + VisibleItemsSlidingWindowSize + ExtraItemsNearTheSlidingWindow
+    return start until end
+}
+
+/**
+ * We use the idea of sliding window as an optimization, so user can scroll up to this number of
+ * items until we have to regenerate the key to index map.
+ */
+private val VisibleItemsSlidingWindowSize = 30
+
+/**
+ * The minimum amount of items near the current first visible item we want to have mapping for.
+ */
+private val ExtraItemsNearTheSlidingWindow = 100
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
index 3da26e5..e58d28f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
@@ -209,9 +209,10 @@
         val layoutHeight =
             constraints.constrainHeight(if (isVertical) mainAxisUsed else maxCrossAxis)
 
-        calculateItemsOffsets(
+        val positionedItems = calculateItemsOffsets(
             items = visibleItems,
-            mainAxisLayoutSize = if (isVertical) layoutHeight else layoutWidth,
+            layoutWidth = layoutWidth,
+            layoutHeight = layoutHeight,
             usedMainAxisSize = mainAxisUsed,
             itemsScrollOffset = visibleItemsScrollOffset,
             isVertical = isVertical,
@@ -224,10 +225,12 @@
 
         val headerItem = if (headerIndexes.isNotEmpty()) {
             findOrComposeLazyListHeader(
-                composedVisibleItems = visibleItems,
+                composedVisibleItems = positionedItems,
                 itemProvider = itemProvider,
                 headerIndexes = headerIndexes,
-                startContentPadding = startContentPadding
+                startContentPadding = startContentPadding,
+                layoutWidth = layoutWidth,
+                layoutHeight = layoutHeight
             )
         } else {
             null
@@ -241,17 +244,17 @@
             canScrollForward = mainAxisUsed > maxOffset,
             consumedScroll = consumedScroll,
             measureResult = layout(layoutWidth, layoutHeight) {
-                visibleItems.fastForEach {
+                positionedItems.fastForEach {
                     if (it !== headerItem) {
-                        it.place(this, layoutWidth, layoutHeight)
+                        it.place(this)
                     }
                 }
                 // the header item should be placed (drawn) after all other items
-                headerItem?.place(this, layoutWidth, layoutHeight)
+                headerItem?.place(this)
             },
             viewportStartOffset = -startContentPadding,
             viewportEndOffset = maximumVisibleOffset,
-            visibleItemsInfo = visibleItems,
+            visibleItemsInfo = positionedItems,
             totalItemsCount = itemsCount,
         )
     }
@@ -262,7 +265,8 @@
  */
 private fun calculateItemsOffsets(
     items: List<LazyMeasuredItem>,
-    mainAxisLayoutSize: Int,
+    layoutWidth: Int,
+    layoutHeight: Int,
     usedMainAxisSize: Int,
     itemsScrollOffset: Int,
     isVertical: Boolean,
@@ -271,12 +275,15 @@
     reverseLayout: Boolean,
     density: Density,
     layoutDirection: LayoutDirection
-) {
+): MutableList<LazyListPositionedItem> {
+    val mainAxisLayoutSize = if (isVertical) layoutHeight else layoutWidth
     val hasSpareSpace = usedMainAxisSize < mainAxisLayoutSize
     if (hasSpareSpace) {
         check(itemsScrollOffset == 0)
     }
 
+    val positionedItems = ArrayList<LazyListPositionedItem>(items.size)
+
     if (hasSpareSpace) {
         val itemsCount = items.size
         val sizes = IntArray(itemsCount) { index ->
@@ -301,13 +308,15 @@
             } else {
                 absoluteOffset
             }
-            item.offset = relativeOffset
+            val addIndex = if (reverseLayout) 0 else positionedItems.size
+            positionedItems.add(addIndex, item.position(relativeOffset, layoutWidth, layoutHeight))
         }
     } else {
         var currentMainAxis = itemsScrollOffset
         items.fastForEach {
-            it.offset = currentMainAxis
+            positionedItems.add(it.position(currentMainAxis, layoutWidth, layoutHeight))
             currentMainAxis += it.sizeWithSpacings
         }
     }
+    return positionedItems
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListScopeImpl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListScopeImpl.kt
new file mode 100644
index 0000000..4e9771e
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListScopeImpl.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.runtime.Composable
+
+internal class LazyListScopeImpl : LazyListScope {
+
+    private val _intervals = MutableIntervalList<LazyListIntervalContent>()
+    val intervals: IntervalList<LazyListIntervalContent> = _intervals
+
+    private var _headerIndexes: MutableList<Int>? = null
+    val headerIndexes: List<Int> get() = _headerIndexes ?: emptyList()
+
+    override fun items(
+        count: Int,
+        key: ((index: Int) -> Any)?,
+        itemContent: @Composable LazyItemScope.(index: Int) -> Unit
+    ) {
+        _intervals.add(
+            count,
+            LazyListIntervalContent(
+                key = key,
+                content = { index -> @Composable { itemContent(index) } }
+            )
+        )
+    }
+
+    override fun item(key: Any?, content: @Composable LazyItemScope.() -> Unit) {
+        _intervals.add(
+            1,
+            LazyListIntervalContent(
+                key = if (key != null) { _: Int -> key } else null,
+                content = { @Composable { content() } }
+            )
+        )
+    }
+
+    @ExperimentalFoundationApi
+    override fun stickyHeader(key: Any?, content: @Composable LazyItemScope.() -> Unit) {
+        val headersIndexes = _headerIndexes ?: mutableListOf<Int>().also {
+            _headerIndexes = it
+        }
+        headersIndexes.add(_intervals.totalSize)
+
+        item(key, content)
+    }
+}
+
+internal class LazyListIntervalContent(
+    val key: ((index: Int) -> Any)?,
+    val content: LazyItemScope.(index: Int) -> @Composable () -> Unit
+)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListScrollPosition.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListScrollPosition.kt
index bd2762f..858a091 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListScrollPosition.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListScrollPosition.kt
@@ -121,30 +121,17 @@
                 // there were no real item during the previous measure
                 return lastKnownIndex
             }
-            val totalCount = itemsProvider.itemsCount
-            if (lastKnownIndex.value < totalCount &&
+            if (lastKnownIndex.value < itemsProvider.itemsCount &&
                 key == itemsProvider.getKey(lastKnownIndex.value)
             ) {
                 // this item is still at the same index
                 return lastKnownIndex
             }
-            // lets try to find where this item was moved
-            var before = minOf(totalCount - 1, lastKnownIndex.value - 1)
-            var after = lastKnownIndex.value + 1
-            while (before >= 0 || after < totalCount) {
-                if (before >= 0) {
-                    if (key == itemsProvider.getKey(before)) {
-                        return DataIndex(before)
-                    }
-                    before--
-                }
-                if (after < totalCount) {
-                    if (key == itemsProvider.getKey(after)) {
-                        return DataIndex(after)
-                    }
-                    after++
-                }
+            val newIndex = itemsProvider.keyToIndexMap[key]
+            if (newIndex != null) {
+                return DataIndex(newIndex)
             }
+            // fallback to the previous index if we don't know the new index of the item
             return lastKnownIndex
         }
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyMeasuredItem.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyMeasuredItem.kt
index 65f4ead..1e1f276 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyMeasuredItem.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyMeasuredItem.kt
@@ -16,17 +16,20 @@
 
 package androidx.compose.foundation.lazy
 
+import androidx.compose.foundation.lazy.layout.LazyLayoutPlaceable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.util.fastForEach
 
 /**
  * Represents one measured item of the lazy list. It can in fact consist of multiple placeables
  * if the user emit multiple layout nodes in the item callback.
  */
 internal class LazyMeasuredItem(
-    override val index: Int,
-    private val placeables: Array<Placeable>,
+    val index: Int,
+    private val placeables: Array<LazyLayoutPlaceable>,
     private val isVertical: Boolean,
     private val horizontalAlignment: Alignment.Horizontal?,
     private val verticalAlignment: Alignment.Vertical?,
@@ -39,12 +42,12 @@
      * is usually representing the spacing after the item.
      */
     private val spacing: Int,
-    override val key: Any
-) : LazyListItemInfo {
+    val key: Any
+) {
     /**
      * Sum of the main axis sizes of all the inner placeables.
      */
-    override val size: Int
+    val size: Int
 
     /**
      * Sum of the main axis sizes of all the inner placeables and [spacing].
@@ -56,14 +59,14 @@
      */
     val crossAxisSize: Int
 
-    override var offset: Int = 0
-
     init {
         var mainAxisSize = 0
         var maxCrossAxis = 0
         placeables.forEach {
-            mainAxisSize += if (isVertical) it.height else it.width
-            maxCrossAxis = maxOf(maxCrossAxis, if (!isVertical) it.height else it.width)
+            val placeable = it.placeable
+            mainAxisSize += if (isVertical) placeable.height else placeable.width
+            maxCrossAxis =
+                maxOf(maxCrossAxis, if (!isVertical) placeable.height else placeable.width)
         }
         size = mainAxisSize
         sizeWithSpacings = size + spacing
@@ -71,17 +74,18 @@
     }
 
     /**
-     * Perform placing for all the inner placeables at [offset] main axis position. [layoutWidth]
+     * Calculates positions for the inner placeables at [offset] main axis position. [layoutWidth]
      * and [layoutHeight] should be provided to not place placeables which are ended up outside of
      * the viewport (for example one item consist of 2 placeables, and the first one is not going
      * to be visible, so we don't place it as an optimization, but place the second one).
      * If [reverseOrder] is true the inner placeables would be placed in the inverted order.
      */
-    fun place(
-        scope: Placeable.PlacementScope,
+    fun position(
+        offset: Int,
         layoutWidth: Int,
         layoutHeight: Int
-    ) = with(scope) {
+    ): LazyListPositionedItem {
+        val wrappers = mutableListOf<LazyListPlaceableWrapper>()
         val mainAxisLayoutSize = if (isVertical) layoutHeight else layoutWidth
         var mainAxisOffset = if (reverseLayout) {
             mainAxisLayoutSize - offset - size
@@ -90,26 +94,73 @@
         }
         var index = if (reverseLayout) placeables.lastIndex else 0
         while (if (reverseLayout) index >= 0 else index < placeables.size) {
-            val it = placeables[index]
-            if (reverseLayout) index-- else index++
-            if (isVertical) {
+            val it = placeables[index].placeable
+            val addIndex = if (reverseLayout) 0 else wrappers.size
+            val placeableOffset = if (isVertical) {
                 val x = requireNotNull(horizontalAlignment)
                     .align(it.width, layoutWidth, layoutDirection)
-                if (mainAxisOffset + it.height > -startContentPadding &&
-                    mainAxisOffset < layoutHeight + endContentPadding
-                ) {
-                    it.placeWithLayer(x, mainAxisOffset)
-                }
-                mainAxisOffset += it.height
+                IntOffset(x, mainAxisOffset)
             } else {
                 val y = requireNotNull(verticalAlignment).align(it.height, layoutHeight)
-                if (mainAxisOffset + it.width > -startContentPadding &&
-                    mainAxisOffset < layoutWidth + endContentPadding
-                ) {
-                    it.placeRelativeWithLayer(mainAxisOffset, y)
+                IntOffset(mainAxisOffset, y)
+            }
+            mainAxisOffset += if (isVertical) it.height else it.width
+            wrappers.add(
+                addIndex,
+                LazyListPlaceableWrapper(placeableOffset, it, placeables[index].parentData)
+            )
+            if (reverseLayout) index-- else index++
+        }
+        return LazyListPositionedItem(
+            offset = offset,
+            index = this.index,
+            key = key,
+            size = size,
+            sizeWithSpacings = sizeWithSpacings,
+            minMainAxisOffset = -startContentPadding,
+            maxMainAxisOffset = mainAxisLayoutSize + endContentPadding,
+            isVertical = isVertical,
+            wrappers = wrappers
+        )
+    }
+}
+
+internal class LazyListPositionedItem(
+    override val offset: Int,
+    override val index: Int,
+    override val key: Any,
+    override val size: Int,
+    val sizeWithSpacings: Int,
+    private val minMainAxisOffset: Int,
+    private val maxMainAxisOffset: Int,
+    private val isVertical: Boolean,
+    private val wrappers: List<LazyListPlaceableWrapper>
+) : LazyListItemInfo {
+
+    fun place(
+        scope: Placeable.PlacementScope,
+    ) = with(scope) {
+        wrappers.fastForEach { wrapper ->
+            val offset = wrapper.offset
+            val placeable = wrapper.placeable
+            if (offset.mainAxis + placeable.mainAxisSize > minMainAxisOffset &&
+                offset.mainAxis < maxMainAxisOffset
+            ) {
+                if (isVertical) {
+                    placeable.placeWithLayer(offset)
+                } else {
+                    placeable.placeRelativeWithLayer(offset)
                 }
-                mainAxisOffset += it.width
             }
         }
     }
+
+    private val IntOffset.mainAxis get() = if (isVertical) y else x
+    private val Placeable.mainAxisSize get() = if (isVertical) height else width
 }
+
+internal class LazyListPlaceableWrapper(
+    val offset: IntOffset,
+    val placeable: Placeable,
+    val parentData: Any?
+)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyMeasuredItemProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyMeasuredItemProvider.kt
index c76c48c..eda6892 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyMeasuredItemProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyMeasuredItemProvider.kt
@@ -16,8 +16,8 @@
 
 package androidx.compose.foundation.lazy
 
+import androidx.compose.foundation.lazy.layout.LazyLayoutPlaceable
 import androidx.compose.foundation.lazy.layout.LazyLayoutPlaceablesProvider
-import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.unit.Constraints
 
 /**
@@ -45,9 +45,19 @@
         val placeables = placeablesProvider.getAndMeasure(index.value, childConstraints)
         return measuredItemFactory.createItem(index, key, placeables)
     }
+
+    /**
+     * Contains the mapping between the key and the index. It could contain not all the items of
+     * the list as an optimization.
+     **/
+    val keyToIndexMap: Map<Any, Int> get() = itemsProvider.keyToIndexMap
 }
 
 // This interface allows to avoid autoboxing on index param
 internal fun interface MeasuredItemFactory {
-    fun createItem(index: DataIndex, key: Any, placeables: Array<Placeable>): LazyMeasuredItem
+    fun createItem(
+        index: DataIndex,
+        key: Any,
+        placeables: Array<LazyLayoutPlaceable>
+    ): LazyMeasuredItem
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory.kt
index 2e23171..3f8190f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory.kt
@@ -25,17 +25,14 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
-import androidx.compose.ui.util.fastForEach
 
 @Composable
 internal fun rememberItemContentFactory(state: LazyLayoutState): LazyLayoutItemContentFactory {
     val saveableStateHolder = rememberSaveableStateHolder()
     val itemsProvider = state.itemsProvider
-    val factory = remember(itemsProvider) {
+    return remember(itemsProvider) {
         LazyLayoutItemContentFactory(saveableStateHolder, itemsProvider)
     }
-    factory.updateKeyIndexMappingForVisibleItems(state)
-    return factory
 }
 
 /**
@@ -61,24 +58,6 @@
     private var constraintsOfCachedLambdas = Constraints()
 
     /**
-     * We iterate through the currently composed keys and update the associated indexes so we can
-     * smartly handle reorderings. If we will not do it and just wait for the next remeasure the
-     * item could be recomposed before it and switch to start displaying the wrong item.
-     */
-    fun updateKeyIndexMappingForVisibleItems(state: LazyLayoutState) {
-        val itemsProvider = itemsProvider()
-        val itemsCount = itemsProvider.itemsCount
-        if (itemsCount > 0) {
-            state.layoutInfoNonObservable.visibleItemsInfo.fastForEach {
-                if (it.index < itemsCount) {
-                    val key = itemsProvider.getKey(it.index)
-                    lambdasCache[key]?.index = it.index
-                }
-            }
-        }
-    }
-
-    /**
      * Invalidate the cached lambas if the density or constraints have changed.
      * TODO(popam): probably LazyLayoutState should provide an invalidate() method instead.
      */
@@ -95,7 +74,7 @@
      */
     fun getContent(index: Int, key: Any): @Composable () -> Unit {
         val cachedContent = lambdasCache[key]
-        return if (cachedContent != null && cachedContent.index == index) {
+        return if (cachedContent != null && cachedContent.lastKnownIndex == index) {
             cachedContent.content
         } else {
             val newContent = CachedItemContent(index, key)
@@ -108,10 +87,14 @@
         initialIndex: Int,
         val key: Any
     ) {
-        var index by mutableStateOf(initialIndex)
+        var lastKnownIndex by mutableStateOf(initialIndex)
+            private set
 
         val content: @Composable () -> Unit = @Composable {
             val itemsProvider = itemsProvider()
+            val index = itemsProvider.keyToIndexMap[key]?.also {
+                lastKnownIndex = it
+            } ?: lastKnownIndex
             if (index < itemsProvider.itemsCount) {
                 val key = itemsProvider.getKey(index)
                 if (key == this.key) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemsProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemsProvider.kt
index fe7a75e7..ed553c5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemsProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemsProvider.kt
@@ -27,4 +27,10 @@
 
     /** Returns the key for the item on this index */
     fun getKey(index: Int): Any
+
+    /**
+     * Contains the mapping between the key and the index. It could contain not all the items of
+     * the list as an optimization.
+     **/
+    val keyToIndexMap: Map<Any, Int>
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPlaceable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPlaceable.kt
new file mode 100644
index 0000000..d4f77ac
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPlaceable.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy.layout
+
+import androidx.compose.ui.layout.Placeable
+
+internal class LazyLayoutPlaceable(
+    val placeable: Placeable,
+    val parentData: Any?
+)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutState.kt
index f92a348..6bcb66f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutState.kt
@@ -101,4 +101,7 @@
     override val itemsCount = 0
 
     override fun getKey(index: Int): Any = error("No items")
+
+    override val keyToIndexMap: Map<Any, Int>
+        get() = error("No items")
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyMeasurePolicy.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyMeasurePolicy.kt
index 1d76738..03cecb4 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyMeasurePolicy.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyMeasurePolicy.kt
@@ -18,7 +18,6 @@
 
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.layout.MeasureScope
-import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.layout.SubcomposeMeasureScope
 import androidx.compose.ui.unit.Constraints
 
@@ -44,12 +43,12 @@
      * A cache of the previously composed items. It allows us to support [get]
      * re-executions with the same index during the same measure pass.
      */
-    private val placeablesCache = hashMapOf<Int, Array<Placeable>>()
+    private val placeablesCache = hashMapOf<Int, Array<LazyLayoutPlaceable>>()
 
     /**
      * Used to subcompose and measure the items of lazy layout.
      */
-    fun getAndMeasure(index: Int, constraints: Constraints): Array<Placeable> {
+    fun getAndMeasure(index: Int, constraints: Constraints): Array<LazyLayoutPlaceable> {
         val cachedPlaceable = placeablesCache[index]
         return if (cachedPlaceable != null) {
             cachedPlaceable
@@ -57,8 +56,9 @@
             val key = itemsProvider.getKey(index)
             val itemContent = itemContentFactory.getContent(index, key)
             val measurables = subcomposeMeasureScope.subcompose(key, itemContent)
-            Array(measurables.size) {
-                measurables[it].measure(constraints)
+            Array(measurables.size) { i ->
+                val measurable = measurables[i]
+                LazyLayoutPlaceable(measurable.measure(constraints), measurable.parentData)
             }.also {
                 placeablesCache[index] = it
             }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt
index 7fff3e0..8d79b16 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt
@@ -21,6 +21,7 @@
 import androidx.compose.foundation.LocalIndication
 import androidx.compose.foundation.gestures.detectTapAndPress
 import androidx.compose.foundation.handlePressInteraction
+import androidx.compose.foundation.hoverable
 import androidx.compose.foundation.indication
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.interaction.PressInteraction
@@ -268,5 +269,6 @@
     this
         .then(semantics)
         .indication(interactionSource, indication)
+        .hoverable(interactionSource = interactionSource)
         .then(gestures)
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
index fcba257..ada1bce 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
@@ -366,7 +366,7 @@
     val semanticsModifier = Modifier.semantics(true) {
         // focused semantics are handled by Modifier.focusable()
         this.imeAction = imeOptions.imeAction
-        this.editableText = value.annotatedString
+        this.editableText = transformedText.text
         this.textSelectionRange = value.selection
         if (!enabled) this.disabled()
         if (isPassword) this.password()
@@ -382,7 +382,21 @@
             onValueChangeWrapper(TextFieldValue(it.text, TextRange(it.text.length)))
             true
         }
-        setSelection { start, end, traversalMode ->
+        setSelection { selectionStart, selectionEnd, traversalMode ->
+            // in traversal mode we get selection from the `textSelectionRange` semantics which is
+            // selection in original text. In non-traversal mode selection comes from the Talkback
+            // and indices are relative to the transformed text
+            val start = if (traversalMode) {
+                selectionStart
+            } else {
+                offsetMapping.transformedToOriginal(selectionStart)
+            }
+            val end = if (traversalMode) {
+                selectionEnd
+            } else {
+                offsetMapping.transformedToOriginal(selectionEnd)
+            }
+
             if (!enabled) {
                 false
             } else if (start == value.selection.start && end == value.selection.end) {
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.desktop.kt
index ac939cd..b4caf02 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.desktop.kt
@@ -25,6 +25,7 @@
 import androidx.compose.foundation.gestures.scrollBy
 import androidx.compose.foundation.interaction.DragInteraction
 import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.collectIsHoveredAsState
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.foundation.shape.RoundedCornerShape
@@ -46,7 +47,6 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.input.pointer.PointerEventType
 import androidx.compose.ui.input.pointer.consumePositionChange
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.input.pointer.positionChange
@@ -190,7 +190,6 @@
 )
 
 // TODO(demin): do we need to stop dragging if cursor is beyond constraints?
-// TODO(demin): add Interaction.Hovered to interactionSource
 @Composable
 private fun Scrollbar(
     adapter: ScrollbarAdapter,
@@ -211,7 +210,7 @@
     }
 
     var containerSize by remember { mutableStateOf(0) }
-    var isHovered by remember { mutableStateOf(false) }
+    val isHovered by interactionSource.collectIsHoveredAsState()
 
     val isHighlighted by remember {
         derivedStateOf {
@@ -253,21 +252,7 @@
             )
         },
         modifier
-            .pointerInput(Unit) {
-                awaitPointerEventScope {
-                    while (true) {
-                        val event = awaitPointerEvent()
-                        when (event.type) {
-                            PointerEventType.Enter -> {
-                                isHovered = true
-                            }
-                            PointerEventType.Exit -> {
-                                isHovered = false
-                            }
-                        }
-                    }
-                }
-            }
+            .hoverable(interactionSource = interactionSource)
             .scrollOnPressOutsideSlider(isVertical, sliderAdapter, adapter, containerSize),
         measurePolicy
     )
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/lazy/IntervalListTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/lazy/MutableIntervalListTest.kt
similarity index 97%
rename from compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/lazy/IntervalListTest.kt
rename to compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/lazy/MutableIntervalListTest.kt
index e3128b1..d909f6a 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/lazy/IntervalListTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/lazy/MutableIntervalListTest.kt
@@ -22,9 +22,9 @@
 import org.junit.runners.JUnit4
 
 @RunWith(JUnit4::class)
-class IntervalListTest {
+class MutableIntervalListTest {
 
-    private val intervalList = IntervalList<Int>()
+    private val intervalList = MutableIntervalList<Int>()
 
     @Test
     fun addOneItem_searchInterval() {
diff --git a/compose/material/material-ripple/build.gradle b/compose/material/material-ripple/build.gradle
index 24c5c5c9..df51954 100644
--- a/compose/material/material-ripple/build.gradle
+++ b/compose/material/material-ripple/build.gradle
@@ -34,7 +34,7 @@
          * When updating dependencies, make sure to make the an an analogous update in the
          * corresponding block below
          */
-        api("androidx.compose.foundation:foundation:1.0.0")
+        api(project(":compose:foundation:foundation"))
         api(project(":compose:runtime:runtime"))
 
         implementation(libs.kotlinStdlibCommon)
diff --git a/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/Ripple.kt b/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/Ripple.kt
index 990d1d3..1ae18ca 100644
--- a/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/Ripple.kt
+++ b/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/Ripple.kt
@@ -23,6 +23,7 @@
 import androidx.compose.foundation.Indication
 import androidx.compose.foundation.IndicationInstance
 import androidx.compose.foundation.interaction.DragInteraction
+import androidx.compose.foundation.interaction.HoverInteraction
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.InteractionSource
 import androidx.compose.foundation.interaction.PressInteraction
@@ -246,8 +247,14 @@
     private var currentInteraction: Interaction? = null
 
     fun handleInteraction(interaction: Interaction, scope: CoroutineScope) {
-        // TODO: handle hover / focus states
+        // TODO: handle focus states
         when (interaction) {
+            is HoverInteraction.Enter -> {
+                interactions.add(interaction)
+            }
+            is HoverInteraction.Exit -> {
+                interactions.remove(interaction.enter)
+            }
             is DragInteraction.Start -> {
                 interactions.add(interaction)
             }
@@ -266,6 +273,7 @@
         if (currentInteraction != newInteraction) {
             if (newInteraction != null) {
                 val targetAlpha = when (interaction) {
+                    is HoverInteraction.Enter -> rippleAlpha.value.hoveredAlpha
                     is DragInteraction.Start -> rippleAlpha.value.draggedAlpha
                     else -> 0f
                 }
@@ -312,26 +320,26 @@
  * @return the [AnimationSpec] used when transitioning to [interaction], either from a previous
  * state, or no state.
  *
- * TODO: handle hover / focus states
+ * TODO: handle focus states
  */
 private fun incomingStateLayerAnimationSpecFor(interaction: Interaction): AnimationSpec<Float> {
-    return if (interaction is DragInteraction.Start) {
-        TweenSpec(durationMillis = 45, easing = LinearEasing)
-    } else {
-        DefaultTweenSpec
+    return when (interaction) {
+        is DragInteraction.Start -> TweenSpec(durationMillis = 45, easing = LinearEasing)
+        is HoverInteraction.Enter -> DefaultTweenSpec
+        else -> DefaultTweenSpec
     }
 }
 
 /**
  * @return the [AnimationSpec] used when transitioning away from [interaction], to no state.
  *
- * TODO: handle hover / focus states
+ * TODO: handle focus states
  */
 private fun outgoingStateLayerAnimationSpecFor(interaction: Interaction?): AnimationSpec<Float> {
-    return if (interaction is DragInteraction.Start) {
-        TweenSpec(durationMillis = 150, easing = LinearEasing)
-    } else {
-        DefaultTweenSpec
+    return when (interaction) {
+        is DragInteraction.Start -> TweenSpec(durationMillis = 150, easing = LinearEasing)
+        is HoverInteraction.Enter -> DefaultTweenSpec
+        else -> DefaultTweenSpec
     }
 }
 
diff --git a/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/RippleTheme.kt b/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/RippleTheme.kt
index b1b1411..09118d6 100644
--- a/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/RippleTheme.kt
+++ b/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/RippleTheme.kt
@@ -105,7 +105,7 @@
  *
  * @property draggedAlpha the alpha used when the ripple is dragged
  * @property focusedAlpha not currently supported
- * @property hoveredAlpha not currently supported
+ * @property hoveredAlpha the alpha used when the ripple is hovered
  * @property pressedAlpha the alpha used when the ripple is pressed
  */
 @Immutable
diff --git a/compose/material/material/api/current.ignore b/compose/material/material/api/current.ignore
new file mode 100644
index 0000000..557e57e
--- /dev/null
+++ b/compose/material/material/api/current.ignore
@@ -0,0 +1,5 @@
+// Baseline format: 1.0
+RemovedMethod: androidx.compose.material.ButtonDefaults#elevation(float, float, float):
+    Removed method androidx.compose.material.ButtonDefaults.elevation(float,float,float)
+RemovedMethod: androidx.compose.material.FloatingActionButtonDefaults#elevation(float, float):
+    Removed method androidx.compose.material.FloatingActionButtonDefaults.elevation(float,float)
diff --git a/compose/material/material/api/current.txt b/compose/material/material/api/current.txt
index 5f957f6..bc66cd7 100644
--- a/compose/material/material/api/current.txt
+++ b/compose/material/material/api/current.txt
@@ -48,6 +48,8 @@
   }
 
   public final class BadgeKt {
+    method @androidx.compose.runtime.Composable public static void Badge(optional androidx.compose.ui.Modifier modifier, optional long backgroundColor, optional long contentColor, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? content);
+    method @androidx.compose.runtime.Composable public static void BadgedBox(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> badge, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
   }
 
   public final class BottomNavigationDefaults {
@@ -79,7 +81,7 @@
 
   public final class ButtonDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors buttonColors(optional long backgroundColor, optional long contentColor, optional long disabledBackgroundColor, optional long disabledContentColor);
-    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonElevation elevation(optional float defaultElevation, optional float pressedElevation, optional float disabledElevation);
+    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonElevation elevation(optional float defaultElevation, optional float pressedElevation, optional float disabledElevation, optional float hoveredElevation, optional float focusedElevation);
     method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
     method public float getIconSize();
     method public float getIconSpacing();
@@ -272,7 +274,7 @@
   }
 
   public final class FloatingActionButtonDefaults {
-    method @androidx.compose.runtime.Composable public androidx.compose.material.FloatingActionButtonElevation elevation(optional float defaultElevation, optional float pressedElevation);
+    method @androidx.compose.runtime.Composable public androidx.compose.material.FloatingActionButtonElevation elevation(optional float defaultElevation, optional float pressedElevation, optional float hoveredElevation, optional float focusedElevation);
     field public static final androidx.compose.material.FloatingActionButtonDefaults INSTANCE;
   }
 
@@ -339,7 +341,15 @@
   public final class ModalBottomSheetKt {
   }
 
+  public final class NavigationRailDefaults {
+    method public float getElevation();
+    property public final float Elevation;
+    field public static final androidx.compose.material.NavigationRailDefaults INSTANCE;
+  }
+
   public final class NavigationRailKt {
+    method @androidx.compose.runtime.Composable public static void NavigationRail(optional androidx.compose.ui.Modifier modifier, optional long backgroundColor, optional long contentColor, optional float elevation, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? header, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void NavigationRailItem(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> icon, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional boolean alwaysShowLabel, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional long selectedContentColor, optional long unselectedContentColor);
   }
 
   public final class OutlinedTextFieldKt {
@@ -519,6 +529,7 @@
   }
 
   public final class TabKt {
+    method @androidx.compose.runtime.Composable public static void LeadingIconTab(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> text, kotlin.jvm.functions.Function0<kotlin.Unit> icon, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional long selectedContentColor, optional long unselectedContentColor);
     method @androidx.compose.runtime.Composable public static void Tab(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional long selectedContentColor, optional long unselectedContentColor);
     method @androidx.compose.runtime.Composable public static void Tab(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional long selectedContentColor, optional long unselectedContentColor, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
   }
diff --git a/compose/material/material/api/public_plus_experimental_current.txt b/compose/material/material/api/public_plus_experimental_current.txt
index ef18be6..1e5c805 100644
--- a/compose/material/material/api/public_plus_experimental_current.txt
+++ b/compose/material/material/api/public_plus_experimental_current.txt
@@ -72,8 +72,8 @@
   }
 
   public final class BadgeKt {
-    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void Badge(optional androidx.compose.ui.Modifier modifier, optional long backgroundColor, optional long contentColor, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? content);
-    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void BadgedBox(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> badge, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void Badge(optional androidx.compose.ui.Modifier modifier, optional long backgroundColor, optional long contentColor, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? content);
+    method @androidx.compose.runtime.Composable public static void BadgedBox(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> badge, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
   }
 
   @androidx.compose.material.ExperimentalMaterialApi public final class BottomDrawerState extends androidx.compose.material.SwipeableState<androidx.compose.material.BottomDrawerValue> {
@@ -162,7 +162,7 @@
 
   public final class ButtonDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors buttonColors(optional long backgroundColor, optional long contentColor, optional long disabledBackgroundColor, optional long disabledContentColor);
-    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonElevation elevation(optional float defaultElevation, optional float pressedElevation, optional float disabledElevation);
+    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonElevation elevation(optional float defaultElevation, optional float pressedElevation, optional float disabledElevation, optional float hoveredElevation, optional float focusedElevation);
     method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
     method public float getIconSize();
     method public float getIconSpacing();
@@ -402,7 +402,7 @@
   }
 
   public final class FloatingActionButtonDefaults {
-    method @androidx.compose.runtime.Composable public androidx.compose.material.FloatingActionButtonElevation elevation(optional float defaultElevation, optional float pressedElevation);
+    method @androidx.compose.runtime.Composable public androidx.compose.material.FloatingActionButtonElevation elevation(optional float defaultElevation, optional float pressedElevation, optional float hoveredElevation, optional float focusedElevation);
     field public static final androidx.compose.material.FloatingActionButtonDefaults INSTANCE;
   }
 
@@ -497,15 +497,15 @@
     enum_constant public static final androidx.compose.material.ModalBottomSheetValue Hidden;
   }
 
-  @androidx.compose.material.ExperimentalMaterialApi public final class NavigationRailDefaults {
+  public final class NavigationRailDefaults {
     method public float getElevation();
     property public final float Elevation;
     field public static final androidx.compose.material.NavigationRailDefaults INSTANCE;
   }
 
   public final class NavigationRailKt {
-    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void NavigationRail(optional androidx.compose.ui.Modifier modifier, optional long backgroundColor, optional long contentColor, optional float elevation, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? header, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
-    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void NavigationRailItem(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> icon, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional boolean alwaysShowLabel, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional long selectedContentColor, optional long unselectedContentColor);
+    method @androidx.compose.runtime.Composable public static void NavigationRail(optional androidx.compose.ui.Modifier modifier, optional long backgroundColor, optional long contentColor, optional float elevation, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? header, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void NavigationRailItem(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> icon, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional boolean alwaysShowLabel, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional long selectedContentColor, optional long unselectedContentColor);
   }
 
   public final class OutlinedTextFieldKt {
@@ -728,7 +728,7 @@
   }
 
   public final class TabKt {
-    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void LeadingIconTab(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> text, kotlin.jvm.functions.Function0<kotlin.Unit> icon, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional long selectedContentColor, optional long unselectedContentColor);
+    method @androidx.compose.runtime.Composable public static void LeadingIconTab(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> text, kotlin.jvm.functions.Function0<kotlin.Unit> icon, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional long selectedContentColor, optional long unselectedContentColor);
     method @androidx.compose.runtime.Composable public static void Tab(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional long selectedContentColor, optional long unselectedContentColor);
     method @androidx.compose.runtime.Composable public static void Tab(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional long selectedContentColor, optional long unselectedContentColor, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
   }
diff --git a/compose/material/material/api/restricted_current.ignore b/compose/material/material/api/restricted_current.ignore
new file mode 100644
index 0000000..557e57e
--- /dev/null
+++ b/compose/material/material/api/restricted_current.ignore
@@ -0,0 +1,5 @@
+// Baseline format: 1.0
+RemovedMethod: androidx.compose.material.ButtonDefaults#elevation(float, float, float):
+    Removed method androidx.compose.material.ButtonDefaults.elevation(float,float,float)
+RemovedMethod: androidx.compose.material.FloatingActionButtonDefaults#elevation(float, float):
+    Removed method androidx.compose.material.FloatingActionButtonDefaults.elevation(float,float)
diff --git a/compose/material/material/api/restricted_current.txt b/compose/material/material/api/restricted_current.txt
index 5f957f6..bc66cd7 100644
--- a/compose/material/material/api/restricted_current.txt
+++ b/compose/material/material/api/restricted_current.txt
@@ -48,6 +48,8 @@
   }
 
   public final class BadgeKt {
+    method @androidx.compose.runtime.Composable public static void Badge(optional androidx.compose.ui.Modifier modifier, optional long backgroundColor, optional long contentColor, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? content);
+    method @androidx.compose.runtime.Composable public static void BadgedBox(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> badge, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
   }
 
   public final class BottomNavigationDefaults {
@@ -79,7 +81,7 @@
 
   public final class ButtonDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors buttonColors(optional long backgroundColor, optional long contentColor, optional long disabledBackgroundColor, optional long disabledContentColor);
-    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonElevation elevation(optional float defaultElevation, optional float pressedElevation, optional float disabledElevation);
+    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonElevation elevation(optional float defaultElevation, optional float pressedElevation, optional float disabledElevation, optional float hoveredElevation, optional float focusedElevation);
     method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
     method public float getIconSize();
     method public float getIconSpacing();
@@ -272,7 +274,7 @@
   }
 
   public final class FloatingActionButtonDefaults {
-    method @androidx.compose.runtime.Composable public androidx.compose.material.FloatingActionButtonElevation elevation(optional float defaultElevation, optional float pressedElevation);
+    method @androidx.compose.runtime.Composable public androidx.compose.material.FloatingActionButtonElevation elevation(optional float defaultElevation, optional float pressedElevation, optional float hoveredElevation, optional float focusedElevation);
     field public static final androidx.compose.material.FloatingActionButtonDefaults INSTANCE;
   }
 
@@ -339,7 +341,15 @@
   public final class ModalBottomSheetKt {
   }
 
+  public final class NavigationRailDefaults {
+    method public float getElevation();
+    property public final float Elevation;
+    field public static final androidx.compose.material.NavigationRailDefaults INSTANCE;
+  }
+
   public final class NavigationRailKt {
+    method @androidx.compose.runtime.Composable public static void NavigationRail(optional androidx.compose.ui.Modifier modifier, optional long backgroundColor, optional long contentColor, optional float elevation, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? header, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void NavigationRailItem(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> icon, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional boolean alwaysShowLabel, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional long selectedContentColor, optional long unselectedContentColor);
   }
 
   public final class OutlinedTextFieldKt {
@@ -519,6 +529,7 @@
   }
 
   public final class TabKt {
+    method @androidx.compose.runtime.Composable public static void LeadingIconTab(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> text, kotlin.jvm.functions.Function0<kotlin.Unit> icon, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional long selectedContentColor, optional long unselectedContentColor);
     method @androidx.compose.runtime.Composable public static void Tab(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional long selectedContentColor, optional long unselectedContentColor);
     method @androidx.compose.runtime.Composable public static void Tab(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional long selectedContentColor, optional long unselectedContentColor, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
   }
diff --git a/compose/material/material/build.gradle b/compose/material/material/build.gradle
index 4086ced..38af064 100644
--- a/compose/material/material/build.gradle
+++ b/compose/material/material/build.gradle
@@ -122,6 +122,7 @@
                 implementation(libs.dexmakerMockito)
                 implementation(libs.mockitoCore)
                 implementation(libs.mockitoKotlin)
+                implementation(libs.testUiautomator)
             }
         }
     }
diff --git a/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/model/Examples.kt b/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/model/Examples.kt
index de07f8a..768ad9c 100644
--- a/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/model/Examples.kt
+++ b/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/model/Examples.kt
@@ -18,6 +18,9 @@
 
 package androidx.compose.material.catalog.library.model
 
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentWidth
 import androidx.compose.material.catalog.library.util.SampleSourceUrl
 import androidx.compose.material.samples.AlertDialogSample
 import androidx.compose.material.samples.BackdropScaffoldSample
@@ -34,6 +37,7 @@
 import androidx.compose.material.samples.ClickableListItems
 import androidx.compose.material.samples.CompactNavigationRailSample
 import androidx.compose.material.samples.CustomAlertDialogSample
+import androidx.compose.material.samples.ExposedDropdownMenuSample
 import androidx.compose.material.samples.FancyIndicatorContainerTabs
 import androidx.compose.material.samples.FancyIndicatorTabs
 import androidx.compose.material.samples.FancyTabs
@@ -72,6 +76,7 @@
 import androidx.compose.material.samples.StepsSliderSample
 import androidx.compose.material.samples.SwitchSample
 import androidx.compose.material.samples.TextAndIconTabs
+import androidx.compose.material.samples.TextArea
 import androidx.compose.material.samples.TextButtonSample
 import androidx.compose.material.samples.TextFieldSample
 import androidx.compose.material.samples.TextFieldWithErrorState
@@ -86,6 +91,8 @@
 import androidx.compose.material.samples.TwoLineListItems
 import androidx.compose.material.samples.TwoLineRtlLtrListItems
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
 
 data class Example(
     val name: String,
@@ -344,6 +351,13 @@
         sourceUrl = MenusExampleSourceUrl
     ) {
         MenuSample()
+    },
+    Example(
+        name = ::ExposedDropdownMenuSample.name,
+        description = MenusExampleDescription,
+        sourceUrl = MenusExampleSourceUrl
+    ) {
+        ExposedDropdownMenuSample()
     }
 )
 
@@ -638,8 +652,22 @@
         sourceUrl = TextFieldsExampleSourceUrl
     ) {
         TextFieldWithHideKeyboardOnImeAction()
+    },
+    Example(
+        name = ::TextArea.name,
+        description = TextFieldsExampleDescription,
+        sourceUrl = TextFieldsExampleSourceUrl
+    ) {
+       TextArea()
     }
-)
+).map {
+    // By default text field samples are minimal and don't have a `width` modifier to restrict the
+    // width. As a result, they grow horizontally if enough text is typed. To prevent this behavior
+    // in Catalog app the code below restricts the width of every text field sample
+    it.copy(content = {
+        Box(Modifier.wrapContentWidth().width(280.dp)) { it.content() }
+    })
+}
 
 private const val NavigationRailExampleDescription = "Navigation Rail examples"
 private const val NavigationRailExampleSourceUrl = "$SampleSourceUrl/NavigationRailSamples.kt"
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/BadgeDemo.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/BadgeDemo.kt
index 10c0360..2e84fc2 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/BadgeDemo.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/BadgeDemo.kt
@@ -28,7 +28,6 @@
 import androidx.compose.material.BottomNavigationItem
 import androidx.compose.material.Button
 import androidx.compose.material.ButtonDefaults
-import androidx.compose.material.ExperimentalMaterialApi
 import androidx.compose.material.Icon
 import androidx.compose.material.IconButton
 import androidx.compose.material.LeadingIconTab
@@ -54,7 +53,6 @@
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
 
-@OptIn(ExperimentalMaterialApi::class)
 @Composable
 fun BadgeDemo() {
     Column(Modifier.verticalScroll(rememberScrollState())) {
@@ -274,7 +272,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterialApi::class)
 @Composable
 fun LeadingIconTabsWithBadge(
     onTab1BadgeClick: () -> Unit,
@@ -340,7 +337,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterialApi::class)
 @Composable
 private fun DemoBadgedBox(
     badgeText: String?,
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialTextField.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialTextField.kt
index df95df9..83fb12e 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialTextField.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialTextField.kt
@@ -16,13 +16,13 @@
 
 package androidx.compose.material.demos
 
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.requiredHeight
 import androidx.compose.foundation.layout.requiredHeightIn
@@ -30,6 +30,7 @@
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.requiredWidth
+import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.selection.selectable
@@ -49,6 +50,7 @@
 import androidx.compose.material.icons.filled.Info
 import androidx.compose.material.samples.PasswordTextField
 import androidx.compose.material.samples.SimpleOutlinedTextFieldSample
+import androidx.compose.material.samples.TextArea
 import androidx.compose.material.samples.TextFieldSample
 import androidx.compose.material.samples.TextFieldWithErrorState
 import androidx.compose.material.samples.TextFieldWithHelperMessage
@@ -69,7 +71,9 @@
 @Composable
 fun TextFieldsDemo() {
     LazyColumn(
-        modifier = Modifier.fillMaxHeight().width(300.dp)
+        modifier = Modifier.wrapContentSize(Alignment.Center).width(280.dp),
+        verticalArrangement = Arrangement.spacedBy(16.dp),
+        contentPadding = PaddingValues(vertical = 16.dp)
     ) {
         item {
             Text("Password text field")
@@ -107,6 +111,10 @@
             Text("Outlined text field with custom shape")
             CustomShapeOutlinedTextFieldSample()
         }
+        item {
+            Text("Text area")
+            TextArea()
+        }
     }
 }
 
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BadgeSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BadgeSamples.kt
index 78666af..7977d9b 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BadgeSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BadgeSamples.kt
@@ -21,14 +21,12 @@
 import androidx.compose.material.BadgedBox
 import androidx.compose.material.BottomNavigation
 import androidx.compose.material.BottomNavigationItem
-import androidx.compose.material.ExperimentalMaterialApi
 import androidx.compose.material.Icon
 import androidx.compose.material.Text
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Favorite
 import androidx.compose.runtime.Composable
 
-@OptIn(ExperimentalMaterialApi::class)
 @Sampled
 @Composable
 fun BottomNavigationItemWithBadge() {
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/NavigationRailSample.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/NavigationRailSample.kt
index 69aa333..0abeb52 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/NavigationRailSample.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/NavigationRailSample.kt
@@ -18,7 +18,6 @@
 
 import androidx.annotation.Sampled
 import androidx.compose.foundation.layout.Spacer
-import androidx.compose.material.ExperimentalMaterialApi
 import androidx.compose.material.Icon
 import androidx.compose.material.NavigationRail
 import androidx.compose.material.NavigationRailItem
@@ -34,7 +33,6 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 
-@OptIn(ExperimentalMaterialApi::class)
 @Sampled
 @Composable
 fun NavigationRailSample() {
@@ -53,7 +51,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterialApi::class)
 @Composable
 fun NavigationRailWithOnlySelectedLabelsSample() {
     var selectedItem by remember { mutableStateOf(0) }
@@ -72,7 +69,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterialApi::class)
 @Composable
 fun CompactNavigationRailSample() {
     var selectedItem by remember { mutableStateOf(0) }
@@ -89,7 +85,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterialApi::class)
 @Composable
 fun NavigationRailBottomAlignSample() {
     var selectedItem by remember { mutableStateOf(0) }
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TabSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TabSamples.kt
index 3d76467..93f09c5 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TabSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TabSamples.kt
@@ -36,7 +36,6 @@
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.ExperimentalMaterialApi
 import androidx.compose.material.Icon
 import androidx.compose.material.MaterialTheme
 import androidx.compose.material.ScrollableTabRow
@@ -130,7 +129,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterialApi::class)
 @Composable
 fun LeadingIconTabs() {
     var state by remember { mutableStateOf(0) }
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TextFieldSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TextFieldSamples.kt
index 7f11810..940784a 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TextFieldSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TextFieldSamples.kt
@@ -18,6 +18,7 @@
 
 import androidx.annotation.Sampled
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.text.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
@@ -308,3 +309,15 @@
         )
     )
 }
+
+@Composable
+fun TextArea() {
+    var text by rememberSaveable { mutableStateOf("This is a very long input that extends beyond " +
+        "the height of the text area.") }
+    TextField(
+        value = text,
+        onValueChange = { text = it },
+        modifier = Modifier.height(100.dp),
+        label = { Text("Label") }
+    )
+}
\ No newline at end of file
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BadgeScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BadgeScreenshotTest.kt
index 791a081..ec962fe 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BadgeScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BadgeScreenshotTest.kt
@@ -43,7 +43,7 @@
 @LargeTest
 @RunWith(AndroidJUnit4::class)
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-@OptIn(ExperimentalMaterialApi::class, ExperimentalTestApi::class)
+@OptIn(ExperimentalTestApi::class)
 class BadgeScreenshotTest {
 
     @get:Rule
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BadgeTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BadgeTest.kt
index 1160bf1..c3c0644 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BadgeTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BadgeTest.kt
@@ -61,7 +61,6 @@
 
 @LargeTest
 @RunWith(AndroidJUnit4::class)
-@OptIn(ExperimentalMaterialApi::class)
 class BadgeTest {
 
     private val icon = Icons.Filled.Favorite
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ButtonScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ButtonScreenshotTest.kt
index 7194dce..58a3991 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ButtonScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ButtonScreenshotTest.kt
@@ -27,6 +27,7 @@
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performMouseInput
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -102,4 +103,22 @@
             .captureToImage()
             .assertAgainstGolden(screenshotRule, "button_ripple")
     }
+
+    @Test
+    fun hover() {
+        rule.setMaterialContent {
+            Box(Modifier.requiredSize(200.dp, 100.dp).wrapContentSize()) {
+                Button(onClick = { }) { }
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .performMouseInput { enter(center) }
+
+        rule.waitForIdle()
+
+        rule.onRoot()
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "button_hover")
+    }
 }
\ No newline at end of file
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/CheckboxScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/CheckboxScreenshotTest.kt
index 36fdfc7..0d8bbba 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/CheckboxScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/CheckboxScreenshotTest.kt
@@ -29,6 +29,7 @@
 import androidx.compose.ui.test.isToggleable
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performMouseInput
 import androidx.compose.ui.test.performTouchInput
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
@@ -205,6 +206,26 @@
         assertToggeableAgainstGolden("checkbox_animateToUnchecked")
     }
 
+    @Test
+    fun checkBoxTest_hover() {
+        rule.setMaterialContent {
+            Box(wrap.testTag(wrapperTestTag)) {
+                Checkbox(
+                    modifier = wrap,
+                    checked = true,
+                    onCheckedChange = { }
+                )
+            }
+        }
+
+        rule.onNode(isToggleable())
+            .performMouseInput { enter(center) }
+
+        rule.waitForIdle()
+
+        assertToggeableAgainstGolden("checkbox_hover")
+    }
+
     private fun assertToggeableAgainstGolden(goldenName: String) {
         // TODO: replace with find(isToggeable()) after b/157687898 is fixed
         rule.onNodeWithTag(wrapperTestTag)
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonScreenshotTest.kt
index 0a6a4d6..08cac4f 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonScreenshotTest.kt
@@ -28,6 +28,7 @@
 import androidx.compose.ui.test.hasClickAction
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performMouseInput
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -121,4 +122,24 @@
             .captureToImage()
             .assertAgainstGolden(screenshotRule, "fab_ripple")
     }
+
+    @Test
+    fun hover() {
+        rule.setMaterialContent {
+            Box(Modifier.requiredSize(100.dp, 100.dp).wrapContentSize()) {
+                FloatingActionButton(onClick = { }) {
+                    Icon(Icons.Filled.Favorite, contentDescription = null)
+                }
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .performMouseInput { enter(center) }
+
+        rule.waitForIdle()
+
+        rule.onRoot()
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "fab_hover")
+    }
 }
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/MaterialRippleThemeTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/MaterialRippleThemeTest.kt
index ec572de..d1509f7 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/MaterialRippleThemeTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/MaterialRippleThemeTest.kt
@@ -26,6 +26,7 @@
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.interaction.PressInteraction
 import androidx.compose.foundation.indication
+import androidx.compose.foundation.interaction.HoverInteraction
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
@@ -106,6 +107,28 @@
     }
 
     @Test
+    fun bounded_lightTheme_highLuminance_hovered() {
+        val interactionSource = MutableInteractionSource()
+
+        val contentColor = Color.White
+
+        val scope = rule.setRippleContent(
+            interactionSource = interactionSource,
+            bounded = true,
+            lightTheme = true,
+            contentColor = contentColor
+        )
+
+        assertRippleMatches(
+            scope,
+            interactionSource,
+            HoverInteraction.Enter(),
+            "ripple_bounded_light_highluminance_hovered",
+            calculateResultingRippleColor(contentColor, rippleOpacity = 0.08f)
+        )
+    }
+
+    @Test
     fun bounded_lightTheme_highLuminance_dragged() {
         val interactionSource = MutableInteractionSource()
 
@@ -150,6 +173,28 @@
     }
 
     @Test
+    fun bounded_lightTheme_lowLuminance_hovered() {
+        val interactionSource = MutableInteractionSource()
+
+        val contentColor = Color.Black
+
+        val scope = rule.setRippleContent(
+            interactionSource = interactionSource,
+            bounded = true,
+            lightTheme = true,
+            contentColor = contentColor
+        )
+
+        assertRippleMatches(
+            scope,
+            interactionSource,
+            HoverInteraction.Enter(),
+            "ripple_bounded_light_lowluminance_hovered",
+            calculateResultingRippleColor(contentColor, rippleOpacity = 0.04f)
+        )
+    }
+
+    @Test
     fun bounded_lightTheme_lowLuminance_dragged() {
         val interactionSource = MutableInteractionSource()
 
@@ -194,6 +239,28 @@
     }
 
     @Test
+    fun bounded_darkTheme_highLuminance_hovered() {
+        val interactionSource = MutableInteractionSource()
+
+        val contentColor = Color.White
+
+        val scope = rule.setRippleContent(
+            interactionSource = interactionSource,
+            bounded = true,
+            lightTheme = false,
+            contentColor = contentColor
+        )
+
+        assertRippleMatches(
+            scope,
+            interactionSource,
+            HoverInteraction.Enter(),
+            "ripple_bounded_dark_highluminance_hovered",
+            calculateResultingRippleColor(contentColor, rippleOpacity = 0.04f)
+        )
+    }
+
+    @Test
     fun bounded_darkTheme_highLuminance_dragged() {
         val interactionSource = MutableInteractionSource()
 
@@ -239,6 +306,29 @@
     }
 
     @Test
+    fun bounded_darkTheme_lowLuminance_hovered() {
+        val interactionSource = MutableInteractionSource()
+
+        val contentColor = Color.Black
+
+        val scope = rule.setRippleContent(
+            interactionSource = interactionSource,
+            bounded = true,
+            lightTheme = false,
+            contentColor = contentColor
+        )
+
+        assertRippleMatches(
+            scope,
+            interactionSource,
+            HoverInteraction.Enter(),
+            "ripple_bounded_dark_lowluminance_hovered",
+            // Low luminance content in dark theme should use a white ripple by default
+            calculateResultingRippleColor(Color.White, rippleOpacity = 0.04f)
+        )
+    }
+
+    @Test
     fun bounded_darkTheme_lowLuminance_dragged() {
         val interactionSource = MutableInteractionSource()
 
@@ -284,6 +374,28 @@
     }
 
     @Test
+    fun unbounded_lightTheme_highLuminance_hovered() {
+        val interactionSource = MutableInteractionSource()
+
+        val contentColor = Color.White
+
+        val scope = rule.setRippleContent(
+            interactionSource = interactionSource,
+            bounded = false,
+            lightTheme = true,
+            contentColor = contentColor
+        )
+
+        assertRippleMatches(
+            scope,
+            interactionSource,
+            HoverInteraction.Enter(),
+            "ripple_unbounded_light_highluminance_hovered",
+            calculateResultingRippleColor(contentColor, rippleOpacity = 0.08f)
+        )
+    }
+
+    @Test
     fun unbounded_lightTheme_highLuminance_dragged() {
         val interactionSource = MutableInteractionSource()
 
@@ -328,6 +440,28 @@
     }
 
     @Test
+    fun unbounded_lightTheme_lowLuminance_hovered() {
+        val interactionSource = MutableInteractionSource()
+
+        val contentColor = Color.Black
+
+        val scope = rule.setRippleContent(
+            interactionSource = interactionSource,
+            bounded = false,
+            lightTheme = true,
+            contentColor = contentColor
+        )
+
+        assertRippleMatches(
+            scope,
+            interactionSource,
+            HoverInteraction.Enter(),
+            "ripple_unbounded_light_lowluminance_hovered",
+            calculateResultingRippleColor(contentColor, rippleOpacity = 0.04f)
+        )
+    }
+
+    @Test
     fun unbounded_lightTheme_lowLuminance_dragged() {
         val interactionSource = MutableInteractionSource()
 
@@ -372,6 +506,28 @@
     }
 
     @Test
+    fun unbounded_darkTheme_highLuminance_hovered() {
+        val interactionSource = MutableInteractionSource()
+
+        val contentColor = Color.White
+
+        val scope = rule.setRippleContent(
+            interactionSource = interactionSource,
+            bounded = false,
+            lightTheme = false,
+            contentColor = contentColor
+        )
+
+        assertRippleMatches(
+            scope,
+            interactionSource,
+            HoverInteraction.Enter(),
+            "ripple_unbounded_dark_highluminance_hovered",
+            calculateResultingRippleColor(contentColor, rippleOpacity = 0.04f)
+        )
+    }
+
+    @Test
     fun unbounded_darkTheme_highLuminance_dragged() {
         val interactionSource = MutableInteractionSource()
 
@@ -417,6 +573,29 @@
     }
 
     @Test
+    fun unbounded_darkTheme_lowLuminance_hovered() {
+        val interactionSource = MutableInteractionSource()
+
+        val contentColor = Color.Black
+
+        val scope = rule.setRippleContent(
+            interactionSource = interactionSource,
+            bounded = false,
+            lightTheme = false,
+            contentColor = contentColor
+        )
+
+        assertRippleMatches(
+            scope,
+            interactionSource,
+            HoverInteraction.Enter(),
+            "ripple_unbounded_dark_lowluminance_hovered",
+            // Low luminance content in dark theme should use a white ripple by default
+            calculateResultingRippleColor(Color.White, rippleOpacity = 0.04f)
+        )
+    }
+
+    @Test
     fun unbounded_darkTheme_lowLuminance_dragged() {
         val interactionSource = MutableInteractionSource()
 
@@ -491,6 +670,57 @@
     }
 
     @Test
+    fun customRippleTheme_hovered() {
+        val interactionSource = MutableInteractionSource()
+
+        val contentColor = Color.Black
+
+        val rippleColor = Color.Red
+        val expectedAlpha = 0.5f
+        val rippleAlpha = RippleAlpha(expectedAlpha, expectedAlpha, expectedAlpha, expectedAlpha)
+
+        val rippleTheme = object : RippleTheme {
+            @Composable
+            override fun defaultColor() = rippleColor
+
+            @Composable
+            override fun rippleAlpha() = rippleAlpha
+        }
+
+        var scope: CoroutineScope? = null
+
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            MaterialTheme {
+                CompositionLocalProvider(LocalRippleTheme provides rippleTheme) {
+                    Surface(contentColor = contentColor) {
+                        Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+                            RippleBoxWithBackground(
+                                interactionSource,
+                                rememberRipple(),
+                                bounded = true
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+        val expectedColor = calculateResultingRippleColor(
+            rippleColor,
+            rippleOpacity = expectedAlpha
+        )
+
+        assertRippleMatches(
+            scope!!,
+            interactionSource,
+            HoverInteraction.Enter(),
+            "ripple_customtheme_hovered",
+            expectedColor
+        )
+    }
+
+    @Test
     fun customRippleTheme_dragged() {
         val interactionSource = MutableInteractionSource()
 
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/NavigationRailScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/NavigationRailScreenshotTest.kt
index 60f4386..396228e 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/NavigationRailScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/NavigationRailScreenshotTest.kt
@@ -50,7 +50,7 @@
 @LargeTest
 @RunWith(AndroidJUnit4::class)
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-@OptIn(ExperimentalMaterialApi::class, ExperimentalTestApi::class)
+@OptIn(ExperimentalTestApi::class)
 class NavigationRailScreenshotTest {
     @get:Rule
     val composeTestRule = createComposeRule()
@@ -229,7 +229,6 @@
  * control its visual state.
  * @param withHeaderFab when true, shows a [FloatingActionButton] as the [NavigationRail] header.
  */
-@OptIn(ExperimentalMaterialApi::class)
 @Composable
 private fun DefaultNavigationRail(
     interactionSource: MutableInteractionSource,
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/NavigationRailTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/NavigationRailTest.kt
index 1f7e16d..52c72d9 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/NavigationRailTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/NavigationRailTest.kt
@@ -60,7 +60,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@ExperimentalMaterialApi
 @LargeTest
 @RunWith(AndroidJUnit4::class)
 /**
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/RadioButtonScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/RadioButtonScreenshotTest.kt
index 3b3c67c..99fe9e3 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/RadioButtonScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/RadioButtonScreenshotTest.kt
@@ -30,6 +30,7 @@
 import androidx.compose.ui.test.isSelectable
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performMouseInput
 import androidx.compose.ui.test.performTouchInput
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
@@ -99,6 +100,20 @@
     }
 
     @Test
+    fun radioButtonTest_hovered() {
+        rule.setMaterialContent {
+            Box(wrap.testTag(wrapperTestTag)) {
+                RadioButton(selected = false, onClick = {})
+            }
+        }
+        rule.onNodeWithTag(wrapperTestTag).performMouseInput {
+            enter(center)
+        }
+
+        assertSelectableAgainstGolden("radioButton_hovered")
+    }
+
+    @Test
     fun radioButtonTest_disabled_selected() {
         rule.setMaterialContent {
             Box(wrap.testTag(wrapperTestTag)) {
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SwitchScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SwitchScreenshotTest.kt
index 43f12ce..ad24ccf 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SwitchScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SwitchScreenshotTest.kt
@@ -34,6 +34,7 @@
 import androidx.compose.ui.test.isToggleable
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performMouseInput
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
@@ -235,6 +236,25 @@
         assertToggeableAgainstGolden("switch_animateToUnchecked")
     }
 
+    @Test
+    fun switchTest_hover() {
+        rule.setMaterialContent {
+            Box(wrapperModifier) {
+                Switch(
+                    checked = true,
+                    onCheckedChange = { }
+                )
+            }
+        }
+
+        rule.onNode(isToggleable())
+            .performMouseInput { enter(center) }
+
+        rule.waitForIdle()
+
+        assertToggeableAgainstGolden("switch_hover")
+    }
+
     private fun assertToggeableAgainstGolden(goldenName: String) {
         rule.onNodeWithTag(wrapperTestTag)
             .captureToImage()
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabScreenshotTest.kt
index b7871dc..61a1821 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabScreenshotTest.kt
@@ -48,7 +48,7 @@
 @LargeTest
 @RunWith(AndroidJUnit4::class)
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-@OptIn(ExperimentalMaterialApi::class, ExperimentalTestApi::class)
+@OptIn(ExperimentalTestApi::class)
 class TabScreenshotTest {
 
     @get:Rule
@@ -463,7 +463,6 @@
  * @param interactionSource the [MutableInteractionSource] for the first [LeadingIconTab], to control its
  * visual state.
  */
-@OptIn(ExperimentalMaterialApi::class)
 @Composable
 private fun DefaultLeadingIconTabs(
     interactionSource: MutableInteractionSource
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabTest.kt
index 4b8a93f..edd3c5f 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabTest.kt
@@ -135,7 +135,6 @@
             .assertHasClickAction()
     }
 
-    @OptIn(ExperimentalMaterialApi::class)
     @Test
     fun leadingIconTab_defaultSemantics() {
         rule.setMaterialContent {
@@ -161,7 +160,6 @@
             .assert(SemanticsMatcher.keyIsDefined(SemanticsProperties.SelectableGroup))
     }
 
-    @OptIn(ExperimentalMaterialApi::class)
     @Test
     fun leadingIconTab_disabledSemantics() {
         rule.setMaterialContent {
@@ -218,7 +216,6 @@
             .assertHeightIsEqualTo(ExpectedLargeTabHeight)
     }
 
-    @OptIn(ExperimentalMaterialApi::class)
     @Test
     fun leadingIconTab_height() {
         rule
@@ -429,7 +426,6 @@
         textBounds.top.assertIsEqualTo(expectedPositionY, "text bounds top y-position")
     }
 
-    @OptIn(ExperimentalMaterialApi::class)
     @Test
     fun LeadingIconTab_textAndIconPosition() {
         rule.setMaterialContent {
@@ -818,7 +814,6 @@
         }
     }
 
-    @OptIn(ExperimentalMaterialApi::class)
     @Test
     fun leadingIconTab_disabled_noClicks() {
         var clicks = 0
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Badge.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Badge.kt
index 780bd0f..2e753d5 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Badge.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Badge.kt
@@ -55,7 +55,6 @@
  * @param content the anchor to which this badge will be positioned
  *
  */
-@ExperimentalMaterialApi
 @Composable
 fun BadgedBox(
     badge: @Composable BoxScope.() -> Unit,
@@ -127,7 +126,6 @@
  * @param content optional content to be rendered inside the badge
  *
  */
-@ExperimentalMaterialApi
 @Composable
 fun Badge(
     modifier: Modifier = Modifier,
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt
index cf6ce1b73..47e85a9 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt
@@ -19,6 +19,7 @@
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.VectorConverter
 import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.interaction.HoverInteraction
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.InteractionSource
 import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -327,7 +328,7 @@
      */
     val IconSpacing = 8.dp
 
-    // TODO: b/152525426 add support for focused and hovered states
+    // TODO: b/152525426 add support for focused states
     /**
      * Creates a [ButtonElevation] that will animate between the provided values according to the
      * Material specification for a [Button].
@@ -338,19 +339,47 @@
      * is pressed.
      * @param disabledElevation the elevation to use when the [Button] is not enabled.
      */
+    @Deprecated("Use another overload of elevation", level = DeprecationLevel.HIDDEN)
     @Composable
     fun elevation(
         defaultElevation: Dp = 2.dp,
         pressedElevation: Dp = 8.dp,
-        // focused: Dp = 4.dp,
-        // hovered: Dp = 4.dp,
         disabledElevation: Dp = 0.dp
+    ): ButtonElevation = elevation(
+        defaultElevation,
+        pressedElevation,
+        disabledElevation,
+        hoveredElevation = 4.dp,
+        focusedElevation = 4.dp,
+    )
+
+    /**
+     * Creates a [ButtonElevation] that will animate between the provided values according to the
+     * Material specification for a [Button].
+     *
+     * @param defaultElevation the elevation to use when the [Button] is enabled, and has no
+     * other [Interaction]s.
+     * @param pressedElevation the elevation to use when the [Button] is enabled and
+     * is pressed.
+     * @param disabledElevation the elevation to use when the [Button] is not enabled.
+     * @param hoveredElevation the elevation to use when the [Button] is enabled and is hovered.
+     * @param focusedElevation not currently supported.
+     */
+    @Suppress("UNUSED_PARAMETER")
+    @Composable
+    fun elevation(
+        defaultElevation: Dp = 2.dp,
+        pressedElevation: Dp = 8.dp,
+        disabledElevation: Dp = 0.dp,
+        hoveredElevation: Dp = 4.dp,
+        focusedElevation: Dp = 4.dp,
     ): ButtonElevation {
-        return remember(defaultElevation, pressedElevation, disabledElevation) {
+        return remember(defaultElevation, pressedElevation, disabledElevation, hoveredElevation) {
             DefaultButtonElevation(
                 defaultElevation = defaultElevation,
                 pressedElevation = pressedElevation,
-                disabledElevation = disabledElevation
+                disabledElevation = disabledElevation,
+                hoveredElevation = hoveredElevation,
             )
         }
     }
@@ -461,6 +490,7 @@
     private val defaultElevation: Dp,
     private val pressedElevation: Dp,
     private val disabledElevation: Dp,
+    private val hoveredElevation: Dp,
 ) : ButtonElevation {
     @Composable
     override fun elevation(enabled: Boolean, interactionSource: InteractionSource): State<Dp> {
@@ -468,6 +498,12 @@
         LaunchedEffect(interactionSource) {
             interactionSource.interactions.collect { interaction ->
                 when (interaction) {
+                    is HoverInteraction.Enter -> {
+                        interactions.add(interaction)
+                    }
+                    is HoverInteraction.Exit -> {
+                        interactions.remove(interaction.enter)
+                    }
                     is PressInteraction.Press -> {
                         interactions.add(interaction)
                     }
@@ -488,6 +524,7 @@
         } else {
             when (interaction) {
                 is PressInteraction.Press -> pressedElevation
+                is HoverInteraction.Enter -> hoveredElevation
                 else -> defaultElevation
             }
         }
@@ -503,6 +540,7 @@
             LaunchedEffect(target) {
                 val lastInteraction = when (animatable.targetValue) {
                     pressedElevation -> PressInteraction.Press(Offset.Zero)
+                    hoveredElevation -> HoverInteraction.Enter()
                     else -> null
                 }
                 animatable.animateElevation(
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Card.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Card.kt
index 277a450..cba9710 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Card.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Card.kt
@@ -104,7 +104,8 @@
  * be clickable
  * @param onClickLabel semantic / accessibility label for the [onClick] action
  * @param role the type of user interface element. Accessibility services might use this
- * to describe the element or do customizations
+ * to describe the element or do customizations. For example, if the Card acts as a button, you
+ * should pass the [Role.Button]
  */
 @ExperimentalMaterialApi
 @Composable
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Elevation.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Elevation.kt
index 1a8d1e1..56e3198 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Elevation.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Elevation.kt
@@ -22,6 +22,7 @@
 import androidx.compose.animation.core.FastOutSlowInEasing
 import androidx.compose.animation.core.TweenSpec
 import androidx.compose.foundation.interaction.DragInteraction
+import androidx.compose.foundation.interaction.HoverInteraction
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.PressInteraction
 import androidx.compose.ui.unit.Dp
@@ -79,6 +80,7 @@
         return when (interaction) {
             is PressInteraction.Press -> DefaultIncomingSpec
             is DragInteraction.Start -> DefaultIncomingSpec
+            is HoverInteraction.Enter -> DefaultIncomingSpec
             else -> null
         }
     }
@@ -93,7 +95,7 @@
         return when (interaction) {
             is PressInteraction.Press -> DefaultOutgoingSpec
             is DragInteraction.Start -> DefaultOutgoingSpec
-            // TODO: use [HoveredOutgoingSpec] when hovered
+            is HoverInteraction.Enter -> HoveredOutgoingSpec
             else -> null
         }
     }
@@ -109,7 +111,6 @@
     easing = CubicBezierEasing(0.40f, 0.00f, 0.60f, 1.00f)
 )
 
-@Suppress("unused")
 private val HoveredOutgoingSpec = TweenSpec<Dp>(
     durationMillis = 120,
     easing = CubicBezierEasing(0.40f, 0.00f, 0.60f, 1.00f)
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/FloatingActionButton.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/FloatingActionButton.kt
index e6bf730..ef23afe 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/FloatingActionButton.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/FloatingActionButton.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.VectorConverter
+import androidx.compose.foundation.interaction.HoverInteraction
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.InteractionSource
 import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -205,7 +206,7 @@
  * Contains the default values used by [FloatingActionButton]
  */
 object FloatingActionButtonDefaults {
-    // TODO: b/152525426 add support for focused and hovered states
+    // TODO: b/152525426 add support for focused states
     /**
      * Creates a [FloatingActionButtonElevation] that will animate between the provided values
      * according to the Material specification.
@@ -215,17 +216,43 @@
      * @param pressedElevation the elevation to use when the [FloatingActionButton] is
      * pressed.
      */
+    @Deprecated("Use another overload of elevation", level = DeprecationLevel.HIDDEN)
     @Composable
     fun elevation(
         defaultElevation: Dp = 6.dp,
-        pressedElevation: Dp = 12.dp
-        // focused: Dp = 8.dp,
-        // hovered: Dp = 8.dp,
+        pressedElevation: Dp = 12.dp,
+    ): FloatingActionButtonElevation = elevation(
+        defaultElevation,
+        pressedElevation,
+        hoveredElevation = 8.dp,
+        focusedElevation = 8.dp,
+    )
+
+    /**
+     * Creates a [FloatingActionButtonElevation] that will animate between the provided values
+     * according to the Material specification.
+     *
+     * @param defaultElevation the elevation to use when the [FloatingActionButton] has no
+     * [Interaction]s
+     * @param pressedElevation the elevation to use when the [FloatingActionButton] is
+     * pressed.
+     * @param hoveredElevation the elevation to use when the [FloatingActionButton] is
+     * hovered.
+     * @param focusedElevation not currently supported.
+     */
+    @Suppress("UNUSED_PARAMETER")
+    @Composable
+    fun elevation(
+        defaultElevation: Dp = 6.dp,
+        pressedElevation: Dp = 12.dp,
+        hoveredElevation: Dp = 8.dp,
+        focusedElevation: Dp = 8.dp,
     ): FloatingActionButtonElevation {
-        return remember(defaultElevation, pressedElevation) {
+        return remember(defaultElevation, pressedElevation, hoveredElevation) {
             DefaultFloatingActionButtonElevation(
                 defaultElevation = defaultElevation,
-                pressedElevation = pressedElevation
+                pressedElevation = pressedElevation,
+                hoveredElevation = hoveredElevation,
             )
         }
     }
@@ -238,6 +265,7 @@
 private class DefaultFloatingActionButtonElevation(
     private val defaultElevation: Dp,
     private val pressedElevation: Dp,
+    private val hoveredElevation: Dp
 ) : FloatingActionButtonElevation {
     @Composable
     override fun elevation(interactionSource: InteractionSource): State<Dp> {
@@ -245,6 +273,12 @@
         LaunchedEffect(interactionSource) {
             interactionSource.interactions.collect { interaction ->
                 when (interaction) {
+                    is HoverInteraction.Enter -> {
+                        interactions.add(interaction)
+                    }
+                    is HoverInteraction.Exit -> {
+                        interactions.remove(interaction.enter)
+                    }
                     is PressInteraction.Press -> {
                         interactions.add(interaction)
                     }
@@ -262,6 +296,7 @@
 
         val target = when (interaction) {
             is PressInteraction.Press -> pressedElevation
+            is HoverInteraction.Enter -> hoveredElevation
             else -> defaultElevation
         }
 
@@ -270,6 +305,7 @@
         LaunchedEffect(target) {
             val lastInteraction = when (animatable.targetValue) {
                 pressedElevation -> PressInteraction.Press(Offset.Zero)
+                hoveredElevation -> HoverInteraction.Enter()
                 else -> null
             }
             animatable.animateElevation(
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/NavigationRail.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/NavigationRail.kt
index dd9d18b..378d983 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/NavigationRail.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/NavigationRail.kt
@@ -59,10 +59,10 @@
 /**
  * <a href="https://material.io/components/navigation-rail" class="external" target="_blank">Material Design navigation rail</a>.
  *
- * The Navigation Rail is a side navigation component that allows movement between primary
- * destinations in an app. The navigation rail should be used to display three to seven app
- * destinations and, optionally, a  Floating Action Button or a logo header. Each destination is
- * typically represented by an icon and an optional text label.
+ * A Navigation Rail is a side navigation component that allows movement between primary
+ * destinations in an app. A navigation rail should be used to display three to seven app
+ * destinations and, optionally, a [FloatingActionButton] or a logo inside [header]. Each
+ * destination is typically represented by an icon and an optional text label.
  *
  * ![Navigation rail image](https://developer.android.com/images/reference/androidx/compose/material/navigation-rail.png)
  *
@@ -85,11 +85,10 @@
  * [backgroundColor] is not a color from the theme, this will keep the same value set above this
  * NavigationRail.
  * @param elevation elevation for this NavigationRail
- * @param header an optional header that may hold a Floating Action Button or a logo
+ * @param header an optional header that may hold a [FloatingActionButton] or a logo
  * @param content destinations inside this NavigationRail, this should contain multiple
  * [NavigationRailItem]s
  */
-@ExperimentalMaterialApi
 @Composable
 fun NavigationRail(
     modifier: Modifier = Modifier,
@@ -144,7 +143,6 @@
  * and the color of the ripple.
  * @param unselectedContentColor the color of the text label and icon when this item is not selected
  */
-@ExperimentalMaterialApi
 @Composable
 fun NavigationRailItem(
     selected: Boolean,
@@ -206,7 +204,6 @@
 /**
  * Contains default values used for [NavigationRail].
  */
-@ExperimentalMaterialApi
 object NavigationRailDefaults {
     /**
      * Default elevation used for [NavigationRail].
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt
index fdee814..bfec8ff 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt
@@ -31,6 +31,7 @@
 import androidx.compose.foundation.gestures.draggable
 import androidx.compose.foundation.gestures.forEachGesture
 import androidx.compose.foundation.gestures.horizontalDrag
+import androidx.compose.foundation.hoverable
 import androidx.compose.foundation.indication
 import androidx.compose.foundation.interaction.DragInteraction
 import androidx.compose.foundation.interaction.Interaction
@@ -625,6 +626,7 @@
                     interactionSource = interactionSource,
                     indication = rememberRipple(bounded = false, radius = ThumbRippleRadius)
                 )
+                .hoverable(interactionSource = interactionSource)
                 .shadow(if (enabled) elevation else 0.dp, CircleShape, clip = false)
                 .background(colors.thumbColor(enabled).value, CircleShape)
         )
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Surface.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Surface.kt
index e45369a..cff87bf 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Surface.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Surface.kt
@@ -191,8 +191,9 @@
  * @param enabled Controls the enabled state of the surface. When `false`, this surface will not
  * be clickable
  * @param onClickLabel semantic / accessibility label for the [onClick] action
- * @param role the type of user interface element. Accessibility services might use this
- * to describe the element or do customizations
+ * @param role the type of user interface element. Accessibility services might use this to
+ * describe the element or do customizations. For example, if the Surface acts as a button, you
+ * should pass the [Role.Button]
  */
 @ExperimentalMaterialApi
 @Composable
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Tab.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Tab.kt
index 7659e51..db4e223 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Tab.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Tab.kt
@@ -148,7 +148,6 @@
  *
  * @see Tab
  */
-@ExperimentalMaterialApi
 @Composable
 fun LeadingIconTab(
     selected: Boolean,
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 05ecf1a..ad1b9a4 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -106,8 +106,8 @@
   }
 
   public final class SurfaceKt {
-    method @androidx.compose.runtime.Composable public static void Surface(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape shape, optional long color, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.BorderStroke? border, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable public static void Surface(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape shape, optional long color, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.foundation.Indication? indication, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void Surface(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape shape, optional long color, optional long contentColor, optional float tonalElevation, optional float shadowElevation, optional androidx.compose.foundation.BorderStroke? border, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void Surface(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape shape, optional long color, optional long contentColor, optional float tonalElevation, optional float shadowElevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.foundation.Indication? indication, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.Dp> getLocalAbsoluteTonalElevation();
   }
 
diff --git a/compose/material3/material3/api/public_plus_experimental_current.txt b/compose/material3/material3/api/public_plus_experimental_current.txt
index 28ecd4a..b246af7 100644
--- a/compose/material3/material3/api/public_plus_experimental_current.txt
+++ b/compose/material3/material3/api/public_plus_experimental_current.txt
@@ -121,8 +121,8 @@
   }
 
   public final class SurfaceKt {
-    method @androidx.compose.runtime.Composable public static void Surface(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape shape, optional long color, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.BorderStroke? border, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable public static void Surface(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape shape, optional long color, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.foundation.Indication? indication, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void Surface(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape shape, optional long color, optional long contentColor, optional float tonalElevation, optional float shadowElevation, optional androidx.compose.foundation.BorderStroke? border, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void Surface(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape shape, optional long color, optional long contentColor, optional float tonalElevation, optional float shadowElevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.foundation.Indication? indication, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.Dp> getLocalAbsoluteTonalElevation();
   }
 
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 05ecf1a..ad1b9a4 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -106,8 +106,8 @@
   }
 
   public final class SurfaceKt {
-    method @androidx.compose.runtime.Composable public static void Surface(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape shape, optional long color, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.BorderStroke? border, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable public static void Surface(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape shape, optional long color, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.foundation.Indication? indication, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void Surface(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape shape, optional long color, optional long contentColor, optional float tonalElevation, optional float shadowElevation, optional androidx.compose.foundation.BorderStroke? border, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void Surface(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape shape, optional long color, optional long contentColor, optional float tonalElevation, optional float shadowElevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.foundation.Indication? indication, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.Dp> getLocalAbsoluteTonalElevation();
   }
 
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SurfaceTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SurfaceTest.kt
index 3011778..007999b 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SurfaceTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SurfaceTest.kt
@@ -22,17 +22,21 @@
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.interaction.PressInteraction
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.testutils.assertPixels
 import androidx.compose.testutils.assertShape
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.asAndroidBitmap
 import androidx.compose.ui.input.pointer.PointerEventPass
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.platform.testTag
@@ -192,6 +196,59 @@
         }
     }
 
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun absoluteElevationIsNotUsedForShadows() {
+        rule.setMaterialContent {
+            Column {
+                Box(
+                    Modifier
+                        .padding(10.dp)
+                        .size(10.dp, 10.dp)
+                        .semantics(mergeDescendants = true) {}
+                        .testTag("top level")
+                ) {
+                    Surface(
+                        Modifier.fillMaxSize().padding(0.dp),
+                        tonalElevation = 2.dp,
+                        shadowElevation = 2.dp,
+                        color = Color.Blue,
+                        content = {}
+                    )
+                }
+
+                // Set LocalAbsoluteTonalElevation to increase the absolute elevation
+                CompositionLocalProvider(
+                    LocalAbsoluteTonalElevation provides 2.dp
+                ) {
+                    Box(
+                        Modifier
+                            .padding(10.dp)
+                            .size(10.dp, 10.dp)
+                            .semantics(mergeDescendants = true) {}
+                            .testTag("nested")
+                    ) {
+                        Surface(
+                            Modifier.fillMaxSize().padding(0.dp),
+                            tonalElevation = 0.dp,
+                            shadowElevation = 2.dp,
+                            color = Color.Blue,
+                            content = {}
+                        )
+                    }
+                }
+            }
+        }
+
+        val topLevelSurfaceBitmap = rule.onNodeWithTag("top level").captureToImage()
+        val nestedSurfaceBitmap = rule.onNodeWithTag("nested").captureToImage()
+            .asAndroidBitmap()
+
+        topLevelSurfaceBitmap.assertPixels {
+            Color(nestedSurfaceBitmap.getPixel(it.x, it.y))
+        }
+    }
+
     /**
      * Tests that composed modifiers applied to Surface are applied within the changes to
      * [LocalContentColor], so they can consume the updated values.
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Surface.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Surface.kt
index c050df3..d2b90c1 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Surface.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Surface.kt
@@ -32,6 +32,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.shadow
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.graphics.Shape
@@ -89,6 +90,10 @@
  * @param tonalElevation When [color] is [ColorScheme.surface], a higher the elevation (surface
  * blended with more primary) will result in a darker surface color in light theme and lighter color
  * in dark theme.
+ * @param shadowElevation The size of the shadow below the surface. To prevent shadow creep, only
+ * apply shadow elevation when absolutely necessary, such as when the surface requires visual
+ * separation from a patterned background. Note that It will not affect z index of the Surface.
+ * If you want to change the drawing order you can use `Modifier.zIndex`.
  * @param border Optional border to draw on top of the surface
  */
 @Composable
@@ -98,6 +103,7 @@
     color: Color = MaterialTheme.colorScheme.surface,
     contentColor: Color = contentColorFor(color),
     tonalElevation: Dp = 0.dp,
+    shadowElevation: Dp = 0.dp,
     border: BorderStroke? = null,
     content: @Composable () -> Unit
 ) {
@@ -107,6 +113,7 @@
         color = color,
         contentColor = contentColor,
         tonalElevation = tonalElevation,
+        shadowElevation = shadowElevation,
         border = border,
         content = content,
         clickAndSemanticsModifier =
@@ -168,6 +175,8 @@
  * @param tonalElevation When [color] is [ColorScheme.surface], a higher the elevation (surface
  * blended with more primary) will result in a darker surface color in light theme and lighter color
  * in dark theme.
+ * @param shadowElevation The size of the shadow below the surface. Note that It will not affect z index
+ * of the Surface. If you want to change the drawing order you can use `Modifier.zIndex`.
  * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
  * for this Surface. You can create and pass in your own remembered [MutableInteractionSource] if
  * you want to observe [Interaction]s and customize the appearance / behavior of this Surface in
@@ -178,8 +187,9 @@
  * @param enabled Controls the enabled state of the surface. When `false`, this surface will not be
  * clickable
  * @param onClickLabel semantic / accessibility label for the [onClick] action
- * @param role the type of user interface element. Accessibility services might use this to describe
- * the element or do customizations
+ * @param role the type of user interface element. Accessibility services might use this to
+ * describe the element or do customizations. For example, if the Surface acts as a button, you
+ * should pass the [Role.Button]
  */
 @Composable
 fun Surface(
@@ -189,6 +199,7 @@
     color: Color = MaterialTheme.colorScheme.surface,
     contentColor: Color = contentColorFor(color),
     tonalElevation: Dp = 0.dp,
+    shadowElevation: Dp = 0.dp,
     border: BorderStroke? = null,
     interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
     indication: Indication? = LocalIndication.current,
@@ -203,6 +214,7 @@
         color = color,
         contentColor = contentColor,
         tonalElevation = tonalElevation,
+        shadowElevation = shadowElevation,
         border = border,
         content = content,
         clickAndSemanticsModifier =
@@ -219,12 +231,13 @@
 
 @Composable
 private fun Surface(
-    modifier: Modifier = Modifier,
-    shape: Shape = RectangleShape,
-    border: BorderStroke? = null,
-    color: Color = MaterialTheme.colorScheme.primary,
-    contentColor: Color = contentColorFor(color),
-    tonalElevation: Dp = 0.dp, // This will be used to compute surface tonal colors
+    modifier: Modifier,
+    shape: Shape,
+    color: Color,
+    contentColor: Color,
+    border: BorderStroke?,
+    tonalElevation: Dp, // This will be used to compute surface tonal colors
+    shadowElevation: Dp,
     clickAndSemanticsModifier: Modifier,
     content: @Composable () -> Unit
 ) {
@@ -241,6 +254,7 @@
     ) {
         Box(
             modifier
+                .shadow(shadowElevation, shape, clip = false)
                 .then(if (border != null) Modifier.border(border, shape) else Modifier)
                 .background(color = backgroundColor, shape = shape)
                 .clip(shape)
diff --git a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Offset.kt b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Offset.kt
index 41c7a25..3595318 100644
--- a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Offset.kt
+++ b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Offset.kt
@@ -258,7 +258,7 @@
 val Offset.isUnspecified: Boolean get() = packedValue == Offset.Unspecified.packedValue
 
 /**
- * If this [Offset] [isSpecified] then this is returned, otherwise [block] is executed
+ * If this [Offset]&nbsp;[isSpecified] then this is returned, otherwise [block] is executed
  * and its result is returned.
  */
 inline fun Offset.takeOrElse(block: () -> Offset): Offset =
diff --git a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Size.kt b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Size.kt
index 449c160..517afbd 100644
--- a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Size.kt
+++ b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Size.kt
@@ -86,7 +86,7 @@
         /**
          * A size whose [width] and [height] are unspecified. This is a sentinel
          * value used to initialize a non-null parameter.
-         * Access to width or height on an unspecified size is not allowed
+         * Access to width or height on an unspecified size is not allowed.
          */
         @Stable
         val Unspecified = Size(Float.NaN, Float.NaN)
@@ -159,7 +159,7 @@
     get() = packedValue == Size.Unspecified.packedValue
 
 /**
- * If this [Size] [isSpecified] then this is returned, otherwise [block] is executed
+ * If this [Size]&nbsp;[isSpecified] then this is returned, otherwise [block] is executed
  * and its result is returned.
  */
 inline fun Size.takeOrElse(block: () -> Size): Size =
diff --git a/compose/ui/ui-graphics/samples/build.gradle b/compose/ui/ui-graphics/samples/build.gradle
index fbb8165..50b2762 100644
--- a/compose/ui/ui-graphics/samples/build.gradle
+++ b/compose/ui/ui-graphics/samples/build.gradle
@@ -32,7 +32,7 @@
     compileOnly(project(":annotation:annotation-sampled"))
 
     api(project(":compose:ui:ui-unit"))
-    implementation(project(":compose:foundation:foundation"))
+    implementation("androidx.compose.foundation:foundation:1.0.0")
     implementation(project(":compose:runtime:runtime"))
     implementation(project(":compose:ui:ui-graphics"))
     implementation(project(":compose:ui:ui-util"))
diff --git a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/FindersTest.kt b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/FindersTest.kt
index 8999ba5..5b58bec 100644
--- a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/FindersTest.kt
+++ b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/FindersTest.kt
@@ -30,6 +30,7 @@
 import androidx.test.filters.MediumTest
 import androidx.compose.testutils.expectError
 import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.editableText
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -89,6 +90,15 @@
     }
 
     @Test
+    fun findByText_withEditableText_matches() {
+        rule.setContent {
+            BoundaryNode { editableText = AnnotatedString("Hello World") }
+        }
+
+        rule.onNodeWithText("Hello World").assertExists()
+    }
+
+    @Test
     fun findByText_merged_matches() {
         rule.setContent {
             Box(Modifier.semantics(mergeDescendants = true) { }) {
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 0f45f7e..7b1dc64 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
@@ -266,7 +266,7 @@
 }
 
 /**
- * Returns whether the node's text contains exactly the given [values] and nothing else.
+ * Returns whether the node's text contains exactly the given [textValues] and nothing else.
  *
  * This will also search in [SemanticsProperties.EditableText] by default.
  *
@@ -275,7 +275,7 @@
  * ones to use.
  *
  * @param textValues List of values to match (the order does not matter)
- * @param includeEditableText Whether to also assert against the editable text.
+ * @param includeEditableText Whether to also assert against the editable text
  *
  * @see SemanticsProperties.Text
  * @see SemanticsProperties.EditableText
@@ -285,7 +285,6 @@
     includeEditableText: Boolean = true
 ): SemanticsMatcher {
     val expected = textValues.toList()
-    val given = mutableListOf<String>()
     val propertyName = if (includeEditableText) {
         "${SemanticsProperties.Text.name} + ${SemanticsProperties.EditableText.name}"
     } else {
@@ -294,14 +293,14 @@
     return SemanticsMatcher(
         "$propertyName = [${textValues.joinToString(",")}]"
     ) { node ->
-        given.clear()
+        val actual = mutableListOf<String>()
         if (includeEditableText) {
             node.config.getOrNull(SemanticsProperties.EditableText)
-                ?.let { given.add(it.text) }
+                ?.let { actual.add(it.text) }
         }
         node.config.getOrNull(SemanticsProperties.Text)
-            ?.let { given.addAll(it.map { anStr -> anStr.text }) }
-        given.containsAll(expected) && expected.containsAll(given)
+            ?.let { actual.addAll(it.map { anStr -> anStr.text }) }
+        actual.containsAll(expected) && expected.containsAll(actual)
     }
 }
 
diff --git a/compose/ui/ui-text/build.gradle b/compose/ui/ui-text/build.gradle
index 736d1f76..c2649ff 100644
--- a/compose/ui/ui-text/build.gradle
+++ b/compose/ui/ui-text/build.gradle
@@ -44,7 +44,7 @@
         implementation(project(":compose:ui:ui-util"))
         implementation(libs.kotlinStdlib)
         implementation("androidx.collection:collection:1.1.0")
-        implementation("androidx.core:core:1.5.0-rc02")
+        implementation("androidx.core:core:1.5.0")
 
         testImplementation(project(":compose:ui:ui-test-font"))
         testImplementation(libs.testRules)
@@ -119,7 +119,7 @@
             androidMain.dependencies {
                 api("androidx.annotation:annotation:1.1.0")
                 implementation("androidx.collection:collection:1.1.0")
-                implementation("androidx.core:core:1.5.0-rc02")
+                implementation("androidx.core:core:1.5.0")
             }
 
             androidMain.kotlin.srcDirs("../../../text/text/src/main/java")
diff --git a/compose/ui/ui-text/samples/build.gradle b/compose/ui/ui-text/samples/build.gradle
index 517eb9b6..03b3528 100644
--- a/compose/ui/ui-text/samples/build.gradle
+++ b/compose/ui/ui-text/samples/build.gradle
@@ -31,8 +31,8 @@
 
     compileOnly(project(":annotation:annotation-sampled"))
 
-    implementation(project(":compose:foundation:foundation"))
-    implementation(project(":compose:material:material"))
+    implementation("androidx.compose.foundation:foundation:1.0.0")
+    implementation("androidx.compose.material:material:1.0.0")
     implementation(project(":compose:runtime:runtime"))
     implementation(project(":compose:ui:ui"))
     implementation(project(":compose:ui:ui-text"))
diff --git a/compose/ui/ui-tooling/build.gradle b/compose/ui/ui-tooling/build.gradle
index 80b5066..ec6d0e0 100644
--- a/compose/ui/ui-tooling/build.gradle
+++ b/compose/ui/ui-tooling/build.gradle
@@ -39,7 +39,7 @@
         api(project(":compose:ui:ui"))
         api(project(":compose:ui:ui-tooling-preview"))
         api(project(":compose:ui:ui-tooling-data"))
-        implementation(project(":compose:material:material"))
+        implementation("androidx.compose.material:material:1.0.0")
         implementation("androidx.activity:activity-compose:1.3.0")
 
         // kotlin-reflect and animation-tooling-internal are provided by Studio at runtime
diff --git a/compose/ui/ui-unit/api/current.txt b/compose/ui/ui-unit/api/current.txt
index 28850dc..c800b19 100644
--- a/compose/ui/ui-unit/api/current.txt
+++ b/compose/ui/ui-unit/api/current.txt
@@ -80,13 +80,19 @@
     method public static inline float getWidth(androidx.compose.ui.unit.DpRect);
     method public static inline boolean isFinite(float);
     method public static inline boolean isSpecified(float);
+    method public static inline boolean isSpecified(long);
+    method public static inline boolean isSpecified(long);
     method public static inline boolean isUnspecified(float);
+    method public static inline boolean isUnspecified(long);
+    method public static inline boolean isUnspecified(long);
     method @androidx.compose.runtime.Stable public static float lerp(float start, float stop, float fraction);
     method @androidx.compose.runtime.Stable public static long lerp(long start, long stop, float fraction);
     method @androidx.compose.runtime.Stable public static long lerp(long start, long stop, float fraction);
     method @androidx.compose.runtime.Stable public static inline float max(float a, float b);
     method @androidx.compose.runtime.Stable public static inline float min(float a, float b);
     method public static inline float takeOrElse(float, kotlin.jvm.functions.Function0<androidx.compose.ui.unit.Dp> block);
+    method public static inline long takeOrElse(long, kotlin.jvm.functions.Function0<androidx.compose.ui.unit.DpOffset> block);
+    method public static inline long takeOrElse(long, kotlin.jvm.functions.Function0<androidx.compose.ui.unit.DpSize> block);
     method @androidx.compose.runtime.Stable public static inline operator float times(float, float other);
     method @androidx.compose.runtime.Stable public static inline operator float times(double, float other);
     method @androidx.compose.runtime.Stable public static inline operator float times(int, float other);
@@ -99,7 +105,9 @@
   }
 
   public static final class DpOffset.Companion {
+    method public long getUnspecified();
     method public long getZero();
+    property public final long Unspecified;
     property public final long Zero;
   }
 
@@ -130,7 +138,9 @@
   }
 
   public static final class DpSize.Companion {
+    method public long getUnspecified();
     method public long getZero();
+    property public final long Unspecified;
     property public final long Zero;
   }
 
diff --git a/compose/ui/ui-unit/api/public_plus_experimental_current.txt b/compose/ui/ui-unit/api/public_plus_experimental_current.txt
index d51cc83..284ff88 100644
--- a/compose/ui/ui-unit/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-unit/api/public_plus_experimental_current.txt
@@ -80,13 +80,19 @@
     method public static inline float getWidth(androidx.compose.ui.unit.DpRect);
     method public static inline boolean isFinite(float);
     method public static inline boolean isSpecified(float);
+    method public static inline boolean isSpecified(long);
+    method public static inline boolean isSpecified(long);
     method public static inline boolean isUnspecified(float);
+    method public static inline boolean isUnspecified(long);
+    method public static inline boolean isUnspecified(long);
     method @androidx.compose.runtime.Stable public static float lerp(float start, float stop, float fraction);
     method @androidx.compose.runtime.Stable public static long lerp(long start, long stop, float fraction);
     method @androidx.compose.runtime.Stable public static long lerp(long start, long stop, float fraction);
     method @androidx.compose.runtime.Stable public static inline float max(float a, float b);
     method @androidx.compose.runtime.Stable public static inline float min(float a, float b);
     method public static inline float takeOrElse(float, kotlin.jvm.functions.Function0<androidx.compose.ui.unit.Dp> block);
+    method public static inline long takeOrElse(long, kotlin.jvm.functions.Function0<androidx.compose.ui.unit.DpOffset> block);
+    method public static inline long takeOrElse(long, kotlin.jvm.functions.Function0<androidx.compose.ui.unit.DpSize> block);
     method @androidx.compose.runtime.Stable public static inline operator float times(float, float other);
     method @androidx.compose.runtime.Stable public static inline operator float times(double, float other);
     method @androidx.compose.runtime.Stable public static inline operator float times(int, float other);
@@ -99,7 +105,9 @@
   }
 
   public static final class DpOffset.Companion {
+    method public long getUnspecified();
     method public long getZero();
+    property public final long Unspecified;
     property public final long Zero;
   }
 
@@ -130,7 +138,9 @@
   }
 
   public static final class DpSize.Companion {
+    method public long getUnspecified();
     method public long getZero();
+    property public final long Unspecified;
     property public final long Zero;
   }
 
diff --git a/compose/ui/ui-unit/api/restricted_current.txt b/compose/ui/ui-unit/api/restricted_current.txt
index 9fba15f..d891eaf 100644
--- a/compose/ui/ui-unit/api/restricted_current.txt
+++ b/compose/ui/ui-unit/api/restricted_current.txt
@@ -80,13 +80,19 @@
     method public static inline float getWidth(androidx.compose.ui.unit.DpRect);
     method public static inline boolean isFinite(float);
     method public static inline boolean isSpecified(float);
+    method public static inline boolean isSpecified(long);
+    method public static inline boolean isSpecified(long);
     method public static inline boolean isUnspecified(float);
+    method public static inline boolean isUnspecified(long);
+    method public static inline boolean isUnspecified(long);
     method @androidx.compose.runtime.Stable public static float lerp(float start, float stop, float fraction);
     method @androidx.compose.runtime.Stable public static long lerp(long start, long stop, float fraction);
     method @androidx.compose.runtime.Stable public static long lerp(long start, long stop, float fraction);
     method @androidx.compose.runtime.Stable public static inline float max(float a, float b);
     method @androidx.compose.runtime.Stable public static inline float min(float a, float b);
     method public static inline float takeOrElse(float, kotlin.jvm.functions.Function0<androidx.compose.ui.unit.Dp> block);
+    method public static inline long takeOrElse(long, kotlin.jvm.functions.Function0<androidx.compose.ui.unit.DpOffset> block);
+    method public static inline long takeOrElse(long, kotlin.jvm.functions.Function0<androidx.compose.ui.unit.DpSize> block);
     method @androidx.compose.runtime.Stable public static inline operator float times(float, float other);
     method @androidx.compose.runtime.Stable public static inline operator float times(double, float other);
     method @androidx.compose.runtime.Stable public static inline operator float times(int, float other);
@@ -99,7 +105,9 @@
   }
 
   public static final class DpOffset.Companion {
+    method public long getUnspecified();
     method public long getZero();
+    property public final long Unspecified;
     property public final long Zero;
   }
 
@@ -130,7 +138,9 @@
   }
 
   public static final class DpSize.Companion {
+    method public long getUnspecified();
     method public long getZero();
+    property public final long Unspecified;
     property public final long Zero;
   }
 
diff --git a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Density.kt b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Density.kt
index 787be8a..1f03221 100644
--- a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Density.kt
+++ b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Density.kt
@@ -20,6 +20,7 @@
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.geometry.isSpecified
 import kotlin.math.roundToInt
 
 /**
@@ -141,11 +142,19 @@
      * Convert a [DpSize] to a [Size].
      */
     @Stable
-    fun DpSize.toSize(): Size = Size(width.toPx(), height.toPx())
+    fun DpSize.toSize(): Size = if (isSpecified) {
+        Size(width.toPx(), height.toPx())
+    } else {
+        Size.Unspecified
+    }
 
     /**
      * Convert a [Size] to a [DpSize].
      */
     @Stable
-    fun Size.toDpSize(): DpSize = DpSize(width.toDp(), height.toDp())
+    fun Size.toDpSize(): DpSize = if (isSpecified) {
+        DpSize(width.toDp(), height.toDp())
+    } else {
+        DpSize.Unspecified
+    }
 }
diff --git a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt
index 3670a6d..9f294ec 100644
--- a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt
+++ b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt
@@ -265,15 +265,27 @@
      * The horizontal aspect of the offset in [Dp]
      */
     @Stable
-    /*inline*/ val x: Dp
-        get() = unpackFloat1(packedValue).dp
+        /*inline*/ val x: Dp
+        get() {
+            // Explicitly compare against packed values to avoid auto-boxing of DpOffset.Unspecified
+            check(this.packedValue != Unspecified.packedValue) {
+                "DpOffset is unspecified"
+            }
+            return unpackFloat1(packedValue).dp
+        }
 
     /**
      * The vertical aspect of the offset in [Dp]
      */
     @Stable
-    /*inline*/ val y: Dp
-        get() = unpackFloat2(packedValue).dp
+        /*inline*/ val y: Dp
+        get() {
+            // Explicitly compare against packed values to avoid auto-boxing of DpOffset.Unspecified
+            check(this.packedValue != Unspecified.packedValue) {
+                "DpOffset is unspecified"
+            }
+            return unpackFloat2(packedValue).dp
+        }
 
     /**
      * Returns a copy of this [DpOffset] instance optionally overriding the
@@ -296,17 +308,50 @@
         DpOffset(x + other.x, y + other.y)
 
     @Stable
-    override fun toString(): String = "($x, $y)"
+    override fun toString(): String =
+        if (isSpecified) {
+            "($x, $y)"
+        } else {
+            "DpOffset.Unspecified"
+        }
 
     companion object {
         /**
          * A [DpOffset] with 0 DP [x] and 0 DP [y] values.
          */
         val Zero = DpOffset(0.dp, 0.dp)
+
+        /**
+         * Represents an offset whose [x] and [y] are unspecified. This is usually a replacement for
+         * `null` when a primitive value is desired.
+         * Access to [x] or [y] on an unspecified offset is not allowed.
+         */
+        val Unspecified = DpOffset(Dp.Unspecified, Dp.Unspecified)
     }
 }
 
 /**
+ * `false` when this is [DpOffset.Unspecified].
+ */
+@Stable
+inline val DpOffset.isSpecified: Boolean
+    get() = packedValue != DpOffset.Unspecified.packedValue
+
+/**
+ * `true` when this is [DpOffset.Unspecified].
+ */
+@Stable
+inline val DpOffset.isUnspecified: Boolean
+    get() = packedValue == DpOffset.Unspecified.packedValue
+
+/**
+ * If this [DpOffset]&nbsp;[isSpecified] then this is returned, otherwise [block] is executed
+ * and its result is returned.
+ */
+inline fun DpOffset.takeOrElse(block: () -> DpOffset): DpOffset =
+    if (isSpecified) this else block()
+
+/**
  * Linearly interpolate between two [DpOffset]s.
  *
  * The [fraction] argument represents position on the timeline, with 0.0 meaning
@@ -338,15 +383,27 @@
      * The horizontal aspect of the Size in [Dp]
      */
     @Stable
-    /*inline*/ val width: Dp
-        get() = unpackFloat1(packedValue).dp
+        /*inline*/ val width: Dp
+        get() {
+            // Explicitly compare against packed values to avoid auto-boxing of DpSize.Unspecified
+            check(this.packedValue != Unspecified.packedValue) {
+                "DpSize is unspecified"
+            }
+            return unpackFloat1(packedValue).dp
+        }
 
     /**
      * The vertical aspect of the Size in [Dp]
      */
     @Stable
-    /*inline*/ val height: Dp
-        get() = unpackFloat2(packedValue).dp
+        /*inline*/ val height: Dp
+        get() {
+            // Explicitly compare against packed values to avoid auto-boxing of DpSize.Unspecified
+            check(this.packedValue != Unspecified.packedValue) {
+                "DpSize is unspecified"
+            }
+            return unpackFloat2(packedValue).dp
+        }
 
     /**
      * Returns a copy of this [DpSize] instance optionally overriding the
@@ -387,17 +444,50 @@
     operator fun div(other: Float): DpSize = DpSize(width / other, height / other)
 
     @Stable
-    override fun toString(): String = "$width x $height"
+    override fun toString(): String =
+        if (isSpecified) {
+            "$width x $height"
+        } else {
+            "DpSize.Unspecified"
+        }
 
     companion object {
         /**
          * A [DpSize] with 0 DP [width] and 0 DP [height] values.
          */
         val Zero = DpSize(0.dp, 0.dp)
+
+        /**
+         * A size whose [width] and [height] are unspecified. This is usually a replacement for
+         * `null` when a primitive value is desired.
+         * Access to [width] or [height] on an unspecified size is not allowed.
+         */
+        val Unspecified = DpSize(Dp.Unspecified, Dp.Unspecified)
     }
 }
 
 /**
+ * `false` when this is [DpSize.Unspecified].
+ */
+@Stable
+inline val DpSize.isSpecified: Boolean
+    get() = packedValue != DpSize.Unspecified.packedValue
+
+/**
+ * `true` when this is [DpSize.Unspecified].
+ */
+@Stable
+inline val DpSize.isUnspecified: Boolean
+    get() = packedValue == DpSize.Unspecified.packedValue
+
+/**
+ * If this [DpSize]&nbsp;[isSpecified] then this is returned, otherwise [block] is executed
+ * and its result is returned.
+ */
+inline fun DpSize.takeOrElse(block: () -> DpSize): DpSize =
+    if (isSpecified) this else block()
+
+/**
  * Returns the [DpOffset] of the center of the rect from the point of [0, 0]
  * with this [DpSize].
  */
diff --git a/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DensityTest.kt b/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DensityTest.kt
index d7a297e..bcb2400 100644
--- a/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DensityTest.kt
+++ b/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DensityTest.kt
@@ -115,4 +115,14 @@
     fun testSizeToDpSize() = with(density) {
         assertEquals(DpSize(1.dp, 3.dp), Size(2f, 6f).toDpSize())
     }
+
+    @Test
+    fun testDpSizeUnspecifiedToSize() = with(density) {
+        assertEquals(Size.Unspecified, DpSize.Unspecified.toSize())
+    }
+
+    @Test
+    fun testSizeUnspecifiedToDpSize() = with(density) {
+        assertEquals(DpSize.Unspecified, Size.Unspecified.toDpSize())
+    }
 }
\ No newline at end of file
diff --git a/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DpOffsetTest.kt b/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DpOffsetTest.kt
new file mode 100644
index 0000000..6e2b181
--- /dev/null
+++ b/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DpOffsetTest.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.unit
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class DpOffsetTest {
+    @Test
+    fun constructor() {
+        val size = DpOffset(x = 5.dp, y = 10.dp)
+        assertEquals(5.dp, size.x)
+        assertEquals(10.dp, size.y)
+    }
+
+    @Test
+    fun copy() {
+        val position = DpOffset(12.dp, 27.dp)
+        assertEquals(position, position.copy())
+    }
+
+    @Test
+    fun copyOverwritesX() {
+        val position = DpOffset(15.dp, 32.dp)
+        val copy = position.copy(x = 59.dp)
+        assertEquals(59.dp, copy.x)
+        assertEquals(32.dp, copy.y)
+    }
+
+    @Test
+    fun copyOverwritesY() {
+        val position = DpOffset(19.dp, 42.dp)
+        val copy = position.copy(y = 67.dp)
+        assertEquals(19.dp, copy.x)
+        assertEquals(67.dp, copy.y)
+    }
+
+    @Test
+    fun dpOffsetPlusDpOffset() {
+        val a = DpOffset(3.dp, 10.dp)
+        val b = DpOffset(5.dp, 8.dp)
+        assertEquals(DpOffset(8.dp, 18.dp), a + b)
+        assertEquals(DpOffset(8.dp, 18.dp), b + a)
+    }
+
+    @Test
+    fun dpOffsetMinusDpOffset() {
+        val a = DpOffset(3.dp, 10.dp)
+        val b = DpOffset(5.dp, 8.dp)
+        assertEquals(DpOffset((-2).dp, 2.dp), a - b)
+        assertEquals(DpOffset(2.dp, (-2).dp), b - a)
+    }
+
+    @Test
+    fun lerp() {
+        val a = DpOffset(3.dp, 10.dp)
+        val b = DpOffset(5.dp, 8.dp)
+        assertEquals(DpOffset(4.dp, 9.dp), lerp(a, b, 0.5f))
+        assertEquals(DpOffset(3.dp, 10.dp), lerp(a, b, 0f))
+        assertEquals(DpOffset(5.dp, 8.dp), lerp(a, b, 1f))
+    }
+
+    @Test
+    fun testIsSpecified() {
+        assertFalse(DpOffset.Unspecified.isSpecified)
+        assertTrue(DpOffset(1.dp, 1.dp).isSpecified)
+    }
+
+    @Test
+    fun testIsUnspecified() {
+        assertTrue(DpOffset.Unspecified.isUnspecified)
+        assertFalse(DpOffset(1.dp, 1.dp).isUnspecified)
+    }
+
+    @Test
+    fun testTakeOrElseTrue() {
+        assertTrue(DpOffset(1.dp, 1.dp).takeOrElse { DpOffset.Unspecified }.isSpecified)
+    }
+
+    @Test
+    fun testTakeOrElseFalse() {
+        assertTrue(DpOffset.Unspecified.takeOrElse { DpOffset(1.dp, 1.dp) }.isSpecified)
+    }
+
+    @Test
+    fun testToString() {
+        assertEquals("(1.0.dp, 1.0.dp)", DpOffset(1.dp, 1.dp).toString())
+    }
+
+    @Test
+    fun testUnspecifiedToString() {
+        assertEquals("DpOffset.Unspecified", DpOffset.Unspecified.toString())
+    }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DpSizeTest.kt b/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DpSizeTest.kt
index d6203f9..14cbd2e 100644
--- a/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DpSizeTest.kt
+++ b/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DpSizeTest.kt
@@ -17,6 +17,8 @@
 package androidx.compose.ui.unit
 
 import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
 import org.junit.Test
 
 class DpSizeTest {
@@ -74,4 +76,36 @@
     fun dpRectSize() {
         assertEquals(DpSize(10.dp, 5.dp), DpRect(2.dp, 3.dp, 12.dp, 8.dp).size)
     }
+
+    @Test
+    fun testIsSpecified() {
+        assertFalse(DpSize.Unspecified.isSpecified)
+        assertTrue(DpSize(1.dp, 1.dp).isSpecified)
+    }
+
+    @Test
+    fun testIsUnspecified() {
+        assertTrue(DpSize.Unspecified.isUnspecified)
+        assertFalse(DpSize(1.dp, 1.dp).isUnspecified)
+    }
+
+    @Test
+    fun testTakeOrElseTrue() {
+        assertTrue(DpSize(1.dp, 1.dp).takeOrElse { DpSize.Unspecified }.isSpecified)
+    }
+
+    @Test
+    fun testTakeOrElseFalse() {
+        assertTrue(DpSize.Unspecified.takeOrElse { DpSize(1.dp, 1.dp) }.isSpecified)
+    }
+
+    @Test
+    fun testToString() {
+        assertEquals("1.0.dp x 1.0.dp", DpSize(1.dp, 1.dp).toString())
+    }
+
+    @Test
+    fun testUnspecifiedToString() {
+        assertEquals("DpSize.Unspecified", DpSize.Unspecified.toString())
+    }
 }
\ No newline at end of file
diff --git a/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DpTest.kt b/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DpTest.kt
index afe2c27..9061916 100644
--- a/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DpTest.kt
+++ b/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DpTest.kt
@@ -162,31 +162,6 @@
     }
 
     @Test
-    fun lerpPosition() {
-        val a = DpOffset(3.dp, 10.dp)
-        val b = DpOffset(5.dp, 8.dp)
-        assertEquals(DpOffset(4.dp, 9.dp), lerp(a, b, 0.5f))
-        assertEquals(DpOffset(3.dp, 10.dp), lerp(a, b, 0f))
-        assertEquals(DpOffset(5.dp, 8.dp), lerp(a, b, 1f))
-    }
-
-    @Test
-    fun positionMinus() {
-        val a = DpOffset(3.dp, 10.dp)
-        val b = DpOffset(5.dp, 8.dp)
-        assertEquals(DpOffset(-2.dp, 2.dp), a - b)
-        assertEquals(DpOffset(2.dp, -2.dp), b - a)
-    }
-
-    @Test
-    fun positionPlus() {
-        val a = DpOffset(3.dp, 10.dp)
-        val b = DpOffset(5.dp, 8.dp)
-        assertEquals(DpOffset(8.dp, 18.dp), a + b)
-        assertEquals(DpOffset(8.dp, 18.dp), b + a)
-    }
-
-    @Test
     fun dpRectConstructor() {
         assertEquals(
             DpRect(10.dp, 5.dp, 25.dp, 15.dp),
@@ -207,28 +182,6 @@
     }
 
     @Test
-    fun testPositionCopy() {
-        val position = DpOffset(12.dp, 27.dp)
-        assertEquals(position, position.copy())
-    }
-
-    @Test
-    fun testPositionCopyOverwriteX() {
-        val position = DpOffset(15.dp, 32.dp)
-        val copy = position.copy(x = 59.dp)
-        assertEquals(59.dp, copy.x)
-        assertEquals(32.dp, copy.y)
-    }
-
-    @Test
-    fun testPositionCopyOverwriteY() {
-        val position = DpOffset(19.dp, 42.dp)
-        val copy = position.copy(y = 67.dp)
-        assertEquals(19.dp, copy.x)
-        assertEquals(67.dp, copy.y)
-    }
-
-    @Test
     fun testIsSpecified() {
         Assert.assertFalse(Dp.Unspecified.isSpecified)
         assertTrue(Dp(1f).isSpecified)
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index ec30e66..a5f0b94 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -287,6 +287,9 @@
     method public boolean moveFocus(int focusDirection);
   }
 
+  public final class FocusManagerKt {
+  }
+
   public final class FocusModifierKt {
     method @Deprecated public static androidx.compose.ui.Modifier focusModifier(androidx.compose.ui.Modifier);
     method public static androidx.compose.ui.Modifier focusTarget(androidx.compose.ui.Modifier);
@@ -333,6 +336,16 @@
     method public static androidx.compose.ui.Modifier focusOrder(androidx.compose.ui.Modifier, androidx.compose.ui.focus.FocusRequester focusRequester, kotlin.jvm.functions.Function1<? super androidx.compose.ui.focus.FocusOrder,kotlin.Unit> focusOrderReceiver);
   }
 
+  public interface FocusProperties {
+    method public boolean getCanFocus();
+    method public void setCanFocus(boolean canFocus);
+    property public abstract boolean canFocus;
+  }
+
+  public final class FocusPropertiesKt {
+    method public static androidx.compose.ui.Modifier focusProperties(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.focus.FocusProperties,kotlin.Unit> scope);
+  }
+
   public final class FocusRequester {
     ctor public FocusRequester();
     method public boolean captureFocus();
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index 6f69075..a0035e9 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -358,6 +358,9 @@
     method public boolean moveFocus(int focusDirection);
   }
 
+  public final class FocusManagerKt {
+  }
+
   public final class FocusModifierKt {
     method @Deprecated public static androidx.compose.ui.Modifier focusModifier(androidx.compose.ui.Modifier);
     method public static androidx.compose.ui.Modifier focusTarget(androidx.compose.ui.Modifier);
@@ -404,6 +407,16 @@
     method public static androidx.compose.ui.Modifier focusOrder(androidx.compose.ui.Modifier, androidx.compose.ui.focus.FocusRequester focusRequester, kotlin.jvm.functions.Function1<? super androidx.compose.ui.focus.FocusOrder,kotlin.Unit> focusOrderReceiver);
   }
 
+  public interface FocusProperties {
+    method public boolean getCanFocus();
+    method public void setCanFocus(boolean canFocus);
+    property public abstract boolean canFocus;
+  }
+
+  public final class FocusPropertiesKt {
+    method public static androidx.compose.ui.Modifier focusProperties(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.focus.FocusProperties,kotlin.Unit> scope);
+  }
+
   public final class FocusRequester {
     ctor public FocusRequester();
     method public boolean captureFocus();
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 28214e6..e34df74 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -287,6 +287,9 @@
     method public boolean moveFocus(int focusDirection);
   }
 
+  public final class FocusManagerKt {
+  }
+
   public final class FocusModifierKt {
     method @Deprecated public static androidx.compose.ui.Modifier focusModifier(androidx.compose.ui.Modifier);
     method public static androidx.compose.ui.Modifier focusTarget(androidx.compose.ui.Modifier);
@@ -333,6 +336,16 @@
     method public static androidx.compose.ui.Modifier focusOrder(androidx.compose.ui.Modifier, androidx.compose.ui.focus.FocusRequester focusRequester, kotlin.jvm.functions.Function1<? super androidx.compose.ui.focus.FocusOrder,kotlin.Unit> focusOrderReceiver);
   }
 
+  public interface FocusProperties {
+    method public boolean getCanFocus();
+    method public void setCanFocus(boolean canFocus);
+    property public abstract boolean canFocus;
+  }
+
+  public final class FocusPropertiesKt {
+    method public static androidx.compose.ui.Modifier focusProperties(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.focus.FocusProperties,kotlin.Unit> scope);
+  }
+
   public final class FocusRequester {
     ctor public FocusRequester();
     method public boolean captureFocus();
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/UiDemos.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/UiDemos.kt
index b19fa00..f415db7 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/UiDemos.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/UiDemos.kt
@@ -53,6 +53,7 @@
 import androidx.compose.ui.demos.gestures.ScrollGestureFilterDemo
 import androidx.compose.ui.demos.gestures.VerticalScrollerInDrawerDemo
 import androidx.compose.ui.demos.input.TouchModeDemo
+import androidx.compose.ui.demos.focus.ConditionalFocusabilityDemo
 import androidx.compose.ui.demos.scroll.BringIntoViewDemo
 import androidx.compose.ui.demos.keyinput.KeyInputDemo
 import androidx.compose.ui.demos.keyinput.InterceptEnterToSendMessageDemo
@@ -132,7 +133,8 @@
         ComposableDemo("Custom Focus Order") { CustomFocusOrderDemo() },
         ComposableDemo("FocusManager.moveFocus()") { FocusManagerMoveFocusDemo() },
         ComposableDemo("Capture/Free Focus") { CaptureFocusDemo() },
-        ComposableDemo("Focus In Scrollable Row") { ScrollableRowFocusDemo() }
+        ComposableDemo("Focus In Scrollable Row") { ScrollableRowFocusDemo() },
+        ComposableDemo("Conditional Focusability") { ConditionalFocusabilityDemo() }
     )
 )
 
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/ConditionalFocusabilityDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/ConditionalFocusabilityDemo.kt
new file mode 100644
index 0000000..08380dd
--- /dev/null
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/ConditionalFocusabilityDemo.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.demos.focus
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.Button
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusProperties
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.graphics.Color.Companion.Gray
+import androidx.compose.ui.graphics.Color.Companion.Red
+import androidx.compose.ui.input.InputMode.Companion.Keyboard
+import androidx.compose.ui.platform.LocalInputModeManager
+import androidx.compose.ui.unit.dp
+
+@OptIn(ExperimentalComposeUiApi::class)
+@Composable
+fun ConditionalFocusabilityDemo() {
+    val localInputModeManager = LocalInputModeManager.current
+    val (item1, item2, item3, item4) = remember { FocusRequester.createRefs() }
+    Column {
+        Text(
+            """
+             The items here are focusable. Use the
+             keyboard or DPad to move focus among them.
+
+             The 1st item is focusable in all modes.
+             Notice that when you touch the screen it
+             does not lose focus like the other items.
+
+             The 2nd item's focusability can be
+             controlled by using the button next to it.
+
+             The 3rd item is not focusable in touch mode.
+
+             The 4th item is not focusable in touch mode,
+             but clicking on it will request the system
+             to switch to keyboard mode, and then call
+             request focus.
+             """.trimIndent()
+        )
+        Text(
+            text = "Focusable in all modes",
+            modifier = Modifier
+                .focusAwareBackground()
+                .focusRequester(item1)
+                .clickable { item1.requestFocus() }
+                .focusable()
+        )
+        Row {
+            var item2active by remember { mutableStateOf(false) }
+            Text(
+                text = "focusable item that is " +
+                    "${if (item2active) "activated" else "deactivated"}",
+                modifier = Modifier
+                    .focusAwareBackground()
+                    .focusRequester(item2)
+                    .clickable { item2.requestFocus() }
+                    .focusProperties { canFocus = item2active }
+                    .focusable()
+            )
+            Button(onClick = { item2active = !item2active }) {
+                Text("${if (item2active) "deactivate" else "activate"} item 2")
+            }
+        }
+        Text(
+            text = "Focusable in keyboard mode",
+            modifier = Modifier
+                .focusAwareBackground()
+                .focusRequester(item3)
+                .clickable { item3.requestFocus() }
+                .focusProperties { canFocus = localInputModeManager.inputMode == Keyboard }
+                .focusable()
+        )
+        Text(
+            text = "Request focus by touch",
+            modifier = Modifier
+                .focusAwareBackground()
+                .focusRequester(item4)
+                .clickable {
+                    if (localInputModeManager.requestInputMode(Keyboard)) {
+                        item4.requestFocus()
+                    }
+                }
+                .focusProperties { canFocus = localInputModeManager.inputMode == Keyboard }
+                .focusable()
+        )
+    }
+}
+
+private fun Modifier.focusAwareBackground() = composed {
+    var color by remember { mutableStateOf(Gray) }
+    Modifier
+        .padding(10.dp)
+        .size(150.dp, 50.dp)
+        .background(color)
+        .onFocusChanged { color = if (it.isFocused) Red else Gray }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/samples/build.gradle b/compose/ui/ui/samples/build.gradle
index 5376a2c..932da27 100644
--- a/compose/ui/ui/samples/build.gradle
+++ b/compose/ui/ui/samples/build.gradle
@@ -31,9 +31,9 @@
 
     compileOnly(project(":annotation:annotation-sampled"))
 
-    implementation(project(":compose:animation:animation-core"))
-    implementation(project(":compose:foundation:foundation-layout"))
-    implementation(project(":compose:material:material"))
+    implementation("androidx.compose.animation:animation-core:1.0.0")
+    implementation("androidx.compose.foundation:foundation-layout:1.0.0")
+    implementation("androidx.compose.material:material:1.0.0")
     implementation(project(":compose:runtime:runtime"))
     implementation(project(":compose:ui:ui"))
 }
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt
index 8f0da4a..9d4ab33 100644
--- a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt
@@ -40,6 +40,7 @@
 import androidx.compose.ui.focus.FocusDirection
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusOrder
+import androidx.compose.ui.focus.focusProperties
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.focus.focusTarget
 import androidx.compose.ui.focus.onFocusChanged
@@ -47,7 +48,9 @@
 import androidx.compose.ui.graphics.Color.Companion.Green
 import androidx.compose.ui.graphics.Color.Companion.Red
 import androidx.compose.ui.graphics.Color.Companion.Transparent
+import androidx.compose.ui.input.InputMode.Companion.Touch
 import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.platform.LocalInputModeManager
 import androidx.compose.ui.unit.dp
 
 @Sampled
@@ -205,3 +208,21 @@
         }
     }
 }
+
+@Sampled
+@Composable
+fun FocusPropertiesSample() {
+    Column {
+        // Always focusable.
+        Box(modifier = Modifier
+            .focusProperties { canFocus = true }
+            .focusTarget()
+        )
+        // Only focusable in non-touch mode.
+        val inputModeManager = LocalInputModeManager.current
+        Box(modifier = Modifier
+            .focusProperties { canFocus = inputModeManager.inputMode != Touch }
+            .focusTarget()
+        )
+    }
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
index 55b1d51..c10eeed 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
@@ -120,8 +120,12 @@
 import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.TextLayoutResult
 import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.input.OffsetMapping
 import androidx.compose.ui.text.input.PasswordVisualTransformation
 import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.text.input.TransformedText
+import androidx.compose.ui.text.intl.LocaleList
+import androidx.compose.ui.text.toUpperCase
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
@@ -477,7 +481,10 @@
         )
         if (Build.VERSION.SDK_INT >= 26) {
             assertEquals(
-                listOf(AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY),
+                listOf(
+                    AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY,
+                    "androidx.compose.ui.semantics.testTag"
+                ),
                 accessibilityNodeInfo.availableExtraData
             )
         }
@@ -937,6 +944,16 @@
         assertEquals(expectedTopLeftInScreenCoords.y, rectF.top)
         assertEquals(expectedRectInLocalCoords.width, rectF.width())
         assertEquals(expectedRectInLocalCoords.height, rectF.height())
+
+        val testTagKey = "androidx.compose.ui.semantics.testTag"
+        provider.addExtraDataToAccessibilityNodeInfo(
+            textFieldNode.id,
+            info,
+            testTagKey,
+            argument
+        )
+        val testTagData = info.extras.getCharSequence(testTagKey)
+        assertEquals(tag, testTagData.toString())
     }
 
     @Test
@@ -1216,6 +1233,7 @@
 
     @Test
     fun sendTextEvents_whenSetText() {
+        val locale = LocaleList("en_US")
         val tag = "TextField"
         val initialText = "h"
         val text = "hello"
@@ -1224,7 +1242,13 @@
             BasicTextField(
                 modifier = Modifier.testTag(tag),
                 value = value,
-                onValueChange = { value = it }
+                onValueChange = { value = it },
+                visualTransformation = {
+                    TransformedText(
+                        it.toUpperCase(locale),
+                        OffsetMapping.Identity
+                    )
+                }
             )
         }
 
@@ -1233,7 +1257,7 @@
             .assert(
                 SemanticsMatcher.expectValue(
                     SemanticsProperties.EditableText,
-                    AnnotatedString(initialText)
+                    AnnotatedString("H")
                 )
             )
 
@@ -1243,7 +1267,7 @@
             .assert(
                 SemanticsMatcher.expectValue(
                     SemanticsProperties.EditableText,
-                    AnnotatedString(text)
+                    AnnotatedString("HELLO")
                 )
             )
 
@@ -1257,8 +1281,8 @@
         textEvent.fromIndex = initialText.length
         textEvent.removedCount = 0
         textEvent.addedCount = text.length - initialText.length
-        textEvent.beforeText = initialText
-        textEvent.text.add(text)
+        textEvent.beforeText = initialText.toUpperCase(locale)
+        textEvent.text.add(text.toUpperCase(locale))
 
         val selectionEvent = delegate.createEvent(
             textFieldNode.id,
@@ -1267,15 +1291,22 @@
         selectionEvent.fromIndex = text.length
         selectionEvent.toIndex = text.length
         selectionEvent.itemCount = text.length
-        selectionEvent.text.add(text)
+        selectionEvent.text.add(text.toUpperCase(locale))
 
         rule.runOnIdle {
             verify(container, atLeastOnce()).requestSendAccessibilityEvent(
                 eq(androidComposeView), argument.capture()
             )
-            val values = argument.allValues
-            assertTrue(containsEvent(values, textEvent))
-            assertTrue(containsEvent(values, selectionEvent))
+
+            val actualTextEvent = argument.allValues.first {
+                it.eventType == AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
+            }
+            assertEquals(textEvent.toString(), actualTextEvent.toString())
+
+            val actualSelectionEvent = argument.allValues.first {
+                it.eventType == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED
+            }
+            assertEquals(selectionEvent.toString(), actualSelectionEvent.toString())
         }
     }
 
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
index edcf54b..19f3344 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
@@ -56,6 +56,7 @@
 import androidx.compose.ui.semantics.disabled
 import androidx.compose.ui.semantics.dismiss
 import androidx.compose.ui.semantics.error
+import androidx.compose.ui.semantics.editableText
 import androidx.compose.ui.semantics.expand
 import androidx.compose.ui.semantics.focused
 import androidx.compose.ui.semantics.getTextLayoutResult
@@ -71,7 +72,6 @@
 import androidx.compose.ui.semantics.setSelection
 import androidx.compose.ui.semantics.setText
 import androidx.compose.ui.semantics.stateDescription
-import androidx.compose.ui.semantics.text
 import androidx.compose.ui.semantics.textSelectionRange
 import androidx.compose.ui.semantics.verticalScrollAxisRange
 import androidx.compose.ui.test.TestActivity
@@ -240,7 +240,7 @@
 
         val semanticsNode = createSemanticsNodeWithProperties(1, true) {
             disabled()
-            text = AnnotatedString("text")
+            editableText = AnnotatedString("text")
             horizontalScrollAxisRange = ScrollAxisRange({ 0f }, { 5f })
             onClick { true }
             onLongClick { true }
@@ -437,7 +437,7 @@
         val setTextActionLabel = "setText"
         val text = "hello"
         val semanticsNode = createSemanticsNodeWithProperties(1, true) {
-            this.text = AnnotatedString(text)
+            this.editableText = AnnotatedString(text)
             this.textSelectionRange = TextRange(1)
             this.focused = true
             getTextLayoutResult { true }
@@ -493,7 +493,7 @@
     @Test
     fun testMovementGranularities_textField_focused() {
         val semanticsNode = createSemanticsNodeWithProperties(1, true) {
-            this.text = AnnotatedString("text")
+            this.editableText = AnnotatedString("text")
             this.textSelectionRange = TextRange(1)
             this.focused = true
             getTextLayoutResult { true }
@@ -515,7 +515,7 @@
     @Test
     fun testMovementGranularities_textField_notFocused() {
         val semanticsNode = createSemanticsNodeWithProperties(1, true) {
-            this.text = AnnotatedString("text")
+            this.editableText = AnnotatedString("text")
             this.textSelectionRange = TextRange(1)
             getTextLayoutResult { true }
             setText { true }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/PainterModifierTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/PainterModifierTest.kt
index 360b423..d139569 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/PainterModifierTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/PainterModifierTest.kt
@@ -18,6 +18,7 @@
 
 import android.graphics.Bitmap
 import android.os.Build
+import androidx.annotation.RequiresApi
 import androidx.compose.foundation.Image
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
@@ -101,7 +102,7 @@
 class PainterModifierTest {
 
     val containerWidth = 100.0f
-    val containerHeight = 100.0f
+    private val containerHeight = 100.0f
 
     @get:Rule
     val rule = createComposeRule()
@@ -751,6 +752,7 @@
     }
 }
 
+@RequiresApi(Build.VERSION_CODES.O)
 private fun ComposeTestRule.obtainScreenshotBitmap(width: Int, height: Int = width): Bitmap {
     val bitmap = onRoot().captureToImage()
     assertEquals(width, bitmap.width)
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/CaptureFocusTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/CaptureFocusTest.kt
index c42fa1c..0dcf0c8 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/CaptureFocusTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/CaptureFocusTest.kt
@@ -16,12 +16,12 @@
 
 package androidx.compose.ui.focus
 
+import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusStateImpl.Active
 import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
 import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Disabled
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -60,6 +60,7 @@
         rule.runOnIdle {
             assertThat(success).isTrue()
             assertThat(focusState.isCaptured).isTrue()
+            assertThat(focusState.isFocused).isTrue()
         }
     }
 
@@ -85,6 +86,7 @@
         // Assert.
         rule.runOnIdle {
             assertThat(success).isFalse()
+            assertThat(focusState.isCaptured).isFalse()
             assertThat(focusState.hasFocus).isTrue()
         }
     }
@@ -116,7 +118,7 @@
     }
 
     @Test
-    fun disabled_captureFocus_retainsStateAsDisabled() {
+    fun deactivated_captureFocus_retainsStateAsDeactivated() {
         // Arrange.
         lateinit var focusState: FocusState
         val focusRequester = FocusRequester()
@@ -125,7 +127,8 @@
                 modifier = Modifier
                     .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
-                    .then(FocusModifier(Disabled))
+                    .focusProperties { canFocus = false }
+                    .focusable()
             )
         }
 
@@ -137,7 +140,45 @@
         // Assert.
         rule.runOnIdle {
             assertThat(success).isFalse()
-            assertThat(focusState).isEqualTo(Disabled)
+            assertThat(focusState.isCaptured).isFalse()
+            assertThat(focusState.isFocused).isFalse()
+            assertThat(focusState.isDeactivated).isTrue()
+        }
+    }
+
+    @Test
+    fun deactivatedParent_captureFocus_retainsStateAsDeactivatedParent() {
+        // Arrange.
+        lateinit var focusState: FocusState
+        val initialFocus = FocusRequester()
+        val focusRequester = FocusRequester()
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .onFocusChanged { focusState = it }
+                    .focusRequester(focusRequester)
+                    .focusProperties { canFocus = false }
+                    .focusable()
+            ) {
+                Box(modifier = Modifier
+                    .focusRequester(initialFocus)
+                    .focusable()
+                )
+            }
+        }
+        rule.runOnIdle { initialFocus.requestFocus() }
+
+        // Act.
+        val success = rule.runOnIdle {
+            focusRequester.captureFocus()
+        }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(success).isFalse()
+            assertThat(focusState.isCaptured).isFalse()
+            assertThat(focusState.hasFocus).isTrue()
+            assertThat(focusState.isDeactivated).isTrue()
         }
     }
 
@@ -163,7 +204,11 @@
         // Assert.
         rule.runOnIdle {
             assertThat(success).isFalse()
+            assertThat(focusState.isCaptured).isFalse()
             assertThat(focusState.isFocused).isFalse()
         }
     }
 }
+
+private val FocusState.isDeactivated: Boolean
+    get() = (this as FocusStateImpl).isDeactivated
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/ClearFocusTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/ClearFocusTest.kt
index 7f981ea..c556531 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/ClearFocusTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/ClearFocusTest.kt
@@ -18,10 +18,12 @@
 
 import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusStateImpl.Active
 import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
 import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Disabled
+import androidx.compose.ui.focus.FocusStateImpl.Deactivated
+import androidx.compose.ui.focus.FocusStateImpl.DeactivatedParent
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.filters.SmallTest
@@ -257,11 +259,11 @@
     }
 
     @Test
-    fun Disabled_isUnchanged() {
+    fun Deactivated_isUnchanged() {
         // Arrange.
-        val modifier = FocusModifier(Disabled)
+        val modifier = FocusModifier(Inactive)
         rule.setFocusableContent {
-            Box(modifier = modifier)
+            Box(modifier = Modifier.focusProperties { canFocus = false }.then(modifier))
         }
 
         // Act.
@@ -272,7 +274,131 @@
         // Assert.
         rule.runOnIdle {
             assertThat(cleared).isTrue()
-            assertThat(modifier.focusState).isEqualTo(Disabled)
+            assertThat(modifier.focusState.isDeactivated).isTrue()
         }
     }
-}
\ No newline at end of file
+
+    @Test(expected = IllegalArgumentException::class)
+    fun deactivatedParent_noFocusedChild_throwsException() {
+        // Arrange.
+        val modifier = FocusModifier(DeactivatedParent)
+        rule.setFocusableContent {
+            Box(modifier = modifier)
+        }
+
+        // Act.
+        rule.runOnIdle {
+            modifier.focusNode.clearFocus(forced)
+        }
+    }
+
+    @Test
+    fun deactivatedParent_isClearedAndRemovedFromParentsFocusedChild() {
+        // Arrange.
+        val parent = FocusModifier(ActiveParent)
+        val modifier = FocusModifier(ActiveParent)
+        val child = FocusModifier(Active)
+        rule.setFocusableContent {
+            Box(modifier = parent) {
+                Box(modifier = Modifier.focusProperties { canFocus = false }.then(modifier)) {
+                    Box(modifier = child)
+                }
+            }
+            SideEffect {
+                parent.focusedChild = modifier.focusNode
+                modifier.focusedChild = child.focusNode
+            }
+        }
+
+        // Act.
+        val cleared = rule.runOnIdle {
+            modifier.focusNode.clearFocus(forced)
+        }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(cleared).isTrue()
+            assertThat(modifier.focusedChild).isNull()
+            assertThat(modifier.focusState.isDeactivated).isTrue()
+        }
+    }
+
+    @Test
+    fun deactivatedParent_withDeactivatedGrandParent_isClearedAndRemovedFromParentsFocusedChild() {
+        // Arrange.
+        val parent = FocusModifier(ActiveParent)
+        val modifier = FocusModifier(ActiveParent)
+        val child = FocusModifier(Active)
+        rule.setFocusableContent {
+            Box(modifier = Modifier.focusProperties { canFocus = false }.then(parent)) {
+                Box(modifier = Modifier.focusProperties { canFocus = false }.then(modifier)) {
+                    Box(modifier = child)
+                }
+            }
+            SideEffect {
+                parent.focusedChild = modifier.focusNode
+                modifier.focusedChild = child.focusNode
+            }
+        }
+
+        // Act.
+        val cleared = rule.runOnIdle {
+            modifier.focusNode.clearFocus(forced)
+        }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(cleared).isTrue()
+            assertThat(modifier.focusedChild).isNull()
+            assertThat(modifier.focusState.isDeactivated).isTrue()
+        }
+    }
+
+    @Test
+    fun deactivatedParent_clearsEntireHierarchy() {
+        // Arrange.
+        val modifier = FocusModifier(ActiveParent)
+        val child = FocusModifier(ActiveParent)
+        val grandchild = FocusModifier(ActiveParent)
+        val greatGrandchild = FocusModifier(ActiveParent)
+        val greatGreatGrandchild = FocusModifier(Active)
+        rule.setFocusableContent {
+            Box(modifier = Modifier.focusProperties { canFocus = false }.then(modifier)) {
+                Box(modifier = child) {
+                    Box(modifier = Modifier
+                        .focusProperties { canFocus = false }
+                        .then(grandchild)
+                    ) {
+                        Box(modifier = greatGrandchild) {
+                            Box(modifier = greatGreatGrandchild)
+                        }
+                    }
+                }
+            }
+            SideEffect {
+                modifier.focusedChild = child.focusNode
+                child.focusedChild = grandchild.focusNode
+                grandchild.focusedChild = greatGrandchild.focusNode
+                greatGrandchild.focusedChild = greatGreatGrandchild.focusNode
+            }
+        }
+
+        // Act.
+        val cleared = rule.runOnIdle {
+            modifier.focusNode.clearFocus(forced)
+        }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(cleared).isTrue()
+            assertThat(modifier.focusedChild).isNull()
+            assertThat(child.focusedChild).isNull()
+            assertThat(grandchild.focusedChild).isNull()
+            assertThat(modifier.focusState).isEqualTo(Deactivated)
+            assertThat(child.focusState).isEqualTo(Inactive)
+            assertThat(grandchild.focusState).isEqualTo(Deactivated)
+            assertThat(greatGrandchild.focusState).isEqualTo(Inactive)
+            assertThat(greatGreatGrandchild.focusState).isEqualTo(Inactive)
+        }
+    }
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/DeactivatedFocusPropertiesTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/DeactivatedFocusPropertiesTest.kt
new file mode 100644
index 0000000..797119d
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/DeactivatedFocusPropertiesTest.kt
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.focus
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class DeactivatedFocusPropertiesTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun notDeactivatedByDefault() {
+        // Arrange.
+        var isDeactivated: Boolean? = null
+        rule.setFocusableContent {
+            Box(modifier = Modifier
+                .onFocusChanged { isDeactivated = it.isDeactivated }
+                .focusTarget()
+            )
+        }
+
+        // Assert.
+        rule.runOnIdle { assertThat(isDeactivated).isFalse() }
+    }
+
+    @Test
+    fun initializedAsNotDeactivated() {
+        // Arrange.
+        var deactivated: Boolean? = null
+        rule.setFocusableContent {
+            Box(modifier = Modifier
+                .focusProperties { canFocus = true }
+                .onFocusChanged { deactivated = it.isDeactivated }
+                .focusTarget()
+            )
+        }
+
+        // Assert.
+        rule.runOnIdle { assertThat(deactivated).isFalse() }
+    }
+
+    @Test
+    fun initializedAsDeactivated() {
+        // Arrange.
+        var isDeactivated: Boolean? = null
+        rule.setFocusableContent {
+            Box(modifier = Modifier
+                .focusProperties { isDeactivated = true }
+                .onFocusChanged { isDeactivated = it.isDeactivated }
+                .focusTarget()
+            )
+        }
+
+        // Assert.
+        rule.runOnIdle { assertThat(isDeactivated).isTrue() }
+    }
+
+    @Test
+    fun leftMostDeactivatedPropertyTakesPrecedence() {
+        // Arrange.
+        var deactivated: Boolean? = null
+        rule.setFocusableContent {
+            Box(modifier = Modifier
+                .focusProperties { canFocus = false }
+                .focusProperties { canFocus = true }
+                .onFocusChanged { deactivated = it.isDeactivated }
+                .focusTarget()
+            )
+        }
+
+        // Assert.
+        rule.runOnIdle { assertThat(deactivated).isTrue() }
+    }
+
+    @Test
+    fun leftMostNonDeactivatedPropertyTakesPrecedence() {
+        // Arrange.
+        var deactivated: Boolean? = null
+        rule.setFocusableContent {
+            Box(modifier = Modifier
+                .focusProperties { canFocus = true }
+                .focusProperties { canFocus = false }
+                .onFocusChanged { deactivated = it.isDeactivated }
+                .focusTarget()
+            )
+        }
+
+        // Assert.
+        rule.runOnIdle { assertThat(deactivated).isFalse() }
+    }
+
+    @Test
+    fun ParentsDeactivatedPropertyTakesPrecedence() {
+        // Arrange.
+        var deactivated: Boolean? = null
+        rule.setFocusableContent {
+            Box(modifier = Modifier.focusProperties { canFocus = false }) {
+                Box(modifier = Modifier
+                    .focusProperties { canFocus = true }
+                    .onFocusChanged { deactivated = it.isDeactivated }
+                    .focusTarget()
+                )
+            }
+        }
+
+        // Assert.
+        rule.runOnIdle { assertThat(deactivated).isTrue() }
+    }
+
+    @Test
+    fun ParentsNotDeactivatedPropertyTakesPrecedence() {
+        // Arrange.
+        var deactivated: Boolean? = null
+        rule.setFocusableContent {
+            Box(modifier = Modifier.focusProperties { canFocus = true }) {
+                Box(modifier = Modifier
+                    .focusProperties { canFocus = false }
+                    .onFocusChanged { deactivated = it.isDeactivated }
+                    .focusTarget()
+                )
+            }
+        }
+
+        // Assert.
+        rule.runOnIdle { assertThat(deactivated).isFalse() }
+    }
+
+    @Test
+    fun deactivatedItemDoesNotGainFocus() {
+        // Arrange.
+        var isFocused: Boolean? = null
+        val focusRequester = FocusRequester()
+        rule.setFocusableContent {
+            Box(modifier = Modifier
+                .focusProperties { canFocus = false }
+                .focusRequester(focusRequester)
+                .onFocusChanged { isFocused = it.isFocused }
+                .focusTarget()
+            )
+        }
+
+        // Act.
+        rule.runOnIdle { focusRequester.requestFocus() }
+
+        // Assert.
+        rule.runOnIdle { assertThat(isFocused).isFalse() }
+    }
+
+    @Test
+    fun deactivatedFocusPropertiesOnNonFocusableParentAppliesToAllChildren() {
+        // Arrange.
+        var isParentDeactivated: Boolean? = null
+        var isChild1Deactivated: Boolean? = null
+        var isChild2Deactivated: Boolean? = null
+        var isGrandChildDeactivated: Boolean? = null
+        rule.setFocusableContent {
+            Column(modifier = Modifier
+                .focusProperties { canFocus = false }
+                .onFocusChanged { isParentDeactivated = it.isDeactivated }
+            ) {
+                Box(modifier = Modifier
+                    .onFocusChanged { isChild1Deactivated = it.isDeactivated }
+                    .focusTarget()
+                )
+                Box(modifier = Modifier
+                        .onFocusChanged { isChild2Deactivated = it.isDeactivated }
+                        .focusTarget()
+                ) {
+                    Box(modifier = Modifier
+                        .onFocusChanged { isGrandChildDeactivated = it.isDeactivated }
+                        .focusTarget()
+                    )
+                }
+            }
+        }
+
+        // Assert.
+        rule.runOnIdle { assertThat(isParentDeactivated).isTrue() }
+        rule.runOnIdle { assertThat(isChild1Deactivated).isTrue() }
+        rule.runOnIdle { assertThat(isChild2Deactivated).isTrue() }
+        rule.runOnIdle { assertThat(isGrandChildDeactivated).isFalse() }
+    }
+
+    @Test
+    fun focusedItemLosesFocusWhenDeactivated() {
+        // Arrange.
+        var isFocused: Boolean? = null
+        val focusRequester = FocusRequester()
+        var deactivated by mutableStateOf(false)
+        rule.setFocusableContent {
+            Box(modifier = Modifier
+                .focusProperties { canFocus = !deactivated }
+                .focusRequester(focusRequester)
+                .onFocusChanged { isFocused = it.isFocused }
+                .focusTarget()
+            )
+        }
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+            assertThat(isFocused).isTrue()
+        }
+
+        // Act.
+        rule.runOnIdle { deactivated = true }
+
+        // Assert.
+        rule.runOnIdle { assertThat(isFocused).isFalse() }
+    }
+}
+
+private val FocusState.isDeactivated: Boolean
+    get() = (this as FocusStateImpl).isDeactivated
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FindFocusableChildrenTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FindFocusableChildrenTest.kt
index 93fd9b8..f3038a1 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FindFocusableChildrenTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FindFocusableChildrenTest.kt
@@ -18,41 +18,60 @@
 
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
+import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.graphics.Color.Companion.Red
 import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
 
 @MediumTest
-@RunWith(AndroidJUnit4::class)
-class FindFocusableChildrenTest {
+@RunWith(Parameterized::class)
+class FindFocusableChildrenTest(private val excludeDeactivated: Boolean) {
     @get:Rule
     val rule = createComposeRule()
 
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "excludeDeactivated = {0}")
+        fun initParameters() = listOf(true, false)
+    }
+
     @Test
     fun returnsFirstFocusNodeInModifierChain() {
         val focusModifier1 = FocusModifier(Inactive)
         val focusModifier2 = FocusModifier(Inactive)
         val focusModifier3 = FocusModifier(Inactive)
+        val focusModifier4 = FocusModifier(Inactive)
         // Arrange.
-        // layoutNode--focusNode1--focusNode2--focusNode3
+        // layoutNode--focusNode1--focusNode2--focusNode3--focusNode4
         rule.setContent {
-            Box(modifier = focusModifier1.then(focusModifier2).then(focusModifier3))
+            Box(
+                modifier = Modifier
+                .then(focusModifier1)
+                .focusProperties { canFocus = false }
+                .then(focusModifier2)
+                .then(focusModifier3)
+                .then(focusModifier4)
+            )
         }
 
         // Act.
         val focusableChildren = rule.runOnIdle {
-            focusModifier1.focusNode.focusableChildren()
+            focusModifier1.focusNode.focusableChildren(excludeDeactivated)
         }
 
         // Assert.
         rule.runOnIdle {
-            assertThat(focusableChildren).containsExactly(focusModifier2.focusNode)
+            if (excludeDeactivated) {
+                assertThat(focusableChildren).containsExactly(focusModifier3.focusNode)
+            } else {
+                assertThat(focusableChildren).containsExactly(focusModifier2.focusNode)
+            }
         }
     }
 
@@ -60,20 +79,32 @@
     fun skipsNonFocusNodesAndReturnsFirstFocusNodeInModifierChain() {
         val focusModifier1 = FocusModifier(Inactive)
         val focusModifier2 = FocusModifier(Inactive)
+        val focusModifier3 = FocusModifier(Inactive)
         // Arrange.
-        // layoutNode--focusNode1--nonFocusNode--focusNode2
+        // layoutNode--focusNode1--nonFocusNode--focusNode2--focusNode3
         rule.setContent {
-            Box(focusModifier1.background(color = Red).then(focusModifier2))
+            Box(
+                modifier = Modifier
+                    .then(focusModifier1)
+                    .background(color = Red)
+                    .focusProperties { canFocus = false }
+                    .then(focusModifier2)
+                    .then(focusModifier3)
+            )
         }
 
         // Act.
         val focusableChildren = rule.runOnIdle {
-            focusModifier1.focusNode.focusableChildren()
+            focusModifier1.focusNode.focusableChildren(excludeDeactivated)
         }
 
         // Assert.
         rule.runOnIdle {
-            assertThat(focusableChildren).containsExactly(focusModifier2.focusNode)
+            if (excludeDeactivated) {
+                assertThat(focusableChildren).containsExactly(focusModifier3.focusNode)
+            } else {
+                assertThat(focusableChildren).containsExactly(focusModifier2.focusNode)
+            }
         }
     }
 
@@ -81,30 +112,45 @@
     fun returnsFirstFocusChildOfEachChildLayoutNode() {
         // Arrange.
         // parentLayoutNode--parentFocusNode
-        //       |___________________________________
-        //       |                                   |
-        // childLayoutNode1--focusNode1          childLayoutNode2--focusNode2--focusNode3
+        //       |___________________________________________
+        //       |                                          |
+        // childLayoutNode1--focusNode1--focusNode2    childLayoutNode2--focusNode3--focusNode4
         val parentFocusModifier = FocusModifier(Inactive)
         val focusModifier1 = FocusModifier(Inactive)
         val focusModifier2 = FocusModifier(Inactive)
         val focusModifier3 = FocusModifier(Inactive)
+        val focusModifier4 = FocusModifier(Inactive)
         rule.setContent {
             Box(modifier = parentFocusModifier) {
-                Box(modifier = focusModifier1)
-                Box(modifier = focusModifier2.then(focusModifier3))
+                Box(modifier = Modifier
+                    .focusProperties { canFocus = false }
+                    .then(focusModifier1)
+                    .then(focusModifier2)
+                )
+                Box(modifier = Modifier
+                    .then(focusModifier3)
+                    .focusProperties { canFocus = false }
+                    .then(focusModifier4)
+                )
             }
         }
 
         // Act.
         val focusableChildren = rule.runOnIdle {
-            parentFocusModifier.focusNode.focusableChildren()
+            parentFocusModifier.focusNode.focusableChildren(excludeDeactivated)
         }
 
         // Assert.
         rule.runOnIdle {
-            assertThat(focusableChildren).containsExactly(
-                focusModifier1.focusNode, focusModifier2.focusNode
-            )
+            if (excludeDeactivated) {
+                assertThat(focusableChildren).containsExactly(
+                    focusModifier2.focusNode, focusModifier3.focusNode
+                )
+            } else {
+                assertThat(focusableChildren).containsExactly(
+                    focusModifier1.focusNode, focusModifier3.focusNode
+                )
+            }
         }
     }
 }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FindParentFocusNodeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FindParentFocusNodeTest.kt
index ec506c2..494246c 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FindParentFocusNodeTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FindParentFocusNodeTest.kt
@@ -18,22 +18,29 @@
 
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
+import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.graphics.Color.Companion.Red
 import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
 
 @MediumTest
-@RunWith(AndroidJUnit4::class)
-class FindParentFocusNodeTest {
+@RunWith(Parameterized::class)
+class FindParentFocusNodeTest(private val deactivated: Boolean) {
     @get:Rule
     val rule = createComposeRule()
 
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "isDeactivated = {0}")
+        fun initParameters() = listOf(true, false)
+    }
+
     @Test
     fun noParentReturnsNull() {
         // Arrange.
@@ -63,7 +70,13 @@
         val modifier4 = FocusModifier(Inactive)
         val modifier5 = FocusModifier(Inactive)
         rule.setFocusableContent {
-            Box(modifier1.then(modifier2).then(modifier3).then(modifier4).then(modifier5)) {}
+            Box(modifier = modifier1
+                .focusProperties { canFocus = !deactivated }
+                .then(modifier2)
+                .then(modifier3)
+                .then(modifier4)
+                .then(modifier5)
+            )
         }
 
         // Act.
@@ -87,6 +100,7 @@
         rule.setFocusableContent {
             Box(
                 modifier = modifier1
+                    .focusProperties { canFocus = !deactivated }
                     .then(modifier2)
                     .background(color = Red)
                     .then(modifier3)
@@ -114,7 +128,10 @@
         val parentFocusModifier2 = FocusModifier(Inactive)
         val focusModifier = FocusModifier(Inactive)
         rule.setFocusableContent {
-            Box(modifier = parentFocusModifier1.then(parentFocusModifier2)) {
+            Box(modifier = parentFocusModifier1
+                .focusProperties { canFocus = !deactivated }
+                .then(parentFocusModifier2)
+            ) {
                 Box(modifier = focusModifier)
             }
         }
@@ -133,18 +150,26 @@
     @Test
     fun returnsImmediateParent() {
         // Arrange.
+        // greatGrandparentLayoutNode--greatGrandparentFocusNode
+        //       |
         // grandparentLayoutNode--grandparentFocusNode
         //       |
         // parentLayoutNode--parentFocusNode
         //       |
         // layoutNode--focusNode
+        val greatGrandparentFocusModifier = FocusModifier(Inactive)
         val grandparentFocusModifier = FocusModifier(Inactive)
         val parentFocusModifier = FocusModifier(Inactive)
         val focusModifier = FocusModifier(Inactive)
         rule.setFocusableContent {
-            Box(modifier = grandparentFocusModifier) {
-                Box(modifier = parentFocusModifier) {
-                    Box(modifier = focusModifier)
+            Box(modifier = greatGrandparentFocusModifier) {
+                Box(modifier = grandparentFocusModifier) {
+                    Box(modifier = Modifier
+                        .focusProperties { canFocus = !deactivated }
+                        .then(parentFocusModifier)
+                    ) {
+                        Box(modifier = focusModifier)
+                    }
                 }
             }
         }
@@ -161,19 +186,25 @@
     }
 
     @Test
-    fun ignoresIntermediateLayoutNodesThatDontHaveFocusNodes() {
+    fun ignoresIntermediateLayoutNodesThatDoNotHaveFocusNodes() {
         // Arrange.
         // grandparentLayoutNode--grandparentFocusNode
         //       |
         // parentLayoutNode
         //       |
         // layoutNode--focusNode
+        val greatGrandparentFocusModifier = FocusModifier(Inactive)
         val grandparentFocusModifier = FocusModifier(Inactive)
         val focusModifier = FocusModifier(Inactive)
         rule.setFocusableContent {
-            Box(modifier = grandparentFocusModifier) {
-                Box {
-                    Box(modifier = focusModifier)
+            Box(modifier = greatGrandparentFocusModifier) {
+                Box(modifier = Modifier
+                    .focusProperties { canFocus = !deactivated }
+                    .then(grandparentFocusModifier)
+                ) {
+                    Box {
+                        Box(modifier = focusModifier)
+                    }
                 }
             }
         }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusChangedTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusChangedTest.kt
index 781fa34..ec729ec 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusChangedTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusChangedTest.kt
@@ -21,7 +21,6 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusStateImpl.Active
 import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Disabled
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -65,6 +64,7 @@
     fun activeParent_requestFocus() {
         // Arrange.
         lateinit var focusState: FocusState
+        lateinit var childFocusState: FocusState
         val (focusRequester, childFocusRequester) = FocusRequester.createRefs()
         rule.setFocusableContent {
             Box(
@@ -75,6 +75,7 @@
             ) {
                 Box(
                     modifier = Modifier
+                        .onFocusChanged { childFocusState = it }
                         .focusRequester(childFocusRequester)
                         .focusTarget()
                 )
@@ -91,6 +92,7 @@
 
             // Assert.
             assertThat(focusState.isFocused).isTrue()
+            assertThat(childFocusState.isFocused).isFalse()
         }
     }
 
@@ -118,7 +120,7 @@
     }
 
     @Test
-    fun disabled_requestFocus() {
+    fun deactivated_requestFocus() {
         // Arrange.
         lateinit var focusState: FocusState
         val focusRequester = FocusRequester()
@@ -127,7 +129,8 @@
                 modifier = Modifier
                     .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
-                    .then(FocusModifier(Disabled))
+                    .focusProperties { canFocus = false }
+                    .focusTarget()
             )
         }
 
@@ -136,7 +139,50 @@
             focusRequester.requestFocus()
 
             // Assert.
-            assertThat(focusState).isEqualTo(Disabled)
+            assertThat(focusState.isDeactivated).isTrue()
+        }
+    }
+
+    @ExperimentalComposeUiApi
+    @Test
+    fun deactivatedParent_requestFocus() {
+        // Arrange.
+        lateinit var focusState: FocusState
+        lateinit var childFocusState: FocusState
+        val (focusRequester, childFocusRequester) = FocusRequester.createRefs()
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .onFocusChanged { focusState = it }
+                    .focusRequester(focusRequester)
+                    .focusProperties { canFocus = false }
+                    .focusTarget()
+            ) {
+                Box(
+                    modifier = Modifier
+                        .onFocusChanged { childFocusState = it }
+                        .focusRequester(childFocusRequester)
+                        .focusTarget()
+                )
+            }
+        }
+        rule.runOnIdle {
+            childFocusRequester.requestFocus()
+            assertThat(childFocusState.isFocused).isTrue()
+            assertThat(focusState.hasFocus).isTrue()
+            assertThat(focusState.isFocused).isFalse()
+            assertThat(focusState.isDeactivated).isTrue()
+        }
+
+        rule.runOnIdle {
+            // Act.
+            focusRequester.requestFocus()
+
+            // Assert.
+            assertThat(childFocusState.isFocused).isTrue()
+            assertThat(focusState.hasFocus).isTrue()
+            assertThat(focusState.isFocused).isFalse()
+            assertThat(focusState.isDeactivated).isTrue()
         }
     }
 
@@ -244,3 +290,6 @@
         }
     }
 }
+
+private val FocusState.isDeactivated: Boolean
+    get() = (this as FocusStateImpl).isDeactivated
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusEventCountTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusEventCountTest.kt
index dcafd34..af002fb 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusEventCountTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusEventCountTest.kt
@@ -17,12 +17,13 @@
 package androidx.compose.ui.focus
 
 import androidx.compose.foundation.layout.Box
-import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.focus.FocusStateImpl.Active
+import androidx.compose.ui.focus.FocusStateImpl.Deactivated
 import androidx.compose.ui.platform.LocalFocusManager
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -42,12 +43,12 @@
     fun initially_onFocusEventIsCalledThrice() {
         // Arrange.
         val focusStates = mutableListOf<FocusState>()
-        val focusReferece = FocusRequester()
+        val focusRequester = FocusRequester()
         rule.setFocusableContent {
             Box(
                 modifier = Modifier
                     .onFocusEvent { focusStates.add(it) }
-                    .focusRequester(focusReferece)
+                    .focusRequester(focusRequester)
                     .focusTarget()
             )
         }
@@ -56,7 +57,6 @@
         rule.runOnIdle {
             assertThat(focusStates).containsExactly(
                 Inactive, // triggered by onFocusEvent node's onModifierChanged().
-                Inactive, // triggered by focus node's onModifierChanged().
                 Inactive, // triggered by focus node's attach().
             )
         }
@@ -153,14 +153,13 @@
         // Arrange.
         val focusStates = mutableListOf<FocusState>()
         val focusRequester = FocusRequester()
-        lateinit var addFocusTarget: MutableState<Boolean>
+        var addFocusTarget by mutableStateOf(true)
         rule.setFocusableContent {
-            addFocusTarget = remember { mutableStateOf(true) }
             Box(
                 modifier = Modifier
                     .onFocusEvent { focusStates.add(it) }
                     .focusRequester(focusRequester)
-                    .then(if (addFocusTarget.value) Modifier.focusTarget() else Modifier)
+                    .then(if (addFocusTarget) Modifier.focusTarget() else Modifier)
             )
         }
         rule.runOnIdle {
@@ -169,7 +168,7 @@
         }
 
         // Act.
-        rule.runOnIdle { addFocusTarget.value = false }
+        rule.runOnIdle { addFocusTarget = false }
 
         // Assert.
         rule.runOnIdle {
@@ -184,19 +183,18 @@
     fun removingInactiveFocusNode_onFocusEventIsCalledOnce() {
         // Arrange.
         val focusStates = mutableListOf<FocusState>()
-        lateinit var addFocusTarget: MutableState<Boolean>
+        var addFocusTarget by mutableStateOf(true)
         rule.setFocusableContent {
-            addFocusTarget = remember { mutableStateOf(true) }
             Box(
                 modifier = Modifier
                     .onFocusEvent { focusStates.add(it) }
-                    .then(if (addFocusTarget.value) Modifier.focusTarget() else Modifier)
+                    .then(if (addFocusTarget) Modifier.focusTarget() else Modifier)
             )
         }
         rule.runOnIdle { focusStates.clear() }
 
         // Act.
-        rule.runOnIdle { addFocusTarget.value = false }
+        rule.runOnIdle { addFocusTarget = false }
 
         // Assert.
         rule.runOnIdle { assertThat(focusStates).containsExactly(Inactive) }
@@ -206,19 +204,18 @@
     fun addingFocusTarget_onFocusEventIsCalledThrice() {
         // Arrange.
         val focusStates = mutableListOf<FocusState>()
-        lateinit var addFocusTarget: MutableState<Boolean>
+        var addFocusTarget by mutableStateOf(false)
         rule.setFocusableContent {
-            addFocusTarget = remember { mutableStateOf(false) }
             Box(
                 modifier = Modifier
                     .onFocusEvent { focusStates.add(it) }
-                    .then(if (addFocusTarget.value) Modifier.focusTarget() else Modifier)
+                    .then(if (addFocusTarget) Modifier.focusTarget() else Modifier)
             )
         }
         rule.runOnIdle { focusStates.clear() }
 
         // Act.
-        rule.runOnIdle { addFocusTarget.value = true }
+        rule.runOnIdle { addFocusTarget = true }
 
         // Assert.
         rule.runOnIdle {
@@ -229,4 +226,75 @@
             )
         }
     }
+
+    @Test
+    fun addingEmptyFocusProperties_onFocusEventIsCalledTwice() {
+        // Arrange.
+        val focusStates = mutableListOf<FocusState>()
+        var addFocusProperties by mutableStateOf(false)
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .onFocusEvent { focusStates.add(it) }
+                    .then(if (addFocusProperties) Modifier.focusProperties {} else Modifier)
+                    .focusTarget()
+            )
+        }
+        rule.runOnIdle { focusStates.clear() }
+
+        // Act.
+        rule.runOnIdle { addFocusProperties = true }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(focusStates).containsExactly(
+                Inactive, // triggered by onFocusEvent node's onModifierChanged().
+                Inactive, // triggered by focus node's onModifierChanged().
+            )
+        }
+    }
+
+    @Test
+    fun deactivatingFocusNode_onFocusEventIsCalledOnce() {
+        // Arrange.
+        val focusStates = mutableListOf<FocusState>()
+        var deactiated by mutableStateOf(false)
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .onFocusEvent { focusStates.add(it) }
+                    .focusProperties { canFocus = !deactiated }
+                    .focusTarget()
+            )
+        }
+        rule.runOnIdle { focusStates.clear() }
+
+        // Act.
+        rule.runOnIdle { deactiated = true }
+
+        // Assert.
+        rule.runOnIdle { assertThat(focusStates).containsExactly(Deactivated) }
+    }
+
+    @Test
+    fun activatingFocusNode_onFocusEventIsCalledOnce() {
+        // Arrange.
+        val focusStates = mutableListOf<FocusState>()
+        var deactiated by mutableStateOf(true)
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .onFocusEvent { focusStates.add(it) }
+                    .focusProperties { canFocus = !deactiated }
+                    .focusTarget()
+            )
+        }
+        rule.runOnIdle { focusStates.clear() }
+
+        // Act.
+        rule.runOnIdle { deactiated = false }
+
+        // Assert.
+        rule.runOnIdle { assertThat(focusStates).containsExactly(Inactive) }
+    }
 }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusTargetAttachDetachTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusTargetAttachDetachTest.kt
index 6939cc9..f03c428 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusTargetAttachDetachTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusTargetAttachDetachTest.kt
@@ -17,9 +17,9 @@
 package androidx.compose.ui.focus
 
 import androidx.compose.foundation.layout.Box
-import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -40,16 +40,15 @@
         // Arrange.
         lateinit var focusState: FocusState
         val focusRequester = FocusRequester()
-        lateinit var observingFocusTarget1: MutableState<Boolean>
+        var observingFocusTarget1 by mutableStateOf(true)
         rule.setFocusableContent {
             val focusRequesterModifier = Modifier.focusRequester(focusRequester)
             val onFocusChanged = Modifier.onFocusChanged { focusState = it }
             val focusTarget1 = Modifier.focusTarget()
             val focusTarget2 = Modifier.focusTarget()
             Box {
-                observingFocusTarget1 = remember { mutableStateOf(true) }
                 Box(
-                    modifier = if (observingFocusTarget1.value) {
+                    modifier = if (observingFocusTarget1) {
                         onFocusChanged
                             .then(focusRequesterModifier)
                             .then(focusTarget1)
@@ -69,7 +68,7 @@
         }
 
         // Act.
-        rule.runOnIdle { observingFocusTarget1.value = false }
+        rule.runOnIdle { observingFocusTarget1 = false }
 
         // Assert.
         rule.runOnIdle { assertThat(focusState.isFocused).isFalse() }
@@ -80,15 +79,14 @@
         // Arrange.
         lateinit var focusState: FocusState
         val focusRequester = FocusRequester()
-        lateinit var onFocusChangedHasFocusTarget: MutableState<Boolean>
+        var onFocusChangedHasFocusTarget by mutableStateOf(true)
         rule.setFocusableContent {
             val focusRequesterModifier = Modifier.focusRequester(focusRequester)
             val onFocusChanged = Modifier.onFocusChanged { focusState = it }
             val focusTarget = Modifier.focusTarget()
             Box {
-                onFocusChangedHasFocusTarget = remember { mutableStateOf(true) }
                 Box(
-                    modifier = if (onFocusChangedHasFocusTarget.value) {
+                    modifier = if (onFocusChangedHasFocusTarget) {
                         onFocusChanged
                             .then(focusRequesterModifier)
                             .then(focusTarget)
@@ -106,7 +104,7 @@
         }
 
         // Act.
-        rule.runOnIdle { onFocusChangedHasFocusTarget.value = false }
+        rule.runOnIdle { onFocusChangedHasFocusTarget = false }
 
         // Assert.
         rule.runOnIdle { assertThat(focusState.isFocused).isFalse() }
@@ -117,13 +115,12 @@
         // Arrange.
         lateinit var focusState: FocusState
         val focusRequester = FocusRequester()
-        lateinit var optionalFocusTarget: MutableState<Boolean>
+        var optionalFocusTarget by mutableStateOf(true)
         rule.setFocusableContent {
-            optionalFocusTarget = remember { mutableStateOf(true) }
             Box(
                 modifier = Modifier.onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
-                    .then(if (optionalFocusTarget.value) Modifier.focusTarget() else Modifier)
+                    .then(if (optionalFocusTarget) Modifier.focusTarget() else Modifier)
             )
         }
         rule.runOnIdle {
@@ -132,7 +129,7 @@
         }
 
         // Act.
-        rule.runOnIdle { optionalFocusTarget.value = false }
+        rule.runOnIdle { optionalFocusTarget = false }
 
         // Assert.
         rule.runOnIdle { assertThat(focusState.isFocused).isFalse() }
@@ -143,13 +140,12 @@
         // Arrange.
         lateinit var focusState: FocusState
         val focusRequester = FocusRequester()
-        lateinit var optionalFocusTarget: MutableState<Boolean>
+        var optionalFocusTarget by mutableStateOf(true)
         rule.setFocusableContent {
-            optionalFocusTarget = remember { mutableStateOf(true) }
             Box(
                 modifier = Modifier.onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
-                    .then(if (optionalFocusTarget.value) Modifier.focusTarget() else Modifier)
+                    .then(if (optionalFocusTarget) Modifier.focusTarget() else Modifier)
             ) {
                 Box(modifier = Modifier.focusTarget())
             }
@@ -160,7 +156,7 @@
         }
 
         // Act.
-        rule.runOnIdle { optionalFocusTarget.value = false }
+        rule.runOnIdle { optionalFocusTarget = false }
 
         // Assert.
         rule.runOnIdle { assertThat(focusState.isFocused).isFalse() }
@@ -171,13 +167,12 @@
         // Arrange.
         lateinit var focusState: FocusState
         val focusRequester = FocusRequester()
-        lateinit var optionalFocusTarget: MutableState<Boolean>
+        var optionalFocusTarget by mutableStateOf(true)
         rule.setFocusableContent {
-            optionalFocusTarget = remember { mutableStateOf(true) }
             Box(
                 modifier = Modifier.onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
-                    .then(if (optionalFocusTarget.value) Modifier.focusTarget() else Modifier)
+                    .then(if (optionalFocusTarget) Modifier.focusTarget() else Modifier)
             ) {
                 Box(modifier = Modifier.focusTarget())
             }
@@ -189,7 +184,7 @@
         }
 
         // Act.
-        rule.runOnIdle { optionalFocusTarget.value = false }
+        rule.runOnIdle { optionalFocusTarget = false }
 
         // Assert.
         rule.runOnIdle { assertThat(focusState.isFocused).isFalse() }
@@ -200,15 +195,17 @@
         // Arrange.
         lateinit var focusState: FocusState
         val focusRequester = FocusRequester()
-        lateinit var optionalFocusTarget: MutableState<Boolean>
+        var optionalFocusTarget by mutableStateOf(true)
         rule.setFocusableContent {
-            optionalFocusTarget = remember { mutableStateOf(true) }
             Box(
-                modifier = Modifier.onFocusChanged { focusState = it }
-                    .then(if (optionalFocusTarget.value) Modifier.focusTarget() else Modifier)
-                    .focusRequester(focusRequester)
+                modifier = Modifier
+                    .onFocusChanged { focusState = it }
+                    .then(if (optionalFocusTarget) Modifier.focusTarget() else Modifier)
             ) {
-                Box(modifier = Modifier.focusTarget())
+                Box(modifier = Modifier
+                    .focusRequester(focusRequester)
+                    .focusTarget()
+                )
             }
         }
         rule.runOnIdle {
@@ -217,7 +214,7 @@
         }
 
         // Act.
-        rule.runOnIdle { optionalFocusTarget.value = false }
+        rule.runOnIdle { optionalFocusTarget = false }
 
         // Assert.
         rule.runOnIdle { assertThat(focusState.isFocused).isTrue() }
@@ -228,13 +225,12 @@
         // Arrange.
         lateinit var focusState: FocusState
         val focusRequester = FocusRequester()
-        lateinit var optionalFocusTarget: MutableState<Boolean>
+        var optionalFocusTarget by mutableStateOf(true)
         rule.setFocusableContent {
-            optionalFocusTarget = remember { mutableStateOf(true) }
             Box(
                 modifier = Modifier.onFocusChanged { focusState = it }
                     .then(
-                        if (optionalFocusTarget.value) {
+                        if (optionalFocusTarget) {
                             Modifier
                                 .focusTarget()
                                 .focusRequester(focusRequester)
@@ -251,7 +247,7 @@
         }
 
         // Act.
-        rule.runOnIdle { optionalFocusTarget.value = false }
+        rule.runOnIdle { optionalFocusTarget = false }
 
         // Assert.
         rule.runOnIdle { assertThat(focusState.isFocused).isFalse() }
@@ -263,9 +259,8 @@
         lateinit var focusState: FocusState
         lateinit var parentFocusState: FocusState
         val focusRequester = FocusRequester()
-        lateinit var optionalFocusTargets: MutableState<Boolean>
+        var optionalFocusTargets by mutableStateOf(true)
         rule.setFocusableContent {
-            optionalFocusTargets = remember { mutableStateOf(true) }
             Box(
                 modifier = Modifier
                     .onFocusChanged { parentFocusState = it }
@@ -273,7 +268,7 @@
             ) {
                 Box(
                     modifier = Modifier.onFocusChanged { focusState = it }.then(
-                        if (optionalFocusTargets.value) {
+                        if (optionalFocusTargets) {
                             Modifier.focusTarget()
                                 .focusRequester(focusRequester)
                                 .focusTarget()
@@ -291,7 +286,7 @@
         }
 
         // Act.
-        rule.runOnIdle { optionalFocusTargets.value = false }
+        rule.runOnIdle { optionalFocusTargets = false }
 
         // Assert.
         rule.runOnIdle {
@@ -301,23 +296,269 @@
     }
 
     @Test
+    fun removedDeactivatedParentFocusTarget_pointsToNextFocusTarget() {
+        // Arrange.
+        lateinit var focusState: FocusState
+        val focusRequester = FocusRequester()
+        var optionalFocusTarget by mutableStateOf(true)
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .onFocusChanged { focusState = it }
+                    .then(
+                        if (optionalFocusTarget)
+                            Modifier
+                                .focusProperties { canFocus = false }
+                                .focusTarget()
+                        else
+                            Modifier
+                    )
+            ) {
+                Box(modifier = Modifier
+                    .focusRequester(focusRequester)
+                    .focusTarget()
+                )
+            }
+        }
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+            assertThat(focusState.hasFocus).isTrue()
+            assertThat(focusState.isDeactivated).isTrue()
+        }
+
+        // Act.
+        rule.runOnIdle { optionalFocusTarget = false }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(focusState.isFocused).isTrue()
+            assertThat(focusState.isDeactivated).isFalse()
+        }
+    }
+
+    @Test
+    fun removedDeactivatedParentFocusTarget_pointsToNextDeactivatedParentFocusTarget() {
+        // Arrange.
+        lateinit var focusState: FocusState
+        val focusRequester = FocusRequester()
+        var optionalFocusTarget by mutableStateOf(true)
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .onFocusChanged { focusState = it }
+                    .then(
+                        if (optionalFocusTarget)
+                            Modifier
+                                .focusProperties { canFocus = false }
+                                .focusTarget()
+                        else
+                            Modifier
+                    )
+            ) {
+                Box(
+                    modifier = Modifier
+                        .onFocusChanged { focusState = it }
+                        .focusProperties { canFocus = false }
+                        .focusTarget()
+                ) {
+                    Box(
+                        modifier = Modifier
+                            .focusRequester(focusRequester)
+                            .focusTarget()
+                    )
+                }
+            }
+        }
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+            assertThat(focusState.hasFocus).isTrue()
+            assertThat(focusState.isDeactivated).isTrue()
+        }
+
+        // Act.
+        rule.runOnIdle { optionalFocusTarget = false }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(focusState.isFocused).isFalse()
+            assertThat(focusState.hasFocus).isTrue()
+            assertThat(focusState.isDeactivated).isTrue()
+        }
+    }
+
+    @Test
+    fun removedDeactivatedParent_parentsFocusTarget_isUnchanged() {
+        // Arrange.
+        lateinit var focusState: FocusState
+        val focusRequester = FocusRequester()
+        var optionalFocusTarget by mutableStateOf(true)
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .onFocusChanged { focusState = it }
+                    .focusProperties { canFocus = false }
+                    .focusTarget()
+            ) {
+                Box(
+                    modifier = Modifier.then(
+                            if (optionalFocusTarget)
+                                Modifier
+                                    .focusProperties { canFocus = false }
+                                    .focusTarget()
+                            else
+                                Modifier
+                        )
+                ) {
+                    Box(
+                        modifier = Modifier
+                            .focusRequester(focusRequester)
+                            .focusTarget()
+                    )
+                }
+            }
+        }
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+            assertThat(focusState.isFocused).isFalse()
+            assertThat(focusState.hasFocus).isTrue()
+            assertThat(focusState.isDeactivated).isTrue()
+        }
+
+        // Act.
+        rule.runOnIdle { optionalFocusTarget = false }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(focusState.isFocused).isFalse()
+            assertThat(focusState.hasFocus).isTrue()
+            assertThat(focusState.isDeactivated).isTrue()
+        }
+    }
+
+    @Test
+    fun removedDeactivatedParentAndActiveChild_grandparent_retainsDeactivatedState() {
+        // Arrange.
+        lateinit var focusState: FocusState
+        val focusRequester = FocusRequester()
+        var optionalFocusTarget by mutableStateOf(true)
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .onFocusChanged { focusState = it }
+                    .focusProperties { canFocus = false }
+                    .focusTarget()
+            ) {
+                Box(
+                    modifier = Modifier
+                        .onFocusChanged { focusState = it }
+                        .then(
+                            if (optionalFocusTarget)
+                                Modifier
+                                    .focusProperties { canFocus = false }
+                                    .focusTarget()
+                            else
+                                Modifier
+                        )
+                ) {
+                    Box(
+                        modifier = Modifier
+                            .focusRequester(focusRequester)
+                            .then(
+                                if (optionalFocusTarget)
+                                    Modifier.focusTarget()
+                                else
+                                    Modifier
+                            )
+                    )
+                }
+            }
+        }
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+            assertThat(focusState.hasFocus).isTrue()
+            assertThat(focusState.isDeactivated).isTrue()
+        }
+
+        // Act.
+        rule.runOnIdle { optionalFocusTarget = false }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(focusState.isFocused).isFalse()
+            assertThat(focusState.hasFocus).isFalse()
+            assertThat(focusState.isDeactivated).isTrue()
+        }
+    }
+
+    @Test
+    fun removedNonDeactivatedParentAndActiveChild_grandParent_retainsNonDeactivatedState() {
+        // Arrange.
+        lateinit var focusState: FocusState
+        val focusRequester = FocusRequester()
+        var optionalFocusTarget by mutableStateOf(true)
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .onFocusChanged { focusState = it }
+                    .focusTarget()
+            ) {
+                Box(
+                    modifier = Modifier.then(
+                            if (optionalFocusTarget)
+                                Modifier
+                                    .focusProperties { canFocus = false }
+                                    .focusTarget()
+                            else
+                                Modifier
+                        )
+                ) {
+                    Box(
+                        modifier = Modifier
+                            .focusRequester(focusRequester)
+                            .then(
+                                if (optionalFocusTarget)
+                                    Modifier.focusTarget()
+                                else
+                                    Modifier
+                            )
+                    )
+                }
+            }
+        }
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+            assertThat(focusState.hasFocus).isTrue()
+            assertThat(focusState.isDeactivated).isFalse()
+        }
+
+        // Act.
+        rule.runOnIdle { optionalFocusTarget = false }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(focusState.isFocused).isFalse()
+            assertThat(focusState.hasFocus).isFalse()
+            assertThat(focusState.isDeactivated).isFalse()
+        }
+    }
+
+    @Test
     fun removedInactiveFocusTarget_pointsToNextFocusTarget() {
         // Arrange.
         lateinit var focusState: FocusState
         val focusRequester = FocusRequester()
-        lateinit var optionalFocusTarget: MutableState<Boolean>
+        var optionalFocusTarget by mutableStateOf(true)
         rule.setFocusableContent {
-            optionalFocusTarget = remember { mutableStateOf(true) }
             Box(
                 modifier = Modifier.onFocusChanged { focusState = it }
-                    .then(if (optionalFocusTarget.value) Modifier.focusTarget() else Modifier)
+                    .then(if (optionalFocusTarget) Modifier.focusTarget() else Modifier)
                     .focusRequester(focusRequester)
                     .focusTarget()
             )
         }
 
         // Act.
-        rule.runOnIdle { optionalFocusTarget.value = false }
+        rule.runOnIdle { optionalFocusTarget = false }
 
         // Assert.
         rule.runOnIdle { assertThat(focusState.isFocused).isFalse() }
@@ -328,13 +569,12 @@
         // Arrange.
         lateinit var focusState: FocusState
         val focusRequester = FocusRequester()
-        lateinit var addFocusTarget: MutableState<Boolean>
+        var addFocusTarget by mutableStateOf(false)
         rule.setFocusableContent {
-            addFocusTarget = remember { mutableStateOf(false) }
             Box(
                 modifier = Modifier.onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
-                    .then(if (addFocusTarget.value) Modifier.focusTarget() else Modifier)
+                    .then(if (addFocusTarget) Modifier.focusTarget() else Modifier)
             ) {
                 Box(modifier = Modifier.focusTarget())
             }
@@ -345,7 +585,7 @@
         }
 
         // Act.
-        rule.runOnIdle { addFocusTarget.value = true }
+        rule.runOnIdle { addFocusTarget = true }
 
         // Assert.
         rule.runOnIdle { assertThat(focusState.isFocused).isFalse() }
@@ -356,13 +596,12 @@
         // Arrange.
         lateinit var focusState: FocusState
         val focusRequester = FocusRequester()
-        lateinit var addFocusTarget: MutableState<Boolean>
+        var addFocusTarget by mutableStateOf(false)
         rule.setFocusableContent {
-            addFocusTarget = remember { mutableStateOf(false) }
             Box(
                 modifier = Modifier.onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
-                    .then(if (addFocusTarget.value) Modifier.focusTarget() else Modifier)
+                    .then(if (addFocusTarget) Modifier.focusTarget() else Modifier)
             )
         }
         rule.runOnIdle {
@@ -371,9 +610,109 @@
         }
 
         // Act.
-        rule.runOnIdle { addFocusTarget.value = true }
+        rule.runOnIdle { addFocusTarget = true }
 
         // Assert.
         rule.runOnIdle { assertThat(focusState.isFocused).isFalse() }
     }
+
+    @Test
+    fun removingDeactivatedItem_withNoNextFocusTarget() {
+        // Arrange.
+        lateinit var focusState: FocusState
+        var removeDeactivatedItem by mutableStateOf(false)
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .onFocusChanged { focusState = it }
+                    .then(
+                        if (removeDeactivatedItem)
+                            Modifier
+                        else
+                            Modifier
+                                .focusProperties { canFocus = false }
+                                .focusTarget()
+                    )
+            )
+        }
+
+        // Act.
+        rule.runOnIdle { removeDeactivatedItem = true }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(focusState.isFocused).isFalse()
+            assertThat(focusState.isDeactivated).isFalse()
+        }
+    }
+
+    @Test
+    fun removingDeactivatedItem_withInactiveNextFocusTarget() {
+        // Arrange.
+        lateinit var focusState: FocusState
+        var removeDeactivatedItem by mutableStateOf(false)
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .onFocusChanged { focusState = it }
+                    .then(
+                        if (removeDeactivatedItem)
+                            Modifier
+                        else
+                            Modifier
+                                .focusProperties { canFocus = false }
+                                .focusTarget()
+                    )
+            ) {
+                Box(modifier = Modifier.focusTarget())
+            }
+        }
+
+        // Act.
+        rule.runOnIdle { removeDeactivatedItem = true }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(focusState.isFocused).isFalse()
+            assertThat(focusState.isDeactivated).isFalse()
+        }
+    }
+
+    @Test
+    fun removingDeactivatedItem_withDeactivatedNextFocusTarget() {
+        // Arrange.
+        lateinit var focusState: FocusState
+        var removeDeactivatedItem by mutableStateOf(false)
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .onFocusChanged { focusState = it }
+                    .then(
+                        if (removeDeactivatedItem)
+                            Modifier
+                        else
+                            Modifier
+                                .focusProperties { canFocus = false }
+                                .focusTarget()
+                    )
+            ) {
+                Box(modifier = Modifier
+                    .focusProperties { canFocus = false }
+                    .focusTarget()
+                )
+            }
+        }
+
+        // Act.
+        rule.runOnIdle { removeDeactivatedItem = true }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(focusState.isFocused).isFalse()
+            assertThat(focusState.isDeactivated).isTrue()
+        }
+    }
 }
+
+private val FocusState.isDeactivated: Boolean
+    get() = (this as FocusStateImpl).isDeactivated
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusTestUtils.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusTestUtils.kt
index 80e2b01..1094abc 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusTestUtils.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusTestUtils.kt
@@ -51,6 +51,7 @@
     width: Int,
     height: Int,
     focusRequester: FocusRequester? = null,
+    deactivated: Boolean = false,
     content: @Composable () -> Unit = {}
 ) {
     Layout(
@@ -59,6 +60,7 @@
             .offset { IntOffset(x, y) }
             .focusRequester(focusRequester ?: remember { FocusRequester() })
             .onFocusChanged { isFocused.value = it.isFocused }
+            .focusProperties { canFocus = !deactivated }
             .focusTarget(),
         measurePolicy = remember(width, height) {
             MeasurePolicy { measurables, constraint ->
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FreeFocusTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FreeFocusTest.kt
index 37eefcd..a0845c9 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FreeFocusTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FreeFocusTest.kt
@@ -21,12 +21,11 @@
 import androidx.compose.ui.focus.FocusStateImpl.Active
 import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
 import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Disabled
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
-import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -56,8 +55,8 @@
             val success = focusRequester.freeFocus()
 
             // Assert.
-            Truth.assertThat(success).isTrue()
-            Truth.assertThat(focusState.isFocused).isTrue()
+            assertThat(success).isTrue()
+            assertThat(focusState.isFocused).isTrue()
         }
     }
 
@@ -80,8 +79,8 @@
             val success = focusRequester.freeFocus()
 
             // Assert.
-            Truth.assertThat(success).isFalse()
-            Truth.assertThat(focusState.hasFocus).isTrue()
+            assertThat(success).isFalse()
+            assertThat(focusState.hasFocus).isTrue()
         }
     }
 
@@ -104,13 +103,13 @@
             val success = focusRequester.freeFocus()
 
             // Assert.
-            Truth.assertThat(success).isTrue()
-            Truth.assertThat(focusState.isFocused).isTrue()
+            assertThat(success).isTrue()
+            assertThat(focusState.isFocused).isTrue()
         }
     }
 
     @Test
-    fun disabled_freeFocus_retainFocusAsDisabled() {
+    fun deactivated_freeFocus_retainFocusAsDeactivated() {
         // Arrange.
         lateinit var focusState: FocusState
         val focusRequester = FocusRequester()
@@ -119,7 +118,8 @@
                 modifier = Modifier
                     .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
-                    .then(FocusModifier(Disabled))
+                    .focusProperties { canFocus = false }
+                    .then(FocusModifier(Inactive))
             )
         }
 
@@ -128,8 +128,8 @@
             val success = focusRequester.freeFocus()
 
             // Assert.
-            Truth.assertThat(success).isFalse()
-            Truth.assertThat(focusState).isEqualTo(Disabled)
+            assertThat(success).isFalse()
+            assertThat(focusState.isDeactivated).isTrue()
         }
     }
 
@@ -152,8 +152,11 @@
             val success = focusRequester.freeFocus()
 
             // Assert.
-            Truth.assertThat(success).isFalse()
-            Truth.assertThat(focusState.isFocused).isFalse()
+            assertThat(success).isFalse()
+            assertThat(focusState.isFocused).isFalse()
         }
     }
 }
+
+private val FocusState.isDeactivated: Boolean
+    get() = (this as FocusStateImpl).isDeactivated
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchNextTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchNextTest.kt
index 2288a5b..4140f6b 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchNextTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchNextTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.focus
 
+import androidx.compose.foundation.layout.Column
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.focus.FocusDirection.Companion.Next
@@ -45,7 +46,22 @@
         rule.setContentWithInitialRootFocus {}
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Next)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Next) }
+
+        // Assert.
+        rule.runOnIdle { assertThat(movedFocusSuccessfully).isFalse() }
+    }
+
+    @Test
+    fun moveFocus_oneDisabledFocusableItem() {
+        // Arrange.
+        val isItemFocused = mutableStateOf(false)
+        rule.setContentWithInitialRootFocus {
+            FocusableBox(isItemFocused, 0, 0, 10, 10, deactivated = true)
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Next) }
 
         // Assert.
         rule.runOnIdle { assertThat(movedFocusSuccessfully).isFalse() }
@@ -60,7 +76,7 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Next)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Next) }
 
         // Assert.
         rule.runOnIdle {
@@ -70,6 +86,28 @@
     }
 
     @Test
+    fun initialFocus_skipsDeactivatedItem() {
+        // Arrange.
+        val (firstItem, secondItem) = List(2) { mutableStateOf(false) }
+        rule.setContentWithInitialRootFocus {
+            Column {
+                FocusableBox(firstItem, 0, 0, 10, 10, deactivated = true)
+                FocusableBox(secondItem, 0, 0, 10, 10)
+            }
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Next) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isTrue()
+            assertThat(firstItem.value).isFalse()
+            assertThat(secondItem.value).isTrue()
+        }
+    }
+
+    @Test
     fun initialFocus_firstItemInCompositionOrderGetsFocus() {
         // Arrange.
         val (firstItem, secondItem) = List(2) { mutableStateOf(false) }
@@ -79,7 +117,7 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Next)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Next) }
 
         // Assert.
         rule.runOnIdle {
@@ -102,7 +140,7 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Next)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Next) }
 
         // Assert.
         rule.runOnIdle {
@@ -123,7 +161,7 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Next)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Next) }
 
         // Assert.
         rule.runOnIdle {
@@ -144,7 +182,7 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Next)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Next) }
 
         // Assert.
         rule.runOnIdle {
@@ -164,7 +202,7 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Next)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Next) }
 
         // Assert.
         rule.runOnIdle {
@@ -174,6 +212,27 @@
     }
 
     @Test
+    fun focusMovesToThirdItem_skipsDeactivatedItem() {
+        // Arrange.
+        val (item1, item2, item3, item4) = List(4) { mutableStateOf(false) }
+        rule.setContentForTest {
+            FocusableBox(item1, 0, 0, 10, 10, initialFocus)
+            FocusableBox(item2, 10, 0, 10, 10, deactivated = true)
+            FocusableBox(item3, 10, 0, 10, 10)
+            FocusableBox(item4, 20, 0, 10, 10)
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Next) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isTrue()
+            assertThat(item3.value).isTrue()
+        }
+    }
+
+    @Test
     fun focusMovesToThirdItem() {
         // Arrange.
         val (item1, item2, item3) = List(3) { mutableStateOf(false) }
@@ -184,7 +243,7 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Next)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Next) }
 
         // Assert.
         rule.runOnIdle {
@@ -194,6 +253,27 @@
     }
 
     @Test
+    fun focusMovesToFourthItem() {
+        // Arrange.
+        val (item1, item2, item3, item4) = List(4) { mutableStateOf(false) }
+        rule.setContentForTest {
+            FocusableBox(item1, 0, 0, 10, 10)
+            FocusableBox(item2, 0, 0, 10, 10, deactivated = true)
+            FocusableBox(item3, 10, 0, 10, 10, initialFocus)
+            FocusableBox(item4, 20, 0, 10, 10)
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Next) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isTrue()
+            assertThat(item4.value).isTrue()
+        }
+    }
+
+    @Test
     fun focusWrapsAroundToFirstItem() {
         // Arrange.
         val (item1, item2, item3) = List(3) { mutableStateOf(false) }
@@ -204,7 +284,7 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Next)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Next) }
 
         // Assert.
         rule.runOnIdle {
@@ -214,11 +294,55 @@
     }
 
     @Test
+    fun focusWrapsAroundToFirstItem_skippingLastDeactivatedItem() {
+        // Arrange.
+        val (item1, item2, item3, item4) = List(4) { mutableStateOf(false) }
+        rule.setContentForTest {
+            FocusableBox(item1, 0, 0, 10, 10)
+            FocusableBox(item2, 10, 0, 10, 10)
+            FocusableBox(item3, 20, 0, 10, 10, initialFocus)
+            FocusableBox(item4, 10, 0, 10, 10, deactivated = true)
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Next) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isTrue()
+            assertThat(item1.value).isTrue()
+        }
+    }
+
+    @Test
+    fun focusWrapsAroundToFirstItem_skippingFirstDeactivatedItem() {
+        // Arrange.
+        val (item1, item2, item3, item4) = List(4) { mutableStateOf(false) }
+        rule.setContentForTest {
+            FocusableBox(item1, 10, 0, 10, 10, deactivated = true)
+            FocusableBox(item2, 0, 0, 10, 10)
+            FocusableBox(item3, 10, 0, 10, 10)
+            FocusableBox(item4, 20, 0, 10, 10, initialFocus)
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Next) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isTrue()
+            assertThat(item2.value).isTrue()
+        }
+    }
+
+    @Test
     fun focusNextOrdering() {
         // Arrange.
         val (parent1, child1, child2, child3) = List(4) { mutableStateOf(false) }
         val (parent2, child4, child5) = List(3) { mutableStateOf(false) }
         val (parent3, child6) = List(2) { mutableStateOf(false) }
+        val (parent4, child7, child8, child9, child10) = List(5) { mutableStateOf(false) }
+        val (parent5, child11) = List(2) { mutableStateOf(false) }
         rule.setContentWithInitialRootFocus {
             FocusableBox(parent1, 0, 0, 10, 10) {
                 FocusableBox(child1, 0, 0, 10, 10)
@@ -232,37 +356,55 @@
                 }
                 FocusableBox(child5, 20, 0, 10, 10)
             }
+            FocusableBox(parent4, 0, 10, 10, 10, deactivated = true) {
+                FocusableBox(child7, 0, 10, 10, 10, deactivated = true)
+                FocusableBox(child8, 0, 10, 10, 10)
+                FocusableBox(parent5, 10, 10, 10, 10, deactivated = true) {
+                    FocusableBox(child11, 0, 0, 10, 10)
+                }
+                FocusableBox(child9, 20, 0, 10, 10)
+                FocusableBox(child10, 20, 0, 10, 10, deactivated = true)
+            }
         }
 
         // Act & Assert.
-        focusManager.moveFocus(Next)
+        rule.runOnIdle { focusManager.moveFocus(Next) }
         rule.runOnIdle { assertThat(parent1.value).isTrue() }
 
-        focusManager.moveFocus(Next)
+        rule.runOnIdle { focusManager.moveFocus(Next) }
         rule.runOnIdle { assertThat(child1.value).isTrue() }
 
-        focusManager.moveFocus(Next)
+        rule.runOnIdle { focusManager.moveFocus(Next) }
         rule.runOnIdle { assertThat(child2.value).isTrue() }
 
-        focusManager.moveFocus(Next)
+        rule.runOnIdle { focusManager.moveFocus(Next) }
         rule.runOnIdle { assertThat(child3.value).isTrue() }
 
-        focusManager.moveFocus(Next)
+        rule.runOnIdle { focusManager.moveFocus(Next) }
         rule.runOnIdle { assertThat(parent2.value).isTrue() }
 
-        focusManager.moveFocus(Next)
+        rule.runOnIdle { focusManager.moveFocus(Next) }
         rule.runOnIdle { assertThat(child4.value).isTrue() }
 
-        focusManager.moveFocus(Next)
+        rule.runOnIdle { focusManager.moveFocus(Next) }
         rule.runOnIdle { assertThat(parent3.value).isTrue() }
 
-        focusManager.moveFocus(Next)
+        rule.runOnIdle { focusManager.moveFocus(Next) }
         rule.runOnIdle { assertThat(child6.value).isTrue() }
 
-        focusManager.moveFocus(Next)
+        rule.runOnIdle { focusManager.moveFocus(Next) }
         rule.runOnIdle { assertThat(child5.value).isTrue() }
 
-        focusManager.moveFocus(Next)
+        rule.runOnIdle { focusManager.moveFocus(Next) }
+        rule.runOnIdle { assertThat(child8.value).isTrue() }
+
+        rule.runOnIdle { focusManager.moveFocus(Next) }
+        rule.runOnIdle { assertThat(child11.value).isTrue() }
+
+        rule.runOnIdle { focusManager.moveFocus(Next) }
+        rule.runOnIdle { assertThat(child9.value).isTrue() }
+
+        rule.runOnIdle { focusManager.moveFocus(Next) }
         rule.runOnIdle { assertThat(parent1.value).isTrue() }
     }
 
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchPreviousTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchPreviousTest.kt
index dc09292..115d70e 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchPreviousTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchPreviousTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.focus
 
+import androidx.compose.foundation.layout.Column
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.focus.FocusDirection.Companion.Previous
@@ -45,7 +46,22 @@
         rule.setContentWithInitialRootFocus {}
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Previous)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Previous) }
+
+        // Assert.
+        rule.runOnIdle { assertThat(movedFocusSuccessfully).isFalse() }
+    }
+
+    @Test
+    fun moveFocus_oneDisabledFocusableItem() {
+        // Arrange.
+        val isItemFocused = mutableStateOf(false)
+        rule.setContentWithInitialRootFocus {
+            FocusableBox(isItemFocused, 0, 0, 10, 10, deactivated = true)
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Previous) }
 
         // Assert.
         rule.runOnIdle { assertThat(movedFocusSuccessfully).isFalse() }
@@ -60,7 +76,7 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Previous)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Previous) }
 
         // Assert.
         rule.runOnIdle {
@@ -70,6 +86,28 @@
     }
 
     @Test
+    fun initialFocus_skipsDeactivatedItem() {
+        // Arrange.
+        val (firstItem, secondItem) = List(2) { mutableStateOf(false) }
+        rule.setContentWithInitialRootFocus {
+            Column {
+                FocusableBox(firstItem, 0, 0, 10, 10)
+                FocusableBox(secondItem, 0, 0, 10, 10, deactivated = true)
+            }
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Previous) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isTrue()
+            assertThat(firstItem.value).isTrue()
+            assertThat(secondItem.value).isFalse()
+        }
+    }
+
+    @Test
     fun initialFocus_lastItemInCompositionOrderGetsFocus() {
         // Arrange.
         val (firstItem, secondItem) = List(2) { mutableStateOf(false) }
@@ -79,7 +117,7 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Previous)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Previous) }
 
         // Assert.
         rule.runOnIdle {
@@ -102,7 +140,7 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Previous)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Previous) }
 
         // Assert.
         rule.runOnIdle {
@@ -123,7 +161,7 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Previous)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Previous) }
 
         // Assert.
         rule.runOnIdle {
@@ -144,7 +182,7 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Previous)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Previous) }
 
         // Assert.
         rule.runOnIdle {
@@ -164,7 +202,28 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Previous)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Previous) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isTrue()
+            assertThat(item2.value).isTrue()
+        }
+    }
+
+    @Test
+    fun focusMovesToSecondItem_skipsDeactivatedItem() {
+        // Arrange.
+        val (item1, item2, item3, item4) = List(4) { mutableStateOf(false) }
+        rule.setContentForTest {
+            FocusableBox(item1, 0, 0, 10, 10)
+            FocusableBox(item2, 10, 0, 10, 10)
+            FocusableBox(item3, 10, 0, 10, 10, deactivated = true)
+            FocusableBox(item4, 20, 0, 10, 10, initialFocus)
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Previous) }
 
         // Assert.
         rule.runOnIdle {
@@ -184,7 +243,28 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Previous)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Previous) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isTrue()
+            assertThat(item1.value).isTrue()
+        }
+    }
+
+    @Test
+    fun focusMovesToFirstItem_ignoresDeactivated() {
+        // Arrange.
+        val (item1, item2, item3, item4) = List(4) { mutableStateOf(false) }
+        rule.setContentForTest {
+            FocusableBox(item1, 0, 0, 10, 10)
+            FocusableBox(item2, 10, 0, 10, 10, initialFocus)
+            FocusableBox(item3, 20, 0, 10, 10, deactivated = true)
+            FocusableBox(item4, 20, 0, 10, 10)
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Previous) }
 
         // Assert.
         rule.runOnIdle {
@@ -204,7 +284,7 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Previous)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Previous) }
 
         // Assert.
         rule.runOnIdle {
@@ -214,11 +294,56 @@
     }
 
     @Test
-    fun focusNextOrdering() {
+    fun focusWrapsAroundToLastItem_skippingFirstDeactivatedItem() {
+        // Arrange.
+        val (item1, item2, item3, item4) = List(4) { mutableStateOf(false) }
+        rule.setContentForTest {
+            FocusableBox(item1, 20, 0, 10, 10, deactivated = true)
+            FocusableBox(item2, 0, 0, 10, 10, initialFocus)
+            FocusableBox(item3, 10, 0, 10, 10)
+            FocusableBox(item4, 20, 0, 10, 10)
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Previous) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isTrue()
+            assertThat(item4.value).isTrue()
+        }
+    }
+
+    @Test
+    fun focusWrapsAroundToLastItem_skippingLastDeactivatedItem() {
+        // Arrange.
+        val (item1, item2, item3, item4) = List(4) { mutableStateOf(false) }
+        rule.setContentForTest {
+            FocusableBox(item1, 0, 0, 10, 10, initialFocus)
+            FocusableBox(item2, 10, 0, 10, 10)
+            FocusableBox(item3, 20, 0, 10, 10)
+            FocusableBox(item4, 20, 0, 10, 10, deactivated = true)
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Previous) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isTrue()
+            assertThat(item3.value).isTrue()
+        }
+    }
+
+    @Test
+    fun focusPreviousOrdering() {
         // Arrange.
         val (parent1, child1, child2, child3) = List(4) { mutableStateOf(false) }
         val (parent2, child4, child5) = List(3) { mutableStateOf(false) }
         val (parent3, child6) = List(2) { mutableStateOf(false) }
+        val (parent4, child7, child8, child11, child12) = List(5) { mutableStateOf(false) }
+        val (child9, child10) = List(2) { mutableStateOf(false) }
+        val (parent5, child13) = List(2) { mutableStateOf(false) }
         rule.setContentWithInitialRootFocus {
             FocusableBox(parent1, 0, 0, 10, 10) {
                 FocusableBox(child1, 0, 0, 10, 10)
@@ -232,38 +357,61 @@
                 }
                 FocusableBox(child5, 20, 0, 10, 10)
             }
+            FocusableBox(parent4, 0, 10, 10, 10, deactivated = true) {
+                FocusableBox(child7, 0, 10, 10, 10, deactivated = true)
+                FocusableBox(child8, 0, 10, 10, 10)
+                FocusableBox(child9, 0, 10, 10, 10, deactivated = true)
+                FocusableBox(child10, 0, 10, 10, 10)
+                FocusableBox(parent5, 10, 10, 10, 10, deactivated = true) {
+                    FocusableBox(child13, 0, 0, 10, 10)
+                }
+                FocusableBox(child11, 20, 0, 10, 10)
+                FocusableBox(child12, 20, 0, 10, 10, deactivated = true)
+            }
         }
 
         // Act & Assert.
-        focusManager.moveFocus(Previous)
+        rule.runOnIdle { focusManager.moveFocus(Previous) }
+        rule.runOnIdle { assertThat(child11.value).isTrue() }
+
+        rule.runOnIdle { focusManager.moveFocus(Previous) }
+        rule.runOnIdle { assertThat(child13.value).isTrue() }
+
+        rule.runOnIdle { focusManager.moveFocus(Previous) }
+        rule.runOnIdle { assertThat(child10.value).isTrue() }
+
+        rule.runOnIdle { focusManager.moveFocus(Previous) }
+        rule.runOnIdle { assertThat(child8.value).isTrue() }
+
+        rule.runOnIdle { focusManager.moveFocus(Previous) }
         rule.runOnIdle { assertThat(child5.value).isTrue() }
 
-        focusManager.moveFocus(Previous)
+        rule.runOnIdle { focusManager.moveFocus(Previous) }
         rule.runOnIdle { assertThat(child6.value).isTrue() }
 
-        focusManager.moveFocus(Previous)
+        rule.runOnIdle { focusManager.moveFocus(Previous) }
         rule.runOnIdle { assertThat(parent3.value).isTrue() }
 
-        focusManager.moveFocus(Previous)
+        rule.runOnIdle { focusManager.moveFocus(Previous) }
         rule.runOnIdle { assertThat(child4.value).isTrue() }
 
-        focusManager.moveFocus(Previous)
+        rule.runOnIdle { focusManager.moveFocus(Previous) }
         rule.runOnIdle { assertThat(parent2.value).isTrue() }
 
-        focusManager.moveFocus(Previous)
+        rule.runOnIdle { focusManager.moveFocus(Previous) }
         rule.runOnIdle { assertThat(child3.value).isTrue() }
 
-        focusManager.moveFocus(Previous)
+        rule.runOnIdle { focusManager.moveFocus(Previous) }
         rule.runOnIdle { assertThat(child2.value).isTrue() }
 
-        focusManager.moveFocus(Previous)
+        rule.runOnIdle { focusManager.moveFocus(Previous) }
         rule.runOnIdle { assertThat(child1.value).isTrue() }
 
-        focusManager.moveFocus(Previous)
+        rule.runOnIdle { focusManager.moveFocus(Previous) }
         rule.runOnIdle { assertThat(parent1.value).isTrue() }
 
-        focusManager.moveFocus(Previous)
-        rule.runOnIdle { assertThat(child5.value).isTrue() }
+        rule.runOnIdle { focusManager.moveFocus(Previous) }
+        rule.runOnIdle { assertThat(child11.value).isTrue() }
     }
 
     private fun ComposeContentTestRule.setContentForTest(composable: @Composable () -> Unit) {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/RequestFocusTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/RequestFocusTest.kt
index 5fda2c1..ad2151b 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/RequestFocusTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/RequestFocusTest.kt
@@ -17,10 +17,12 @@
 package androidx.compose.ui.focus
 
 import androidx.compose.foundation.layout.Box
+import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusStateImpl.Active
 import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
 import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Disabled
+import androidx.compose.ui.focus.FocusStateImpl.Deactivated
+import androidx.compose.ui.focus.FocusStateImpl.DeactivatedParent
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.filters.SmallTest
@@ -82,11 +84,11 @@
     }
 
     @Test
-    fun disabled_isUnchanged() {
+    fun deactivated_isUnchanged() {
         // Arrange.
-        val focusModifier = FocusModifier(Disabled)
+        val focusModifier = FocusModifier(Inactive)
         rule.setFocusableContent {
-            Box(modifier = focusModifier)
+            Box(modifier = Modifier.focusProperties { canFocus = false }.then(focusModifier))
         }
 
         // Act.
@@ -96,7 +98,7 @@
 
         // Assert.
         rule.runOnIdle {
-            assertThat(focusModifier.focusState).isEqualTo(Disabled)
+            assertThat(focusModifier.focusState).isEqualTo(Deactivated)
         }
     }
 
@@ -150,6 +152,87 @@
         }
     }
 
+    @Test(expected = IllegalArgumentException::class)
+    fun deactivatedParent_withNoFocusedChild_throwsException() {
+        // Arrange.
+        val focusModifier = FocusModifier(DeactivatedParent)
+        rule.setFocusableContent {
+            Box(modifier = focusModifier)
+        }
+
+        // Act.
+        rule.runOnIdle {
+            focusModifier.focusNode.requestFocus(propagateFocus)
+        }
+    }
+
+    @Test
+    fun deactivatedParent_propagateFocus() {
+        // Arrange.
+        val focusModifier = FocusModifier(ActiveParent)
+        val childFocusModifier = FocusModifier(Active)
+        rule.setFocusableContent {
+            Box(modifier = Modifier.focusProperties { canFocus = false }.then(focusModifier)) {
+                Box(modifier = childFocusModifier)
+            }
+        }
+        rule.runOnIdle {
+            focusModifier.focusedChild = childFocusModifier.focusNode
+        }
+
+        // Act.
+        rule.runOnIdle {
+            focusModifier.focusNode.requestFocus(propagateFocus)
+        }
+
+        // Assert.
+        rule.runOnIdle {
+           // Unchanged.
+           assertThat(focusModifier.focusState).isEqualTo(DeactivatedParent)
+           assertThat(childFocusModifier.focusState).isEqualTo(Active)
+        }
+    }
+
+    @Test
+    fun deactivatedParent_activeChild_propagateFocus() {
+        // Arrange.
+        val focusModifier = FocusModifier(ActiveParent)
+        val childFocusModifier = FocusModifier(Active)
+        val grandchildFocusModifier = FocusModifier(Inactive)
+        rule.setFocusableContent {
+            Box(modifier = Modifier.focusProperties { canFocus = false }.then(focusModifier)) {
+                Box(modifier = childFocusModifier) {
+                    Box(modifier = grandchildFocusModifier)
+                }
+            }
+        }
+        rule.runOnIdle {
+            focusModifier.focusedChild = childFocusModifier.focusNode
+        }
+
+        // Act.
+        rule.runOnIdle {
+            focusModifier.focusNode.requestFocus(propagateFocus)
+        }
+
+        // Assert.
+        rule.runOnIdle {
+            when (propagateFocus) {
+                true -> {
+                    assertThat(focusModifier.focusState).isEqualTo(DeactivatedParent)
+                    assertThat(childFocusModifier.focusState).isEqualTo(Active)
+                    assertThat(grandchildFocusModifier.focusState).isEqualTo(Inactive)
+                }
+                false -> {
+                    assertThat(focusModifier.focusState).isEqualTo(DeactivatedParent)
+                    assertThat(childFocusModifier.focusState).isEqualTo(Active)
+                    assertThat(childFocusModifier.focusedChild).isNull()
+                    assertThat(grandchildFocusModifier.focusState).isEqualTo(Inactive)
+                }
+            }
+        }
+    }
+
     @Test
     fun inactiveRoot_propagateFocusSendsRequestToOwner_systemCanGrantFocus() {
         // Arrange.
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInTest.kt
index bcfcd7c..5e15e48 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInTest.kt
@@ -57,7 +57,7 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(In)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(In) }
 
         // Assert.
         rule.runOnIdle {
@@ -68,6 +68,37 @@
     }
 
     /**
+     *      ___________________   ____________
+     *     |    focusedItem   |  |           |
+     *     |  ______________  |  |           |
+     *     | | deactivated |  |  | otherItem |
+     *     | |_____________|  |  |           |
+     *     |__________________|  |___________|
+     */
+    @Test
+    fun focusIn_deactivatedChild_doesNotMoveFocus() {
+        // Arrange.
+        val (focusedItem, deactivatedItem, otherItem) = List(3) { mutableStateOf(false) }
+        rule.setContentForTest {
+            FocusableBox(focusedItem, 0, 0, 10, 10, initialFocus) {
+                FocusableBox(deactivatedItem, 10, 0, 10, 10, deactivated = true)
+            }
+            FocusableBox(otherItem, 10, 0, 10, 10)
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(In) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isFalse()
+            assertThat(focusedItem.value).isTrue()
+            assertThat(deactivatedItem.value).isFalse()
+            assertThat(otherItem.value).isFalse()
+        }
+    }
+
+    /**
      *      _______________
      *     |  focusedItem |
      *     |   _________  |
@@ -86,7 +117,7 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(In)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(In) }
 
         // Assert.
         rule.runOnIdle {
@@ -120,7 +151,7 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(In)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(In) }
 
         // Assert.
         rule.runOnIdle {
@@ -131,6 +162,41 @@
         }
     }
 
+    /**
+     *      _________________________
+     *     |  focusedItem           |
+     *     |   ___________________  |
+     *     |  |  child           |  |
+     *     |  |   _____________  |  |
+     *     |  |  | grandchild |  |  |
+     *     |  |  |____________|  |  |
+     *     |  |__________________|  |
+     *     |________________________|
+     */
+    @Test
+    fun focusIn_skipsImmediateDeactivatedChild() {
+        // Arrange.
+        val (child, grandchild) = List(2) { mutableStateOf(false) }
+        rule.setContentForTest {
+            FocusableBox(focusedItem, 0, 0, 30, 30, initialFocus) {
+                FocusableBox(child, 10, 10, 10, 10, deactivated = true) {
+                    FocusableBox(grandchild, 10, 10, 10, 10)
+                }
+            }
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(In) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isTrue()
+            assertThat(focusedItem.value).isFalse()
+            assertThat(child.value).isFalse()
+            assertThat(grandchild.value).isTrue()
+        }
+    }
+
     // TODO(b/176847718): After RTL support is added, add a similar test where the topRight child
     //  is focused.
     /**
@@ -160,7 +226,7 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(In)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(In) }
 
         // Assert.
         rule.runOnIdle {
@@ -170,6 +236,43 @@
         }
     }
 
+    /**
+     *      _______________________________________
+     *     |  focusedItem                         |
+     *     |   _________   _________   _________  |
+     *     |  | child1 |  | child2 |  | child3 |  |
+     *     |  |________|  |________|  |________|  |
+     *     |   _________   _________   _________  |
+     *     |  | child4 |  | child5 |  | child6 |  |
+     *     |  |________|  |________|  |________|  |
+     *     |______________________________________|
+     */
+    @Test
+    fun focusIn_deactivatedTopLeftChildIsSkipped() {
+        // Arrange.
+        val children = List(6) { mutableStateOf(false) }
+        rule.setContentForTest {
+            FocusableBox(focusedItem, 0, 0, 70, 50, initialFocus) {
+                FocusableBox(children[0], 10, 10, 10, 10, deactivated = true)
+                FocusableBox(children[1], 30, 10, 10, 10)
+                FocusableBox(children[2], 50, 10, 10, 10)
+                FocusableBox(children[3], 10, 30, 10, 10)
+                FocusableBox(children[4], 30, 30, 10, 10)
+                FocusableBox(children[5], 50, 30, 10, 10)
+            }
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(In) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isTrue()
+            assertThat(focusedItem.value).isFalse()
+            assertThat(children.values).containsExactly(false, true, false, false, false, false)
+        }
+    }
+
     private fun ComposeContentTestRule.setContentForTest(composable: @Composable () -> Unit) {
         setContent {
             focusManager = LocalFocusManager.current
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInitialFocusTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInitialFocusTest.kt
index 8f7cd87..1831252 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInitialFocusTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInitialFocusTest.kt
@@ -24,7 +24,6 @@
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusDirection.Companion.Down
 import androidx.compose.ui.focus.FocusDirection.Companion.Left
 import androidx.compose.ui.focus.FocusDirection.Companion.Right
@@ -84,7 +83,7 @@
         rule.runOnIdle { view.requestFocus() }
 
         // Act.
-        focusManager.moveFocus(focusDirection)
+        rule.runOnIdle { focusManager.moveFocus(focusDirection) }
 
         // Assert.
         rule.runOnIdle {
@@ -99,6 +98,66 @@
     }
 
     @Test
+    fun initialFocus_DeactivatedItemIsSkipped() {
+        // Arrange.
+        lateinit var view: View
+        lateinit var focusManager: FocusManager
+        val isFocused = MutableList(9) { mutableStateOf(false) }
+        rule.setContent {
+            view = LocalView.current
+            focusManager = LocalFocusManager.current
+            Column {
+                Row {
+                    FocusableBox(isFocused[0], deactivated = true)
+                    FocusableBox(isFocused[1])
+                    FocusableBox(isFocused[2])
+                }
+                Row {
+                    FocusableBox(isFocused[3])
+                    FocusableBox(isFocused[4])
+                    FocusableBox(isFocused[5])
+                }
+                Row {
+                    FocusableBox(isFocused[6], deactivated = true)
+                    FocusableBox(isFocused[7])
+                    FocusableBox(isFocused[8])
+                }
+            }
+        }
+        rule.runOnIdle { view.requestFocus() }
+
+        // Act.
+        rule.runOnIdle { focusManager.moveFocus(focusDirection) }
+
+        // Assert.
+        rule.runOnIdle {
+            when (focusDirection) {
+                Up -> assertThat(isFocused.values).containsExactly(
+                    false, false, false,
+                    false, false, false,
+                    false, true, false
+                )
+                Down -> assertThat(isFocused.values).containsExactly(
+                    false, true, false,
+                    false, false, false,
+                    false, false, false
+                )
+                Left -> assertThat(isFocused.values).containsExactly(
+                    false, true, false,
+                    false, false, false,
+                    false, false, false
+                )
+                Right -> assertThat(isFocused.values).containsExactly(
+                    false, true, false,
+                    false, false, false,
+                    false, false, false
+                )
+                else -> error(invalid)
+            }
+        }
+    }
+
+    @Test
     fun initialFocus_whenThereIsOnlyOneFocusable() {
         // Arrange.
         val isFocused = mutableStateOf(false)
@@ -112,7 +171,7 @@
         rule.runOnIdle { view.requestFocus() }
 
         // Act.
-        focusManager.moveFocus(focusDirection)
+        rule.runOnIdle { focusManager.moveFocus(focusDirection) }
 
         // Assert.
         rule.runOnIdle { assertThat(isFocused.value).isTrue() }
@@ -131,44 +190,23 @@
         rule.runOnIdle { view.requestFocus() }
 
         // Act.
-        focusManager.moveFocus(focusDirection)
+        rule.runOnIdle { focusManager.moveFocus(focusDirection) }
     }
 
     @Test
-    fun initialFocus_notTriggeredIfActiveElementIsNotRoot() {
+    fun doesNotCrash_whenThereIsOneDeactivatedItem() {
         // Arrange.
+        lateinit var view: View
         lateinit var focusManager: FocusManager
-        var isColumnFocused = false
-        val isFocused = MutableList(4) { mutableStateOf(false) }
-        val initialFocusRequester = FocusRequester()
         rule.setContent {
+            view = LocalView.current
             focusManager = LocalFocusManager.current
-            Column(
-                Modifier
-                    .focusRequester(initialFocusRequester)
-                    .onFocusChanged { isColumnFocused = it.isFocused }
-                    .focusTarget()
-            ) {
-                Row {
-                    FocusableBox(isFocused[0])
-                    FocusableBox(isFocused[1])
-                }
-                Row {
-                    FocusableBox(isFocused[2])
-                    FocusableBox(isFocused[3])
-                }
-            }
+            FocusableBox(deactivated = true)
         }
-        rule.runOnIdle { initialFocusRequester.requestFocus() }
+        rule.runOnIdle { view.requestFocus() }
 
         // Act.
-        focusManager.moveFocus(focusDirection)
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(isColumnFocused).isTrue()
-            assertThat(isFocused.values).containsExactly(false, false, false, false)
-        }
+        rule.runOnIdle { focusManager.moveFocus(focusDirection) }
     }
 
     @OptIn(ExperimentalComposeUiApi::class)
@@ -210,7 +248,7 @@
         rule.runOnIdle { initialFocusedItem.requestFocus() }
 
         // Act.
-        focusManager.moveFocus(focusDirection)
+        rule.runOnIdle { focusManager.moveFocus(focusDirection) }
 
         // Assert.
         rule.runOnIdle {
@@ -221,15 +259,68 @@
             }
         }
     }
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    @Test
+    fun movesFocusAmongSiblingsDeepInTheFocusHierarchy_skipsDeactivatedSibling() {
+        // Arrange.
+        lateinit var focusManager: FocusManager
+        val isFocused = MutableList(3) { mutableStateOf(false) }
+        val (item1, item3) = FocusRequester.createRefs()
+        val siblings = @Composable {
+            FocusableBox(isFocused[0], item1)
+            FocusableBox(isFocused[1])
+            FocusableBox(isFocused[2], item3)
+        }
+        val initialFocusedItem = when (focusDirection) {
+            Up, Left -> item3
+            Down, Right -> item1
+            else -> error(invalid)
+        }
+        rule.setContent {
+            focusManager = LocalFocusManager.current
+            FocusableBox {
+                FocusableBox {
+                    FocusableBox {
+                        FocusableBox {
+                            FocusableBox {
+                                FocusableBox {
+                                    when (focusDirection) {
+                                        Up, Down -> Column { siblings() }
+                                        Left, Right -> Row { siblings() }
+                                        else -> error(invalid)
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        rule.runOnIdle { initialFocusedItem.requestFocus() }
+
+        // Act.
+        rule.runOnIdle { focusManager.moveFocus(focusDirection) }
+
+        // Assert.
+        rule.runOnIdle {
+            when (focusDirection) {
+                Up, Left -> assertThat(isFocused.values).containsExactly(true, false, false)
+                Down, Right -> assertThat(isFocused.values).containsExactly(false, false, true)
+                else -> error(invalid)
+            }
+        }
+    }
 }
 
 @Composable
 private fun FocusableBox(
     isFocused: MutableState<Boolean> = mutableStateOf(false),
     focusRequester: FocusRequester? = null,
+    deactivated: Boolean = false,
     content: @Composable () -> Unit = {}
 ) {
-    FocusableBox(isFocused, 0, 0, 10, 10, focusRequester, content)
+    FocusableBox(isFocused, 0, 0, 10, 10, focusRequester, deactivated, content)
 }
 
 private val MutableList<MutableState<Boolean>>.values get() = this.map { it.value }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalOutTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalOutTest.kt
index 5f2a84d..4f33fbd 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalOutTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalOutTest.kt
@@ -58,7 +58,7 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Out)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Out) }
 
         // Assert.
         rule.runOnIdle {
@@ -86,7 +86,7 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Out)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Out) }
 
         // Assert.
         rule.runOnIdle {
@@ -97,6 +97,35 @@
     }
 
     /**
+     *      _____________________
+     *     |  parent            |
+     *     |   _______________  |
+     *     |  | focusedItem  |  |
+     *     |  |______________|  |
+     *     |____________________|
+     */
+    @Test
+    fun focusOut_doesNotfocusOnDeactivatedParent() {
+        // Arrange.
+        val parent = mutableStateOf(false)
+        rule.setContentForTest {
+            FocusableBox(parent, 0, 0, 30, 30, deactivated = true) {
+                FocusableBox(focusedItem, 10, 10, 10, 10, initialFocus)
+            }
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Out) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isFalse()
+            assertThat(focusedItem.value).isTrue()
+            assertThat(parent.value).isFalse()
+        }
+    }
+
+    /**
      *      __________________________
      *     |  grandparent            |
      *     |   ____________________  |
@@ -120,7 +149,7 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Out)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Out) }
 
         // Assert.
         rule.runOnIdle {
@@ -132,6 +161,76 @@
     }
 
     /**
+     *      __________________________
+     *     |  grandparent            |
+     *     |   ____________________  |
+     *     |  |  parent           |  |
+     *     |  |   ______________  |  |
+     *     |  |  | focusedItem |  |  |
+     *     |  |  |_____________|  |  |
+     *     |  |___________________|  |
+     *     |_________________________|
+     */
+    @Test
+    fun focusOut_skipsImmediateParentIfItIsDeactivated() {
+        // Arrange.
+        val (parent, grandparent) = List(2) { mutableStateOf(false) }
+        rule.setContentForTest {
+            FocusableBox(grandparent, 0, 0, 50, 50) {
+                FocusableBox(parent, 10, 10, 30, 30, deactivated = true) {
+                    FocusableBox(focusedItem, 10, 10, 10, 10, initialFocus)
+                }
+            }
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Out) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isTrue()
+            assertThat(focusedItem.value).isFalse()
+            assertThat(parent.value).isFalse()
+            assertThat(grandparent.value).isTrue()
+        }
+    }
+
+    /**
+     *      __________________________
+     *     |  grandparent            |
+     *     |   ____________________  |
+     *     |  |  parent           |  |
+     *     |  |   ______________  |  |
+     *     |  |  | focusedItem |  |  |
+     *     |  |  |_____________|  |  |
+     *     |  |___________________|  |
+     *     |_________________________|
+     */
+    @Test
+    fun focusOut_doesNotChangeIfAllParentsAreDeactivated() {
+        // Arrange.
+        val (parent, grandparent) = List(2) { mutableStateOf(false) }
+        rule.setContentForTest {
+            FocusableBox(grandparent, 0, 0, 50, 50, deactivated = true) {
+                FocusableBox(parent, 10, 10, 30, 30, deactivated = true) {
+                    FocusableBox(focusedItem, 10, 10, 10, 10, initialFocus)
+                }
+            }
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Out) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isFalse()
+            assertThat(focusedItem.value).isTrue()
+            assertThat(parent.value).isFalse()
+            assertThat(grandparent.value).isFalse()
+        }
+    }
+
+    /**
      *      _____________________
      *     |  parent            |
      *     |   _______________  |   ____________
@@ -151,7 +250,7 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Right)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Right) }
 
         // Assert.
         rule.runOnIdle {
@@ -163,6 +262,39 @@
     }
 
     /**
+     *      _____________________
+     *     |  parent            |
+     *     |   _______________  |   ___________________   ____________
+     *     |  | focusedItem  |  |  | deactivatedItem  |  | nextItem  |
+     *     |  |______________|  |  |__________________|  |___________|
+     *     |____________________|
+     */
+    @Test
+    fun focusRight_focusesOnNonDeactivatedSiblingOfParent() {
+        // Arrange.
+        val (parent, deactivated, nextItem) = List(3) { mutableStateOf(false) }
+        rule.setContentForTest {
+            FocusableBox(parent, 0, 0, 30, 30) {
+                FocusableBox(focusedItem, 10, 10, 10, 10, initialFocus)
+            }
+            FocusableBox(deactivated, 40, 10, 10, 10, deactivated = true)
+            FocusableBox(nextItem, 40, 10, 10, 10)
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Right) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isTrue()
+            assertThat(focusedItem.value).isFalse()
+            assertThat(parent.value).isFalse()
+            assertThat(deactivated.value).isFalse()
+            assertThat(nextItem.value).isTrue()
+        }
+    }
+
+    /**
      *    ___________________________
      *   |  grandparent             |
      *   |   _____________________  |
@@ -187,7 +319,7 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Right)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Right) }
 
         // Assert.
         rule.runOnIdle {
@@ -230,7 +362,53 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Left)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Left) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isTrue()
+            assertThat(focusedItem.value).isFalse()
+            assertThat(parent.value).isFalse()
+            assertThat(item1.value).isTrue()
+            assertThat(item2.value).isFalse()
+            assertThat(item3.value).isFalse()
+            assertThat(item4.value).isFalse()
+            assertThat(item5.value).isFalse()
+        }
+    }
+
+    /**
+     *                       _____________________
+     *                      |  parent            |
+     *    _______________   |   _______________  |
+     *   |    item1     |   |  |  focusedItem |  |
+     *   |______________|   |  |______________|  |
+     *    _______________   |   _______________  |
+     *   |    item2     |   |  |    item4     |  |
+     *   |______________|   |  |______________|  |
+     *    _______________   |   _______________  |
+     *   |    item3     |   |  |    item5     |  |
+     *   |______________|   |  |______________|  |
+     *                      |____________________|
+     */
+    @Test
+    fun focusLeft_fromItemOnLeftEdge_movesFocusOutsideDeactivatedParent() {
+        // Arrange.
+        val parent = mutableStateOf(false)
+        val (item1, item2, item3, item4, item5) = List(5) { mutableStateOf(false) }
+        rule.setContentForTest {
+            FocusableBox(item1, 0, 10, 10, 10)
+            FocusableBox(item2, 0, 30, 10, 10)
+            FocusableBox(item3, 0, 50, 10, 10)
+            FocusableBox(parent, 20, 0, 30, 70, deactivated = true) {
+                FocusableBox(focusedItem, 10, 10, 10, 10, initialFocus)
+                FocusableBox(item4, 10, 30, 10, 10)
+                FocusableBox(item5, 10, 50, 10, 10)
+            }
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Left) }
 
         // Assert.
         rule.runOnIdle {
@@ -276,7 +454,53 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Right)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Right) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isTrue()
+            assertThat(focusedItem.value).isFalse()
+            assertThat(parent.value).isFalse()
+            assertThat(item1.value).isFalse()
+            assertThat(item2.value).isFalse()
+            assertThat(item3.value).isTrue()
+            assertThat(item4.value).isFalse()
+            assertThat(item5.value).isFalse()
+        }
+    }
+
+    /**
+     *      _____________________
+     *     |  parent            |
+     *     |   _______________  |    _______________
+     *     |  | focusedItem  |  |   |    item3     |
+     *     |  |______________|  |   |______________|
+     *     |   _______________  |    _______________
+     *     |  |    item1     |  |   |    item4     |
+     *     |  |______________|  |   |______________|
+     *     |   _______________  |    _______________
+     *     |  |    item2     |  |   |    item5     |
+     *     |  |______________|  |   |______________|
+     *     |____________________|
+     */
+    @Test
+    fun focusRight_fromItemOnRightEdge_movesFocusOutsideDeactivatedParent() {
+        // Arrange.
+        val parent = mutableStateOf(false)
+        val (item1, item2, item3, item4, item5) = List(5) { mutableStateOf(false) }
+        rule.setContentForTest {
+            FocusableBox(parent, 0, 0, 30, 70, deactivated = true) {
+                FocusableBox(focusedItem, 10, 10, 10, 10, initialFocus)
+                FocusableBox(item1, 10, 30, 10, 10)
+                FocusableBox(item2, 10, 50, 10, 10)
+            }
+            FocusableBox(item3, 40, 10, 10, 10)
+            FocusableBox(item4, 40, 30, 10, 10)
+            FocusableBox(item5, 40, 50, 10, 10)
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Right) }
 
         // Assert.
         rule.runOnIdle {
@@ -319,7 +543,50 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Up)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Up) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isTrue()
+            assertThat(focusedItem.value).isFalse()
+            assertThat(parent.value).isFalse()
+            assertThat(item1.value).isTrue()
+            assertThat(item2.value).isFalse()
+            assertThat(item3.value).isFalse()
+            assertThat(item4.value).isFalse()
+            assertThat(item5.value).isFalse()
+        }
+    }
+
+    /**
+     *       _______________   _______________   _______________
+     *      |    item1     |  |    item2     |  |    item3     |
+     *      |______________|  |______________|  |______________|
+     *    _________________________________________________________
+     *   |   parent                                               |
+     *   |   _______________   _______________   _______________  |
+     *   |  | focusedItem  |  |    item4     |  |    item5     |  |
+     *   |  |______________|  |______________|  |______________|  |
+     *   |________________________________________________________|
+     */
+    @Test
+    fun focusUp_fromTopmostItem_movesFocusOutsideDeactivatedParent() {
+        // Arrange.
+        val parent = mutableStateOf(false)
+        val (item1, item2, item3, item4, item5) = List(5) { mutableStateOf(false) }
+        rule.setContentForTest {
+            FocusableBox(item1, 10, 0, 10, 10)
+            FocusableBox(item2, 30, 0, 10, 10)
+            FocusableBox(item3, 50, 0, 10, 10)
+            FocusableBox(parent, 0, 20, 70, 30, deactivated = true) {
+                FocusableBox(focusedItem, 10, 10, 10, 10, initialFocus)
+                FocusableBox(item4, 30, 10, 10, 10)
+                FocusableBox(item5, 50, 10, 10, 10)
+            }
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Up) }
 
         // Assert.
         rule.runOnIdle {
@@ -362,7 +629,50 @@
         }
 
         // Act.
-        val movedFocusSuccessfully = focusManager.moveFocus(Down)
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Down) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isTrue()
+            assertThat(focusedItem.value).isFalse()
+            assertThat(parent.value).isFalse()
+            assertThat(item1.value).isFalse()
+            assertThat(item2.value).isFalse()
+            assertThat(item3.value).isTrue()
+            assertThat(item4.value).isFalse()
+            assertThat(item5.value).isFalse()
+        }
+    }
+
+    /**
+     *    _________________________________________________________
+     *   |   parent                                               |
+     *   |   _______________   _______________   _______________  |
+     *   |  | focusedItem  |  |    item1     |  |    item2     |  |
+     *   |  |______________|  |______________|  |______________|  |
+     *   |________________________________________________________|
+     *       _______________   _______________   _______________
+     *      |    item3     |  |    item4     |  |    item5     |
+     *      |______________|  |______________|  |______________|
+     */
+    @Test
+    fun focusDown_fromBottommostItem_movesFocusOutsideDeactivatedParent() {
+        // Arrange.
+        val parent = mutableStateOf(false)
+        val (item1, item2, item3, item4, item5) = List(5) { mutableStateOf(false) }
+        rule.setContentForTest {
+            FocusableBox(parent, 0, 0, 70, 30, deactivated = true) {
+                FocusableBox(focusedItem, 10, 10, 10, 10, initialFocus)
+                FocusableBox(item1, 30, 10, 10, 10)
+                FocusableBox(item2, 50, 10, 10, 10)
+            }
+            FocusableBox(item3, 10, 40, 10, 10)
+            FocusableBox(item4, 30, 40, 10, 10)
+            FocusableBox(item5, 50, 40, 10, 10)
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Down) }
 
         // Assert.
         rule.runOnIdle {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/InputModeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/InputModeTest.kt
index 2c956f2..13798785 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/InputModeTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/InputModeTest.kt
@@ -44,8 +44,8 @@
     @get:Rule
     val rule = createComposeRule()
 
-    lateinit var inputModeManager: InputModeManager
-    lateinit var view: View
+    private lateinit var inputModeManager: InputModeManager
+    private lateinit var view: View
 
     init {
         InstrumentationRegistry.getInstrumentation().setInTouchMode(param.inputMode == Touch)
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt
index 284ce94..e474c89 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt
@@ -94,7 +94,6 @@
         AndroidPointerInputTestActivity::class.java
     )
 
-    private lateinit var androidComposeView: AndroidComposeView
     private lateinit var container: OpenComposeView
 
     @Before
@@ -238,7 +237,7 @@
     @Test
     fun dispatchTouchEvent_notMeasuredLayoutsAreMeasuredFirst() {
         val size = mutableStateOf(10)
-        var latch = CountDownLatch(1)
+        val latch = CountDownLatch(1)
         var consumedDownPosition: Offset? = null
         rule.runOnUiThread {
             container.setContent {
@@ -475,7 +474,7 @@
         var didLongPress = false
         var didTap = false
         var inputLatch = CountDownLatch(1)
-        var positionedLatch = CountDownLatch(1)
+        val positionedLatch = CountDownLatch(1)
 
         rule.runOnUiThread {
             container.setContent {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index aff897cd..391bd08 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -342,6 +342,7 @@
     // executed whenever the touch mode changes.
     private val touchModeChangeListener = ViewTreeObserver.OnTouchModeChangeListener { touchMode ->
         _inputModeManager.inputMode = if (touchMode) Touch else Keyboard
+        _focusManager.fetchUpdatedFocusProperties()
     }
 
     private val textInputServiceAndroid = TextInputServiceAndroid(this)
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
index 5c06c55..64cd3cf 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
@@ -65,6 +65,7 @@
 import androidx.compose.ui.semantics.ScrollAxisRange
 import androidx.compose.ui.semantics.SemanticsActions
 import androidx.compose.ui.semantics.SemanticsActions.CustomActions
+import androidx.compose.ui.semantics.SemanticsConfiguration
 import androidx.compose.ui.semantics.SemanticsNode
 import androidx.compose.ui.semantics.SemanticsOwner
 import androidx.compose.ui.semantics.SemanticsProperties
@@ -116,6 +117,8 @@
         const val InvalidId = Integer.MIN_VALUE
         const val ClassName = "android.view.View"
         const val LogTag = "AccessibilityDelegate"
+        const val ExtraDataTestTagKey = "androidx.compose.ui.semantics.testTag"
+
         /**
          * Intent size limitations prevent sending over a megabyte of data. Limit
          * text length to 100K characters - 200KB.
@@ -649,13 +652,23 @@
                     AccessibilityNodeInfoCompat.MOVEMENT_GRANULARITY_PAGE
             }
         }
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !info.text.isNullOrEmpty() &&
-            semanticsNode.unmergedConfig.contains(SemanticsActions.GetTextLayoutResult)
-        ) {
-            AccessibilityNodeInfoVerificationHelperMethods.setAvailableExtraData(
-                info.unwrap(),
-                listOf(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)
-            )
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            val extraDataKeys: MutableList<String> = mutableListOf()
+            if (!info.text.isNullOrEmpty() &&
+                semanticsNode.unmergedConfig.contains(SemanticsActions.GetTextLayoutResult)
+            ) {
+                extraDataKeys.add(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)
+            }
+            if (semanticsNode.unmergedConfig.contains(SemanticsProperties.TestTag)) {
+                extraDataKeys.add(ExtraDataTestTagKey)
+            }
+
+            if (!extraDataKeys.isEmpty()) {
+                AccessibilityNodeInfoVerificationHelperMethods.setAvailableExtraData(
+                    info.unwrap(),
+                    extraDataKeys
+                )
+            }
         }
 
         val rangeInfo =
@@ -875,7 +888,7 @@
         info: AccessibilityNodeInfoCompat
     ) {
         val editableTextToAssign = trimToSize(
-            node.unmergedConfig.getOrNull(SemanticsProperties.EditableText)
+            node.unmergedConfig.getTextForTextField()
                 ?.toAccessibilitySpannableString(density = view.density, view.fontLoader),
             ParcelSafeTextLength
         )
@@ -1378,6 +1391,13 @@
                 boundingRects.add(boundsOnScreen)
             }
             info.extras.putParcelableArray(extraDataKey, boundingRects.toTypedArray())
+        } else if (node.unmergedConfig.contains(SemanticsProperties.TestTag) &&
+            arguments != null && extraDataKey == ExtraDataTestTagKey
+        ) {
+            val testTag = node.unmergedConfig.getOrNull(SemanticsProperties.TestTag)
+            if (testTag != null) {
+                info.extras.putCharSequence(extraDataKey, testTag)
+            }
         }
     }
 
@@ -1834,12 +1854,8 @@
                     SemanticsProperties.EditableText -> {
                         // TODO(b/160184953) Add test for SemanticsProperty Text change event
                         if (newNode.isTextField) {
-                            val oldText = oldNode.unmergedConfig.getOrNull(
-                                SemanticsProperties.EditableText
-                            )?.text ?: ""
-                            val newText = newNode.unmergedConfig.getOrNull(
-                                SemanticsProperties.EditableText
-                            )?.text ?: ""
+                            val oldText = oldNode.unmergedConfig.getTextForTextField() ?: ""
+                            val newText = newNode.unmergedConfig.getTextForTextField() ?: ""
                             var startCount = 0
                             // endCount records how many characters are the same from the end.
                             var endCount = 0
@@ -1884,7 +1900,7 @@
                     }
                     // do we need to overwrite TextRange equals?
                     SemanticsProperties.TextSelectionRange -> {
-                        val newText = getTextForTextField(newNode) ?: ""
+                        val newText = newNode.unmergedConfig.getTextForTextField()?.text ?: ""
                         val textRange =
                             newNode.unmergedConfig[SemanticsProperties.TextSelectionRange]
                         val event = createTextSelectionChangedEvent(
@@ -2140,6 +2156,7 @@
             accessibilityCursorPosition = AccessibilityCursorPositionUndefined
             previousTraversedNode = node.id
         }
+
         val text = getIterableTextForAccessibility(node)
         if (text.isNullOrEmpty()) {
             return false
@@ -2244,6 +2261,7 @@
         return true
     }
 
+    /** Returns selection start and end indices in original text */
     private fun getAccessibilitySelectionStart(node: SemanticsNode): Int {
         // If there is ContentDescription, it will be used instead of text during traversal.
         if (!node.unmergedConfig.contains(SemanticsProperties.ContentDescription) &&
@@ -2350,25 +2368,14 @@
         }
 
         if (node.isTextField) {
-            return getTextForTextField(node)
+            return node.unmergedConfig.getTextForTextField()?.text
         }
 
         return node.unmergedConfig.getOrNull(SemanticsProperties.Text)?.firstOrNull()?.text
     }
 
-    /**
-     * If there is an "editable" text inside text field, it is reported as a text. Otherwise
-     * label's text is used
-     */
-    private fun getTextForTextField(node: SemanticsNode?): String? {
-        if (node == null) return null
-
-        val editableText = node.unmergedConfig.getOrNull(SemanticsProperties.EditableText)
-        return if (editableText.isNullOrEmpty()) {
-            node.unmergedConfig.getOrNull(SemanticsProperties.Text)?.firstOrNull()?.text
-        } else {
-            editableText.text
-        }
+    private fun SemanticsConfiguration.getTextForTextField(): AnnotatedString? {
+        return getOrNull(SemanticsProperties.EditableText)
     }
 
     // TODO(b/160820721): use AccessibilityNodeProviderCompat instead of AccessibilityNodeProvider
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt
index c972610..b9c3df2 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt
@@ -16,17 +16,18 @@
 
 package androidx.compose.ui.focus
 
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusDirection.Companion.Next
-import androidx.compose.ui.focus.FocusDirection.Companion.Out
 import androidx.compose.ui.focus.FocusDirection.Companion.Previous
 import androidx.compose.ui.focus.FocusStateImpl.Active
 import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
 import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Disabled
+import androidx.compose.ui.focus.FocusStateImpl.Deactivated
+import androidx.compose.ui.focus.FocusStateImpl.DeactivatedParent
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
+import androidx.compose.ui.node.ModifiedFocusNode
 import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.util.fastForEach
 
 interface FocusManager {
     /**
@@ -109,25 +110,21 @@
      */
     override fun clearFocus(force: Boolean) {
         // If this hierarchy had focus before clearing it, it indicates that the host view has
-        // focus. So after clearing focus within the compose hierarchy, we should reset the root
-        // focus modifier to "Active" to maintain consistency with the host view.
-        val rootWasFocused = when (focusModifier.focusState) {
-            Active, ActiveParent, Captured -> true
-            Disabled, Inactive -> false
-        }
-
-        if (focusModifier.focusNode.clearFocus(force) && rootWasFocused) {
-            focusModifier.focusState = Active
+        // focus. So after clearing focus within the compose hierarchy, we should restore focus to
+        // the root focus modifier to maintain consistency with the host view.
+        val rootInitialState = focusModifier.focusState
+        if (focusModifier.focusNode.clearFocus(force)) {
+            focusModifier.focusState = when (rootInitialState) {
+                Active, ActiveParent, Captured -> Active
+                Deactivated, DeactivatedParent -> Deactivated
+                Inactive -> Inactive
+            }
         }
     }
 
     /**
      * Moves focus in the specified direction.
      *
-     * Focus moving is still being implemented. Right now, focus will move only if the user
-     * specified a custom focus traversal order for the item that is currently focused. (Using the
-     * [Modifier.focusOrder()][focusOrder] API).
-     *
      * @return true if focus was moved successfully. false if the focused item is unchanged.
      */
     override fun moveFocus(focusDirection: FocusDirection): Boolean {
@@ -145,30 +142,60 @@
         }
 
         val destination = focusModifier.focusNode.focusSearch(focusDirection, layoutDirection)
-        if (destination == null || destination == source) {
+        if (destination == source) {
             return false
         }
 
-        // We don't want moveFocus to set focus to the root, as this would essentially clear focus.
-        if (destination.findParentFocusNode() == null) {
-            return when (focusDirection) {
-                // Skip the root and proceed to the next/previous item from the root's perspective.
-                Next, Previous -> {
-                    destination.requestFocus(propagateFocus = false)
-                    moveFocus(focusDirection)
-                }
-                // Instead of moving out to the root, we return false.
-                // When we return false the key event will not be consumed, but it will bubble
-                // up to the owner. (In the case of Android, the back key will be sent to the
-                // activity, where it can be handled appropriately).
-                @OptIn(ExperimentalComposeUiApi::class)
-                Out -> false
-                else -> error("Move focus landed at the root through an unknown path.")
+        // TODO(b/144116848): This is a hack to make Next/Previous wrap around. This must be
+        //  replaced by code that sends the move request back to the view system. The view system
+        //  will then pass focus to other views, and ultimately return back to this compose view.
+        if (destination == null) {
+            // Check if we need to wrap around (no destination and a non-root item is focused)
+            if (focusModifier.focusState.hasFocus && !focusModifier.focusState.isFocused) {
+                    // Next and Previous wraps around.
+                    return when (focusDirection) {
+                        Next, Previous -> {
+                            // Clear Focus to send focus the root node.
+                            // Wrap around by requesting focus for the root and then calling moveFocus.
+                            clearFocus(force = false)
+
+                            if (focusModifier.focusState.isFocused) {
+                                moveFocus(focusDirection)
+                            } else {
+                                false
+                            }
+                        }
+                        else -> false
+                    }
             }
+            return false
         }
 
+        checkNotNull(destination.findParentFocusNode()) { "Move focus landed at the root." }
+
         // If we found a potential next item, call requestFocus() to move focus to it.
         destination.requestFocus(propagateFocus = false)
         return true
     }
+
+    /**
+     * Runs the focus properties block for all [focusProperties] modifiers to fetch updated
+     * [FocusProperties].
+     *
+     * The [focusProperties] block is run automatically whenever the properties change, and you
+     * rarely need to invoke this function manually. However, if you have a situation where you want
+     * to change a property, and need to see the change in the current snapshot, use this API.
+     */
+    fun fetchUpdatedFocusProperties() {
+        focusModifier.focusNode.updateProperties()
+    }
+}
+
+private fun ModifiedFocusNode.updateProperties() {
+    // Update the focus node with the current focus properties.
+    with(modifier.modifierLocalReadScope) {
+        setUpdatedProperties(ModifierLocalFocusProperties.current)
+    }
+    // Update the focus properties for all children.
+    focusableChildren(excludeDeactivated = false).fastForEach { it.updateProperties() }
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusModifier.kt
index 81121a9..770a4eb 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusModifier.kt
@@ -20,6 +20,9 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
+import androidx.compose.ui.modifier.ModifierLocalConsumer
+import androidx.compose.ui.modifier.ModifierLocalProvider
+import androidx.compose.ui.modifier.ModifierLocalReadScope
 import androidx.compose.ui.node.ModifiedFocusNode
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.platform.InspectorValueInfo
@@ -36,16 +39,35 @@
     //  Set this value in AndroidComposeView, and other places where we create a focus modifier
     //  using this internal constructor.
     inspectorInfo: InspectorInfo.() -> Unit = NoInspectorInfo
-) : Modifier.Element, InspectorValueInfo(inspectorInfo) {
+) : ModifierLocalConsumer,
+    ModifierLocalProvider<FocusProperties>,
+    InspectorValueInfo(inspectorInfo) {
 
     // TODO(b/188684110): Move focusState and focusedChild to ModifiedFocusNode and make this
     //  modifier stateless.
-
     var focusState: FocusStateImpl = initialFocus
 
     var focusedChild: ModifiedFocusNode? = null
 
     lateinit var focusNode: ModifiedFocusNode
+
+    lateinit var modifierLocalReadScope: ModifierLocalReadScope
+
+    // Reading the FocusProperties ModifierLocal.
+    override fun onModifierLocalsUpdated(scope: ModifierLocalReadScope) {
+        modifierLocalReadScope = scope
+
+        // Update the focus node with the current focus properties.
+        with(scope) {
+            focusNode.setUpdatedProperties(ModifierLocalFocusProperties.current)
+        }
+    }
+
+    override val key = ModifierLocalFocusProperties
+
+    // Writing the FocusProperties ModifierLocal so that any child focus modifiers don't read
+    // properties that were meant for this focus modifier.
+    override val value = defaultFocusProperties
 }
 
 /**
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusNodeUtils.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusNodeUtils.kt
index 08168fb..a2b8995 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusNodeUtils.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusNodeUtils.kt
@@ -26,11 +26,15 @@
 
 // TODO(b/152051577): Measure the performance of findFocusableChildren().
 //  Consider caching the children.
-internal fun LayoutNode.findFocusableChildren(focusableChildren: MutableList<ModifiedFocusNode>) {
+internal fun LayoutNode.findFocusableChildren(
+    focusableChildren: MutableList<ModifiedFocusNode>,
+    excludeDeactivated: Boolean
+) {
     // TODO(b/152529395): Write a test for LayoutNode.focusableChildren(). We were calling the wrong
     //  function on [LayoutNodeWrapper] but no test caught this.
-    outerLayoutNodeWrapper.findNextFocusWrapper()?.let { focusableChildren.add(it) }
-        ?: children.fastForEach { it.findFocusableChildren(focusableChildren) }
+    outerLayoutNodeWrapper.findNextFocusWrapper(excludeDeactivated)
+        ?.let { focusableChildren.add(it) }
+        ?: children.fastForEach { it.findFocusableChildren(focusableChildren, excludeDeactivated) }
 }
 
 // TODO(b/144126759): For now we always return the first focusable child. We might want to
@@ -42,11 +46,12 @@
  * @param queue a mutable list used as a queue for breadth-first search.
  */
 internal fun LayoutNode.searchChildrenForFocusNode(
-    queue: MutableVector<LayoutNode> = mutableVectorOf()
+    queue: MutableVector<LayoutNode> = mutableVectorOf(),
+    excludeDeactivated: Boolean
 ): ModifiedFocusNode? {
     // Check if any child has a focus Wrapper.
     _children.forEach { layoutNode ->
-        val focusNode = layoutNode.outerLayoutNodeWrapper.findNextFocusWrapper()
+        val focusNode = layoutNode.outerLayoutNodeWrapper.findNextFocusWrapper(excludeDeactivated)
         if (focusNode != null) {
             return focusNode
         } else {
@@ -56,7 +61,7 @@
 
     // Perform a breadth-first search through the children.
     while (queue.isNotEmpty()) {
-        val focusNode = queue.removeAt(0).searchChildrenForFocusNode(queue)
+        val focusNode = queue.removeAt(0).searchChildrenForFocusNode(queue, excludeDeactivated)
         if (focusNode != null) {
             return focusNode
         }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusProperties.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusProperties.kt
new file mode 100644
index 0000000..a0f3166
--- /dev/null
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusProperties.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.focus
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.modifier.ModifierLocalConsumer
+import androidx.compose.ui.modifier.ModifierLocalProvider
+import androidx.compose.ui.modifier.ModifierLocalReadScope
+import androidx.compose.ui.modifier.modifierLocalOf
+import androidx.compose.ui.node.ModifiedFocusNode
+import androidx.compose.ui.platform.debugInspectorInfo
+
+/**
+ * A Modifier local that stores [FocusProperties] for a sub-hierarchy.
+ *
+ * @see [focusProperties]
+ */
+internal val ModifierLocalFocusProperties = modifierLocalOf { defaultFocusProperties }
+
+internal val defaultFocusProperties: FocusProperties = FocusPropertiesImpl(canFocus = true)
+
+/**
+ * Properties that are applied to [focusTarget]s that can read the [ModifierLocalFocusProperties]
+ * Modifier Local.
+ *
+ * @see [focusProperties]
+ */
+interface FocusProperties {
+    /**
+     * When set to false, indicates that the [focusTarget] that this is applied to can no longer
+     * take focus. If the [focusTarget] is currently focused, setting this property to false will
+     * end up clearing focus.
+     */
+    var canFocus: Boolean
+}
+
+/**
+ * This modifier allows you to specify properties that are accessible to [focusTarget]s further
+ * down the modifier chain or on child layout nodes.
+ *
+ * @sample androidx.compose.ui.samples.FocusPropertiesSample
+ */
+fun Modifier.focusProperties(scope: FocusProperties.() -> Unit): Modifier = composed(
+    debugInspectorInfo {
+        name = "focusProperties"
+        properties["scope"] = scope
+    }
+) {
+    val rememberedScope by rememberUpdatedState(scope)
+    remember { FocusPropertiesModifier(focusPropertiesScope = rememberedScope) }
+}
+
+internal class FocusPropertiesModifier(
+    val focusPropertiesScope: FocusProperties.() -> Unit
+) : ModifierLocalConsumer, ModifierLocalProvider<FocusProperties> {
+
+    private var parentFocusProperties: FocusProperties? = null
+
+    override fun onModifierLocalsUpdated(scope: ModifierLocalReadScope) {
+        parentFocusProperties = scope.run { ModifierLocalFocusProperties.current }
+    }
+
+    override val key = ModifierLocalFocusProperties
+
+    override val value: FocusProperties
+        get() = defaultFocusProperties.copy {
+            // Populate with the specified focus properties.
+            apply(focusPropertiesScope)
+
+            // current value for deactivated can be overridden by a parent's value.
+            parentFocusProperties?.let {
+                if (it != defaultFocusProperties) {
+                    canFocus = it.canFocus
+                }
+            }
+        }
+}
+
+internal fun ModifiedFocusNode.setUpdatedProperties(properties: FocusProperties) {
+    if (properties.canFocus) activateNode() else deactivateNode()
+}
+
+private class FocusPropertiesImpl(override var canFocus: Boolean) : FocusProperties
+
+private fun FocusProperties.copy(scope: FocusProperties.() -> Unit): FocusProperties {
+    return FocusPropertiesImpl(canFocus = canFocus).apply(scope)
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusState.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusState.kt
index 83d314f..ba5cb16 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusState.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusState.kt
@@ -68,7 +68,10 @@
     Captured,
 
     // The focusable component is not currently focusable. (eg. A disabled button).
-    Disabled,
+    Deactivated,
+
+    // One of the descendants of this deactivated component is Active.
+    DeactivatedParent,
 
     // The focusable component does not receive any key events. (ie it is not active, nor are any
     // of its descendants active).
@@ -77,18 +80,30 @@
     override val isFocused: Boolean
         get() = when (this) {
             Captured, Active -> true
-            ActiveParent, Disabled, Inactive -> false
+            ActiveParent, Deactivated, DeactivatedParent, Inactive -> false
         }
 
     override val hasFocus: Boolean
         get() = when (this) {
-            Active, ActiveParent, Captured -> true
-            Disabled, Inactive -> false
+            Active, ActiveParent, Captured, DeactivatedParent -> true
+            Deactivated, Inactive -> false
         }
 
     override val isCaptured: Boolean
         get() = when (this) {
             Captured -> true
-            Active, ActiveParent, Inactive, Disabled -> false
+            Active, ActiveParent, Deactivated, DeactivatedParent, Inactive -> false
+        }
+
+    /**
+     * Whether the focusable component is deactivated.
+     *
+     * TODO(ralu): Consider making this public when we can add methods to interfaces without
+     * breaking compatibility.
+     */
+     val isDeactivated: Boolean
+        get() = when (this) {
+            Active, ActiveParent, Captured, Inactive -> false
+            Deactivated, DeactivatedParent -> true
         }
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactions.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactions.kt
index e4b2feb..6399c69 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactions.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactions.kt
@@ -19,7 +19,8 @@
 import androidx.compose.ui.focus.FocusStateImpl.Active
 import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
 import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Disabled
+import androidx.compose.ui.focus.FocusStateImpl.Deactivated
+import androidx.compose.ui.focus.FocusStateImpl.DeactivatedParent
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.node.ModifiedFocusNode
 
@@ -34,7 +35,7 @@
  */
 internal fun ModifiedFocusNode.requestFocus(propagateFocus: Boolean = true) {
     when (focusState) {
-        Active, Captured, Disabled -> {
+        Active, Captured, Deactivated, DeactivatedParent -> {
             // There is no change in focus state, but we send a focus event to notify the user
             // that the focus request is completed.
             sendOnFocusEvent(focusState)
@@ -69,6 +70,37 @@
 }
 
 /**
+ * Activate this node so that it can be focused.
+ *
+ * Deactivated nodes are excluded from focus search, and reject requests to gain focus.
+ * Calling this function activates a deactivated node.
+ */
+internal fun ModifiedFocusNode.activateNode() {
+    when (focusState) {
+        ActiveParent, Active, Captured, Inactive -> { }
+        Deactivated -> focusState = Inactive
+        DeactivatedParent -> focusState = ActiveParent
+    }
+}
+
+/**
+ * Deactivate this node so that it can't be focused.
+ *
+ * Deactivated nodes are excluded from focus search.
+ */
+internal fun ModifiedFocusNode.deactivateNode() {
+    when (focusState) {
+        ActiveParent -> focusState = DeactivatedParent
+        Active, Captured -> {
+            layoutNode.owner?.focusManager?.clearFocus(force = true)
+            focusState = Deactivated
+        }
+        Inactive -> focusState = Deactivated
+        Deactivated, DeactivatedParent -> { }
+    }
+}
+
+/**
  * Deny requests to clear focus.
  *
  * This is used when a component wants to hold onto focus (eg. A phone number field with an
@@ -82,7 +114,7 @@
         true
     }
     Captured -> true
-    else -> false
+    ActiveParent, Deactivated, DeactivatedParent, Inactive -> false
 }
 
 /**
@@ -98,7 +130,7 @@
         true
     }
     Active -> true
-    else -> false
+    ActiveParent, Deactivated, DeactivatedParent, Inactive -> false
 }
 
 /**
@@ -116,7 +148,7 @@
         }
         /**
          * If the node is [ActiveParent], we need to clear focus from the [Active] descendant
-         * first, before clearing focus of this node.
+         * first, before clearing focus from this node.
          */
         ActiveParent -> {
             val currentFocusedChild = focusedChild
@@ -129,6 +161,20 @@
             }
         }
         /**
+         * If the node is [DeactivatedParent], we need to clear focus from the [Active] descendant
+         * first, before clearing focus from this node.
+         */
+        DeactivatedParent -> {
+            val currentFocusedChild = focusedChild
+            requireNotNull(currentFocusedChild)
+            currentFocusedChild.clearFocus(forcedClear).also { success ->
+                if (success) {
+                    focusState = Deactivated
+                    focusedChild = null
+                }
+            }
+        }
+        /**
          * If the node is [Captured], deny requests to clear focus, except for a forced clear.
          */
         Captured -> {
@@ -140,7 +186,7 @@
         /**
          * Nothing to do if the node is not focused.
          */
-        Inactive, Disabled -> true
+        Inactive, Deactivated -> true
     }
 }
 
@@ -159,13 +205,21 @@
 
     // TODO (b/144126759): Design a system to decide which child gets focus.
     //  for now we grant focus to the first child.
-    val focusedCandidate = focusableChildren().firstOrNull()
+    val focusedCandidate = focusableChildren(excludeDeactivated = false).firstOrNull()
 
     if (focusedCandidate == null || !propagateFocus) {
         // No Focused Children, or we don't want to propagate focus to children.
-        focusState = Active
+        focusState = when (focusState) {
+            Inactive, Active, ActiveParent -> Active
+            Captured -> Captured
+            Deactivated, DeactivatedParent -> error("Granting focus to a deactivated node.")
+        }
     } else {
-        focusState = ActiveParent
+        focusState = when (focusState) {
+            Inactive, Active, ActiveParent -> ActiveParent
+            Captured -> { Captured; return }
+            Deactivated, DeactivatedParent -> DeactivatedParent
+        }
         focusedChild = focusedCandidate
         focusedCandidate.grantFocus(propagateFocus)
     }
@@ -185,7 +239,7 @@
 ): Boolean {
 
     // Only this node's children can ask for focus.
-    if (!focusableChildren().contains(childNode)) {
+    if (!focusableChildren(excludeDeactivated = false).contains(childNode)) {
         error("Non child node cannot request focus.")
     }
 
@@ -215,6 +269,23 @@
                 false
             }
         }
+        DeactivatedParent -> {
+            val previouslyFocusedNode = focusedChild
+            if (previouslyFocusedNode == null) {
+                // we use DeactivatedParent and focusedchild == null to indicate an intermediate
+                // state where a parent requested focus so that it can transfer it to a child.
+                focusedChild = childNode
+                childNode.grantFocus(propagateFocus)
+                true
+            } else if (previouslyFocusedNode.clearFocus()) {
+                focusedChild = childNode
+                childNode.grantFocus(propagateFocus)
+                true
+            } else {
+                // Currently focused component does not want to give up focus.
+                false
+            }
+        }
         /**
          * If this node is not [Active], we must gain focus first before granting it
          * to the requesting child.
@@ -241,9 +312,15 @@
          */
         Captured -> false
         /**
-         * Children of a [Disabled] parent should also be [Disabled].
+         * If this node is [Deactivated], send a requestFocusForChild to its parent to attempt to
+         * change its state to [DeactivatedParent] before granting focus to the child.
          */
-        Disabled -> error("non root FocusNode needs a focusable parent")
+        Deactivated -> {
+            activateNode()
+            val childGrantedFocus = requestFocusForChild(childNode, propagateFocus)
+            deactivateNode()
+            childGrantedFocus
+        }
     }
 }
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt
index 9098b8e..7d6cea5 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt
@@ -28,7 +28,8 @@
 import androidx.compose.ui.focus.FocusStateImpl.Active
 import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
 import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Disabled
+import androidx.compose.ui.focus.FocusStateImpl.Deactivated
+import androidx.compose.ui.focus.FocusStateImpl.DeactivatedParent
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.node.ModifiedFocusNode
 import androidx.compose.ui.unit.LayoutDirection
@@ -148,7 +149,11 @@
             findActiveFocusNode()?.twoDimensionalFocusSearch(direction)
         }
         @OptIn(ExperimentalComposeUiApi::class)
-        Out -> findActiveFocusNode()?.findParentFocusNode()
+        Out -> {
+            findActiveFocusNode()?.findActiveParent().let {
+                if (it == this) null else it
+            }
+        }
         else -> error(invalidFocusDirection)
     }
 }
@@ -156,7 +161,14 @@
 internal fun ModifiedFocusNode.findActiveFocusNode(): ModifiedFocusNode? {
     return when (focusState) {
         Active, Captured -> this
-        ActiveParent -> focusedChild?.findActiveFocusNode()
-        Inactive, Disabled -> null
+        ActiveParent, DeactivatedParent -> focusedChild?.findActiveFocusNode()
+        Inactive, Deactivated -> null
     }
 }
+
+internal fun ModifiedFocusNode.findActiveParent(): ModifiedFocusNode? = findParentFocusNode()?.let {
+        when (focusState) {
+            Active, Captured, Deactivated, DeactivatedParent, Inactive -> it.findActiveParent()
+            ActiveParent -> this
+        }
+    }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearch.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearch.kt
index d26db77..e495a97 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearch.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearch.kt
@@ -21,70 +21,138 @@
 import androidx.compose.ui.focus.FocusStateImpl.Captured
 import androidx.compose.ui.focus.FocusDirection.Companion.Next
 import androidx.compose.ui.focus.FocusDirection.Companion.Previous
-import androidx.compose.ui.focus.FocusStateImpl.Disabled
+import androidx.compose.ui.focus.FocusStateImpl.Deactivated
+import androidx.compose.ui.focus.FocusStateImpl.DeactivatedParent
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.node.ModifiedFocusNode
 import androidx.compose.ui.util.fastForEach
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
 
 private const val InvalidFocusDirection = "This function should only be used for 1-D focus search"
 private const val NoActiveChild = "ActiveParent must have a focusedChild"
-private const val NotYetAvailable = "Implement this after adding API to disable a node"
 
 internal fun ModifiedFocusNode.oneDimensionalFocusSearch(
     direction: FocusDirection
-): ModifiedFocusNode = when (direction) {
-    Next -> forwardFocusSearch() ?: this
+): ModifiedFocusNode? = when (direction) {
+    Next -> forwardFocusSearch()
     Previous -> backwardFocusSearch()
     else -> error(InvalidFocusDirection)
 }
 
-private fun ModifiedFocusNode.forwardFocusSearch(): ModifiedFocusNode? = when (focusState) {
-    ActiveParent -> {
-        val focusedChild = focusedChild ?: error(NoActiveChild)
-        focusedChild.forwardFocusSearch()?.let { return it }
+private fun ModifiedFocusNode.forwardFocusSearch(): ModifiedFocusNode? {
+    when (focusState) {
+        ActiveParent, DeactivatedParent -> {
+            val focusedChild = focusedChild ?: error(NoActiveChild)
+            focusedChild.forwardFocusSearch()?.let { return it }
 
-        var currentItemIsAfterFocusedItem = false
-        // TODO(b/192681045): Instead of fetching the children and then iterating on them, add a
-        //  forEachFocusableChild function that does not allocate a list.
-        focusableChildren().fastForEach {
-            if (currentItemIsAfterFocusedItem) {
-                return it
+            // TODO(b/192681045): Instead of fetching the children and then iterating on them, add a
+            //  forEachFocusableChild() function that does not allocate a list.
+            focusableChildren(excludeDeactivated = false).forEachItemAfter(focusedChild) { child ->
+                child.forwardFocusSearch()?.let { return it }
             }
-            if (it == focusedChild) {
-                currentItemIsAfterFocusedItem = true
-            }
+            return null
         }
-        null // Couldn't find a focusable child after the current focused child.
+        Active, Captured, Deactivated -> {
+            focusableChildren(excludeDeactivated = false).fastForEach { focusableChild ->
+                focusableChild.forwardFocusSearch()?.let { return it }
+            }
+            return null
+        }
+        Inactive -> return this
     }
-    Active, Captured -> focusableChildren().firstOrNull()
-    Inactive -> this
-    Disabled -> TODO(NotYetAvailable)
 }
 
-private fun ModifiedFocusNode.backwardFocusSearch(): ModifiedFocusNode = when (focusState) {
-    ActiveParent -> {
-        val focusedChild = focusedChild ?: error(NoActiveChild)
-        when (focusedChild.focusState) {
-            ActiveParent -> focusedChild.backwardFocusSearch()
-            Active, Captured -> {
-                var previousFocusedItem: ModifiedFocusNode? = null
-                // TODO(b/192681045): Instead of fetching the children and then iterating on them, add a
-                //  forEachFocusableChild() function that does not allocate a list.
-                focusableChildren().fastForEach {
-                    if (it == focusedChild) {
-                        return previousFocusedItem?.backwardFocusSearch() ?: this
+private fun ModifiedFocusNode.backwardFocusSearch(): ModifiedFocusNode? {
+    when (focusState) {
+        ActiveParent -> {
+            val focusedChild = focusedChild ?: error(NoActiveChild)
+            when (focusedChild.focusState) {
+                ActiveParent -> return focusedChild.backwardFocusSearch() ?: focusedChild
+                DeactivatedParent -> {
+                    focusedChild.backwardFocusSearch()?.let { return it }
+                    focusableChildren(excludeDeactivated = false).forEachItemBefore(focusedChild) {
+                        it.backwardFocusSearch()?.let { return it }
                     }
-                    previousFocusedItem = it
+                    // backward search returns the parent unless it is the root
+                    // (We don't want to move focus to the root).
+                    return if (isRoot()) null else this
                 }
-                error(NoActiveChild)
+                Active, Captured -> {
+                    focusableChildren(excludeDeactivated = false).forEachItemBefore(focusedChild) {
+                        it.backwardFocusSearch()?.let { return it }
+                    }
+                    // backward search returns the parent unless it is the root
+                    // (We don't want to move focus to the root).
+                    return if (isRoot()) null else this
+                }
+                Deactivated, Inactive -> error(NoActiveChild)
             }
-            else -> error(NoActiveChild)
+        }
+        DeactivatedParent -> {
+            val focusedChild = focusedChild ?: error(NoActiveChild)
+            when (focusedChild.focusState) {
+                ActiveParent -> return focusedChild.backwardFocusSearch() ?: focusedChild
+                DeactivatedParent -> {
+                    focusedChild.backwardFocusSearch()?.let { return it }
+                    focusableChildren(excludeDeactivated = false).forEachItemBefore(focusedChild) {
+                        it.backwardFocusSearch()?.let { return it }
+                    }
+                    return null
+                }
+                Active, Captured -> {
+                    focusableChildren(excludeDeactivated = false).forEachItemBefore(focusedChild) {
+                        it.backwardFocusSearch()?.let { return it }
+                    }
+                    return null
+                }
+                Deactivated, Inactive -> error(NoActiveChild)
+            }
+        }
+        // BackwardFocusSearch Searches among siblings of the ActiveParent for a child that is
+        // focused. So this function should never be called when this node is focused. If we
+        // reached here, it indicates that this is an initial focus state, so we run the same logic
+        // as if this node was Inactive. If we can't find an item for initial focus, we return this
+        // root node as the result.
+        Active, Captured, Inactive ->
+            return focusableChildren(excludeDeactivated = true)
+                .lastOrNull()?.backwardFocusSearch() ?: this
+        // BackwardFocusSearch Searches among siblings of the ActiveParent for a child that is
+        // focused. The search excludes deactivated items, so this function should never be called
+        // on a node with a Deactivated state. If we reached here, it indicates that this is an
+        // initial focus state, so we run the same logic as if this node was Inactive. If we can't
+        // find an item for initial focus, we return null.
+        Deactivated ->
+            return focusableChildren(excludeDeactivated = true).lastOrNull()?.backwardFocusSearch()
+    }
+}
+
+private fun ModifiedFocusNode.isRoot() = findParentFocusNode() == null
+
+@OptIn(ExperimentalContracts::class)
+private inline fun <T> List<T>.forEachItemAfter(item: T, action: (T) -> Unit) {
+    contract { callsInPlace(action) }
+    var itemFound = false
+    for (index in indices) {
+        if (itemFound) {
+            action(get(index))
+        }
+        if (get(index) == item) {
+            itemFound = true
         }
     }
-    // The BackwardFocusSearch Searches among siblings of the ActiveParent for a child that is
-    // focused. So this function should never be called when this node is focused. If we reached
-    // here, it indicates an initial focus state, so we run the same logic as if this node was
-    // Inactive.
-    Active, Captured, Inactive -> focusableChildren().lastOrNull()?.backwardFocusSearch() ?: this
-    Disabled -> TODO(NotYetAvailable)
+}
+
+@OptIn(ExperimentalContracts::class)
+private inline fun <T> List<T>.forEachItemBefore(item: T, action: (T) -> Unit) {
+    contract { callsInPlace(action) }
+    var itemFound = false
+    for (index in indices.reversed()) {
+        if (itemFound) {
+            action(get(index))
+        }
+        if (get(index) == item) {
+            itemFound = true
+        }
+    }
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt
index cb9c350..d95db18 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt
@@ -23,7 +23,8 @@
 import androidx.compose.ui.focus.FocusStateImpl.Active
 import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
 import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Disabled
+import androidx.compose.ui.focus.FocusStateImpl.Deactivated
+import androidx.compose.ui.focus.FocusStateImpl.DeactivatedParent
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.node.ModifiedFocusNode
@@ -46,8 +47,8 @@
 ): ModifiedFocusNode? {
     return when (focusState) {
         Inactive -> this
-        Disabled -> null
-        ActiveParent -> {
+        Deactivated -> null
+        ActiveParent, DeactivatedParent -> {
             // If the focusedChild is an intermediate parent, we continue searching among it's
             // children, and return a focus node if we find one.
             val focusedChild = focusedChild ?: error(NoActiveChild)
@@ -58,13 +59,13 @@
             // Use the focus rect of the active node as the starting point and pick one of our
             // children as the next focused item.
             val activeRect = findActiveFocusNode()?.focusRect() ?: error(NoActiveChild)
-            focusableChildren().findBestCandidate(activeRect, direction)
+            focusableChildren(excludeDeactivated = true).findBestCandidate(activeRect, direction)
         }
         Active, Captured -> {
             // The 2-D focus search starts form the root. If we reached here, it means that there
             // was no intermediate node that was ActiveParent. This is an initial focus scenario.
             // We need to search among this node's children to find the best focus candidate.
-            val focusableChildren = focusableChildren()
+            val focusableChildren = focusableChildren(excludeDeactivated = true)
 
             // If there are aren't multiple children to choose from, return the first child.
             if (focusableChildren.size <= 1) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatingLayoutNodeWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatingLayoutNodeWrapper.kt
index a0af689..a78d9cc 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatingLayoutNodeWrapper.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatingLayoutNodeWrapper.kt
@@ -131,16 +131,18 @@
 
     override fun findPreviousFocusWrapper() = wrappedBy?.findPreviousFocusWrapper()
 
-    override fun findNextFocusWrapper() = wrapped.findNextFocusWrapper()
+    override fun findNextFocusWrapper(excludeDeactivated: Boolean): ModifiedFocusNode? {
+        return wrapped.findNextFocusWrapper(excludeDeactivated)
+    }
 
     override fun findLastFocusWrapper(): ModifiedFocusNode? {
         var lastFocusWrapper: ModifiedFocusNode? = null
 
         // Find last focus wrapper for the current layout node.
-        var next: ModifiedFocusNode? = findNextFocusWrapper()
+        var next: ModifiedFocusNode? = findNextFocusWrapper(excludeDeactivated = false)
         while (next != null) {
             lastFocusWrapper = next
-            next = next.wrapped.findNextFocusWrapper()
+            next = next.wrapped.findNextFocusWrapper(excludeDeactivated = false)
         }
         return lastFocusWrapper
     }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerPlaceable.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerPlaceable.kt
index 63746e2..6c3b85b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerPlaceable.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerPlaceable.kt
@@ -20,7 +20,8 @@
 import androidx.compose.ui.focus.FocusStateImpl.Active
 import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
 import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Disabled
+import androidx.compose.ui.focus.FocusStateImpl.Deactivated
+import androidx.compose.ui.focus.FocusStateImpl.DeactivatedParent
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Canvas
@@ -58,7 +59,7 @@
 
     override fun findPreviousFocusWrapper() = wrappedBy?.findPreviousFocusWrapper()
 
-    override fun findNextFocusWrapper(): ModifiedFocusNode? = null
+    override fun findNextFocusWrapper(excludeDeactivated: Boolean): ModifiedFocusNode? = null
 
     override fun findLastFocusWrapper(): ModifiedFocusNode? = findPreviousFocusWrapper()
 
@@ -70,16 +71,19 @@
         var allChildrenDisabled: Boolean? = null
         // TODO(b/192681045): Create a utility like fun LayoutNodeWrapper.forEachFocusableChild{...}
         //  that does not allocate, but just iterates over all the focusable children.
-        focusableChildren().fastForEach {
+        focusableChildren(excludeDeactivated = false).fastForEach {
             when (it.focusState) {
-                Active, ActiveParent, Captured -> { focusedChild = it; allChildrenDisabled = false }
-                Disabled -> if (allChildrenDisabled == null) { allChildrenDisabled = true }
+                Active, ActiveParent, Captured, DeactivatedParent -> {
+                    focusedChild = it
+                    allChildrenDisabled = false
+                }
+                Deactivated -> if (allChildrenDisabled == null) { allChildrenDisabled = true }
                 Inactive -> allChildrenDisabled = false
             }
         }
 
         super.propagateFocusEvent(
-            focusedChild?.focusState ?: if (allChildrenDisabled == true) Disabled else Inactive
+            focusedChild?.focusState ?: if (allChildrenDisabled == true) Deactivated else Inactive
         )
     }
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
index 607c31b..4da75b6 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
@@ -702,7 +702,7 @@
      * Note: This method only goes to the modifiers that follow the one wrapped by
      * this [LayoutNodeWrapper], it doesn't to the children [LayoutNode]s.
      */
-    abstract fun findNextFocusWrapper(): ModifiedFocusNode?
+    abstract fun findNextFocusWrapper(excludeDeactivated: Boolean): ModifiedFocusNode?
 
     /**
      * Returns the last [focus node][ModifiedFocusNode] found following this [LayoutNodeWrapper].
@@ -878,10 +878,10 @@
 
     // TODO(b/152051577): Measure the performance of focusableChildren.
     //  Consider caching the children.
-    fun focusableChildren(): List<ModifiedFocusNode> {
+    fun focusableChildren(excludeDeactivated: Boolean): List<ModifiedFocusNode> {
         // Check the modifier chain that this focus node is part of. If it has a focus modifier,
         // that means you have found the only focusable child for this node.
-        val focusableChild = wrapped?.findNextFocusWrapper()
+        val focusableChild = wrapped?.findNextFocusWrapper(excludeDeactivated)
         // findChildFocusNodeInWrapperChain()
         if (focusableChild != null) {
             return listOf(focusableChild)
@@ -889,7 +889,9 @@
 
         // Go through all your children and find the first focusable node from each child.
         val focusableChildren = mutableListOf<ModifiedFocusNode>()
-        layoutNode.children.fastForEach { it.findFocusableChildren(focusableChildren) }
+        layoutNode.children.fastForEach {
+            it.findFocusableChildren(focusableChildren, excludeDeactivated)
+        }
         return focusableChildren
     }
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedFocusEventNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedFocusEventNode.kt
index f387e3b..7fab9d3 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedFocusEventNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedFocusEventNode.kt
@@ -37,7 +37,8 @@
         // For instance, if the observer is moved to the end of the list, and there is no focus
         // modifier following this observer, it's focus state will be invalid. To solve this, we
         // always reset the focus state when a focus observer is re-used.
-        val focusNode = wrapped.findNextFocusWrapper() ?: layoutNode.searchChildrenForFocusNode()
+        val focusNode = wrapped.findNextFocusWrapper(excludeDeactivated = false)
+            ?: layoutNode.searchChildrenForFocusNode(excludeDeactivated = false)
         modifier.onFocusEvent(focusNode?.modifier?.focusState ?: Inactive)
     }
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedFocusNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedFocusNode.kt
index 0c602b2..77e9c95 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedFocusNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedFocusNode.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.node
 
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.focus.FocusModifier
 import androidx.compose.ui.focus.FocusOrder
 import androidx.compose.ui.focus.FocusState
@@ -23,7 +24,8 @@
 import androidx.compose.ui.focus.FocusStateImpl.Active
 import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
 import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Disabled
+import androidx.compose.ui.focus.FocusStateImpl.Deactivated
+import androidx.compose.ui.focus.FocusStateImpl.DeactivatedParent
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.focus.searchChildrenForFocusNode
 import androidx.compose.ui.geometry.Rect
@@ -56,7 +58,9 @@
     fun focusRect(): Rect = findRoot().localBoundingBoxOf(this, clipBounds = false)
 
     fun sendOnFocusEvent(focusState: FocusState) {
-        wrappedBy?.propagateFocusEvent(focusState)
+        if (isAttached) {
+            wrappedBy?.propagateFocusEvent(focusState)
+        }
     }
 
     override fun onModifierChanged() {
@@ -64,6 +68,7 @@
         sendOnFocusEvent(focusState)
     }
 
+    // TODO(b/202621526) Handle cases where a focus modifier is attached to a node that is focused.
     override fun attach() {
         super.attach()
         sendOnFocusEvent(focusState)
@@ -76,29 +81,44 @@
                 layoutNode.owner?.focusManager?.clearFocus(force = true)
             }
             // Propagate the state of the next focus node to any focus observers in the hierarchy.
-            ActiveParent -> {
-                // Find the next focus node.
-                val nextFocusNode = wrapped.findNextFocusWrapper()
-                    ?: layoutNode.searchChildrenForFocusNode()
-                if (nextFocusNode != null) {
-                    findParentFocusNode()?.modifier?.focusedChild = nextFocusNode
-                    sendOnFocusEvent(nextFocusNode.focusState)
-                } else {
-                    sendOnFocusEvent(Inactive)
+            ActiveParent, DeactivatedParent -> {
+                val nextFocusNode = wrapped.findNextFocusWrapper(excludeDeactivated = false)
+                    ?: layoutNode.searchChildrenForFocusNode(excludeDeactivated = false)
+                val parentFocusNode = findParentFocusNode()
+                if (parentFocusNode != null) {
+                    parentFocusNode.modifier.focusedChild = nextFocusNode
+                    if (nextFocusNode != null) {
+                        sendOnFocusEvent(nextFocusNode.focusState)
+                    } else {
+                        parentFocusNode.focusState = when (parentFocusNode.focusState) {
+                            ActiveParent -> Inactive
+                            DeactivatedParent -> Deactivated
+                            else -> parentFocusNode.focusState
+                        }
+                    }
                 }
             }
-            // TODO(b/155212782): Implement this after adding support for disabling focus modifiers.
-            Disabled -> {}
+            Deactivated -> {
+                val nextFocusNode = wrapped.findNextFocusWrapper(excludeDeactivated = false)
+                    ?: layoutNode.searchChildrenForFocusNode(excludeDeactivated = false)
+                sendOnFocusEvent(nextFocusNode?.focusState ?: Inactive)
+            }
             // Do nothing, as the nextFocusNode is also Inactive.
             Inactive -> {}
         }
-
         super.detach()
     }
 
     override fun findPreviousFocusWrapper() = this
 
-    override fun findNextFocusWrapper() = this
+    @OptIn(ExperimentalComposeUiApi::class)
+    override fun findNextFocusWrapper(excludeDeactivated: Boolean): ModifiedFocusNode? {
+        return if (modifier.focusState.isDeactivated && excludeDeactivated) {
+            super.findNextFocusWrapper(excludeDeactivated)
+        } else {
+            this
+        }
+    }
 
     override fun propagateFocusEvent(focusState: FocusState) {
         // Do nothing. Stop propagating the focus change (since we hit another focus node).
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedFocusRequesterNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedFocusRequesterNode.kt
index 35595f9..7b80f95 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedFocusRequesterNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedFocusRequesterNode.kt
@@ -35,7 +35,8 @@
 
     // Searches for the focus node associated with this focus requester node.
     internal fun findFocusNode(): ModifiedFocusNode? {
-        return findNextFocusWrapper() ?: layoutNode.searchChildrenForFocusNode()
+        return findNextFocusWrapper(excludeDeactivated = false)
+            ?: layoutNode.searchChildrenForFocusNode(excludeDeactivated = false)
     }
 
     override fun onModifierChanged() {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt
index 23c5517..b59c966 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt
@@ -176,7 +176,6 @@
         }
     )
 
-    // TODO(b/178121203) might need to be transformed text
     /**
      * @see SemanticsPropertyReceiver.editableText
      */
@@ -784,7 +783,8 @@
     set(value) { set(SemanticsProperties.Text, listOf(value)) }
 
 /**
- * Input text of the text field. It must be real text entered by the user instead of
+ * Input text of the text field with visual transformation applied to it. It must be a real text
+ * entered by the user with visual transformation applied on top of the input text instead of a
  * developer-set content description.
  */
 var SemanticsPropertyReceiver.editableText by SemanticsProperties.EditableText
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/focus/FocusManagerTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/focus/FocusManagerTest.kt
index e18c76c..0ee1040 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/focus/FocusManagerTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/focus/FocusManagerTest.kt
@@ -19,7 +19,8 @@
 import androidx.compose.ui.focus.FocusStateImpl.Active
 import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
 import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Disabled
+import androidx.compose.ui.focus.FocusStateImpl.Deactivated
+import androidx.compose.ui.focus.FocusStateImpl.DeactivatedParent
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.node.InnerPlaceable
 import androidx.compose.ui.node.LayoutNode
@@ -65,7 +66,7 @@
         assertThat(focusModifier.focusState).isEqualTo(
             when (initialFocusState) {
                 Inactive -> Active
-                Active, ActiveParent, Captured, Disabled -> initialFocusState
+                Active, ActiveParent, Captured, Deactivated, DeactivatedParent -> initialFocusState
             }
         )
     }
@@ -74,7 +75,7 @@
     fun releaseFocus_changesStateToInactive() {
         // Arrange.
         focusModifier.focusState = initialFocusState as FocusStateImpl
-        if (initialFocusState == ActiveParent) {
+        if (initialFocusState == ActiveParent || initialFocusState == DeactivatedParent) {
             val childLayoutNode = LayoutNode()
             val child = ModifiedFocusNode(InnerPlaceable(childLayoutNode), FocusModifier(Active))
             focusModifier.focusNode.layoutNode._children.add(childLayoutNode)
@@ -88,7 +89,7 @@
         assertThat(focusModifier.focusState).isEqualTo(
             when (initialFocusState) {
                 Active, ActiveParent, Captured, Inactive -> Inactive
-                Disabled -> initialFocusState
+                Deactivated, DeactivatedParent -> Deactivated
             }
         )
     }
@@ -97,7 +98,7 @@
     fun clearFocus_forced() {
         // Arrange.
         focusModifier.focusState = initialFocusState as FocusStateImpl
-        if (initialFocusState == ActiveParent) {
+        if (initialFocusState == ActiveParent || initialFocusState == DeactivatedParent) {
             val childLayoutNode = LayoutNode()
             val child = ModifiedFocusNode(InnerPlaceable(childLayoutNode), FocusModifier(Active))
             focusModifier.focusNode.layoutNode._children.add(childLayoutNode)
@@ -113,7 +114,8 @@
                 // If the initial state was focused, assert that after clearing the hierarchy,
                 // the root is set to Active.
                 Active, ActiveParent, Captured -> Active
-                Disabled, Inactive -> initialFocusState
+                Deactivated, DeactivatedParent -> Deactivated
+                Inactive -> Inactive
             }
         )
     }
@@ -122,7 +124,7 @@
     fun clearFocus_notForced() {
         // Arrange.
         focusModifier.focusState = initialFocusState as FocusStateImpl
-        if (initialFocusState == ActiveParent) {
+        if (initialFocusState == ActiveParent || initialFocusState == DeactivatedParent) {
             val childLayoutNode = LayoutNode()
             val child = ModifiedFocusNode(InnerPlaceable(childLayoutNode), FocusModifier(Active))
             focusModifier.focusNode.layoutNode._children.add(childLayoutNode)
@@ -138,24 +140,28 @@
                 // If the initial state was focused, assert that after clearing the hierarchy,
                 // the root is set to Active.
                 Active, ActiveParent -> Active
-                Captured, Disabled, Inactive -> initialFocusState
+                Deactivated, DeactivatedParent -> Deactivated
+                Captured -> Captured
+                Inactive -> Inactive
             }
         )
     }
 
     @Test
     fun clearFocus_childIsCaptured() {
-        // Arrange.
-        focusModifier.focusState = ActiveParent
-        val childLayoutNode = LayoutNode()
-        val child = ModifiedFocusNode(InnerPlaceable(childLayoutNode), FocusModifier(Captured))
-        focusModifier.focusNode.layoutNode._children.add(childLayoutNode)
-        focusModifier.focusedChild = child
+        if (initialFocusState == ActiveParent || initialFocusState == DeactivatedParent) {
+            // Arrange.
+            focusModifier.focusState = initialFocusState as FocusStateImpl
+            val childLayoutNode = LayoutNode()
+            val child = ModifiedFocusNode(InnerPlaceable(childLayoutNode), FocusModifier(Captured))
+            focusModifier.focusNode.layoutNode._children.add(childLayoutNode)
+            focusModifier.focusedChild = child
 
-        // Act.
-        focusManager.clearFocus()
+            // Act.
+            focusManager.clearFocus()
 
-        // Assert.
-        assertThat(focusModifier.focusState).isEqualTo(ActiveParent)
+            // Assert.
+            assertThat(focusModifier.focusState).isEqualTo(initialFocusState)
+        }
     }
 }
diff --git a/core/core-appdigest/lint-baseline.xml b/core/core-appdigest/lint-baseline.xml
index d764360..0ca1950 100644
--- a/core/core-appdigest/lint-baseline.xml
+++ b/core/core-appdigest/lint-baseline.xml
@@ -23,4 +23,59 @@
             column="35"/>
     </issue>
 
+    <issue
+        id="ClassVerificationFailure"
+        message="This call references a method added in API level 31; however, the containing class null is reachable from earlier API levels and will fail run-time class verification."
+        errorLine1="                                checksums[i] = new Checksum(apkChecksum.getSplitName(),"
+        errorLine2="                                                                        ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/core/appdigest/ChecksumsApiSImpl.java"
+            line="121"
+            column="73"/>
+    </issue>
+
+    <issue
+        id="ClassVerificationFailure"
+        message="This call references a method added in API level 31; however, the containing class null is reachable from earlier API levels and will fail run-time class verification."
+        errorLine1="                                        apkChecksum.getType(), apkChecksum.getValue(),"
+        errorLine2="                                                    ~~~~~~~">
+        <location
+            file="src/main/java/androidx/core/appdigest/ChecksumsApiSImpl.java"
+            line="122"
+            column="53"/>
+    </issue>
+
+    <issue
+        id="ClassVerificationFailure"
+        message="This call references a method added in API level 31; however, the containing class null is reachable from earlier API levels and will fail run-time class verification."
+        errorLine1="                                        apkChecksum.getType(), apkChecksum.getValue(),"
+        errorLine2="                                                                           ~~~~~~~~">
+        <location
+            file="src/main/java/androidx/core/appdigest/ChecksumsApiSImpl.java"
+            line="122"
+            column="76"/>
+    </issue>
+
+    <issue
+        id="ClassVerificationFailure"
+        message="This call references a method added in API level 31; however, the containing class null is reachable from earlier API levels and will fail run-time class verification."
+        errorLine1="                                        apkChecksum.getInstallerPackageName(),"
+        errorLine2="                                                    ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/core/appdigest/ChecksumsApiSImpl.java"
+            line="123"
+            column="53"/>
+    </issue>
+
+    <issue
+        id="ClassVerificationFailure"
+        message="This call references a method added in API level 31; however, the containing class null is reachable from earlier API levels and will fail run-time class verification."
+        errorLine1="                                        apkChecksum.getInstallerCertificate());"
+        errorLine2="                                                    ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/core/appdigest/ChecksumsApiSImpl.java"
+            line="124"
+            column="53"/>
+    </issue>
+
 </issues>
diff --git a/core/core-ktx/lint-baseline.xml b/core/core-ktx/lint-baseline.xml
index 5467d6f..2806442 100644
--- a/core/core-ktx/lint-baseline.xml
+++ b/core/core-ktx/lint-baseline.xml
@@ -47,6 +47,17 @@
 
     <issue
         id="NewApi"
+        message="Call requires API level S (current min is 14): `android.util.SparseArray#set`"
+        errorLine1="        array[1] = &quot;one&quot;"
+        errorLine2="             ~">
+        <location
+            file="src/androidTest/java/androidx/core/util/SparseArrayTest.kt"
+            line="61"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="NewApi"
         message="Call requires API level 17 (current min is 14): `updatePaddingRelative`"
         errorLine1="        view.updatePaddingRelative(start = 10, end = 20)"
         errorLine2="             ~~~~~~~~~~~~~~~~~~~~~">
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index 38b153e..0043002 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -319,6 +319,7 @@
     field @Deprecated public static final String EXTRA_PEOPLE = "android.people";
     field public static final String EXTRA_PEOPLE_LIST = "android.people.list";
     field public static final String EXTRA_PICTURE = "android.picture";
+    field public static final String EXTRA_PICTURE_CONTENT_DESCRIPTION = "android.pictureContentDescription";
     field public static final String EXTRA_PROGRESS = "android.progress";
     field public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
     field public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
@@ -442,6 +443,7 @@
     method public androidx.core.app.NotificationCompat.BigPictureStyle bigLargeIcon(android.graphics.Bitmap?);
     method public androidx.core.app.NotificationCompat.BigPictureStyle bigPicture(android.graphics.Bitmap?);
     method public androidx.core.app.NotificationCompat.BigPictureStyle setBigContentTitle(CharSequence?);
+    method @RequiresApi(31) public androidx.core.app.NotificationCompat.BigPictureStyle setContentDescription(CharSequence?);
     method public androidx.core.app.NotificationCompat.BigPictureStyle setSummaryText(CharSequence?);
     method @RequiresApi(31) public androidx.core.app.NotificationCompat.BigPictureStyle showBigPictureWhenCollapsed(boolean);
   }
diff --git a/core/core/api/public_plus_experimental_current.txt b/core/core/api/public_plus_experimental_current.txt
index 4afa0aa..4f2f9a2 100644
--- a/core/core/api/public_plus_experimental_current.txt
+++ b/core/core/api/public_plus_experimental_current.txt
@@ -319,6 +319,7 @@
     field @Deprecated public static final String EXTRA_PEOPLE = "android.people";
     field public static final String EXTRA_PEOPLE_LIST = "android.people.list";
     field public static final String EXTRA_PICTURE = "android.picture";
+    field public static final String EXTRA_PICTURE_CONTENT_DESCRIPTION = "android.pictureContentDescription";
     field public static final String EXTRA_PROGRESS = "android.progress";
     field public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
     field public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
@@ -442,6 +443,7 @@
     method public androidx.core.app.NotificationCompat.BigPictureStyle bigLargeIcon(android.graphics.Bitmap?);
     method public androidx.core.app.NotificationCompat.BigPictureStyle bigPicture(android.graphics.Bitmap?);
     method public androidx.core.app.NotificationCompat.BigPictureStyle setBigContentTitle(CharSequence?);
+    method @RequiresApi(31) public androidx.core.app.NotificationCompat.BigPictureStyle setContentDescription(CharSequence?);
     method public androidx.core.app.NotificationCompat.BigPictureStyle setSummaryText(CharSequence?);
     method @RequiresApi(31) public androidx.core.app.NotificationCompat.BigPictureStyle showBigPictureWhenCollapsed(boolean);
   }
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index c28a0bf..82e22b2 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -364,6 +364,7 @@
     field @Deprecated public static final String EXTRA_PEOPLE = "android.people";
     field public static final String EXTRA_PEOPLE_LIST = "android.people.list";
     field public static final String EXTRA_PICTURE = "android.picture";
+    field public static final String EXTRA_PICTURE_CONTENT_DESCRIPTION = "android.pictureContentDescription";
     field public static final String EXTRA_PROGRESS = "android.progress";
     field public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
     field public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
@@ -491,6 +492,7 @@
     method public androidx.core.app.NotificationCompat.BigPictureStyle bigLargeIcon(android.graphics.Bitmap?);
     method public androidx.core.app.NotificationCompat.BigPictureStyle bigPicture(android.graphics.Bitmap?);
     method public androidx.core.app.NotificationCompat.BigPictureStyle setBigContentTitle(CharSequence?);
+    method @RequiresApi(31) public androidx.core.app.NotificationCompat.BigPictureStyle setContentDescription(CharSequence?);
     method public androidx.core.app.NotificationCompat.BigPictureStyle setSummaryText(CharSequence?);
     method @RequiresApi(31) public androidx.core.app.NotificationCompat.BigPictureStyle showBigPictureWhenCollapsed(boolean);
   }
diff --git a/core/core/src/androidTest/java/androidx/core/app/NotificationCompatTest.java b/core/core/src/androidTest/java/androidx/core/app/NotificationCompatTest.java
index 5967993..9a47e38 100644
--- a/core/core/src/androidTest/java/androidx/core/app/NotificationCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/app/NotificationCompatTest.java
@@ -1380,6 +1380,28 @@
 
     @SdkSuppress(minSdkVersion = 31)
     @Test
+    public void testBigPictureStyle_encodesAndRecoversSetContentDescription() {
+        String contentDesc = "content!";
+        Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(),
+                R.drawable.notification_bg_low_pressed);
+        Notification n = new NotificationCompat.Builder(mContext, "channelId")
+                .setSmallIcon(1)
+                .setStyle(new NotificationCompat.BigPictureStyle()
+                        .bigPicture(bitmap)
+                        .bigLargeIcon(bitmap)
+                        .setContentDescription(contentDesc)
+                        .setBigContentTitle("Big Content Title")
+                        .setSummaryText("Summary Text"))
+                .build();
+        assertEquals(contentDesc,
+                n.extras.getCharSequence(Notification.EXTRA_PICTURE_CONTENT_DESCRIPTION));
+        Notification recovered = Notification.Builder.recoverBuilder(mContext, n).build();
+        assertEquals(contentDesc,
+                recovered.extras.getCharSequence(Notification.EXTRA_PICTURE_CONTENT_DESCRIPTION));
+    }
+
+    @SdkSuppress(minSdkVersion = 31)
+    @Test
     public void testBigPictureStyle_encodesAndRecoversShowBigPictureWhenCollapsed() {
         Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(),
                 R.drawable.notification_bg_low_pressed);
diff --git a/core/core/src/androidTest/java/androidx/core/widget/EdgeEffectCompatTest.java b/core/core/src/androidTest/java/androidx/core/widget/EdgeEffectCompatTest.java
index c4a7136..6ca1bf9 100644
--- a/core/core/src/androidTest/java/androidx/core/widget/EdgeEffectCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/widget/EdgeEffectCompatTest.java
@@ -20,7 +20,6 @@
 
 import android.app.Activity;
 import android.content.Context;
-import android.os.Build;
 import android.support.v4.BaseInstrumentationTestCase;
 import android.support.v4.BaseTestActivity;
 import android.util.AttributeSet;
@@ -30,9 +29,9 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
+import androidx.core.os.BuildCompat;
 import androidx.core.test.R;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
@@ -57,38 +56,21 @@
         mEdgeEffect = mView.mEdgeEffect;
     }
 
-    // TODO(b/181171227): Change to R
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.Q)
     @Test
-    public void distanceApi30() {
-        assertEquals(0, EdgeEffectCompat.getDistance(mEdgeEffect), 0f);
-        assertEquals(1f, EdgeEffectCompat.onPullDistance(mEdgeEffect, 1, 0.5f), 0f);
-        assertEquals(0, EdgeEffectCompat.getDistance(mEdgeEffect), 0f);
-    }
-
-    // TODO(b/181171227): Change to S
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
-    @Test
-    public void distanceApi31() {
-        // TODO(b/181171227): Remove this condition
-        if (isSOrHigher()) {
+    public void distanceApi() {
+        if (BuildCompat.isAtLeastS()) {
             assertEquals(0, EdgeEffectCompat.getDistance(mEdgeEffect), 0f);
             assertEquals(1f, EdgeEffectCompat.onPullDistance(mEdgeEffect, 1, 0.5f), 0f);
             assertEquals(1, EdgeEffectCompat.getDistance(mEdgeEffect), 0f);
             assertEquals(-1f, EdgeEffectCompat.onPullDistance(mEdgeEffect, -1.5f, 0.5f), 0f);
             assertEquals(0, EdgeEffectCompat.getDistance(mEdgeEffect), 0f);
         } else {
-            distanceApi30();
+            assertEquals(0, EdgeEffectCompat.getDistance(mEdgeEffect), 0f);
+            assertEquals(1f, EdgeEffectCompat.onPullDistance(mEdgeEffect, 1, 0.5f), 0f);
+            assertEquals(0, EdgeEffectCompat.getDistance(mEdgeEffect), 0f);
         }
     }
 
-    // TODO(b/181171227): Remove this.
-    private static boolean isSOrHigher() {
-        int sdk = Build.VERSION.SDK_INT;
-        return sdk > Build.VERSION_CODES.R
-                || (sdk == Build.VERSION_CODES.R && Build.VERSION.PREVIEW_SDK_INT != 0);
-    }
-
     public static class EdgeEffectCompatTestActivity extends BaseTestActivity {
         @Override
         protected int getContentViewLayoutResId() {
@@ -123,7 +105,7 @@
         }
 
         private void initEdgeEffect(@NonNull Context context, @Nullable AttributeSet attrs) {
-            mEdgeEffect = EdgeEffectCompat.create(context, attrs);
+            mEdgeEffect = new EdgeEffectSubstitute(context);
         }
     }
 }
diff --git a/core/core/src/androidTest/java/androidx/core/widget/EdgeEffectSubstitute.kt b/core/core/src/androidTest/java/androidx/core/widget/EdgeEffectSubstitute.kt
new file mode 100644
index 0000000..5d780ff6
--- /dev/null
+++ b/core/core/src/androidTest/java/androidx/core/widget/EdgeEffectSubstitute.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.widget
+
+import android.content.Context
+import android.graphics.BlendMode
+import android.graphics.Canvas
+import android.widget.EdgeEffect
+import kotlin.math.abs
+
+/**
+ * This class can be used as a simple substitute for EdgeEffect. When animations are disabled,
+ * stretch EdgeEffect doesn't work as expected. This is an implementation of
+ * EdgeEffect that supports stretch to the minimum necessary to make tests work.
+ */
+class EdgeEffectSubstitute(context: Context) : EdgeEffect(context) {
+    var width = 0
+    var height = 0
+
+    var currentDistance = 0f
+    var state = State.Idle
+
+    var absorbed = 0
+
+    override fun setSize(width: Int, height: Int) {
+        this.width = width
+        this.height = height
+    }
+
+    override fun isFinished(): Boolean = state == State.Idle
+
+    override fun finish() {
+        currentDistance = 0f
+        state = State.Idle
+    }
+
+    override fun onPull(deltaDistance: Float) {
+        onPull(deltaDistance, 0.5f)
+    }
+
+    override fun onPull(deltaDistance: Float, displacement: Float) {
+        currentDistance = (currentDistance + deltaDistance).coerceIn(0f, 1f)
+        state = if (distance == 0f) {
+            State.Idle
+        } else {
+            State.Pull
+        }
+    }
+
+    override fun onPullDistance(deltaDistance: Float, displacement: Float): Float {
+        val distance = currentDistance
+        onPull(deltaDistance, displacement)
+        return currentDistance - distance
+    }
+
+    override fun getDistance(): Float {
+        return currentDistance
+    }
+
+    override fun onRelease() {
+        state = if (distance == 0f) {
+            State.Idle
+        } else {
+            State.Animating
+        }
+    }
+
+    override fun onAbsorb(velocity: Int) {
+        absorbed = velocity
+        state = State.Animating
+    }
+
+    override fun setColor(color: Int) {
+    }
+
+    override fun setBlendMode(blendmode: BlendMode?) {
+    }
+
+    override fun getColor(): Int = 0
+
+    override fun getBlendMode(): BlendMode? {
+        return null
+    }
+
+    override fun draw(canvas: Canvas): Boolean {
+        if (state == State.Idle) {
+            return false
+        }
+        if (absorbed != 0) {
+            currentDistance = (distance + (absorbed / height)).coerceIn(0f, 1f)
+            absorbed /= 4
+        }
+        if (currentDistance != 0f && state != State.Pull) {
+            currentDistance *= 0.75f
+            if (abs(currentDistance) < 0.01f) {
+                currentDistance = 0f
+                state = State.Idle
+            }
+        } else {
+            state = State.Idle
+        }
+        return state != State.Idle
+    }
+
+    override fun getMaxHeight(): Int = height
+
+    enum class State {
+        Idle,
+        Pull,
+        Animating
+    }
+}
\ No newline at end of file
diff --git a/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewTest.java b/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewTest.java
index 90d984a..00466d6 100644
--- a/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewTest.java
+++ b/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewTest.java
@@ -29,7 +29,6 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
-import android.widget.EdgeEffect;
 
 import androidx.core.os.BuildCompat;
 import androidx.test.core.app.ApplicationProvider;
@@ -312,6 +311,7 @@
     @Test
     public void testTopEdgeEffectReversal() {
         setup(200);
+        mNestedScrollView.mEdgeGlowTop = new EdgeEffectSubstitute(mNestedScrollView.getContext());
         setChildMargins(0, 0);
         measureAndLayout(100);
         swipeDown(false);
@@ -329,6 +329,8 @@
     @Test
     public void testBottomEdgeEffectReversal() {
         setup(200);
+        mNestedScrollView.mEdgeGlowBottom =
+                new EdgeEffectSubstitute(mNestedScrollView.getContext());
         setChildMargins(0, 0);
         measureAndLayout(100);
         int scrollRange = mNestedScrollView.getScrollRange();
@@ -351,16 +353,15 @@
         setup(200);
         setChildMargins(0, 0);
         measureAndLayout(100);
-        CaptureOnAbsorbEdgeEffect edgeEffect =
-                new CaptureOnAbsorbEdgeEffect(mNestedScrollView.getContext());
+        EdgeEffectSubstitute edgeEffect = new EdgeEffectSubstitute(mNestedScrollView.getContext());
         mNestedScrollView.mEdgeGlowTop = edgeEffect;
         flingDown();
-        assertTrue(edgeEffect.pullDistance > 0);
+        assertTrue(edgeEffect.getDistance() > 0);
 
         if (BuildCompat.isAtLeastS()) {
-            assertTrue(edgeEffect.absorbVelocity > 0);
+            assertTrue(edgeEffect.getAbsorbed() > 0);
         } else {
-            assertEquals(0, edgeEffect.absorbVelocity);
+            assertEquals(0, edgeEffect.getAbsorbed());
             flingUp();
             assertNotEquals(0, mNestedScrollView.getScrollY());
         }
@@ -371,21 +372,20 @@
         setup(200);
         setChildMargins(0, 0);
         measureAndLayout(100);
-        CaptureOnAbsorbEdgeEffect edgeEffect =
-                new CaptureOnAbsorbEdgeEffect(mNestedScrollView.getContext());
+        EdgeEffectSubstitute edgeEffect = new EdgeEffectSubstitute(mNestedScrollView.getContext());
         mNestedScrollView.mEdgeGlowBottom = edgeEffect;
 
         int scrollRange = mNestedScrollView.getScrollRange();
         mNestedScrollView.scrollTo(0, scrollRange);
         assertEquals(scrollRange, mNestedScrollView.getScrollY());
         flingUp();
-        assertTrue(edgeEffect.pullDistance > 0);
+        assertTrue(edgeEffect.getDistance() > 0);
         assertEquals(scrollRange, mNestedScrollView.getScrollY());
 
         if (BuildCompat.isAtLeastS()) {
-            assertTrue(edgeEffect.absorbVelocity > 0);
+            assertTrue(edgeEffect.getAbsorbed() > 0);
         } else {
-            assertEquals(0, edgeEffect.absorbVelocity);
+            assertEquals(0, edgeEffect.getAbsorbed());
             flingDown();
             assertNotEquals(scrollRange, mNestedScrollView.getScrollY());
         }
@@ -466,36 +466,4 @@
         measure(height);
         mNestedScrollView.layout(0, 0, 100, height);
     }
-
-    private static class CaptureOnAbsorbEdgeEffect extends EdgeEffect {
-        public int absorbVelocity;
-        public float pullDistance;
-
-        CaptureOnAbsorbEdgeEffect(Context context) {
-            super(context);
-        }
-
-        @Override
-        public void onPull(float deltaDistance) {
-            pullDistance += deltaDistance;
-            super.onPull(deltaDistance);
-        }
-
-        @Override
-        public void onPull(float deltaDistance, float displacement) {
-            pullDistance += deltaDistance;
-            super.onPull(deltaDistance, displacement);
-        }
-
-        @Override
-        public void onAbsorb(int velocity) {
-            absorbVelocity = velocity;
-            super.onAbsorb(velocity);
-        }
-
-        @Override
-        public void onRelease() {
-            super.onRelease();
-        }
-    }
 }
diff --git a/core/core/src/main/java/androidx/core/app/NotificationCompat.java b/core/core/src/main/java/androidx/core/app/NotificationCompat.java
index aca91d4..ee63746 100644
--- a/core/core/src/main/java/androidx/core/app/NotificationCompat.java
+++ b/core/core/src/main/java/androidx/core/app/NotificationCompat.java
@@ -436,6 +436,15 @@
     public static final String EXTRA_PICTURE = "android.picture";
 
     /**
+     * {@link #extras} key: this is a content description of the big picture supplied from
+     * {@link BigPictureStyle#bigPicture(Bitmap)}, supplied to
+     * {@link BigPictureStyle#setContentDescription(CharSequence)}.
+     */
+    @SuppressLint("ActionValue")  // Field & value copied from android.app.Notification
+    public static final String EXTRA_PICTURE_CONTENT_DESCRIPTION =
+            "android.pictureContentDescription";
+
+    /**
      * {@link #getExtras extras} key: this is a boolean to indicate that the
      * {@link BigPictureStyle#bigPicture(Bitmap) big picture} is to be shown in the collapsed state
      * of a {@link BigPictureStyle} notification.  This will replace a
@@ -3032,6 +3041,7 @@
         private Bitmap mPicture;
         private IconCompat mBigLargeIcon;
         private boolean mBigLargeIconSet;
+        private CharSequence mPictureContentDescription;
         private boolean mShowBigPictureWhenCollapsed;
 
         public BigPictureStyle() {
@@ -3060,6 +3070,17 @@
         }
 
         /**
+         * Set the content description of the big picture.
+         */
+        @RequiresApi(31)
+        @NonNull
+        public BigPictureStyle setContentDescription(
+                @Nullable CharSequence contentDescription) {
+            mPictureContentDescription = contentDescription;
+            return this;
+        }
+
+        /**
          * Provide the bitmap to be used as the payload for the BigPicture notification.
          */
         public @NonNull BigPictureStyle bigPicture(@Nullable Bitmap b) {
@@ -3132,6 +3153,7 @@
                 }
                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                     Api31Impl.showBigPictureWhenCollapsed(style, mShowBigPictureWhenCollapsed);
+                    Api31Impl.setContentDescription(style, mPictureContentDescription);
                 }
             }
         }
@@ -3243,6 +3265,15 @@
                     boolean show) {
                 style.showBigPictureWhenCollapsed(show);
             }
+
+            /**
+             * Calls {@link Notification.BigPictureStyle#setContentDescription(CharSequence)}
+             */
+            @RequiresApi(31)
+            static void setContentDescription(Notification.BigPictureStyle style,
+                    CharSequence contentDescription) {
+                style.setContentDescription(contentDescription);
+            }
         }
     }
 
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index 88b74ae..66b6df8 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -95,8 +95,8 @@
     docs("androidx.core:core-role:1.1.0-alpha02")
     docs("androidx.core:core-animation:1.0.0-alpha02")
     docs("androidx.core:core-animation-testing:1.0.0-alpha02")
-    docs("androidx.core:core:1.7.0-rc01")
-    docs("androidx.core:core-ktx:1.7.0-rc01")
+    docs("androidx.core:core:1.7.0")
+    docs("androidx.core:core-ktx:1.7.0")
     docs("androidx.core:core-splashscreen:1.0.0-alpha02")
     docs("androidx.cursoradapter:cursoradapter:1.0.0")
     docs("androidx.customview:customview:1.1.0")
diff --git a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
index 0c15f69..950156e 100644
--- a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
+++ b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
@@ -466,7 +466,11 @@
     @LargeTest
     public void testHeifFile() throws Throwable {
         if (Build.VERSION.SDK_INT >= 28) {
-            readFromFilesWithExif(HEIF_WITH_EXIF, R.array.heif_with_exif);
+            // Reading XMP data from HEIF was added in SDK 31.
+            readFromFilesWithExif(HEIF_WITH_EXIF,
+                    Build.VERSION.SDK_INT >= 31
+                            ? R.array.heif_with_exif_31_and_above
+                            : R.array.heif_with_exif);
         } else {
             // Make sure that an exception is not thrown and that image length/width tag values
             // return default values, not the actual values.
diff --git a/exifinterface/exifinterface/src/androidTest/res/values/arrays.xml b/exifinterface/exifinterface/src/androidTest/res/values/arrays.xml
index bc0947f..75c221c 100644
--- a/exifinterface/exifinterface/src/androidTest/res/values/arrays.xml
+++ b/exifinterface/exifinterface/src/androidTest/res/values/arrays.xml
@@ -411,4 +411,48 @@
         <item>0</item>
         <item>0</item>
     </array>
+    <array name="heif_with_exif_31_and_above">
+        <!--Whether thumbnail exists-->
+        <item>false</item>
+        <item>0</item>
+        <item>0</item>
+        <item>0</item>
+        <item>0</item>
+        <item>false</item>
+        <!--Whether GPS LatLong information exists-->
+        <item>false</item>
+        <item>0</item>
+        <item>0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>3519</item>
+        <item>4</item>
+        <item>LGE</item>
+        <item>Nexus 5</item>
+        <item>0.0</item>
+        <item />
+        <item>0.0</item>
+        <item>0</item>
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item>1080</item>
+        <item>1920</item>
+        <item />
+        <item>1</item>
+        <item>0</item>
+        <item>true</item>
+        <item>3721</item>
+        <item>3020</item>
+    </array>
 </resources>
\ No newline at end of file
diff --git a/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java b/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
index 07827a8..a5badb5 100644
--- a/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
+++ b/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
@@ -86,6 +86,10 @@
  * Supported for reading: JPEG, PNG, WebP, HEIF, DNG, CR2, NEF, NRW, ARW, RW2, ORF, PEF, SRW, RAF.
  * <p>
  * Supported for writing: JPEG, PNG, WebP, DNG.
+ * <p>
+ * Note: JPEG and HEIF files may contain XMP data either inside the Exif data chunk or outside of
+ * it. This class will search both locations for XMP data, but if XMP data exist both inside and
+ * outside Exif, will favor the XMP data inside Exif over the one outside.
  */
 public class ExifInterface {
     private static final String TAG = "ExifInterface";
@@ -5965,6 +5969,24 @@
                     readExifSegment(bytes, IFD_TYPE_PRIMARY);
                 }
 
+                String xmpOffsetStr = retriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_XMP_OFFSET);
+                String xmpLengthStr = retriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_XMP_LENGTH);
+                if (xmpOffsetStr != null && xmpLengthStr != null) {
+                    int offset = Integer.parseInt(xmpOffsetStr);
+                    int length = Integer.parseInt(xmpLengthStr);
+                    in.seek(offset);
+                    byte[] xmpBytes = new byte[length];
+                    if (in.read(xmpBytes) != length) {
+                        throw new IOException("Failed to read XMP from HEIF");
+                    }
+                    if (getAttribute(TAG_XMP) == null) {
+                        mAttributes[IFD_TYPE_PRIMARY].put(TAG_XMP, new ExifAttribute(
+                                IFD_FORMAT_BYTE, xmpBytes.length, offset, xmpBytes));
+                    }
+                }
+
                 if (DEBUG) {
                     Log.d(TAG, "Heif meta: " + width + "x" + height + ", rotation " + rotation);
                 }
diff --git a/fragment/fragment-ktx/api/1.4.0-beta01.txt b/fragment/fragment-ktx/api/1.4.0-beta01.txt
new file mode 100644
index 0000000..64ef804
--- /dev/null
+++ b/fragment/fragment-ktx/api/1.4.0-beta01.txt
@@ -0,0 +1,34 @@
+// Signature format: 4.0
+package androidx.fragment.app {
+
+  public final class FragmentKt {
+    method public static void clearFragmentResult(androidx.fragment.app.Fragment, String requestKey);
+    method public static void clearFragmentResultListener(androidx.fragment.app.Fragment, String requestKey);
+    method public static void setFragmentResult(androidx.fragment.app.Fragment, String requestKey, android.os.Bundle result);
+    method public static void setFragmentResultListener(androidx.fragment.app.Fragment, String requestKey, kotlin.jvm.functions.Function2<? super java.lang.String,? super android.os.Bundle,kotlin.Unit> listener);
+  }
+
+  public final class FragmentManagerKt {
+    method public static inline void commit(androidx.fragment.app.FragmentManager, optional boolean allowStateLoss, kotlin.jvm.functions.Function1<? super androidx.fragment.app.FragmentTransaction,kotlin.Unit> body);
+    method public static inline void commitNow(androidx.fragment.app.FragmentManager, optional boolean allowStateLoss, kotlin.jvm.functions.Function1<? super androidx.fragment.app.FragmentTransaction,kotlin.Unit> body);
+    method @Deprecated public static inline void transaction(androidx.fragment.app.FragmentManager, optional boolean now, optional boolean allowStateLoss, kotlin.jvm.functions.Function1<? super androidx.fragment.app.FragmentTransaction,kotlin.Unit> body);
+  }
+
+  public final class FragmentTransactionKt {
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction! add(androidx.fragment.app.FragmentTransaction, @IdRes int containerViewId, optional String tag, optional android.os.Bundle? args);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction! add(androidx.fragment.app.FragmentTransaction, String tag, optional android.os.Bundle? args);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction! replace(androidx.fragment.app.FragmentTransaction, @IdRes int containerViewId, optional String tag, optional android.os.Bundle? args);
+  }
+
+  public final class FragmentViewModelLazyKt {
+    method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<? extends VM>! activityViewModels(androidx.fragment.app.Fragment, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+    method @MainThread public static <VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> createViewModelLazy(androidx.fragment.app.Fragment, kotlin.reflect.KClass<VM> viewModelClass, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStore> storeProducer, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+    method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<? extends VM>! viewModels(androidx.fragment.app.Fragment, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStoreOwner> ownerProducer, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+  }
+
+  public final class ViewKt {
+    method public static <F extends androidx.fragment.app.Fragment> F findFragment(android.view.View);
+  }
+
+}
+
diff --git a/fragment/fragment-ktx/api/public_plus_experimental_1.4.0-beta01.txt b/fragment/fragment-ktx/api/public_plus_experimental_1.4.0-beta01.txt
new file mode 100644
index 0000000..64ef804
--- /dev/null
+++ b/fragment/fragment-ktx/api/public_plus_experimental_1.4.0-beta01.txt
@@ -0,0 +1,34 @@
+// Signature format: 4.0
+package androidx.fragment.app {
+
+  public final class FragmentKt {
+    method public static void clearFragmentResult(androidx.fragment.app.Fragment, String requestKey);
+    method public static void clearFragmentResultListener(androidx.fragment.app.Fragment, String requestKey);
+    method public static void setFragmentResult(androidx.fragment.app.Fragment, String requestKey, android.os.Bundle result);
+    method public static void setFragmentResultListener(androidx.fragment.app.Fragment, String requestKey, kotlin.jvm.functions.Function2<? super java.lang.String,? super android.os.Bundle,kotlin.Unit> listener);
+  }
+
+  public final class FragmentManagerKt {
+    method public static inline void commit(androidx.fragment.app.FragmentManager, optional boolean allowStateLoss, kotlin.jvm.functions.Function1<? super androidx.fragment.app.FragmentTransaction,kotlin.Unit> body);
+    method public static inline void commitNow(androidx.fragment.app.FragmentManager, optional boolean allowStateLoss, kotlin.jvm.functions.Function1<? super androidx.fragment.app.FragmentTransaction,kotlin.Unit> body);
+    method @Deprecated public static inline void transaction(androidx.fragment.app.FragmentManager, optional boolean now, optional boolean allowStateLoss, kotlin.jvm.functions.Function1<? super androidx.fragment.app.FragmentTransaction,kotlin.Unit> body);
+  }
+
+  public final class FragmentTransactionKt {
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction! add(androidx.fragment.app.FragmentTransaction, @IdRes int containerViewId, optional String tag, optional android.os.Bundle? args);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction! add(androidx.fragment.app.FragmentTransaction, String tag, optional android.os.Bundle? args);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction! replace(androidx.fragment.app.FragmentTransaction, @IdRes int containerViewId, optional String tag, optional android.os.Bundle? args);
+  }
+
+  public final class FragmentViewModelLazyKt {
+    method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<? extends VM>! activityViewModels(androidx.fragment.app.Fragment, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+    method @MainThread public static <VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> createViewModelLazy(androidx.fragment.app.Fragment, kotlin.reflect.KClass<VM> viewModelClass, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStore> storeProducer, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+    method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<? extends VM>! viewModels(androidx.fragment.app.Fragment, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStoreOwner> ownerProducer, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+  }
+
+  public final class ViewKt {
+    method public static <F extends androidx.fragment.app.Fragment> F findFragment(android.view.View);
+  }
+
+}
+
diff --git a/fragment/fragment-ktx/api/res-1.4.0-beta01.txt b/fragment/fragment-ktx/api/res-1.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/fragment/fragment-ktx/api/res-1.4.0-beta01.txt
diff --git a/fragment/fragment-ktx/api/restricted_1.4.0-beta01.txt b/fragment/fragment-ktx/api/restricted_1.4.0-beta01.txt
new file mode 100644
index 0000000..64ef804
--- /dev/null
+++ b/fragment/fragment-ktx/api/restricted_1.4.0-beta01.txt
@@ -0,0 +1,34 @@
+// Signature format: 4.0
+package androidx.fragment.app {
+
+  public final class FragmentKt {
+    method public static void clearFragmentResult(androidx.fragment.app.Fragment, String requestKey);
+    method public static void clearFragmentResultListener(androidx.fragment.app.Fragment, String requestKey);
+    method public static void setFragmentResult(androidx.fragment.app.Fragment, String requestKey, android.os.Bundle result);
+    method public static void setFragmentResultListener(androidx.fragment.app.Fragment, String requestKey, kotlin.jvm.functions.Function2<? super java.lang.String,? super android.os.Bundle,kotlin.Unit> listener);
+  }
+
+  public final class FragmentManagerKt {
+    method public static inline void commit(androidx.fragment.app.FragmentManager, optional boolean allowStateLoss, kotlin.jvm.functions.Function1<? super androidx.fragment.app.FragmentTransaction,kotlin.Unit> body);
+    method public static inline void commitNow(androidx.fragment.app.FragmentManager, optional boolean allowStateLoss, kotlin.jvm.functions.Function1<? super androidx.fragment.app.FragmentTransaction,kotlin.Unit> body);
+    method @Deprecated public static inline void transaction(androidx.fragment.app.FragmentManager, optional boolean now, optional boolean allowStateLoss, kotlin.jvm.functions.Function1<? super androidx.fragment.app.FragmentTransaction,kotlin.Unit> body);
+  }
+
+  public final class FragmentTransactionKt {
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction! add(androidx.fragment.app.FragmentTransaction, @IdRes int containerViewId, optional String tag, optional android.os.Bundle? args);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction! add(androidx.fragment.app.FragmentTransaction, String tag, optional android.os.Bundle? args);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction! replace(androidx.fragment.app.FragmentTransaction, @IdRes int containerViewId, optional String tag, optional android.os.Bundle? args);
+  }
+
+  public final class FragmentViewModelLazyKt {
+    method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<? extends VM>! activityViewModels(androidx.fragment.app.Fragment, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+    method @MainThread public static <VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> createViewModelLazy(androidx.fragment.app.Fragment, kotlin.reflect.KClass<VM> viewModelClass, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStore> storeProducer, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+    method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<? extends VM>! viewModels(androidx.fragment.app.Fragment, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStoreOwner> ownerProducer, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+  }
+
+  public final class ViewKt {
+    method public static <F extends androidx.fragment.app.Fragment> F findFragment(android.view.View);
+  }
+
+}
+
diff --git a/fragment/fragment-testing/api/1.4.0-beta01.txt b/fragment/fragment-testing/api/1.4.0-beta01.txt
new file mode 100644
index 0000000..271ab04
--- /dev/null
+++ b/fragment/fragment-testing/api/1.4.0-beta01.txt
@@ -0,0 +1,60 @@
+// Signature format: 4.0
+package androidx.fragment.app.testing {
+
+  public final class FragmentScenario<F extends androidx.fragment.app.Fragment> implements java.io.Closeable {
+    method public void close();
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, @StyleRes int themeResId, androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, @StyleRes int themeResId, androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass);
+    method public androidx.fragment.app.testing.FragmentScenario<F> moveToState(androidx.lifecycle.Lifecycle.State newState);
+    method public androidx.fragment.app.testing.FragmentScenario<F> onFragment(androidx.fragment.app.testing.FragmentScenario.FragmentAction<F> action);
+    method public androidx.fragment.app.testing.FragmentScenario<F> recreate();
+    field public static final androidx.fragment.app.testing.FragmentScenario.Companion Companion;
+  }
+
+  public static final class FragmentScenario.Companion {
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, @StyleRes int themeResId, androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, @StyleRes int themeResId, androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass);
+  }
+
+  public static fun interface FragmentScenario.FragmentAction<F extends androidx.fragment.app.Fragment> {
+    method public void perform(F fragment);
+  }
+
+  public final class FragmentScenarioKt {
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragment(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragment(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, kotlin.jvm.functions.Function0<? extends F> instantiate);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragment(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragment(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, kotlin.jvm.functions.Function0<? extends F> instantiate);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragmentInContainer(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragmentInContainer(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, kotlin.jvm.functions.Function0<? extends F> instantiate);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragmentInContainer(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragmentInContainer(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, kotlin.jvm.functions.Function0<? extends F> instantiate);
+    method public static inline <reified F extends androidx.fragment.app.Fragment, T> T! withFragment(androidx.fragment.app.testing.FragmentScenario<F>, kotlin.jvm.functions.Function1<? super F,? extends T> block);
+  }
+
+}
+
diff --git a/fragment/fragment-testing/api/public_plus_experimental_1.4.0-beta01.txt b/fragment/fragment-testing/api/public_plus_experimental_1.4.0-beta01.txt
new file mode 100644
index 0000000..271ab04
--- /dev/null
+++ b/fragment/fragment-testing/api/public_plus_experimental_1.4.0-beta01.txt
@@ -0,0 +1,60 @@
+// Signature format: 4.0
+package androidx.fragment.app.testing {
+
+  public final class FragmentScenario<F extends androidx.fragment.app.Fragment> implements java.io.Closeable {
+    method public void close();
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, @StyleRes int themeResId, androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, @StyleRes int themeResId, androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass);
+    method public androidx.fragment.app.testing.FragmentScenario<F> moveToState(androidx.lifecycle.Lifecycle.State newState);
+    method public androidx.fragment.app.testing.FragmentScenario<F> onFragment(androidx.fragment.app.testing.FragmentScenario.FragmentAction<F> action);
+    method public androidx.fragment.app.testing.FragmentScenario<F> recreate();
+    field public static final androidx.fragment.app.testing.FragmentScenario.Companion Companion;
+  }
+
+  public static final class FragmentScenario.Companion {
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, @StyleRes int themeResId, androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, @StyleRes int themeResId, androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass);
+  }
+
+  public static fun interface FragmentScenario.FragmentAction<F extends androidx.fragment.app.Fragment> {
+    method public void perform(F fragment);
+  }
+
+  public final class FragmentScenarioKt {
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragment(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragment(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, kotlin.jvm.functions.Function0<? extends F> instantiate);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragment(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragment(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, kotlin.jvm.functions.Function0<? extends F> instantiate);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragmentInContainer(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragmentInContainer(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, kotlin.jvm.functions.Function0<? extends F> instantiate);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragmentInContainer(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragmentInContainer(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, kotlin.jvm.functions.Function0<? extends F> instantiate);
+    method public static inline <reified F extends androidx.fragment.app.Fragment, T> T! withFragment(androidx.fragment.app.testing.FragmentScenario<F>, kotlin.jvm.functions.Function1<? super F,? extends T> block);
+  }
+
+}
+
diff --git a/fragment/fragment-testing/api/res-1.4.0-beta01.txt b/fragment/fragment-testing/api/res-1.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/fragment/fragment-testing/api/res-1.4.0-beta01.txt
diff --git a/fragment/fragment-testing/api/restricted_1.4.0-beta01.txt b/fragment/fragment-testing/api/restricted_1.4.0-beta01.txt
new file mode 100644
index 0000000..271ab04
--- /dev/null
+++ b/fragment/fragment-testing/api/restricted_1.4.0-beta01.txt
@@ -0,0 +1,60 @@
+// Signature format: 4.0
+package androidx.fragment.app.testing {
+
+  public final class FragmentScenario<F extends androidx.fragment.app.Fragment> implements java.io.Closeable {
+    method public void close();
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, @StyleRes int themeResId, androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, @StyleRes int themeResId, androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs);
+    method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass);
+    method public androidx.fragment.app.testing.FragmentScenario<F> moveToState(androidx.lifecycle.Lifecycle.State newState);
+    method public androidx.fragment.app.testing.FragmentScenario<F> onFragment(androidx.fragment.app.testing.FragmentScenario.FragmentAction<F> action);
+    method public androidx.fragment.app.testing.FragmentScenario<F> recreate();
+    field public static final androidx.fragment.app.testing.FragmentScenario.Companion Companion;
+  }
+
+  public static final class FragmentScenario.Companion {
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, @StyleRes int themeResId, androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F> fragmentClass);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, android.os.Bundle? fragmentArgs, @StyleRes int themeResId, androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass, optional android.os.Bundle? fragmentArgs);
+    method public <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F> fragmentClass);
+  }
+
+  public static fun interface FragmentScenario.FragmentAction<F extends androidx.fragment.app.Fragment> {
+    method public void perform(F fragment);
+  }
+
+  public final class FragmentScenarioKt {
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragment(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragment(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, kotlin.jvm.functions.Function0<? extends F> instantiate);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragment(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragment(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, kotlin.jvm.functions.Function0<? extends F> instantiate);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragmentInContainer(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragmentInContainer(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, kotlin.jvm.functions.Function0<? extends F> instantiate);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragmentInContainer(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, optional androidx.fragment.app.FragmentFactory? factory);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragmentInContainer(optional android.os.Bundle? fragmentArgs, optional @StyleRes int themeResId, optional androidx.lifecycle.Lifecycle.State initialState, kotlin.jvm.functions.Function0<? extends F> instantiate);
+    method public static inline <reified F extends androidx.fragment.app.Fragment, T> T! withFragment(androidx.fragment.app.testing.FragmentScenario<F>, kotlin.jvm.functions.Function1<? super F,? extends T> block);
+  }
+
+}
+
diff --git a/fragment/fragment/api/1.4.0-beta01.txt b/fragment/fragment/api/1.4.0-beta01.txt
new file mode 100644
index 0000000..d8d30ad
--- /dev/null
+++ b/fragment/fragment/api/1.4.0-beta01.txt
@@ -0,0 +1,543 @@
+// Signature format: 4.0
+package androidx.fragment.app {
+
+  public class DialogFragment extends androidx.fragment.app.Fragment implements android.content.DialogInterface.OnCancelListener android.content.DialogInterface.OnDismissListener {
+    ctor public DialogFragment();
+    ctor public DialogFragment(@LayoutRes int);
+    method public void dismiss();
+    method public void dismissAllowingStateLoss();
+    method public android.app.Dialog? getDialog();
+    method public boolean getShowsDialog();
+    method @StyleRes public int getTheme();
+    method public boolean isCancelable();
+    method public void onCancel(android.content.DialogInterface);
+    method @MainThread public android.app.Dialog onCreateDialog(android.os.Bundle?);
+    method public void onDismiss(android.content.DialogInterface);
+    method public final android.app.Dialog requireDialog();
+    method public void setCancelable(boolean);
+    method public void setShowsDialog(boolean);
+    method public void setStyle(int, @StyleRes int);
+    method public void show(androidx.fragment.app.FragmentManager, String?);
+    method public int show(androidx.fragment.app.FragmentTransaction, String?);
+    method public void showNow(androidx.fragment.app.FragmentManager, String?);
+    field public static final int STYLE_NORMAL = 0; // 0x0
+    field public static final int STYLE_NO_FRAME = 2; // 0x2
+    field public static final int STYLE_NO_INPUT = 3; // 0x3
+    field public static final int STYLE_NO_TITLE = 1; // 0x1
+  }
+
+  public class Fragment implements androidx.activity.result.ActivityResultCaller android.content.ComponentCallbacks androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner android.view.View.OnCreateContextMenuListener androidx.lifecycle.ViewModelStoreOwner {
+    ctor public Fragment();
+    ctor @ContentView public Fragment(@LayoutRes int);
+    method public void dump(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
+    method public final boolean equals(Object?);
+    method public final androidx.fragment.app.FragmentActivity? getActivity();
+    method public boolean getAllowEnterTransitionOverlap();
+    method public boolean getAllowReturnTransitionOverlap();
+    method public final android.os.Bundle? getArguments();
+    method public final androidx.fragment.app.FragmentManager getChildFragmentManager();
+    method public android.content.Context? getContext();
+    method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
+    method public Object? getEnterTransition();
+    method public Object? getExitTransition();
+    method @Deprecated public final androidx.fragment.app.FragmentManager? getFragmentManager();
+    method public final Object? getHost();
+    method public final int getId();
+    method public final android.view.LayoutInflater getLayoutInflater();
+    method public androidx.lifecycle.Lifecycle getLifecycle();
+    method @Deprecated public androidx.loader.app.LoaderManager getLoaderManager();
+    method public final androidx.fragment.app.Fragment? getParentFragment();
+    method public final androidx.fragment.app.FragmentManager getParentFragmentManager();
+    method public Object? getReenterTransition();
+    method public final android.content.res.Resources getResources();
+    method @Deprecated public final boolean getRetainInstance();
+    method public Object? getReturnTransition();
+    method public final androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+    method public Object? getSharedElementEnterTransition();
+    method public Object? getSharedElementReturnTransition();
+    method public final String getString(@StringRes int);
+    method public final String getString(@StringRes int, java.lang.Object!...);
+    method public final String? getTag();
+    method @Deprecated public final androidx.fragment.app.Fragment? getTargetFragment();
+    method @Deprecated public final int getTargetRequestCode();
+    method public final CharSequence getText(@StringRes int);
+    method @Deprecated public boolean getUserVisibleHint();
+    method public android.view.View? getView();
+    method @MainThread public androidx.lifecycle.LifecycleOwner getViewLifecycleOwner();
+    method public androidx.lifecycle.LiveData<androidx.lifecycle.LifecycleOwner!> getViewLifecycleOwnerLiveData();
+    method public androidx.lifecycle.ViewModelStore getViewModelStore();
+    method public final int hashCode();
+    method @Deprecated public static androidx.fragment.app.Fragment instantiate(android.content.Context, String);
+    method @Deprecated public static androidx.fragment.app.Fragment instantiate(android.content.Context, String, android.os.Bundle?);
+    method public final boolean isAdded();
+    method public final boolean isDetached();
+    method public final boolean isHidden();
+    method public final boolean isInLayout();
+    method public final boolean isRemoving();
+    method public final boolean isResumed();
+    method public final boolean isStateSaved();
+    method public final boolean isVisible();
+    method @Deprecated @CallSuper @MainThread public void onActivityCreated(android.os.Bundle?);
+    method @Deprecated public void onActivityResult(int, int, android.content.Intent?);
+    method @CallSuper @MainThread public void onAttach(android.content.Context);
+    method @Deprecated @CallSuper @MainThread public void onAttach(android.app.Activity);
+    method @Deprecated @MainThread public void onAttachFragment(androidx.fragment.app.Fragment);
+    method @CallSuper public void onConfigurationChanged(android.content.res.Configuration);
+    method @MainThread public boolean onContextItemSelected(android.view.MenuItem);
+    method @CallSuper @MainThread public void onCreate(android.os.Bundle?);
+    method @MainThread public android.view.animation.Animation? onCreateAnimation(int, boolean, int);
+    method @MainThread public android.animation.Animator? onCreateAnimator(int, boolean, int);
+    method @MainThread public void onCreateContextMenu(android.view.ContextMenu, android.view.View, android.view.ContextMenu.ContextMenuInfo?);
+    method @MainThread public void onCreateOptionsMenu(android.view.Menu, android.view.MenuInflater);
+    method @MainThread public android.view.View? onCreateView(android.view.LayoutInflater, android.view.ViewGroup?, android.os.Bundle?);
+    method @CallSuper @MainThread public void onDestroy();
+    method @MainThread public void onDestroyOptionsMenu();
+    method @CallSuper @MainThread public void onDestroyView();
+    method @CallSuper @MainThread public void onDetach();
+    method public android.view.LayoutInflater onGetLayoutInflater(android.os.Bundle?);
+    method @MainThread public void onHiddenChanged(boolean);
+    method @CallSuper @UiThread public void onInflate(android.content.Context, android.util.AttributeSet, android.os.Bundle?);
+    method @Deprecated @CallSuper @UiThread public void onInflate(android.app.Activity, android.util.AttributeSet, android.os.Bundle?);
+    method @CallSuper @MainThread public void onLowMemory();
+    method public void onMultiWindowModeChanged(boolean);
+    method @MainThread public boolean onOptionsItemSelected(android.view.MenuItem);
+    method @MainThread public void onOptionsMenuClosed(android.view.Menu);
+    method @CallSuper @MainThread public void onPause();
+    method public void onPictureInPictureModeChanged(boolean);
+    method @MainThread public void onPrepareOptionsMenu(android.view.Menu);
+    method @MainThread public void onPrimaryNavigationFragmentChanged(boolean);
+    method @Deprecated public void onRequestPermissionsResult(int, String![], int[]);
+    method @CallSuper @MainThread public void onResume();
+    method @MainThread public void onSaveInstanceState(android.os.Bundle);
+    method @CallSuper @MainThread public void onStart();
+    method @CallSuper @MainThread public void onStop();
+    method @MainThread public void onViewCreated(android.view.View, android.os.Bundle?);
+    method @CallSuper @MainThread public void onViewStateRestored(android.os.Bundle?);
+    method public void postponeEnterTransition();
+    method public final void postponeEnterTransition(long, java.util.concurrent.TimeUnit);
+    method @MainThread public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultCallback<O!>);
+    method @MainThread public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultRegistry, androidx.activity.result.ActivityResultCallback<O!>);
+    method public void registerForContextMenu(android.view.View);
+    method @Deprecated public final void requestPermissions(String![], int);
+    method public final androidx.fragment.app.FragmentActivity requireActivity();
+    method public final android.os.Bundle requireArguments();
+    method public final android.content.Context requireContext();
+    method @Deprecated public final androidx.fragment.app.FragmentManager requireFragmentManager();
+    method public final Object requireHost();
+    method public final androidx.fragment.app.Fragment requireParentFragment();
+    method public final android.view.View requireView();
+    method public void setAllowEnterTransitionOverlap(boolean);
+    method public void setAllowReturnTransitionOverlap(boolean);
+    method public void setArguments(android.os.Bundle?);
+    method public void setEnterSharedElementCallback(androidx.core.app.SharedElementCallback?);
+    method public void setEnterTransition(Object?);
+    method public void setExitSharedElementCallback(androidx.core.app.SharedElementCallback?);
+    method public void setExitTransition(Object?);
+    method public void setHasOptionsMenu(boolean);
+    method public void setInitialSavedState(androidx.fragment.app.Fragment.SavedState?);
+    method public void setMenuVisibility(boolean);
+    method public void setReenterTransition(Object?);
+    method @Deprecated public void setRetainInstance(boolean);
+    method public void setReturnTransition(Object?);
+    method public void setSharedElementEnterTransition(Object?);
+    method public void setSharedElementReturnTransition(Object?);
+    method @Deprecated public void setTargetFragment(androidx.fragment.app.Fragment?, int);
+    method @Deprecated public void setUserVisibleHint(boolean);
+    method public boolean shouldShowRequestPermissionRationale(String);
+    method public void startActivity(android.content.Intent!);
+    method public void startActivity(android.content.Intent!, android.os.Bundle?);
+    method @Deprecated public void startActivityForResult(android.content.Intent!, int);
+    method @Deprecated public void startActivityForResult(android.content.Intent!, int, android.os.Bundle?);
+    method @Deprecated public void startIntentSenderForResult(android.content.IntentSender!, int, android.content.Intent?, int, int, int, android.os.Bundle?) throws android.content.IntentSender.SendIntentException;
+    method public void startPostponedEnterTransition();
+    method public void unregisterForContextMenu(android.view.View);
+  }
+
+  public static class Fragment.InstantiationException extends java.lang.RuntimeException {
+    ctor public Fragment.InstantiationException(String, Exception?);
+  }
+
+  public static class Fragment.SavedState implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<androidx.fragment.app.Fragment.SavedState!> CREATOR;
+  }
+
+  public class FragmentActivity extends androidx.activity.ComponentActivity implements androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback androidx.lifecycle.LifecycleOwner {
+    ctor public FragmentActivity();
+    ctor @ContentView public FragmentActivity(@LayoutRes int);
+    method public androidx.fragment.app.FragmentManager getSupportFragmentManager();
+    method @Deprecated public androidx.loader.app.LoaderManager getSupportLoaderManager();
+    method @Deprecated @MainThread public void onAttachFragment(androidx.fragment.app.Fragment);
+    method @CallSuper public void onMultiWindowModeChanged(boolean);
+    method @CallSuper public void onPictureInPictureModeChanged(boolean);
+    method protected void onResumeFragments();
+    method public void onStateNotSaved();
+    method public void setEnterSharedElementCallback(androidx.core.app.SharedElementCallback?);
+    method public void setExitSharedElementCallback(androidx.core.app.SharedElementCallback?);
+    method public void startActivityFromFragment(androidx.fragment.app.Fragment, android.content.Intent!, int);
+    method public void startActivityFromFragment(androidx.fragment.app.Fragment, android.content.Intent!, int, android.os.Bundle?);
+    method @Deprecated public void startIntentSenderFromFragment(androidx.fragment.app.Fragment, android.content.IntentSender!, int, android.content.Intent?, int, int, int, android.os.Bundle?) throws android.content.IntentSender.SendIntentException;
+    method public void supportFinishAfterTransition();
+    method @Deprecated public void supportInvalidateOptionsMenu();
+    method public void supportPostponeEnterTransition();
+    method public void supportStartPostponedEnterTransition();
+    method @Deprecated public final void validateRequestPermissionsRequestCode(int);
+  }
+
+  public abstract class FragmentContainer {
+    ctor public FragmentContainer();
+    method @Deprecated public androidx.fragment.app.Fragment instantiate(android.content.Context, String, android.os.Bundle?);
+    method public abstract android.view.View? onFindViewById(@IdRes int);
+    method public abstract boolean onHasView();
+  }
+
+  public final class FragmentContainerView extends android.widget.FrameLayout {
+    ctor public FragmentContainerView(android.content.Context context);
+    ctor public FragmentContainerView(android.content.Context context, android.util.AttributeSet? attrs, optional int defStyleAttr);
+    ctor public FragmentContainerView(android.content.Context context, android.util.AttributeSet? attrs);
+    method public <F extends androidx.fragment.app.Fragment> F! getFragment();
+  }
+
+  public class FragmentController {
+    method public void attachHost(androidx.fragment.app.Fragment?);
+    method public static androidx.fragment.app.FragmentController createController(androidx.fragment.app.FragmentHostCallback<?>);
+    method public void dispatchActivityCreated();
+    method public void dispatchConfigurationChanged(android.content.res.Configuration);
+    method public boolean dispatchContextItemSelected(android.view.MenuItem);
+    method public void dispatchCreate();
+    method public boolean dispatchCreateOptionsMenu(android.view.Menu, android.view.MenuInflater);
+    method public void dispatchDestroy();
+    method public void dispatchDestroyView();
+    method public void dispatchLowMemory();
+    method public void dispatchMultiWindowModeChanged(boolean);
+    method public boolean dispatchOptionsItemSelected(android.view.MenuItem);
+    method public void dispatchOptionsMenuClosed(android.view.Menu);
+    method public void dispatchPause();
+    method public void dispatchPictureInPictureModeChanged(boolean);
+    method public boolean dispatchPrepareOptionsMenu(android.view.Menu);
+    method @Deprecated public void dispatchReallyStop();
+    method public void dispatchResume();
+    method public void dispatchStart();
+    method public void dispatchStop();
+    method @Deprecated public void doLoaderDestroy();
+    method @Deprecated public void doLoaderRetain();
+    method @Deprecated public void doLoaderStart();
+    method @Deprecated public void doLoaderStop(boolean);
+    method @Deprecated public void dumpLoaders(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
+    method public boolean execPendingActions();
+    method public androidx.fragment.app.Fragment? findFragmentByWho(String);
+    method public java.util.List<androidx.fragment.app.Fragment!> getActiveFragments(java.util.List<androidx.fragment.app.Fragment!>!);
+    method public int getActiveFragmentsCount();
+    method public androidx.fragment.app.FragmentManager getSupportFragmentManager();
+    method @Deprecated public androidx.loader.app.LoaderManager! getSupportLoaderManager();
+    method public void noteStateNotSaved();
+    method public android.view.View? onCreateView(android.view.View?, String, android.content.Context, android.util.AttributeSet);
+    method @Deprecated public void reportLoaderStart();
+    method @Deprecated public void restoreAllState(android.os.Parcelable?, java.util.List<androidx.fragment.app.Fragment!>?);
+    method @Deprecated public void restoreAllState(android.os.Parcelable?, androidx.fragment.app.FragmentManagerNonConfig?);
+    method @Deprecated public void restoreLoaderNonConfig(androidx.collection.SimpleArrayMap<java.lang.String!,androidx.loader.app.LoaderManager!>!);
+    method @Deprecated public void restoreSaveState(android.os.Parcelable?);
+    method @Deprecated public androidx.collection.SimpleArrayMap<java.lang.String!,androidx.loader.app.LoaderManager!>? retainLoaderNonConfig();
+    method @Deprecated public androidx.fragment.app.FragmentManagerNonConfig? retainNestedNonConfig();
+    method @Deprecated public java.util.List<androidx.fragment.app.Fragment!>? retainNonConfig();
+    method @Deprecated public android.os.Parcelable? saveAllState();
+  }
+
+  public class FragmentFactory {
+    ctor public FragmentFactory();
+    method public androidx.fragment.app.Fragment instantiate(ClassLoader, String);
+    method public static Class<? extends androidx.fragment.app.Fragment> loadFragmentClass(ClassLoader, String);
+  }
+
+  public abstract class FragmentHostCallback<E> extends androidx.fragment.app.FragmentContainer {
+    ctor public FragmentHostCallback(android.content.Context, android.os.Handler, int);
+    method public void onDump(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
+    method public android.view.View? onFindViewById(int);
+    method public abstract E? onGetHost();
+    method public android.view.LayoutInflater onGetLayoutInflater();
+    method public int onGetWindowAnimations();
+    method public boolean onHasView();
+    method public boolean onHasWindowAnimations();
+    method @Deprecated public void onRequestPermissionsFromFragment(androidx.fragment.app.Fragment, String![], int);
+    method public boolean onShouldSaveFragmentState(androidx.fragment.app.Fragment);
+    method public boolean onShouldShowRequestPermissionRationale(String);
+    method public void onStartActivityFromFragment(androidx.fragment.app.Fragment, android.content.Intent!, int);
+    method public void onStartActivityFromFragment(androidx.fragment.app.Fragment, android.content.Intent!, int, android.os.Bundle?);
+    method @Deprecated public void onStartIntentSenderFromFragment(androidx.fragment.app.Fragment, android.content.IntentSender!, int, android.content.Intent?, int, int, int, android.os.Bundle?) throws android.content.IntentSender.SendIntentException;
+    method public void onSupportInvalidateOptionsMenu();
+  }
+
+  public abstract class FragmentManager implements androidx.fragment.app.FragmentResultOwner {
+    ctor public FragmentManager();
+    method public void addFragmentOnAttachListener(androidx.fragment.app.FragmentOnAttachListener);
+    method public void addOnBackStackChangedListener(androidx.fragment.app.FragmentManager.OnBackStackChangedListener);
+    method public androidx.fragment.app.FragmentTransaction beginTransaction();
+    method public void clearBackStack(String);
+    method public final void clearFragmentResult(String);
+    method public final void clearFragmentResultListener(String);
+    method public void dump(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
+    method @Deprecated public static void enableDebugLogging(boolean);
+    method public boolean executePendingTransactions();
+    method public static <F extends androidx.fragment.app.Fragment> F findFragment(android.view.View);
+    method public androidx.fragment.app.Fragment? findFragmentById(@IdRes int);
+    method public androidx.fragment.app.Fragment? findFragmentByTag(String?);
+    method public androidx.fragment.app.FragmentManager.BackStackEntry getBackStackEntryAt(int);
+    method public int getBackStackEntryCount();
+    method public androidx.fragment.app.Fragment? getFragment(android.os.Bundle, String);
+    method public androidx.fragment.app.FragmentFactory getFragmentFactory();
+    method public java.util.List<androidx.fragment.app.Fragment!> getFragments();
+    method public androidx.fragment.app.Fragment? getPrimaryNavigationFragment();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy? getStrictModePolicy();
+    method public boolean isDestroyed();
+    method public boolean isStateSaved();
+    method public void popBackStack();
+    method public void popBackStack(String?, int);
+    method public void popBackStack(int, int);
+    method public boolean popBackStackImmediate();
+    method public boolean popBackStackImmediate(String?, int);
+    method public boolean popBackStackImmediate(int, int);
+    method public void putFragment(android.os.Bundle, String, androidx.fragment.app.Fragment);
+    method public void registerFragmentLifecycleCallbacks(androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks, boolean);
+    method public void removeFragmentOnAttachListener(androidx.fragment.app.FragmentOnAttachListener);
+    method public void removeOnBackStackChangedListener(androidx.fragment.app.FragmentManager.OnBackStackChangedListener);
+    method public void restoreBackStack(String);
+    method public void saveBackStack(String);
+    method public androidx.fragment.app.Fragment.SavedState? saveFragmentInstanceState(androidx.fragment.app.Fragment);
+    method public void setFragmentFactory(androidx.fragment.app.FragmentFactory);
+    method public final void setFragmentResult(String, android.os.Bundle);
+    method public final void setFragmentResultListener(String, androidx.lifecycle.LifecycleOwner, androidx.fragment.app.FragmentResultListener);
+    method public void setStrictModePolicy(androidx.fragment.app.strictmode.FragmentStrictMode.Policy?);
+    method public void unregisterFragmentLifecycleCallbacks(androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks);
+    field public static final int POP_BACK_STACK_INCLUSIVE = 1; // 0x1
+  }
+
+  public static interface FragmentManager.BackStackEntry {
+    method @Deprecated public CharSequence? getBreadCrumbShortTitle();
+    method @Deprecated @StringRes public int getBreadCrumbShortTitleRes();
+    method @Deprecated public CharSequence? getBreadCrumbTitle();
+    method @Deprecated @StringRes public int getBreadCrumbTitleRes();
+    method public int getId();
+    method public String? getName();
+  }
+
+  public abstract static class FragmentManager.FragmentLifecycleCallbacks {
+    ctor public FragmentManager.FragmentLifecycleCallbacks();
+    method @Deprecated public void onFragmentActivityCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle?);
+    method public void onFragmentAttached(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.content.Context);
+    method public void onFragmentCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle?);
+    method public void onFragmentDestroyed(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentDetached(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentPaused(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentPreAttached(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.content.Context);
+    method public void onFragmentPreCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle?);
+    method public void onFragmentResumed(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentSaveInstanceState(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle);
+    method public void onFragmentStarted(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentStopped(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentViewCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.view.View, android.os.Bundle?);
+    method public void onFragmentViewDestroyed(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+  }
+
+  public static interface FragmentManager.OnBackStackChangedListener {
+    method @MainThread public void onBackStackChanged();
+  }
+
+  @Deprecated public class FragmentManagerNonConfig {
+  }
+
+  public interface FragmentOnAttachListener {
+    method @MainThread public void onAttachFragment(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+  }
+
+  @Deprecated public abstract class FragmentPagerAdapter extends androidx.viewpager.widget.PagerAdapter {
+    ctor @Deprecated public FragmentPagerAdapter(androidx.fragment.app.FragmentManager);
+    ctor @Deprecated public FragmentPagerAdapter(androidx.fragment.app.FragmentManager, int);
+    method @Deprecated public abstract androidx.fragment.app.Fragment getItem(int);
+    method @Deprecated public long getItemId(int);
+    method @Deprecated public boolean isViewFromObject(android.view.View, Object);
+    field @Deprecated public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1; // 0x1
+    field @Deprecated public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0; // 0x0
+  }
+
+  public interface FragmentResultListener {
+    method public void onFragmentResult(String, android.os.Bundle);
+  }
+
+  public interface FragmentResultOwner {
+    method public void clearFragmentResult(String);
+    method public void clearFragmentResultListener(String);
+    method public void setFragmentResult(String, android.os.Bundle);
+    method public void setFragmentResultListener(String, androidx.lifecycle.LifecycleOwner, androidx.fragment.app.FragmentResultListener);
+  }
+
+  @Deprecated public abstract class FragmentStatePagerAdapter extends androidx.viewpager.widget.PagerAdapter {
+    ctor @Deprecated public FragmentStatePagerAdapter(androidx.fragment.app.FragmentManager);
+    ctor @Deprecated public FragmentStatePagerAdapter(androidx.fragment.app.FragmentManager, int);
+    method @Deprecated public abstract androidx.fragment.app.Fragment getItem(int);
+    method @Deprecated public boolean isViewFromObject(android.view.View, Object);
+    field @Deprecated public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1; // 0x1
+    field @Deprecated public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0; // 0x0
+  }
+
+  @Deprecated public class FragmentTabHost extends android.widget.TabHost implements android.widget.TabHost.OnTabChangeListener {
+    ctor @Deprecated public FragmentTabHost(android.content.Context);
+    ctor @Deprecated public FragmentTabHost(android.content.Context, android.util.AttributeSet?);
+    method @Deprecated public void addTab(android.widget.TabHost.TabSpec, Class<?>, android.os.Bundle?);
+    method @Deprecated public void onTabChanged(String?);
+    method @Deprecated public void setup(android.content.Context, androidx.fragment.app.FragmentManager);
+    method @Deprecated public void setup(android.content.Context, androidx.fragment.app.FragmentManager, int);
+  }
+
+  public abstract class FragmentTransaction {
+    ctor @Deprecated public FragmentTransaction();
+    method public final androidx.fragment.app.FragmentTransaction add(Class<? extends androidx.fragment.app.Fragment>, android.os.Bundle?, String?);
+    method public androidx.fragment.app.FragmentTransaction add(androidx.fragment.app.Fragment, String?);
+    method public final androidx.fragment.app.FragmentTransaction add(@IdRes int, Class<? extends androidx.fragment.app.Fragment>, android.os.Bundle?);
+    method public androidx.fragment.app.FragmentTransaction add(@IdRes int, androidx.fragment.app.Fragment);
+    method public final androidx.fragment.app.FragmentTransaction add(@IdRes int, Class<? extends androidx.fragment.app.Fragment>, android.os.Bundle?, String?);
+    method public androidx.fragment.app.FragmentTransaction add(@IdRes int, androidx.fragment.app.Fragment, String?);
+    method public androidx.fragment.app.FragmentTransaction addSharedElement(android.view.View, String);
+    method public androidx.fragment.app.FragmentTransaction addToBackStack(String?);
+    method public androidx.fragment.app.FragmentTransaction attach(androidx.fragment.app.Fragment);
+    method public abstract int commit();
+    method public abstract int commitAllowingStateLoss();
+    method public abstract void commitNow();
+    method public abstract void commitNowAllowingStateLoss();
+    method public androidx.fragment.app.FragmentTransaction detach(androidx.fragment.app.Fragment);
+    method public androidx.fragment.app.FragmentTransaction disallowAddToBackStack();
+    method public androidx.fragment.app.FragmentTransaction hide(androidx.fragment.app.Fragment);
+    method public boolean isAddToBackStackAllowed();
+    method public boolean isEmpty();
+    method public androidx.fragment.app.FragmentTransaction remove(androidx.fragment.app.Fragment);
+    method public final androidx.fragment.app.FragmentTransaction replace(@IdRes int, Class<? extends androidx.fragment.app.Fragment>, android.os.Bundle?);
+    method public androidx.fragment.app.FragmentTransaction replace(@IdRes int, androidx.fragment.app.Fragment);
+    method public final androidx.fragment.app.FragmentTransaction replace(@IdRes int, Class<? extends androidx.fragment.app.Fragment>, android.os.Bundle?, String?);
+    method public androidx.fragment.app.FragmentTransaction replace(@IdRes int, androidx.fragment.app.Fragment, String?);
+    method public androidx.fragment.app.FragmentTransaction runOnCommit(Runnable);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setAllowOptimization(boolean);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(@StringRes int);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(CharSequence?);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(@StringRes int);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(CharSequence?);
+    method public androidx.fragment.app.FragmentTransaction setCustomAnimations(@AnimRes @AnimatorRes int, @AnimRes @AnimatorRes int);
+    method public androidx.fragment.app.FragmentTransaction setCustomAnimations(@AnimRes @AnimatorRes int, @AnimRes @AnimatorRes int, @AnimRes @AnimatorRes int, @AnimRes @AnimatorRes int);
+    method public androidx.fragment.app.FragmentTransaction setMaxLifecycle(androidx.fragment.app.Fragment, androidx.lifecycle.Lifecycle.State);
+    method public androidx.fragment.app.FragmentTransaction setPrimaryNavigationFragment(androidx.fragment.app.Fragment?);
+    method public androidx.fragment.app.FragmentTransaction setReorderingAllowed(boolean);
+    method public androidx.fragment.app.FragmentTransaction setTransition(int);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setTransitionStyle(@StyleRes int);
+    method public androidx.fragment.app.FragmentTransaction show(androidx.fragment.app.Fragment);
+    field public static final int TRANSIT_ENTER_MASK = 4096; // 0x1000
+    field public static final int TRANSIT_EXIT_MASK = 8192; // 0x2000
+    field public static final int TRANSIT_FRAGMENT_CLOSE = 8194; // 0x2002
+    field public static final int TRANSIT_FRAGMENT_FADE = 4099; // 0x1003
+    field public static final int TRANSIT_FRAGMENT_MATCH_ACTIVITY_CLOSE = 8197; // 0x2005
+    field public static final int TRANSIT_FRAGMENT_MATCH_ACTIVITY_OPEN = 4100; // 0x1004
+    field public static final int TRANSIT_FRAGMENT_OPEN = 4097; // 0x1001
+    field public static final int TRANSIT_NONE = 0; // 0x0
+    field public static final int TRANSIT_UNSET = -1; // 0xffffffff
+  }
+
+  public class ListFragment extends androidx.fragment.app.Fragment {
+    ctor public ListFragment();
+    method public android.widget.ListAdapter? getListAdapter();
+    method public android.widget.ListView getListView();
+    method public long getSelectedItemId();
+    method public int getSelectedItemPosition();
+    method public void onListItemClick(android.widget.ListView, android.view.View, int, long);
+    method public final android.widget.ListAdapter requireListAdapter();
+    method public void setEmptyText(CharSequence?);
+    method public void setListAdapter(android.widget.ListAdapter?);
+    method public void setListShown(boolean);
+    method public void setListShownNoAnimation(boolean);
+    method public void setSelection(int);
+  }
+
+}
+
+package androidx.fragment.app.strictmode {
+
+  public final class FragmentReuseViolation extends androidx.fragment.app.strictmode.Violation {
+    method public String getPreviousFragmentId();
+    property public final String previousFragmentId;
+  }
+
+  public final class FragmentStrictMode {
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy getDefaultPolicy();
+    method @VisibleForTesting public void onPolicyViolation(androidx.fragment.app.strictmode.Violation violation);
+    method public void setDefaultPolicy(androidx.fragment.app.strictmode.FragmentStrictMode.Policy defaultPolicy);
+    property public final androidx.fragment.app.strictmode.FragmentStrictMode.Policy defaultPolicy;
+    field public static final androidx.fragment.app.strictmode.FragmentStrictMode INSTANCE;
+  }
+
+  public static fun interface FragmentStrictMode.OnViolationListener {
+    method public void onViolation(androidx.fragment.app.strictmode.Violation violation);
+  }
+
+  public static final class FragmentStrictMode.Policy {
+    field public static final androidx.fragment.app.strictmode.FragmentStrictMode.Policy LAX;
+  }
+
+  public static final class FragmentStrictMode.Policy.Builder {
+    ctor public FragmentStrictMode.Policy.Builder();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder allowViolation(Class<? extends androidx.fragment.app.Fragment> fragmentClass, Class<? extends androidx.fragment.app.strictmode.Violation> violationClass);
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy build();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectFragmentReuse();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectFragmentTagUsage();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectRetainInstanceUsage();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectSetUserVisibleHint();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectTargetFragmentUsage();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectWrongFragmentContainer();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyDeath();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyListener(androidx.fragment.app.strictmode.FragmentStrictMode.OnViolationListener listener);
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyLog();
+  }
+
+  public final class FragmentTagUsageViolation extends androidx.fragment.app.strictmode.Violation {
+    method public android.view.ViewGroup? getParentContainer();
+    property public final android.view.ViewGroup? parentContainer;
+  }
+
+  public final class GetRetainInstanceUsageViolation extends androidx.fragment.app.strictmode.RetainInstanceUsageViolation {
+  }
+
+  public final class GetTargetFragmentRequestCodeUsageViolation extends androidx.fragment.app.strictmode.TargetFragmentUsageViolation {
+  }
+
+  public final class GetTargetFragmentUsageViolation extends androidx.fragment.app.strictmode.TargetFragmentUsageViolation {
+  }
+
+  public abstract class RetainInstanceUsageViolation extends androidx.fragment.app.strictmode.Violation {
+  }
+
+  public final class SetRetainInstanceUsageViolation extends androidx.fragment.app.strictmode.RetainInstanceUsageViolation {
+  }
+
+  public final class SetTargetFragmentUsageViolation extends androidx.fragment.app.strictmode.TargetFragmentUsageViolation {
+    method public int getRequestCode();
+    method public androidx.fragment.app.Fragment getTargetFragment();
+    property public final int requestCode;
+    property public final androidx.fragment.app.Fragment targetFragment;
+  }
+
+  public final class SetUserVisibleHintViolation extends androidx.fragment.app.strictmode.Violation {
+    method public boolean isVisibleToUser();
+    property public final boolean isVisibleToUser;
+  }
+
+  public abstract class TargetFragmentUsageViolation extends androidx.fragment.app.strictmode.Violation {
+  }
+
+  public abstract class Violation extends java.lang.RuntimeException {
+    method public final androidx.fragment.app.Fragment getFragment();
+    property public final androidx.fragment.app.Fragment fragment;
+  }
+
+  public final class WrongFragmentContainerViolation extends androidx.fragment.app.strictmode.Violation {
+    method public android.view.ViewGroup getContainer();
+    property public final android.view.ViewGroup container;
+  }
+
+}
+
diff --git a/fragment/fragment/api/public_plus_experimental_1.4.0-beta01.txt b/fragment/fragment/api/public_plus_experimental_1.4.0-beta01.txt
new file mode 100644
index 0000000..d8d30ad
--- /dev/null
+++ b/fragment/fragment/api/public_plus_experimental_1.4.0-beta01.txt
@@ -0,0 +1,543 @@
+// Signature format: 4.0
+package androidx.fragment.app {
+
+  public class DialogFragment extends androidx.fragment.app.Fragment implements android.content.DialogInterface.OnCancelListener android.content.DialogInterface.OnDismissListener {
+    ctor public DialogFragment();
+    ctor public DialogFragment(@LayoutRes int);
+    method public void dismiss();
+    method public void dismissAllowingStateLoss();
+    method public android.app.Dialog? getDialog();
+    method public boolean getShowsDialog();
+    method @StyleRes public int getTheme();
+    method public boolean isCancelable();
+    method public void onCancel(android.content.DialogInterface);
+    method @MainThread public android.app.Dialog onCreateDialog(android.os.Bundle?);
+    method public void onDismiss(android.content.DialogInterface);
+    method public final android.app.Dialog requireDialog();
+    method public void setCancelable(boolean);
+    method public void setShowsDialog(boolean);
+    method public void setStyle(int, @StyleRes int);
+    method public void show(androidx.fragment.app.FragmentManager, String?);
+    method public int show(androidx.fragment.app.FragmentTransaction, String?);
+    method public void showNow(androidx.fragment.app.FragmentManager, String?);
+    field public static final int STYLE_NORMAL = 0; // 0x0
+    field public static final int STYLE_NO_FRAME = 2; // 0x2
+    field public static final int STYLE_NO_INPUT = 3; // 0x3
+    field public static final int STYLE_NO_TITLE = 1; // 0x1
+  }
+
+  public class Fragment implements androidx.activity.result.ActivityResultCaller android.content.ComponentCallbacks androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner android.view.View.OnCreateContextMenuListener androidx.lifecycle.ViewModelStoreOwner {
+    ctor public Fragment();
+    ctor @ContentView public Fragment(@LayoutRes int);
+    method public void dump(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
+    method public final boolean equals(Object?);
+    method public final androidx.fragment.app.FragmentActivity? getActivity();
+    method public boolean getAllowEnterTransitionOverlap();
+    method public boolean getAllowReturnTransitionOverlap();
+    method public final android.os.Bundle? getArguments();
+    method public final androidx.fragment.app.FragmentManager getChildFragmentManager();
+    method public android.content.Context? getContext();
+    method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
+    method public Object? getEnterTransition();
+    method public Object? getExitTransition();
+    method @Deprecated public final androidx.fragment.app.FragmentManager? getFragmentManager();
+    method public final Object? getHost();
+    method public final int getId();
+    method public final android.view.LayoutInflater getLayoutInflater();
+    method public androidx.lifecycle.Lifecycle getLifecycle();
+    method @Deprecated public androidx.loader.app.LoaderManager getLoaderManager();
+    method public final androidx.fragment.app.Fragment? getParentFragment();
+    method public final androidx.fragment.app.FragmentManager getParentFragmentManager();
+    method public Object? getReenterTransition();
+    method public final android.content.res.Resources getResources();
+    method @Deprecated public final boolean getRetainInstance();
+    method public Object? getReturnTransition();
+    method public final androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+    method public Object? getSharedElementEnterTransition();
+    method public Object? getSharedElementReturnTransition();
+    method public final String getString(@StringRes int);
+    method public final String getString(@StringRes int, java.lang.Object!...);
+    method public final String? getTag();
+    method @Deprecated public final androidx.fragment.app.Fragment? getTargetFragment();
+    method @Deprecated public final int getTargetRequestCode();
+    method public final CharSequence getText(@StringRes int);
+    method @Deprecated public boolean getUserVisibleHint();
+    method public android.view.View? getView();
+    method @MainThread public androidx.lifecycle.LifecycleOwner getViewLifecycleOwner();
+    method public androidx.lifecycle.LiveData<androidx.lifecycle.LifecycleOwner!> getViewLifecycleOwnerLiveData();
+    method public androidx.lifecycle.ViewModelStore getViewModelStore();
+    method public final int hashCode();
+    method @Deprecated public static androidx.fragment.app.Fragment instantiate(android.content.Context, String);
+    method @Deprecated public static androidx.fragment.app.Fragment instantiate(android.content.Context, String, android.os.Bundle?);
+    method public final boolean isAdded();
+    method public final boolean isDetached();
+    method public final boolean isHidden();
+    method public final boolean isInLayout();
+    method public final boolean isRemoving();
+    method public final boolean isResumed();
+    method public final boolean isStateSaved();
+    method public final boolean isVisible();
+    method @Deprecated @CallSuper @MainThread public void onActivityCreated(android.os.Bundle?);
+    method @Deprecated public void onActivityResult(int, int, android.content.Intent?);
+    method @CallSuper @MainThread public void onAttach(android.content.Context);
+    method @Deprecated @CallSuper @MainThread public void onAttach(android.app.Activity);
+    method @Deprecated @MainThread public void onAttachFragment(androidx.fragment.app.Fragment);
+    method @CallSuper public void onConfigurationChanged(android.content.res.Configuration);
+    method @MainThread public boolean onContextItemSelected(android.view.MenuItem);
+    method @CallSuper @MainThread public void onCreate(android.os.Bundle?);
+    method @MainThread public android.view.animation.Animation? onCreateAnimation(int, boolean, int);
+    method @MainThread public android.animation.Animator? onCreateAnimator(int, boolean, int);
+    method @MainThread public void onCreateContextMenu(android.view.ContextMenu, android.view.View, android.view.ContextMenu.ContextMenuInfo?);
+    method @MainThread public void onCreateOptionsMenu(android.view.Menu, android.view.MenuInflater);
+    method @MainThread public android.view.View? onCreateView(android.view.LayoutInflater, android.view.ViewGroup?, android.os.Bundle?);
+    method @CallSuper @MainThread public void onDestroy();
+    method @MainThread public void onDestroyOptionsMenu();
+    method @CallSuper @MainThread public void onDestroyView();
+    method @CallSuper @MainThread public void onDetach();
+    method public android.view.LayoutInflater onGetLayoutInflater(android.os.Bundle?);
+    method @MainThread public void onHiddenChanged(boolean);
+    method @CallSuper @UiThread public void onInflate(android.content.Context, android.util.AttributeSet, android.os.Bundle?);
+    method @Deprecated @CallSuper @UiThread public void onInflate(android.app.Activity, android.util.AttributeSet, android.os.Bundle?);
+    method @CallSuper @MainThread public void onLowMemory();
+    method public void onMultiWindowModeChanged(boolean);
+    method @MainThread public boolean onOptionsItemSelected(android.view.MenuItem);
+    method @MainThread public void onOptionsMenuClosed(android.view.Menu);
+    method @CallSuper @MainThread public void onPause();
+    method public void onPictureInPictureModeChanged(boolean);
+    method @MainThread public void onPrepareOptionsMenu(android.view.Menu);
+    method @MainThread public void onPrimaryNavigationFragmentChanged(boolean);
+    method @Deprecated public void onRequestPermissionsResult(int, String![], int[]);
+    method @CallSuper @MainThread public void onResume();
+    method @MainThread public void onSaveInstanceState(android.os.Bundle);
+    method @CallSuper @MainThread public void onStart();
+    method @CallSuper @MainThread public void onStop();
+    method @MainThread public void onViewCreated(android.view.View, android.os.Bundle?);
+    method @CallSuper @MainThread public void onViewStateRestored(android.os.Bundle?);
+    method public void postponeEnterTransition();
+    method public final void postponeEnterTransition(long, java.util.concurrent.TimeUnit);
+    method @MainThread public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultCallback<O!>);
+    method @MainThread public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultRegistry, androidx.activity.result.ActivityResultCallback<O!>);
+    method public void registerForContextMenu(android.view.View);
+    method @Deprecated public final void requestPermissions(String![], int);
+    method public final androidx.fragment.app.FragmentActivity requireActivity();
+    method public final android.os.Bundle requireArguments();
+    method public final android.content.Context requireContext();
+    method @Deprecated public final androidx.fragment.app.FragmentManager requireFragmentManager();
+    method public final Object requireHost();
+    method public final androidx.fragment.app.Fragment requireParentFragment();
+    method public final android.view.View requireView();
+    method public void setAllowEnterTransitionOverlap(boolean);
+    method public void setAllowReturnTransitionOverlap(boolean);
+    method public void setArguments(android.os.Bundle?);
+    method public void setEnterSharedElementCallback(androidx.core.app.SharedElementCallback?);
+    method public void setEnterTransition(Object?);
+    method public void setExitSharedElementCallback(androidx.core.app.SharedElementCallback?);
+    method public void setExitTransition(Object?);
+    method public void setHasOptionsMenu(boolean);
+    method public void setInitialSavedState(androidx.fragment.app.Fragment.SavedState?);
+    method public void setMenuVisibility(boolean);
+    method public void setReenterTransition(Object?);
+    method @Deprecated public void setRetainInstance(boolean);
+    method public void setReturnTransition(Object?);
+    method public void setSharedElementEnterTransition(Object?);
+    method public void setSharedElementReturnTransition(Object?);
+    method @Deprecated public void setTargetFragment(androidx.fragment.app.Fragment?, int);
+    method @Deprecated public void setUserVisibleHint(boolean);
+    method public boolean shouldShowRequestPermissionRationale(String);
+    method public void startActivity(android.content.Intent!);
+    method public void startActivity(android.content.Intent!, android.os.Bundle?);
+    method @Deprecated public void startActivityForResult(android.content.Intent!, int);
+    method @Deprecated public void startActivityForResult(android.content.Intent!, int, android.os.Bundle?);
+    method @Deprecated public void startIntentSenderForResult(android.content.IntentSender!, int, android.content.Intent?, int, int, int, android.os.Bundle?) throws android.content.IntentSender.SendIntentException;
+    method public void startPostponedEnterTransition();
+    method public void unregisterForContextMenu(android.view.View);
+  }
+
+  public static class Fragment.InstantiationException extends java.lang.RuntimeException {
+    ctor public Fragment.InstantiationException(String, Exception?);
+  }
+
+  public static class Fragment.SavedState implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<androidx.fragment.app.Fragment.SavedState!> CREATOR;
+  }
+
+  public class FragmentActivity extends androidx.activity.ComponentActivity implements androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback androidx.lifecycle.LifecycleOwner {
+    ctor public FragmentActivity();
+    ctor @ContentView public FragmentActivity(@LayoutRes int);
+    method public androidx.fragment.app.FragmentManager getSupportFragmentManager();
+    method @Deprecated public androidx.loader.app.LoaderManager getSupportLoaderManager();
+    method @Deprecated @MainThread public void onAttachFragment(androidx.fragment.app.Fragment);
+    method @CallSuper public void onMultiWindowModeChanged(boolean);
+    method @CallSuper public void onPictureInPictureModeChanged(boolean);
+    method protected void onResumeFragments();
+    method public void onStateNotSaved();
+    method public void setEnterSharedElementCallback(androidx.core.app.SharedElementCallback?);
+    method public void setExitSharedElementCallback(androidx.core.app.SharedElementCallback?);
+    method public void startActivityFromFragment(androidx.fragment.app.Fragment, android.content.Intent!, int);
+    method public void startActivityFromFragment(androidx.fragment.app.Fragment, android.content.Intent!, int, android.os.Bundle?);
+    method @Deprecated public void startIntentSenderFromFragment(androidx.fragment.app.Fragment, android.content.IntentSender!, int, android.content.Intent?, int, int, int, android.os.Bundle?) throws android.content.IntentSender.SendIntentException;
+    method public void supportFinishAfterTransition();
+    method @Deprecated public void supportInvalidateOptionsMenu();
+    method public void supportPostponeEnterTransition();
+    method public void supportStartPostponedEnterTransition();
+    method @Deprecated public final void validateRequestPermissionsRequestCode(int);
+  }
+
+  public abstract class FragmentContainer {
+    ctor public FragmentContainer();
+    method @Deprecated public androidx.fragment.app.Fragment instantiate(android.content.Context, String, android.os.Bundle?);
+    method public abstract android.view.View? onFindViewById(@IdRes int);
+    method public abstract boolean onHasView();
+  }
+
+  public final class FragmentContainerView extends android.widget.FrameLayout {
+    ctor public FragmentContainerView(android.content.Context context);
+    ctor public FragmentContainerView(android.content.Context context, android.util.AttributeSet? attrs, optional int defStyleAttr);
+    ctor public FragmentContainerView(android.content.Context context, android.util.AttributeSet? attrs);
+    method public <F extends androidx.fragment.app.Fragment> F! getFragment();
+  }
+
+  public class FragmentController {
+    method public void attachHost(androidx.fragment.app.Fragment?);
+    method public static androidx.fragment.app.FragmentController createController(androidx.fragment.app.FragmentHostCallback<?>);
+    method public void dispatchActivityCreated();
+    method public void dispatchConfigurationChanged(android.content.res.Configuration);
+    method public boolean dispatchContextItemSelected(android.view.MenuItem);
+    method public void dispatchCreate();
+    method public boolean dispatchCreateOptionsMenu(android.view.Menu, android.view.MenuInflater);
+    method public void dispatchDestroy();
+    method public void dispatchDestroyView();
+    method public void dispatchLowMemory();
+    method public void dispatchMultiWindowModeChanged(boolean);
+    method public boolean dispatchOptionsItemSelected(android.view.MenuItem);
+    method public void dispatchOptionsMenuClosed(android.view.Menu);
+    method public void dispatchPause();
+    method public void dispatchPictureInPictureModeChanged(boolean);
+    method public boolean dispatchPrepareOptionsMenu(android.view.Menu);
+    method @Deprecated public void dispatchReallyStop();
+    method public void dispatchResume();
+    method public void dispatchStart();
+    method public void dispatchStop();
+    method @Deprecated public void doLoaderDestroy();
+    method @Deprecated public void doLoaderRetain();
+    method @Deprecated public void doLoaderStart();
+    method @Deprecated public void doLoaderStop(boolean);
+    method @Deprecated public void dumpLoaders(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
+    method public boolean execPendingActions();
+    method public androidx.fragment.app.Fragment? findFragmentByWho(String);
+    method public java.util.List<androidx.fragment.app.Fragment!> getActiveFragments(java.util.List<androidx.fragment.app.Fragment!>!);
+    method public int getActiveFragmentsCount();
+    method public androidx.fragment.app.FragmentManager getSupportFragmentManager();
+    method @Deprecated public androidx.loader.app.LoaderManager! getSupportLoaderManager();
+    method public void noteStateNotSaved();
+    method public android.view.View? onCreateView(android.view.View?, String, android.content.Context, android.util.AttributeSet);
+    method @Deprecated public void reportLoaderStart();
+    method @Deprecated public void restoreAllState(android.os.Parcelable?, java.util.List<androidx.fragment.app.Fragment!>?);
+    method @Deprecated public void restoreAllState(android.os.Parcelable?, androidx.fragment.app.FragmentManagerNonConfig?);
+    method @Deprecated public void restoreLoaderNonConfig(androidx.collection.SimpleArrayMap<java.lang.String!,androidx.loader.app.LoaderManager!>!);
+    method @Deprecated public void restoreSaveState(android.os.Parcelable?);
+    method @Deprecated public androidx.collection.SimpleArrayMap<java.lang.String!,androidx.loader.app.LoaderManager!>? retainLoaderNonConfig();
+    method @Deprecated public androidx.fragment.app.FragmentManagerNonConfig? retainNestedNonConfig();
+    method @Deprecated public java.util.List<androidx.fragment.app.Fragment!>? retainNonConfig();
+    method @Deprecated public android.os.Parcelable? saveAllState();
+  }
+
+  public class FragmentFactory {
+    ctor public FragmentFactory();
+    method public androidx.fragment.app.Fragment instantiate(ClassLoader, String);
+    method public static Class<? extends androidx.fragment.app.Fragment> loadFragmentClass(ClassLoader, String);
+  }
+
+  public abstract class FragmentHostCallback<E> extends androidx.fragment.app.FragmentContainer {
+    ctor public FragmentHostCallback(android.content.Context, android.os.Handler, int);
+    method public void onDump(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
+    method public android.view.View? onFindViewById(int);
+    method public abstract E? onGetHost();
+    method public android.view.LayoutInflater onGetLayoutInflater();
+    method public int onGetWindowAnimations();
+    method public boolean onHasView();
+    method public boolean onHasWindowAnimations();
+    method @Deprecated public void onRequestPermissionsFromFragment(androidx.fragment.app.Fragment, String![], int);
+    method public boolean onShouldSaveFragmentState(androidx.fragment.app.Fragment);
+    method public boolean onShouldShowRequestPermissionRationale(String);
+    method public void onStartActivityFromFragment(androidx.fragment.app.Fragment, android.content.Intent!, int);
+    method public void onStartActivityFromFragment(androidx.fragment.app.Fragment, android.content.Intent!, int, android.os.Bundle?);
+    method @Deprecated public void onStartIntentSenderFromFragment(androidx.fragment.app.Fragment, android.content.IntentSender!, int, android.content.Intent?, int, int, int, android.os.Bundle?) throws android.content.IntentSender.SendIntentException;
+    method public void onSupportInvalidateOptionsMenu();
+  }
+
+  public abstract class FragmentManager implements androidx.fragment.app.FragmentResultOwner {
+    ctor public FragmentManager();
+    method public void addFragmentOnAttachListener(androidx.fragment.app.FragmentOnAttachListener);
+    method public void addOnBackStackChangedListener(androidx.fragment.app.FragmentManager.OnBackStackChangedListener);
+    method public androidx.fragment.app.FragmentTransaction beginTransaction();
+    method public void clearBackStack(String);
+    method public final void clearFragmentResult(String);
+    method public final void clearFragmentResultListener(String);
+    method public void dump(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
+    method @Deprecated public static void enableDebugLogging(boolean);
+    method public boolean executePendingTransactions();
+    method public static <F extends androidx.fragment.app.Fragment> F findFragment(android.view.View);
+    method public androidx.fragment.app.Fragment? findFragmentById(@IdRes int);
+    method public androidx.fragment.app.Fragment? findFragmentByTag(String?);
+    method public androidx.fragment.app.FragmentManager.BackStackEntry getBackStackEntryAt(int);
+    method public int getBackStackEntryCount();
+    method public androidx.fragment.app.Fragment? getFragment(android.os.Bundle, String);
+    method public androidx.fragment.app.FragmentFactory getFragmentFactory();
+    method public java.util.List<androidx.fragment.app.Fragment!> getFragments();
+    method public androidx.fragment.app.Fragment? getPrimaryNavigationFragment();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy? getStrictModePolicy();
+    method public boolean isDestroyed();
+    method public boolean isStateSaved();
+    method public void popBackStack();
+    method public void popBackStack(String?, int);
+    method public void popBackStack(int, int);
+    method public boolean popBackStackImmediate();
+    method public boolean popBackStackImmediate(String?, int);
+    method public boolean popBackStackImmediate(int, int);
+    method public void putFragment(android.os.Bundle, String, androidx.fragment.app.Fragment);
+    method public void registerFragmentLifecycleCallbacks(androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks, boolean);
+    method public void removeFragmentOnAttachListener(androidx.fragment.app.FragmentOnAttachListener);
+    method public void removeOnBackStackChangedListener(androidx.fragment.app.FragmentManager.OnBackStackChangedListener);
+    method public void restoreBackStack(String);
+    method public void saveBackStack(String);
+    method public androidx.fragment.app.Fragment.SavedState? saveFragmentInstanceState(androidx.fragment.app.Fragment);
+    method public void setFragmentFactory(androidx.fragment.app.FragmentFactory);
+    method public final void setFragmentResult(String, android.os.Bundle);
+    method public final void setFragmentResultListener(String, androidx.lifecycle.LifecycleOwner, androidx.fragment.app.FragmentResultListener);
+    method public void setStrictModePolicy(androidx.fragment.app.strictmode.FragmentStrictMode.Policy?);
+    method public void unregisterFragmentLifecycleCallbacks(androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks);
+    field public static final int POP_BACK_STACK_INCLUSIVE = 1; // 0x1
+  }
+
+  public static interface FragmentManager.BackStackEntry {
+    method @Deprecated public CharSequence? getBreadCrumbShortTitle();
+    method @Deprecated @StringRes public int getBreadCrumbShortTitleRes();
+    method @Deprecated public CharSequence? getBreadCrumbTitle();
+    method @Deprecated @StringRes public int getBreadCrumbTitleRes();
+    method public int getId();
+    method public String? getName();
+  }
+
+  public abstract static class FragmentManager.FragmentLifecycleCallbacks {
+    ctor public FragmentManager.FragmentLifecycleCallbacks();
+    method @Deprecated public void onFragmentActivityCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle?);
+    method public void onFragmentAttached(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.content.Context);
+    method public void onFragmentCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle?);
+    method public void onFragmentDestroyed(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentDetached(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentPaused(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentPreAttached(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.content.Context);
+    method public void onFragmentPreCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle?);
+    method public void onFragmentResumed(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentSaveInstanceState(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle);
+    method public void onFragmentStarted(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentStopped(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentViewCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.view.View, android.os.Bundle?);
+    method public void onFragmentViewDestroyed(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+  }
+
+  public static interface FragmentManager.OnBackStackChangedListener {
+    method @MainThread public void onBackStackChanged();
+  }
+
+  @Deprecated public class FragmentManagerNonConfig {
+  }
+
+  public interface FragmentOnAttachListener {
+    method @MainThread public void onAttachFragment(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+  }
+
+  @Deprecated public abstract class FragmentPagerAdapter extends androidx.viewpager.widget.PagerAdapter {
+    ctor @Deprecated public FragmentPagerAdapter(androidx.fragment.app.FragmentManager);
+    ctor @Deprecated public FragmentPagerAdapter(androidx.fragment.app.FragmentManager, int);
+    method @Deprecated public abstract androidx.fragment.app.Fragment getItem(int);
+    method @Deprecated public long getItemId(int);
+    method @Deprecated public boolean isViewFromObject(android.view.View, Object);
+    field @Deprecated public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1; // 0x1
+    field @Deprecated public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0; // 0x0
+  }
+
+  public interface FragmentResultListener {
+    method public void onFragmentResult(String, android.os.Bundle);
+  }
+
+  public interface FragmentResultOwner {
+    method public void clearFragmentResult(String);
+    method public void clearFragmentResultListener(String);
+    method public void setFragmentResult(String, android.os.Bundle);
+    method public void setFragmentResultListener(String, androidx.lifecycle.LifecycleOwner, androidx.fragment.app.FragmentResultListener);
+  }
+
+  @Deprecated public abstract class FragmentStatePagerAdapter extends androidx.viewpager.widget.PagerAdapter {
+    ctor @Deprecated public FragmentStatePagerAdapter(androidx.fragment.app.FragmentManager);
+    ctor @Deprecated public FragmentStatePagerAdapter(androidx.fragment.app.FragmentManager, int);
+    method @Deprecated public abstract androidx.fragment.app.Fragment getItem(int);
+    method @Deprecated public boolean isViewFromObject(android.view.View, Object);
+    field @Deprecated public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1; // 0x1
+    field @Deprecated public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0; // 0x0
+  }
+
+  @Deprecated public class FragmentTabHost extends android.widget.TabHost implements android.widget.TabHost.OnTabChangeListener {
+    ctor @Deprecated public FragmentTabHost(android.content.Context);
+    ctor @Deprecated public FragmentTabHost(android.content.Context, android.util.AttributeSet?);
+    method @Deprecated public void addTab(android.widget.TabHost.TabSpec, Class<?>, android.os.Bundle?);
+    method @Deprecated public void onTabChanged(String?);
+    method @Deprecated public void setup(android.content.Context, androidx.fragment.app.FragmentManager);
+    method @Deprecated public void setup(android.content.Context, androidx.fragment.app.FragmentManager, int);
+  }
+
+  public abstract class FragmentTransaction {
+    ctor @Deprecated public FragmentTransaction();
+    method public final androidx.fragment.app.FragmentTransaction add(Class<? extends androidx.fragment.app.Fragment>, android.os.Bundle?, String?);
+    method public androidx.fragment.app.FragmentTransaction add(androidx.fragment.app.Fragment, String?);
+    method public final androidx.fragment.app.FragmentTransaction add(@IdRes int, Class<? extends androidx.fragment.app.Fragment>, android.os.Bundle?);
+    method public androidx.fragment.app.FragmentTransaction add(@IdRes int, androidx.fragment.app.Fragment);
+    method public final androidx.fragment.app.FragmentTransaction add(@IdRes int, Class<? extends androidx.fragment.app.Fragment>, android.os.Bundle?, String?);
+    method public androidx.fragment.app.FragmentTransaction add(@IdRes int, androidx.fragment.app.Fragment, String?);
+    method public androidx.fragment.app.FragmentTransaction addSharedElement(android.view.View, String);
+    method public androidx.fragment.app.FragmentTransaction addToBackStack(String?);
+    method public androidx.fragment.app.FragmentTransaction attach(androidx.fragment.app.Fragment);
+    method public abstract int commit();
+    method public abstract int commitAllowingStateLoss();
+    method public abstract void commitNow();
+    method public abstract void commitNowAllowingStateLoss();
+    method public androidx.fragment.app.FragmentTransaction detach(androidx.fragment.app.Fragment);
+    method public androidx.fragment.app.FragmentTransaction disallowAddToBackStack();
+    method public androidx.fragment.app.FragmentTransaction hide(androidx.fragment.app.Fragment);
+    method public boolean isAddToBackStackAllowed();
+    method public boolean isEmpty();
+    method public androidx.fragment.app.FragmentTransaction remove(androidx.fragment.app.Fragment);
+    method public final androidx.fragment.app.FragmentTransaction replace(@IdRes int, Class<? extends androidx.fragment.app.Fragment>, android.os.Bundle?);
+    method public androidx.fragment.app.FragmentTransaction replace(@IdRes int, androidx.fragment.app.Fragment);
+    method public final androidx.fragment.app.FragmentTransaction replace(@IdRes int, Class<? extends androidx.fragment.app.Fragment>, android.os.Bundle?, String?);
+    method public androidx.fragment.app.FragmentTransaction replace(@IdRes int, androidx.fragment.app.Fragment, String?);
+    method public androidx.fragment.app.FragmentTransaction runOnCommit(Runnable);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setAllowOptimization(boolean);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(@StringRes int);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(CharSequence?);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(@StringRes int);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(CharSequence?);
+    method public androidx.fragment.app.FragmentTransaction setCustomAnimations(@AnimRes @AnimatorRes int, @AnimRes @AnimatorRes int);
+    method public androidx.fragment.app.FragmentTransaction setCustomAnimations(@AnimRes @AnimatorRes int, @AnimRes @AnimatorRes int, @AnimRes @AnimatorRes int, @AnimRes @AnimatorRes int);
+    method public androidx.fragment.app.FragmentTransaction setMaxLifecycle(androidx.fragment.app.Fragment, androidx.lifecycle.Lifecycle.State);
+    method public androidx.fragment.app.FragmentTransaction setPrimaryNavigationFragment(androidx.fragment.app.Fragment?);
+    method public androidx.fragment.app.FragmentTransaction setReorderingAllowed(boolean);
+    method public androidx.fragment.app.FragmentTransaction setTransition(int);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setTransitionStyle(@StyleRes int);
+    method public androidx.fragment.app.FragmentTransaction show(androidx.fragment.app.Fragment);
+    field public static final int TRANSIT_ENTER_MASK = 4096; // 0x1000
+    field public static final int TRANSIT_EXIT_MASK = 8192; // 0x2000
+    field public static final int TRANSIT_FRAGMENT_CLOSE = 8194; // 0x2002
+    field public static final int TRANSIT_FRAGMENT_FADE = 4099; // 0x1003
+    field public static final int TRANSIT_FRAGMENT_MATCH_ACTIVITY_CLOSE = 8197; // 0x2005
+    field public static final int TRANSIT_FRAGMENT_MATCH_ACTIVITY_OPEN = 4100; // 0x1004
+    field public static final int TRANSIT_FRAGMENT_OPEN = 4097; // 0x1001
+    field public static final int TRANSIT_NONE = 0; // 0x0
+    field public static final int TRANSIT_UNSET = -1; // 0xffffffff
+  }
+
+  public class ListFragment extends androidx.fragment.app.Fragment {
+    ctor public ListFragment();
+    method public android.widget.ListAdapter? getListAdapter();
+    method public android.widget.ListView getListView();
+    method public long getSelectedItemId();
+    method public int getSelectedItemPosition();
+    method public void onListItemClick(android.widget.ListView, android.view.View, int, long);
+    method public final android.widget.ListAdapter requireListAdapter();
+    method public void setEmptyText(CharSequence?);
+    method public void setListAdapter(android.widget.ListAdapter?);
+    method public void setListShown(boolean);
+    method public void setListShownNoAnimation(boolean);
+    method public void setSelection(int);
+  }
+
+}
+
+package androidx.fragment.app.strictmode {
+
+  public final class FragmentReuseViolation extends androidx.fragment.app.strictmode.Violation {
+    method public String getPreviousFragmentId();
+    property public final String previousFragmentId;
+  }
+
+  public final class FragmentStrictMode {
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy getDefaultPolicy();
+    method @VisibleForTesting public void onPolicyViolation(androidx.fragment.app.strictmode.Violation violation);
+    method public void setDefaultPolicy(androidx.fragment.app.strictmode.FragmentStrictMode.Policy defaultPolicy);
+    property public final androidx.fragment.app.strictmode.FragmentStrictMode.Policy defaultPolicy;
+    field public static final androidx.fragment.app.strictmode.FragmentStrictMode INSTANCE;
+  }
+
+  public static fun interface FragmentStrictMode.OnViolationListener {
+    method public void onViolation(androidx.fragment.app.strictmode.Violation violation);
+  }
+
+  public static final class FragmentStrictMode.Policy {
+    field public static final androidx.fragment.app.strictmode.FragmentStrictMode.Policy LAX;
+  }
+
+  public static final class FragmentStrictMode.Policy.Builder {
+    ctor public FragmentStrictMode.Policy.Builder();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder allowViolation(Class<? extends androidx.fragment.app.Fragment> fragmentClass, Class<? extends androidx.fragment.app.strictmode.Violation> violationClass);
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy build();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectFragmentReuse();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectFragmentTagUsage();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectRetainInstanceUsage();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectSetUserVisibleHint();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectTargetFragmentUsage();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectWrongFragmentContainer();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyDeath();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyListener(androidx.fragment.app.strictmode.FragmentStrictMode.OnViolationListener listener);
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyLog();
+  }
+
+  public final class FragmentTagUsageViolation extends androidx.fragment.app.strictmode.Violation {
+    method public android.view.ViewGroup? getParentContainer();
+    property public final android.view.ViewGroup? parentContainer;
+  }
+
+  public final class GetRetainInstanceUsageViolation extends androidx.fragment.app.strictmode.RetainInstanceUsageViolation {
+  }
+
+  public final class GetTargetFragmentRequestCodeUsageViolation extends androidx.fragment.app.strictmode.TargetFragmentUsageViolation {
+  }
+
+  public final class GetTargetFragmentUsageViolation extends androidx.fragment.app.strictmode.TargetFragmentUsageViolation {
+  }
+
+  public abstract class RetainInstanceUsageViolation extends androidx.fragment.app.strictmode.Violation {
+  }
+
+  public final class SetRetainInstanceUsageViolation extends androidx.fragment.app.strictmode.RetainInstanceUsageViolation {
+  }
+
+  public final class SetTargetFragmentUsageViolation extends androidx.fragment.app.strictmode.TargetFragmentUsageViolation {
+    method public int getRequestCode();
+    method public androidx.fragment.app.Fragment getTargetFragment();
+    property public final int requestCode;
+    property public final androidx.fragment.app.Fragment targetFragment;
+  }
+
+  public final class SetUserVisibleHintViolation extends androidx.fragment.app.strictmode.Violation {
+    method public boolean isVisibleToUser();
+    property public final boolean isVisibleToUser;
+  }
+
+  public abstract class TargetFragmentUsageViolation extends androidx.fragment.app.strictmode.Violation {
+  }
+
+  public abstract class Violation extends java.lang.RuntimeException {
+    method public final androidx.fragment.app.Fragment getFragment();
+    property public final androidx.fragment.app.Fragment fragment;
+  }
+
+  public final class WrongFragmentContainerViolation extends androidx.fragment.app.strictmode.Violation {
+    method public android.view.ViewGroup getContainer();
+    property public final android.view.ViewGroup container;
+  }
+
+}
+
diff --git a/fragment/fragment/api/res-1.4.0-beta01.txt b/fragment/fragment/api/res-1.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/fragment/fragment/api/res-1.4.0-beta01.txt
diff --git a/fragment/fragment/api/restricted_1.4.0-beta01.txt b/fragment/fragment/api/restricted_1.4.0-beta01.txt
new file mode 100644
index 0000000..06de951
--- /dev/null
+++ b/fragment/fragment/api/restricted_1.4.0-beta01.txt
@@ -0,0 +1,573 @@
+// Signature format: 4.0
+package androidx.fragment.app {
+
+  public class DialogFragment extends androidx.fragment.app.Fragment implements android.content.DialogInterface.OnCancelListener android.content.DialogInterface.OnDismissListener {
+    ctor public DialogFragment();
+    ctor public DialogFragment(@LayoutRes int);
+    method public void dismiss();
+    method public void dismissAllowingStateLoss();
+    method public android.app.Dialog? getDialog();
+    method public boolean getShowsDialog();
+    method @StyleRes public int getTheme();
+    method public boolean isCancelable();
+    method public void onCancel(android.content.DialogInterface);
+    method @MainThread public android.app.Dialog onCreateDialog(android.os.Bundle?);
+    method public void onDismiss(android.content.DialogInterface);
+    method public final android.app.Dialog requireDialog();
+    method public void setCancelable(boolean);
+    method public void setShowsDialog(boolean);
+    method public void setStyle(int, @StyleRes int);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setupDialog(android.app.Dialog, int);
+    method public void show(androidx.fragment.app.FragmentManager, String?);
+    method public int show(androidx.fragment.app.FragmentTransaction, String?);
+    method public void showNow(androidx.fragment.app.FragmentManager, String?);
+    field public static final int STYLE_NORMAL = 0; // 0x0
+    field public static final int STYLE_NO_FRAME = 2; // 0x2
+    field public static final int STYLE_NO_INPUT = 3; // 0x3
+    field public static final int STYLE_NO_TITLE = 1; // 0x1
+  }
+
+  public class Fragment implements androidx.activity.result.ActivityResultCaller android.content.ComponentCallbacks androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner android.view.View.OnCreateContextMenuListener androidx.lifecycle.ViewModelStoreOwner {
+    ctor public Fragment();
+    ctor @ContentView public Fragment(@LayoutRes int);
+    method public void dump(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
+    method public final boolean equals(Object?);
+    method public final androidx.fragment.app.FragmentActivity? getActivity();
+    method public boolean getAllowEnterTransitionOverlap();
+    method public boolean getAllowReturnTransitionOverlap();
+    method public final android.os.Bundle? getArguments();
+    method public final androidx.fragment.app.FragmentManager getChildFragmentManager();
+    method public android.content.Context? getContext();
+    method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
+    method public Object? getEnterTransition();
+    method public Object? getExitTransition();
+    method @Deprecated public final androidx.fragment.app.FragmentManager? getFragmentManager();
+    method public final Object? getHost();
+    method public final int getId();
+    method public final android.view.LayoutInflater getLayoutInflater();
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.view.LayoutInflater getLayoutInflater(android.os.Bundle?);
+    method public androidx.lifecycle.Lifecycle getLifecycle();
+    method @Deprecated public androidx.loader.app.LoaderManager getLoaderManager();
+    method public final androidx.fragment.app.Fragment? getParentFragment();
+    method public final androidx.fragment.app.FragmentManager getParentFragmentManager();
+    method public Object? getReenterTransition();
+    method public final android.content.res.Resources getResources();
+    method @Deprecated public final boolean getRetainInstance();
+    method public Object? getReturnTransition();
+    method public final androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+    method public Object? getSharedElementEnterTransition();
+    method public Object? getSharedElementReturnTransition();
+    method public final String getString(@StringRes int);
+    method public final String getString(@StringRes int, java.lang.Object!...);
+    method public final String? getTag();
+    method @Deprecated public final androidx.fragment.app.Fragment? getTargetFragment();
+    method @Deprecated public final int getTargetRequestCode();
+    method public final CharSequence getText(@StringRes int);
+    method @Deprecated public boolean getUserVisibleHint();
+    method public android.view.View? getView();
+    method @MainThread public androidx.lifecycle.LifecycleOwner getViewLifecycleOwner();
+    method public androidx.lifecycle.LiveData<androidx.lifecycle.LifecycleOwner!> getViewLifecycleOwnerLiveData();
+    method public androidx.lifecycle.ViewModelStore getViewModelStore();
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final boolean hasOptionsMenu();
+    method public final int hashCode();
+    method @Deprecated public static androidx.fragment.app.Fragment instantiate(android.content.Context, String);
+    method @Deprecated public static androidx.fragment.app.Fragment instantiate(android.content.Context, String, android.os.Bundle?);
+    method public final boolean isAdded();
+    method public final boolean isDetached();
+    method public final boolean isHidden();
+    method public final boolean isInLayout();
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final boolean isMenuVisible();
+    method public final boolean isRemoving();
+    method public final boolean isResumed();
+    method public final boolean isStateSaved();
+    method public final boolean isVisible();
+    method @Deprecated @CallSuper @MainThread public void onActivityCreated(android.os.Bundle?);
+    method @Deprecated public void onActivityResult(int, int, android.content.Intent?);
+    method @CallSuper @MainThread public void onAttach(android.content.Context);
+    method @Deprecated @CallSuper @MainThread public void onAttach(android.app.Activity);
+    method @Deprecated @MainThread public void onAttachFragment(androidx.fragment.app.Fragment);
+    method @CallSuper public void onConfigurationChanged(android.content.res.Configuration);
+    method @MainThread public boolean onContextItemSelected(android.view.MenuItem);
+    method @CallSuper @MainThread public void onCreate(android.os.Bundle?);
+    method @MainThread public android.view.animation.Animation? onCreateAnimation(int, boolean, int);
+    method @MainThread public android.animation.Animator? onCreateAnimator(int, boolean, int);
+    method @MainThread public void onCreateContextMenu(android.view.ContextMenu, android.view.View, android.view.ContextMenu.ContextMenuInfo?);
+    method @MainThread public void onCreateOptionsMenu(android.view.Menu, android.view.MenuInflater);
+    method @MainThread public android.view.View? onCreateView(android.view.LayoutInflater, android.view.ViewGroup?, android.os.Bundle?);
+    method @CallSuper @MainThread public void onDestroy();
+    method @MainThread public void onDestroyOptionsMenu();
+    method @CallSuper @MainThread public void onDestroyView();
+    method @CallSuper @MainThread public void onDetach();
+    method public android.view.LayoutInflater onGetLayoutInflater(android.os.Bundle?);
+    method @MainThread public void onHiddenChanged(boolean);
+    method @CallSuper @UiThread public void onInflate(android.content.Context, android.util.AttributeSet, android.os.Bundle?);
+    method @Deprecated @CallSuper @UiThread public void onInflate(android.app.Activity, android.util.AttributeSet, android.os.Bundle?);
+    method @CallSuper @MainThread public void onLowMemory();
+    method public void onMultiWindowModeChanged(boolean);
+    method @MainThread public boolean onOptionsItemSelected(android.view.MenuItem);
+    method @MainThread public void onOptionsMenuClosed(android.view.Menu);
+    method @CallSuper @MainThread public void onPause();
+    method public void onPictureInPictureModeChanged(boolean);
+    method @MainThread public void onPrepareOptionsMenu(android.view.Menu);
+    method @MainThread public void onPrimaryNavigationFragmentChanged(boolean);
+    method @Deprecated public void onRequestPermissionsResult(int, String![], int[]);
+    method @CallSuper @MainThread public void onResume();
+    method @MainThread public void onSaveInstanceState(android.os.Bundle);
+    method @CallSuper @MainThread public void onStart();
+    method @CallSuper @MainThread public void onStop();
+    method @MainThread public void onViewCreated(android.view.View, android.os.Bundle?);
+    method @CallSuper @MainThread public void onViewStateRestored(android.os.Bundle?);
+    method public void postponeEnterTransition();
+    method public final void postponeEnterTransition(long, java.util.concurrent.TimeUnit);
+    method @MainThread public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultCallback<O!>);
+    method @MainThread public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultRegistry, androidx.activity.result.ActivityResultCallback<O!>);
+    method public void registerForContextMenu(android.view.View);
+    method @Deprecated public final void requestPermissions(String![], int);
+    method public final androidx.fragment.app.FragmentActivity requireActivity();
+    method public final android.os.Bundle requireArguments();
+    method public final android.content.Context requireContext();
+    method @Deprecated public final androidx.fragment.app.FragmentManager requireFragmentManager();
+    method public final Object requireHost();
+    method public final androidx.fragment.app.Fragment requireParentFragment();
+    method public final android.view.View requireView();
+    method public void setAllowEnterTransitionOverlap(boolean);
+    method public void setAllowReturnTransitionOverlap(boolean);
+    method public void setArguments(android.os.Bundle?);
+    method public void setEnterSharedElementCallback(androidx.core.app.SharedElementCallback?);
+    method public void setEnterTransition(Object?);
+    method public void setExitSharedElementCallback(androidx.core.app.SharedElementCallback?);
+    method public void setExitTransition(Object?);
+    method public void setHasOptionsMenu(boolean);
+    method public void setInitialSavedState(androidx.fragment.app.Fragment.SavedState?);
+    method public void setMenuVisibility(boolean);
+    method public void setReenterTransition(Object?);
+    method @Deprecated public void setRetainInstance(boolean);
+    method public void setReturnTransition(Object?);
+    method public void setSharedElementEnterTransition(Object?);
+    method public void setSharedElementReturnTransition(Object?);
+    method @Deprecated public void setTargetFragment(androidx.fragment.app.Fragment?, int);
+    method @Deprecated public void setUserVisibleHint(boolean);
+    method public boolean shouldShowRequestPermissionRationale(String);
+    method public void startActivity(android.content.Intent!);
+    method public void startActivity(android.content.Intent!, android.os.Bundle?);
+    method @Deprecated public void startActivityForResult(android.content.Intent!, int);
+    method @Deprecated public void startActivityForResult(android.content.Intent!, int, android.os.Bundle?);
+    method @Deprecated public void startIntentSenderForResult(android.content.IntentSender!, int, android.content.Intent?, int, int, int, android.os.Bundle?) throws android.content.IntentSender.SendIntentException;
+    method public void startPostponedEnterTransition();
+    method public void unregisterForContextMenu(android.view.View);
+  }
+
+  public static class Fragment.InstantiationException extends java.lang.RuntimeException {
+    ctor public Fragment.InstantiationException(String, Exception?);
+  }
+
+  public static class Fragment.SavedState implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<androidx.fragment.app.Fragment.SavedState!> CREATOR;
+  }
+
+  public class FragmentActivity extends androidx.activity.ComponentActivity implements androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback androidx.core.app.ActivityCompat.RequestPermissionsRequestCodeValidator {
+    ctor public FragmentActivity();
+    ctor @ContentView public FragmentActivity(@LayoutRes int);
+    method public androidx.fragment.app.FragmentManager getSupportFragmentManager();
+    method @Deprecated public androidx.loader.app.LoaderManager getSupportLoaderManager();
+    method @Deprecated @MainThread public void onAttachFragment(androidx.fragment.app.Fragment);
+    method @CallSuper public void onMultiWindowModeChanged(boolean);
+    method @CallSuper public void onPictureInPictureModeChanged(boolean);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) protected boolean onPrepareOptionsPanel(android.view.View?, android.view.Menu);
+    method protected void onResumeFragments();
+    method public void onStateNotSaved();
+    method public void setEnterSharedElementCallback(androidx.core.app.SharedElementCallback?);
+    method public void setExitSharedElementCallback(androidx.core.app.SharedElementCallback?);
+    method public void startActivityFromFragment(androidx.fragment.app.Fragment, android.content.Intent!, int);
+    method public void startActivityFromFragment(androidx.fragment.app.Fragment, android.content.Intent!, int, android.os.Bundle?);
+    method @Deprecated public void startIntentSenderFromFragment(androidx.fragment.app.Fragment, android.content.IntentSender!, int, android.content.Intent?, int, int, int, android.os.Bundle?) throws android.content.IntentSender.SendIntentException;
+    method public void supportFinishAfterTransition();
+    method @Deprecated public void supportInvalidateOptionsMenu();
+    method public void supportPostponeEnterTransition();
+    method public void supportStartPostponedEnterTransition();
+    method @Deprecated public final void validateRequestPermissionsRequestCode(int);
+  }
+
+  public abstract class FragmentContainer {
+    ctor public FragmentContainer();
+    method @Deprecated public androidx.fragment.app.Fragment instantiate(android.content.Context, String, android.os.Bundle?);
+    method public abstract android.view.View? onFindViewById(@IdRes int);
+    method public abstract boolean onHasView();
+  }
+
+  public final class FragmentContainerView extends android.widget.FrameLayout {
+    ctor public FragmentContainerView(android.content.Context context);
+    ctor public FragmentContainerView(android.content.Context context, android.util.AttributeSet? attrs, optional int defStyleAttr);
+    ctor public FragmentContainerView(android.content.Context context, android.util.AttributeSet? attrs);
+    method public <F extends androidx.fragment.app.Fragment> F! getFragment();
+  }
+
+  public class FragmentController {
+    method public void attachHost(androidx.fragment.app.Fragment?);
+    method public static androidx.fragment.app.FragmentController createController(androidx.fragment.app.FragmentHostCallback<?>);
+    method public void dispatchActivityCreated();
+    method public void dispatchConfigurationChanged(android.content.res.Configuration);
+    method public boolean dispatchContextItemSelected(android.view.MenuItem);
+    method public void dispatchCreate();
+    method public boolean dispatchCreateOptionsMenu(android.view.Menu, android.view.MenuInflater);
+    method public void dispatchDestroy();
+    method public void dispatchDestroyView();
+    method public void dispatchLowMemory();
+    method public void dispatchMultiWindowModeChanged(boolean);
+    method public boolean dispatchOptionsItemSelected(android.view.MenuItem);
+    method public void dispatchOptionsMenuClosed(android.view.Menu);
+    method public void dispatchPause();
+    method public void dispatchPictureInPictureModeChanged(boolean);
+    method public boolean dispatchPrepareOptionsMenu(android.view.Menu);
+    method @Deprecated public void dispatchReallyStop();
+    method public void dispatchResume();
+    method public void dispatchStart();
+    method public void dispatchStop();
+    method @Deprecated public void doLoaderDestroy();
+    method @Deprecated public void doLoaderRetain();
+    method @Deprecated public void doLoaderStart();
+    method @Deprecated public void doLoaderStop(boolean);
+    method @Deprecated public void dumpLoaders(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
+    method public boolean execPendingActions();
+    method public androidx.fragment.app.Fragment? findFragmentByWho(String);
+    method public java.util.List<androidx.fragment.app.Fragment!> getActiveFragments(java.util.List<androidx.fragment.app.Fragment!>!);
+    method public int getActiveFragmentsCount();
+    method public androidx.fragment.app.FragmentManager getSupportFragmentManager();
+    method @Deprecated public androidx.loader.app.LoaderManager! getSupportLoaderManager();
+    method public void noteStateNotSaved();
+    method public android.view.View? onCreateView(android.view.View?, String, android.content.Context, android.util.AttributeSet);
+    method @Deprecated public void reportLoaderStart();
+    method @Deprecated public void restoreAllState(android.os.Parcelable?, java.util.List<androidx.fragment.app.Fragment!>?);
+    method @Deprecated public void restoreAllState(android.os.Parcelable?, androidx.fragment.app.FragmentManagerNonConfig?);
+    method @Deprecated public void restoreLoaderNonConfig(androidx.collection.SimpleArrayMap<java.lang.String!,androidx.loader.app.LoaderManager!>!);
+    method @Deprecated public void restoreSaveState(android.os.Parcelable?);
+    method @Deprecated public androidx.collection.SimpleArrayMap<java.lang.String!,androidx.loader.app.LoaderManager!>? retainLoaderNonConfig();
+    method @Deprecated public androidx.fragment.app.FragmentManagerNonConfig? retainNestedNonConfig();
+    method @Deprecated public java.util.List<androidx.fragment.app.Fragment!>? retainNonConfig();
+    method @Deprecated public android.os.Parcelable? saveAllState();
+  }
+
+  public class FragmentFactory {
+    ctor public FragmentFactory();
+    method public androidx.fragment.app.Fragment instantiate(ClassLoader, String);
+    method public static Class<? extends androidx.fragment.app.Fragment> loadFragmentClass(ClassLoader, String);
+  }
+
+  public abstract class FragmentHostCallback<E> extends androidx.fragment.app.FragmentContainer {
+    ctor public FragmentHostCallback(android.content.Context, android.os.Handler, int);
+    method public void onDump(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
+    method public android.view.View? onFindViewById(int);
+    method public abstract E? onGetHost();
+    method public android.view.LayoutInflater onGetLayoutInflater();
+    method public int onGetWindowAnimations();
+    method public boolean onHasView();
+    method public boolean onHasWindowAnimations();
+    method @Deprecated public void onRequestPermissionsFromFragment(androidx.fragment.app.Fragment, String![], int);
+    method public boolean onShouldSaveFragmentState(androidx.fragment.app.Fragment);
+    method public boolean onShouldShowRequestPermissionRationale(String);
+    method public void onStartActivityFromFragment(androidx.fragment.app.Fragment, android.content.Intent!, int);
+    method public void onStartActivityFromFragment(androidx.fragment.app.Fragment, android.content.Intent!, int, android.os.Bundle?);
+    method @Deprecated public void onStartIntentSenderFromFragment(androidx.fragment.app.Fragment, android.content.IntentSender!, int, android.content.Intent?, int, int, int, android.os.Bundle?) throws android.content.IntentSender.SendIntentException;
+    method public void onSupportInvalidateOptionsMenu();
+  }
+
+  public abstract class FragmentManager implements androidx.fragment.app.FragmentResultOwner {
+    ctor public FragmentManager();
+    method public void addFragmentOnAttachListener(androidx.fragment.app.FragmentOnAttachListener);
+    method public void addOnBackStackChangedListener(androidx.fragment.app.FragmentManager.OnBackStackChangedListener);
+    method public androidx.fragment.app.FragmentTransaction beginTransaction();
+    method public void clearBackStack(String);
+    method public final void clearFragmentResult(String);
+    method public final void clearFragmentResultListener(String);
+    method public void dump(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
+    method @Deprecated public static void enableDebugLogging(boolean);
+    method public boolean executePendingTransactions();
+    method public static <F extends androidx.fragment.app.Fragment> F findFragment(android.view.View);
+    method public androidx.fragment.app.Fragment? findFragmentById(@IdRes int);
+    method public androidx.fragment.app.Fragment? findFragmentByTag(String?);
+    method public androidx.fragment.app.FragmentManager.BackStackEntry getBackStackEntryAt(int);
+    method public int getBackStackEntryCount();
+    method public androidx.fragment.app.Fragment? getFragment(android.os.Bundle, String);
+    method public androidx.fragment.app.FragmentFactory getFragmentFactory();
+    method public java.util.List<androidx.fragment.app.Fragment!> getFragments();
+    method public androidx.fragment.app.Fragment? getPrimaryNavigationFragment();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy? getStrictModePolicy();
+    method public boolean isDestroyed();
+    method public boolean isStateSaved();
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.fragment.app.FragmentTransaction openTransaction();
+    method public void popBackStack();
+    method public void popBackStack(String?, int);
+    method public void popBackStack(int, int);
+    method public boolean popBackStackImmediate();
+    method public boolean popBackStackImmediate(String?, int);
+    method public boolean popBackStackImmediate(int, int);
+    method public void putFragment(android.os.Bundle, String, androidx.fragment.app.Fragment);
+    method public void registerFragmentLifecycleCallbacks(androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks, boolean);
+    method public void removeFragmentOnAttachListener(androidx.fragment.app.FragmentOnAttachListener);
+    method public void removeOnBackStackChangedListener(androidx.fragment.app.FragmentManager.OnBackStackChangedListener);
+    method public void restoreBackStack(String);
+    method public void saveBackStack(String);
+    method public androidx.fragment.app.Fragment.SavedState? saveFragmentInstanceState(androidx.fragment.app.Fragment);
+    method public void setFragmentFactory(androidx.fragment.app.FragmentFactory);
+    method public final void setFragmentResult(String, android.os.Bundle);
+    method public final void setFragmentResultListener(String, androidx.lifecycle.LifecycleOwner, androidx.fragment.app.FragmentResultListener);
+    method public void setStrictModePolicy(androidx.fragment.app.strictmode.FragmentStrictMode.Policy?);
+    method public void unregisterFragmentLifecycleCallbacks(androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks);
+    field public static final int POP_BACK_STACK_INCLUSIVE = 1; // 0x1
+  }
+
+  public static interface FragmentManager.BackStackEntry {
+    method @Deprecated public CharSequence? getBreadCrumbShortTitle();
+    method @Deprecated @StringRes public int getBreadCrumbShortTitleRes();
+    method @Deprecated public CharSequence? getBreadCrumbTitle();
+    method @Deprecated @StringRes public int getBreadCrumbTitleRes();
+    method public int getId();
+    method public String? getName();
+  }
+
+  public abstract static class FragmentManager.FragmentLifecycleCallbacks {
+    ctor public FragmentManager.FragmentLifecycleCallbacks();
+    method @Deprecated public void onFragmentActivityCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle?);
+    method public void onFragmentAttached(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.content.Context);
+    method public void onFragmentCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle?);
+    method public void onFragmentDestroyed(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentDetached(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentPaused(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentPreAttached(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.content.Context);
+    method public void onFragmentPreCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle?);
+    method public void onFragmentResumed(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentSaveInstanceState(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle);
+    method public void onFragmentStarted(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentStopped(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+    method public void onFragmentViewCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.view.View, android.os.Bundle?);
+    method public void onFragmentViewDestroyed(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+  }
+
+  public static interface FragmentManager.OnBackStackChangedListener {
+    method @MainThread public void onBackStackChanged();
+  }
+
+  @Deprecated public class FragmentManagerNonConfig {
+  }
+
+  public interface FragmentOnAttachListener {
+    method @MainThread public void onAttachFragment(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+  }
+
+  @Deprecated public abstract class FragmentPagerAdapter extends androidx.viewpager.widget.PagerAdapter {
+    ctor @Deprecated public FragmentPagerAdapter(androidx.fragment.app.FragmentManager);
+    ctor @Deprecated public FragmentPagerAdapter(androidx.fragment.app.FragmentManager, int);
+    method @Deprecated public abstract androidx.fragment.app.Fragment getItem(int);
+    method @Deprecated public long getItemId(int);
+    method @Deprecated public boolean isViewFromObject(android.view.View, Object);
+    field @Deprecated public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1; // 0x1
+    field @Deprecated public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0; // 0x0
+  }
+
+  public interface FragmentResultListener {
+    method public void onFragmentResult(String, android.os.Bundle);
+  }
+
+  public interface FragmentResultOwner {
+    method public void clearFragmentResult(String);
+    method public void clearFragmentResultListener(String);
+    method public void setFragmentResult(String, android.os.Bundle);
+    method public void setFragmentResultListener(String, androidx.lifecycle.LifecycleOwner, androidx.fragment.app.FragmentResultListener);
+  }
+
+  @Deprecated public abstract class FragmentStatePagerAdapter extends androidx.viewpager.widget.PagerAdapter {
+    ctor @Deprecated public FragmentStatePagerAdapter(androidx.fragment.app.FragmentManager);
+    ctor @Deprecated public FragmentStatePagerAdapter(androidx.fragment.app.FragmentManager, int);
+    method @Deprecated public abstract androidx.fragment.app.Fragment getItem(int);
+    method @Deprecated public boolean isViewFromObject(android.view.View, Object);
+    field @Deprecated public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1; // 0x1
+    field @Deprecated public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0; // 0x0
+  }
+
+  @Deprecated public class FragmentTabHost extends android.widget.TabHost implements android.widget.TabHost.OnTabChangeListener {
+    ctor @Deprecated public FragmentTabHost(android.content.Context);
+    ctor @Deprecated public FragmentTabHost(android.content.Context, android.util.AttributeSet?);
+    method @Deprecated public void addTab(android.widget.TabHost.TabSpec, Class<?>, android.os.Bundle?);
+    method @Deprecated public void onTabChanged(String?);
+    method @Deprecated public void setup(android.content.Context, androidx.fragment.app.FragmentManager);
+    method @Deprecated public void setup(android.content.Context, androidx.fragment.app.FragmentManager, int);
+  }
+
+  public abstract class FragmentTransaction {
+    ctor @Deprecated public FragmentTransaction();
+    method public final androidx.fragment.app.FragmentTransaction add(Class<? extends androidx.fragment.app.Fragment>, android.os.Bundle?, String?);
+    method public androidx.fragment.app.FragmentTransaction add(androidx.fragment.app.Fragment, String?);
+    method public final androidx.fragment.app.FragmentTransaction add(@IdRes int, Class<? extends androidx.fragment.app.Fragment>, android.os.Bundle?);
+    method public androidx.fragment.app.FragmentTransaction add(@IdRes int, androidx.fragment.app.Fragment);
+    method public final androidx.fragment.app.FragmentTransaction add(@IdRes int, Class<? extends androidx.fragment.app.Fragment>, android.os.Bundle?, String?);
+    method public androidx.fragment.app.FragmentTransaction add(@IdRes int, androidx.fragment.app.Fragment, String?);
+    method public androidx.fragment.app.FragmentTransaction addSharedElement(android.view.View, String);
+    method public androidx.fragment.app.FragmentTransaction addToBackStack(String?);
+    method public androidx.fragment.app.FragmentTransaction attach(androidx.fragment.app.Fragment);
+    method public abstract int commit();
+    method public abstract int commitAllowingStateLoss();
+    method public abstract void commitNow();
+    method public abstract void commitNowAllowingStateLoss();
+    method public androidx.fragment.app.FragmentTransaction detach(androidx.fragment.app.Fragment);
+    method public androidx.fragment.app.FragmentTransaction disallowAddToBackStack();
+    method public androidx.fragment.app.FragmentTransaction hide(androidx.fragment.app.Fragment);
+    method public boolean isAddToBackStackAllowed();
+    method public boolean isEmpty();
+    method public androidx.fragment.app.FragmentTransaction remove(androidx.fragment.app.Fragment);
+    method public final androidx.fragment.app.FragmentTransaction replace(@IdRes int, Class<? extends androidx.fragment.app.Fragment>, android.os.Bundle?);
+    method public androidx.fragment.app.FragmentTransaction replace(@IdRes int, androidx.fragment.app.Fragment);
+    method public final androidx.fragment.app.FragmentTransaction replace(@IdRes int, Class<? extends androidx.fragment.app.Fragment>, android.os.Bundle?, String?);
+    method public androidx.fragment.app.FragmentTransaction replace(@IdRes int, androidx.fragment.app.Fragment, String?);
+    method public androidx.fragment.app.FragmentTransaction runOnCommit(Runnable);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setAllowOptimization(boolean);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(@StringRes int);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(CharSequence?);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(@StringRes int);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(CharSequence?);
+    method public androidx.fragment.app.FragmentTransaction setCustomAnimations(@AnimRes @AnimatorRes int, @AnimRes @AnimatorRes int);
+    method public androidx.fragment.app.FragmentTransaction setCustomAnimations(@AnimRes @AnimatorRes int, @AnimRes @AnimatorRes int, @AnimRes @AnimatorRes int, @AnimRes @AnimatorRes int);
+    method public androidx.fragment.app.FragmentTransaction setMaxLifecycle(androidx.fragment.app.Fragment, androidx.lifecycle.Lifecycle.State);
+    method public androidx.fragment.app.FragmentTransaction setPrimaryNavigationFragment(androidx.fragment.app.Fragment?);
+    method public androidx.fragment.app.FragmentTransaction setReorderingAllowed(boolean);
+    method public androidx.fragment.app.FragmentTransaction setTransition(int);
+    method @Deprecated public androidx.fragment.app.FragmentTransaction setTransitionStyle(@StyleRes int);
+    method public androidx.fragment.app.FragmentTransaction show(androidx.fragment.app.Fragment);
+    field public static final int TRANSIT_ENTER_MASK = 4096; // 0x1000
+    field public static final int TRANSIT_EXIT_MASK = 8192; // 0x2000
+    field public static final int TRANSIT_FRAGMENT_CLOSE = 8194; // 0x2002
+    field public static final int TRANSIT_FRAGMENT_FADE = 4099; // 0x1003
+    field public static final int TRANSIT_FRAGMENT_MATCH_ACTIVITY_CLOSE = 8197; // 0x2005
+    field public static final int TRANSIT_FRAGMENT_MATCH_ACTIVITY_OPEN = 4100; // 0x1004
+    field public static final int TRANSIT_FRAGMENT_OPEN = 4097; // 0x1001
+    field public static final int TRANSIT_NONE = 0; // 0x0
+    field public static final int TRANSIT_UNSET = -1; // 0xffffffff
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class FragmentTransitionImpl {
+    ctor public FragmentTransitionImpl();
+    method public abstract void addTarget(Object!, android.view.View!);
+    method public abstract void addTargets(Object!, java.util.ArrayList<android.view.View!>!);
+    method public abstract void beginDelayedTransition(android.view.ViewGroup!, Object!);
+    method protected static void bfsAddViewChildren(java.util.List<android.view.View!>!, android.view.View!);
+    method public abstract boolean canHandle(Object!);
+    method public abstract Object! cloneTransition(Object!);
+    method protected void getBoundsOnScreen(android.view.View!, android.graphics.Rect!);
+    method protected static boolean isNullOrEmpty(java.util.List!);
+    method public abstract Object! mergeTransitionsInSequence(Object!, Object!, Object!);
+    method public abstract Object! mergeTransitionsTogether(Object!, Object!, Object!);
+    method public abstract void removeTarget(Object!, android.view.View!);
+    method public abstract void replaceTargets(Object!, java.util.ArrayList<android.view.View!>!, java.util.ArrayList<android.view.View!>!);
+    method public abstract void scheduleHideFragmentView(Object!, android.view.View!, java.util.ArrayList<android.view.View!>!);
+    method public abstract void scheduleRemoveTargets(Object!, Object!, java.util.ArrayList<android.view.View!>!, Object!, java.util.ArrayList<android.view.View!>!, Object!, java.util.ArrayList<android.view.View!>!);
+    method public abstract void setEpicenter(Object!, android.view.View!);
+    method public abstract void setEpicenter(Object!, android.graphics.Rect!);
+    method public void setListenerForTransitionEnd(androidx.fragment.app.Fragment, Object, androidx.core.os.CancellationSignal, Runnable);
+    method public abstract void setSharedElementTargets(Object!, android.view.View!, java.util.ArrayList<android.view.View!>!);
+    method public abstract void swapSharedElementTargets(Object!, java.util.ArrayList<android.view.View!>!, java.util.ArrayList<android.view.View!>!);
+    method public abstract Object! wrapTransitionInSet(Object!);
+  }
+
+  public class ListFragment extends androidx.fragment.app.Fragment {
+    ctor public ListFragment();
+    method public android.widget.ListAdapter? getListAdapter();
+    method public android.widget.ListView getListView();
+    method public long getSelectedItemId();
+    method public int getSelectedItemPosition();
+    method public void onListItemClick(android.widget.ListView, android.view.View, int, long);
+    method public final android.widget.ListAdapter requireListAdapter();
+    method public void setEmptyText(CharSequence?);
+    method public void setListAdapter(android.widget.ListAdapter?);
+    method public void setListShown(boolean);
+    method public void setListShownNoAnimation(boolean);
+    method public void setSelection(int);
+  }
+
+}
+
+package androidx.fragment.app.strictmode {
+
+  public final class FragmentReuseViolation extends androidx.fragment.app.strictmode.Violation {
+    method public String getPreviousFragmentId();
+    property public final String previousFragmentId;
+  }
+
+  public final class FragmentStrictMode {
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy getDefaultPolicy();
+    method @VisibleForTesting public void onPolicyViolation(androidx.fragment.app.strictmode.Violation violation);
+    method public void setDefaultPolicy(androidx.fragment.app.strictmode.FragmentStrictMode.Policy defaultPolicy);
+    property public final androidx.fragment.app.strictmode.FragmentStrictMode.Policy defaultPolicy;
+    field public static final androidx.fragment.app.strictmode.FragmentStrictMode INSTANCE;
+  }
+
+  public static fun interface FragmentStrictMode.OnViolationListener {
+    method public void onViolation(androidx.fragment.app.strictmode.Violation violation);
+  }
+
+  public static final class FragmentStrictMode.Policy {
+    field public static final androidx.fragment.app.strictmode.FragmentStrictMode.Policy LAX;
+  }
+
+  public static final class FragmentStrictMode.Policy.Builder {
+    ctor public FragmentStrictMode.Policy.Builder();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder allowViolation(Class<? extends androidx.fragment.app.Fragment> fragmentClass, Class<? extends androidx.fragment.app.strictmode.Violation> violationClass);
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy build();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectFragmentReuse();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectFragmentTagUsage();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectRetainInstanceUsage();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectSetUserVisibleHint();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectTargetFragmentUsage();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectWrongFragmentContainer();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyDeath();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyListener(androidx.fragment.app.strictmode.FragmentStrictMode.OnViolationListener listener);
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyLog();
+  }
+
+  public final class FragmentTagUsageViolation extends androidx.fragment.app.strictmode.Violation {
+    method public android.view.ViewGroup? getParentContainer();
+    property public final android.view.ViewGroup? parentContainer;
+  }
+
+  public final class GetRetainInstanceUsageViolation extends androidx.fragment.app.strictmode.RetainInstanceUsageViolation {
+  }
+
+  public final class GetTargetFragmentRequestCodeUsageViolation extends androidx.fragment.app.strictmode.TargetFragmentUsageViolation {
+  }
+
+  public final class GetTargetFragmentUsageViolation extends androidx.fragment.app.strictmode.TargetFragmentUsageViolation {
+  }
+
+  public abstract class RetainInstanceUsageViolation extends androidx.fragment.app.strictmode.Violation {
+  }
+
+  public final class SetRetainInstanceUsageViolation extends androidx.fragment.app.strictmode.RetainInstanceUsageViolation {
+  }
+
+  public final class SetTargetFragmentUsageViolation extends androidx.fragment.app.strictmode.TargetFragmentUsageViolation {
+    method public int getRequestCode();
+    method public androidx.fragment.app.Fragment getTargetFragment();
+    property public final int requestCode;
+    property public final androidx.fragment.app.Fragment targetFragment;
+  }
+
+  public final class SetUserVisibleHintViolation extends androidx.fragment.app.strictmode.Violation {
+    method public boolean isVisibleToUser();
+    property public final boolean isVisibleToUser;
+  }
+
+  public abstract class TargetFragmentUsageViolation extends androidx.fragment.app.strictmode.Violation {
+  }
+
+  public abstract class Violation extends java.lang.RuntimeException {
+    method public final androidx.fragment.app.Fragment getFragment();
+    property public final androidx.fragment.app.Fragment fragment;
+  }
+
+  public final class WrongFragmentContainerViolation extends androidx.fragment.app.strictmode.Violation {
+    method public android.view.ViewGroup getContainer();
+    property public final android.view.ViewGroup container;
+  }
+
+}
+
diff --git a/glance/glance-appwidget/glance-layout-generator/src/main/kotlin/androidx/glance/appwidget/layoutgenerator/GenerateRegistry.kt b/glance/glance-appwidget/glance-layout-generator/src/main/kotlin/androidx/glance/appwidget/layoutgenerator/GenerateRegistry.kt
index 7f513eb..243130bc 100644
--- a/glance/glance-appwidget/glance-layout-generator/src/main/kotlin/androidx/glance/appwidget/layoutgenerator/GenerateRegistry.kt
+++ b/glance/glance-appwidget/glance-layout-generator/src/main/kotlin/androidx/glance/appwidget/layoutgenerator/GenerateRegistry.kt
@@ -72,27 +72,27 @@
 
 private fun createFileInitializer(layout: File, mainViewId: String): CodeBlock = buildCodeBlock {
     val viewType = layout.nameWithoutExtension.toLayoutType()
-    ValidSize.values().forEach { width ->
-        ValidSize.values().forEach { height ->
-            addLayout(
-                resourceName = makeSimpleResourceName(layout, width, height),
-                viewType = viewType,
-                width = width,
-                height = height,
-                canResize = false,
-                mainViewId = "R.id.$mainViewId",
-                sizeViewId = null
-            )
-            addLayout(
-                resourceName = makeComplexResourceName(layout, width, height),
-                viewType = viewType,
-                width = width,
-                height = height,
-                canResize = true,
-                mainViewId = "R.id.$mainViewId",
-                sizeViewId = "R.id.sizeView"
-            )
-        }
+    forEachConfiguration(layout) { width, height, childCount ->
+        addLayout(
+            resourceName = makeSimpleResourceName(layout, width, height, childCount),
+            viewType = viewType,
+            width = width,
+            height = height,
+            canResize = false,
+            mainViewId = "R.id.$mainViewId",
+            sizeViewId = null,
+            childCount = childCount
+        )
+        addLayout(
+            resourceName = makeComplexResourceName(layout, width, height, childCount),
+            viewType = viewType,
+            width = width,
+            height = height,
+            canResize = true,
+            mainViewId = "R.id.$mainViewId",
+            sizeViewId = "R.id.sizeView",
+            childCount = childCount
+        )
     }
 }
 
@@ -103,14 +103,16 @@
     height: ValidSize,
     canResize: Boolean,
     mainViewId: String,
-    sizeViewId: String?
+    sizeViewId: String?,
+    childCount: Int
 ) {
     addStatement(
-        "%T(type = %M, width = %M, height = %M, canResize = $canResize) to ",
+        "%T(type = %M, width = %M, height = %M, canResize = $canResize, childCount = %L) to ",
         LayoutSelector,
         makeViewType(viewType),
         width.toValue(),
         height.toValue(),
+        childCount
     )
     withIndent {
         addStatement("%T(", LayoutIds)
@@ -149,11 +151,36 @@
     ValidSize.Match -> MatchValue
 }
 
-internal fun makeSimpleResourceName(file: File, width: ValidSize, height: ValidSize) =
-    "${file.nameWithoutExtension}_simple_${width.resourceName}_${height.resourceName}"
+internal fun makeSimpleResourceName(
+    file: File,
+    width: ValidSize,
+    height: ValidSize,
+    childCount: Int
+) = makeResourceName(file, width, height, childCount, isSimple = true)
 
-internal fun makeComplexResourceName(file: File, width: ValidSize, height: ValidSize) =
-    "${file.nameWithoutExtension}_complex_${width.resourceName}_${height.resourceName}"
+internal fun makeComplexResourceName(
+    file: File,
+    width: ValidSize,
+    height: ValidSize,
+    childCount: Int
+) = makeResourceName(file, width, height, childCount, isSimple = false)
+
+private fun makeResourceName(
+    file: File,
+    width: ValidSize,
+    height: ValidSize,
+    childCount: Int,
+    isSimple: Boolean,
+): String {
+    return listOfNotNull(
+        file.nameWithoutExtension,
+        if (isSimple) "simple" else "complex",
+        width.resourceName,
+        height.resourceName,
+        if (childCount > 0) "${childCount}child" else null
+    )
+        .joinToString(separator = "_")
+}
 
 fun CodeBlock.Builder.withIndent(builderAction: CodeBlock.Builder.() -> Unit): CodeBlock.Builder {
     indent()
@@ -162,6 +189,28 @@
     return this
 }
 
+/**
+ * The list of layout templates corresponding to collections that should have view stub children.
+ */
+private val CollectionFiles = listOf("box", "column", "row")
+
+/**
+ * Returns whether the [File] is for a collection layout that should have generated view stub
+ * children.
+ */
+internal fun File.isCollectionLayout() = nameWithoutExtension in CollectionFiles
+
+internal fun File.allChildCounts(): List<Int> {
+    return if (isCollectionLayout()) {
+        (0..MaxChildren).toList()
+    } else {
+        listOf(0)
+    }
+}
+
+/** The maximum number of direct children that a collection layout can have. */
+internal const val MaxChildren = 10
+
 val LicenseComment =
     """
         Copyright 2021 The Android Open Source Project
diff --git a/glance/glance-appwidget/glance-layout-generator/src/main/kotlin/androidx/glance/appwidget/layoutgenerator/LayoutGenerator.kt b/glance/glance-appwidget/glance-layout-generator/src/main/kotlin/androidx/glance/appwidget/layoutgenerator/LayoutGenerator.kt
index b79fc53..e06690d 100644
--- a/glance/glance-appwidget/glance-layout-generator/src/main/kotlin/androidx/glance/appwidget/layoutgenerator/LayoutGenerator.kt
+++ b/glance/glance-appwidget/glance-layout-generator/src/main/kotlin/androidx/glance/appwidget/layoutgenerator/LayoutGenerator.kt
@@ -78,26 +78,35 @@
     fun generateAllFiles(files: List<File>, outputResourcesDir: File): Map<File, LayoutProperties> {
         val outputLayoutDir = outputResourcesDir.resolve("layout")
         outputLayoutDir.mkdirs()
-        return files.associate {
-            it to generateForFile(it, outputLayoutDir)
-        }
+        return files.associateWith { generateForFile(it, outputLayoutDir) }
     }
 
     private fun generateForFile(file: File, outputLayoutDir: File): LayoutProperties {
         val document = parseLayoutTemplate(file)
-        ValidSize.values().forEach { width ->
-            ValidSize.values().forEach { height ->
-                val simpleLayout = generateSimpleLayout(document, width, height)
-                writeGeneratedLayout(
-                    simpleLayout,
-                    outputLayoutDir.resolve("${makeSimpleResourceName(file, width, height)}.xml")
+        forEachConfiguration(file) { width, height, childCount ->
+            writeGeneratedLayout(
+                generateSimpleLayout(document, width, height, childCount),
+                outputLayoutDir.resolve(
+                    makeSimpleResourceName(
+                        file,
+                        width,
+                        height,
+                        childCount
+                    ) + ".xml"
                 )
-                val complexLayout = generateComplexLayout(document, width, height)
-                writeGeneratedLayout(
-                    complexLayout,
-                    outputLayoutDir.resolve("${makeComplexResourceName(file, width, height)}.xml")
+            )
+            writeGeneratedLayout(
+                generateComplexLayout(document, width, height, childCount),
+                outputLayoutDir.resolve(
+                    makeComplexResourceName(
+                        file,
+                        width,
+                        height,
+                        childCount
+                    ) + ".xml"
+
                 )
-            }
+            )
         }
         return LayoutProperties(mainViewId = extractMainViewId(document))
     }
@@ -111,7 +120,8 @@
     fun generateSimpleLayout(
         document: Document,
         width: ValidSize,
-        height: ValidSize
+        height: ValidSize,
+        childCount: Int
     ): Document {
         val generated = documentBuilder.newDocument()
         val root = generated.importNode(document.documentElement, true)
@@ -129,6 +139,7 @@
             }
             setNamedItemNS(generated.androidLayoutDirection("locale"))
         }
+        generated.appendViewStubs(root, childCount)
         return generated
     }
 
@@ -179,7 +190,8 @@
     fun generateComplexLayout(
         document: Document,
         width: ValidSize,
-        height: ValidSize
+        height: ValidSize,
+        childCount: Int
     ): Document {
         val generated = documentBuilder.newDocument()
         val root = generated.createElement("RelativeLayout")
@@ -228,10 +240,25 @@
             }
             setNamedItemNS(generated.androidLayoutDirection("locale"))
         }
+        generated.appendViewStubs(mainNode, childCount)
         return generated
     }
 }
 
+internal fun Document.appendViewStubs(node: Node, childCount: Int) {
+    repeat(childCount) { node.appendChild(createViewStub(index = it)) }
+}
+
+internal fun Document.createViewStub(index: Int): Node {
+    val stub = createElement("ViewStub")
+    stub.attributes.apply {
+        setNamedItemNS(androidId("@id/stub$index"))
+        setNamedItemNS(androidWidth(ValidSize.Wrap))
+        setNamedItemNS(androidHeight(ValidSize.Wrap))
+    }
+    return stub
+}
+
 internal data class LayoutProperties(
     val mainViewId: String
 )
@@ -282,3 +309,16 @@
     ValidSize.Wrap, ValidSize.Fixed -> ValidSize.Wrap
     ValidSize.Match, ValidSize.Expand -> ValidSize.Match
 }
+
+internal inline fun forEachConfiguration(
+    file: File,
+    function: (width: ValidSize, height: ValidSize, childCount: Int) -> Unit
+) {
+    ValidSize.values().forEach { width ->
+        ValidSize.values().forEach { height ->
+            file.allChildCounts().forEach { childCount ->
+                function(width, height, childCount)
+            }
+        }
+    }
+}
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ResponsiveAppWidget.kt b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ResponsiveAppWidget.kt
index aeef24d..fb4a4db 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ResponsiveAppWidget.kt
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ResponsiveAppWidget.kt
@@ -21,7 +21,7 @@
 import androidx.glance.LocalContext
 import androidx.glance.LocalSize
 import androidx.glance.Modifier
-import androidx.glance.action.launchActivityAction
+import androidx.glance.action.actionLaunchActivity
 import androidx.glance.appwidget.GlanceAppWidget
 import androidx.glance.appwidget.GlanceAppWidgetReceiver
 import androidx.glance.appwidget.SizeMode
@@ -66,7 +66,7 @@
                 )
             }
             Text(content)
-            Button("Button", onClick = launchActivityAction<Activity>())
+            Button("Button", onClick = actionLaunchActivity<Activity>())
         }
     }
 }
diff --git a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverTest.kt b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverTest.kt
index 883b4ebb..e6c8408 100644
--- a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverTest.kt
+++ b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverTest.kt
@@ -31,7 +31,7 @@
 import androidx.glance.LocalContext
 import androidx.glance.LocalSize
 import androidx.glance.Modifier
-import androidx.glance.action.launchActivityAction
+import androidx.glance.action.actionLaunchActivity
 import androidx.glance.layout.Box
 import androidx.glance.layout.Button
 import androidx.glance.layout.Column
@@ -385,7 +385,7 @@
     @Test
     fun createButton() {
         TestGlanceAppWidget.uiDefinition = {
-            Button("Button", onClick = launchActivityAction<Activity>(), enabled = false)
+            Button("Button", onClick = actionLaunchActivity<Activity>(), enabled = false)
         }
 
         mHostRule.startHost()
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/ActionRunnableBroadcastReceiver.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/ActionRunnableBroadcastReceiver.kt
index ce5df1a..8c0e3ab 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/ActionRunnableBroadcastReceiver.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/ActionRunnableBroadcastReceiver.kt
@@ -22,10 +22,10 @@
 import android.content.Intent
 import android.net.Uri
 import androidx.glance.action.ActionRunnable
-import androidx.glance.action.UpdateAction
+import androidx.glance.action.UpdateContentAction
 
 /**
- * Responds to broadcasts from [UpdateAction] clicks by executing the associated action.
+ * Responds to broadcasts from [UpdateContentAction] clicks by executing the associated action.
  */
 internal class ActionRunnableBroadcastReceiver : BroadcastReceiver() {
 
@@ -39,7 +39,7 @@
                 "The custom work intent must contain a work class name string using extra: " +
                     ExtraClassName
             }
-            UpdateAction.run(context, className)
+            UpdateContentAction.run(context, className)
         }
     }
 
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
index 3ca77ca..5209c78 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/ApplyModifiers.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/ApplyModifiers.kt
@@ -38,7 +38,7 @@
 import androidx.glance.action.LaunchActivityAction
 import androidx.glance.action.LaunchActivityClassAction
 import androidx.glance.action.LaunchActivityComponentAction
-import androidx.glance.action.UpdateAction
+import androidx.glance.action.UpdateContentAction
 import androidx.glance.appwidget.action.LaunchActivityIntentAction
 import androidx.glance.layout.Dimension
 import androidx.glance.layout.HeightModifier
@@ -121,7 +121,7 @@
                 )
             rv.setOnClickPendingIntent(viewId, pendingIntent)
         }
-        is UpdateAction -> {
+        is UpdateContentAction -> {
             val pendingIntent =
                 ActionRunnableBroadcastReceiver.createPendingIntent(context, action.runnableClass)
             rv.setOnClickPendingIntent(viewId, pendingIntent)
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/LayoutIds.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/LayoutIds.kt
index 72d9d2e..9e9539e 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/LayoutIds.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/LayoutIds.kt
@@ -53,6 +53,7 @@
     val width: Size,
     val height: Size,
     val canResize: Boolean,
+    val childCount: Int
 ) {
 
     internal enum class Size {
@@ -101,8 +102,16 @@
     val width = widthMod.resolveDimension(context).toSpecSize()
     val height = heightMod.resolveDimension(context).toSpecSize()
     val needResize = width == LayoutSelector.Size.Fixed || height == LayoutSelector.Size.Fixed
-    return generatedLayouts[LayoutSelector(type, width, height, needResize)]
-        ?: (if (!needResize) generatedLayouts[LayoutSelector(type, width, height, true)] else null)
+    // TODO(b/202868171): Add the real child count.
+    return generatedLayouts[LayoutSelector(type, width, height, needResize, childCount = 0)]
+        ?: (if (!needResize) generatedLayouts[LayoutSelector(
+            type,
+            width,
+            height,
+            true,
+            // TODO(b/202868171): Add the real child count.
+            childCount = 0
+        )] else null)
         ?: throw IllegalArgumentException(
             "Could not find layout for $type, width=$width, height=$height, canResize=$needResize"
         )
@@ -139,7 +148,14 @@
     val heightMod = modifier.findModifier<HeightModifier>()?.height ?: Dimension.Wrap
     val width = widthMod.toSpecSize()
     val height = heightMod.toSpecSize()
-    return generatedLayouts[LayoutSelector(type, width, height, canResize = false)]
+    return generatedLayouts[LayoutSelector(
+        type,
+        width,
+        height,
+        canResize = false,
+        // TODO(b/202868171): Add the real child count.
+        childCount = 0
+    )]
         ?: throw IllegalArgumentException(
             "Could not find layout for $type, width=$width, height=$height, canResize=false"
         )
diff --git a/glance/glance-appwidget/src/androidMain/res/values/ids.xml b/glance/glance-appwidget/src/androidMain/res/values/ids.xml
index f6750d1..2b3520d 100644
--- a/glance/glance-appwidget/src/androidMain/res/values/ids.xml
+++ b/glance/glance-appwidget/src/androidMain/res/values/ids.xml
@@ -26,4 +26,15 @@
     <id name="switchText"/>
     <id name="switchTrack"/>
     <id name="switchThumb"/>
+
+    <id name="stub0"/>
+    <id name="stub1"/>
+    <id name="stub2"/>
+    <id name="stub3"/>
+    <id name="stub4"/>
+    <id name="stub5"/>
+    <id name="stub6"/>
+    <id name="stub7"/>
+    <id name="stub8"/>
+    <id name="stub9"/>
 </resources>
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 e6c1d7d..2253063 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
@@ -36,7 +36,7 @@
 import androidx.compose.runtime.Composable
 import androidx.core.view.children
 import androidx.glance.Modifier
-import androidx.glance.action.launchActivityAction
+import androidx.glance.action.actionLaunchActivity
 import androidx.glance.appwidget.layout.AndroidRemoteViews
 import androidx.glance.appwidget.layout.CheckBox
 import androidx.glance.appwidget.layout.LazyColumn
@@ -684,7 +684,7 @@
         val rv = runAndTranslate {
             Button(
                 "Button",
-                onClick = launchActivityAction<Activity>(),
+                onClick = actionLaunchActivity<Activity>(),
                 enabled = true
             )
         }
@@ -700,7 +700,7 @@
         val rv = runAndTranslate {
             Button(
                 "Button",
-                onClick = launchActivityAction<Activity>(),
+                onClick = actionLaunchActivity<Activity>(),
                 enabled = false
             )
         }
diff --git a/glance/glance-wear/src/test/kotlin/androidx/glance/wear/WearCompositionTranslatorTest.kt b/glance/glance-wear/src/test/kotlin/androidx/glance/wear/WearCompositionTranslatorTest.kt
index 9eae920..a8ddeb4e 100644
--- a/glance/glance-wear/src/test/kotlin/androidx/glance/wear/WearCompositionTranslatorTest.kt
+++ b/glance/glance-wear/src/test/kotlin/androidx/glance/wear/WearCompositionTranslatorTest.kt
@@ -22,7 +22,7 @@
 import androidx.glance.Modifier
 import androidx.glance.background
 import androidx.glance.action.clickable
-import androidx.glance.action.launchActivityAction
+import androidx.glance.action.actionLaunchActivity
 import androidx.glance.layout.Alignment
 import androidx.glance.layout.Box
 import androidx.glance.layout.Button
@@ -419,7 +419,7 @@
     fun canInflateLaunchAction() = fakeCoroutineScope.runBlockingTest {
         val content = runAndTranslate {
             Text(
-                modifier = Modifier.clickable(launchActivityAction(TestActivity::class.java)),
+                modifier = Modifier.clickable(actionLaunchActivity(TestActivity::class.java)),
                 text = "Hello World"
             )
         }
@@ -451,7 +451,7 @@
             )
             Button(
                 "Hello World",
-                onClick = launchActivityAction(TestActivity::class.java),
+                onClick = actionLaunchActivity(TestActivity::class.java),
                 modifier = Modifier.padding(1.dp),
                 style = style
             )
diff --git a/glance/glance/api/current.txt b/glance/glance/api/current.txt
index de58fc4..190036e 100644
--- a/glance/glance/api/current.txt
+++ b/glance/glance/api/current.txt
@@ -60,14 +60,14 @@
   }
 
   public final class LaunchActivityActionKt {
-    method public static androidx.glance.action.Action launchActivityAction(android.content.ComponentName componentName);
-    method public static <T extends android.app.Activity> androidx.glance.action.Action launchActivityAction(Class<T> activity);
-    method public static inline <reified T extends android.app.Activity> androidx.glance.action.Action! launchActivityAction();
+    method public static androidx.glance.action.Action actionLaunchActivity(android.content.ComponentName componentName);
+    method public static <T extends android.app.Activity> androidx.glance.action.Action actionLaunchActivity(Class<T> activity);
+    method public static inline <reified T extends android.app.Activity> androidx.glance.action.Action! actionLaunchActivity();
   }
 
-  public final class UpdateActionKt {
-    method public static <T extends androidx.glance.action.ActionRunnable> androidx.glance.action.Action updateContentAction(Class<T> runnable);
-    method public static inline <reified T extends androidx.glance.action.ActionRunnable> androidx.glance.action.Action! updateContentAction();
+  public final class UpdateContentActionKt {
+    method public static <T extends androidx.glance.action.ActionRunnable> androidx.glance.action.Action actionUpdateContent(Class<T> runnable);
+    method public static inline <reified T extends androidx.glance.action.ActionRunnable> androidx.glance.action.Action! actionUpdateContent();
   }
 
 }
diff --git a/glance/glance/api/public_plus_experimental_current.txt b/glance/glance/api/public_plus_experimental_current.txt
index de58fc4..190036e 100644
--- a/glance/glance/api/public_plus_experimental_current.txt
+++ b/glance/glance/api/public_plus_experimental_current.txt
@@ -60,14 +60,14 @@
   }
 
   public final class LaunchActivityActionKt {
-    method public static androidx.glance.action.Action launchActivityAction(android.content.ComponentName componentName);
-    method public static <T extends android.app.Activity> androidx.glance.action.Action launchActivityAction(Class<T> activity);
-    method public static inline <reified T extends android.app.Activity> androidx.glance.action.Action! launchActivityAction();
+    method public static androidx.glance.action.Action actionLaunchActivity(android.content.ComponentName componentName);
+    method public static <T extends android.app.Activity> androidx.glance.action.Action actionLaunchActivity(Class<T> activity);
+    method public static inline <reified T extends android.app.Activity> androidx.glance.action.Action! actionLaunchActivity();
   }
 
-  public final class UpdateActionKt {
-    method public static <T extends androidx.glance.action.ActionRunnable> androidx.glance.action.Action updateContentAction(Class<T> runnable);
-    method public static inline <reified T extends androidx.glance.action.ActionRunnable> androidx.glance.action.Action! updateContentAction();
+  public final class UpdateContentActionKt {
+    method public static <T extends androidx.glance.action.ActionRunnable> androidx.glance.action.Action actionUpdateContent(Class<T> runnable);
+    method public static inline <reified T extends androidx.glance.action.ActionRunnable> androidx.glance.action.Action! actionUpdateContent();
   }
 
 }
diff --git a/glance/glance/api/restricted_current.txt b/glance/glance/api/restricted_current.txt
index de58fc4..190036e 100644
--- a/glance/glance/api/restricted_current.txt
+++ b/glance/glance/api/restricted_current.txt
@@ -60,14 +60,14 @@
   }
 
   public final class LaunchActivityActionKt {
-    method public static androidx.glance.action.Action launchActivityAction(android.content.ComponentName componentName);
-    method public static <T extends android.app.Activity> androidx.glance.action.Action launchActivityAction(Class<T> activity);
-    method public static inline <reified T extends android.app.Activity> androidx.glance.action.Action! launchActivityAction();
+    method public static androidx.glance.action.Action actionLaunchActivity(android.content.ComponentName componentName);
+    method public static <T extends android.app.Activity> androidx.glance.action.Action actionLaunchActivity(Class<T> activity);
+    method public static inline <reified T extends android.app.Activity> androidx.glance.action.Action! actionLaunchActivity();
   }
 
-  public final class UpdateActionKt {
-    method public static <T extends androidx.glance.action.ActionRunnable> androidx.glance.action.Action updateContentAction(Class<T> runnable);
-    method public static inline <reified T extends androidx.glance.action.ActionRunnable> androidx.glance.action.Action! updateContentAction();
+  public final class UpdateContentActionKt {
+    method public static <T extends androidx.glance.action.ActionRunnable> androidx.glance.action.Action actionUpdateContent(Class<T> runnable);
+    method public static inline <reified T extends androidx.glance.action.ActionRunnable> androidx.glance.action.Action! actionUpdateContent();
   }
 
 }
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/action/Action.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/action/Action.kt
index 71ff4f3..744d5f1 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/action/Action.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/action/Action.kt
@@ -22,7 +22,7 @@
 
 /**
  * An Action defines the actions a user can take. Implementations specify what operation will be
- * performed in response to the action, eg. [launchActivityAction] creates an Action that launches
+ * performed in response to the action, eg. [actionLaunchActivity] creates an Action that launches
  * the specified [Activity].
  */
 public interface Action
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/action/LaunchActivityAction.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/action/LaunchActivityAction.kt
index f75863d..f09201b 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/action/LaunchActivityAction.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/action/LaunchActivityAction.kt
@@ -26,27 +26,28 @@
 
 /** @suppress */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class LaunchActivityComponentAction(val componentName: ComponentName) : LaunchActivityAction
+public class LaunchActivityComponentAction(val componentName: ComponentName) : LaunchActivityAction
 
 /** @suppress */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class LaunchActivityClassAction(val activityClass: Class<out Activity>) : LaunchActivityAction
+public class LaunchActivityClassAction(val activityClass: Class<out Activity>) :
+    LaunchActivityAction
 
 /**
  * Creates an [Action] that launches the [Activity] specified by the given [ComponentName].
  */
-public fun launchActivityAction(componentName: ComponentName): Action =
+public fun actionLaunchActivity(componentName: ComponentName): Action =
     LaunchActivityComponentAction(componentName)
 
 /**
  * Creates an [Action] that launches the specified [Activity] when triggered.
  */
-public fun <T : Activity> launchActivityAction(activity: Class<T>): Action =
+public fun <T : Activity> actionLaunchActivity(activity: Class<T>): Action =
     LaunchActivityClassAction(activity)
 
 @Suppress("MissingNullability") /* Shouldn't need to specify @NonNull. b/199284086 */
 /**
  * Creates an [Action] that launches the specified [Activity] when triggered.
  */
-public inline fun <reified T : Activity> launchActivityAction(): Action =
-    launchActivityAction(T::class.java)
+public inline fun <reified T : Activity> actionLaunchActivity(): Action =
+    actionLaunchActivity(T::class.java)
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/action/UpdateAction.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/action/UpdateContentAction.kt
similarity index 80%
rename from glance/glance/src/androidMain/kotlin/androidx/glance/action/UpdateAction.kt
rename to glance/glance/src/androidMain/kotlin/androidx/glance/action/UpdateContentAction.kt
index 660929f..3ea29c2 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/action/UpdateAction.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/action/UpdateContentAction.kt
@@ -21,7 +21,7 @@
 
 /** @suppress */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class UpdateAction(val runnableClass: Class<out ActionRunnable>) : Action {
+public class UpdateContentAction(val runnableClass: Class<out ActionRunnable>) : Action {
     companion object {
 
         public suspend fun run(context: Context, className: String) {
@@ -42,20 +42,22 @@
  * implementing class must have a public zero argument constructor, this is used to instantiate
  * the class at runtime.
  */
-interface ActionRunnable {
+public interface ActionRunnable {
     suspend fun run(context: Context)
 }
 
 /**
- * Creates an [Action] that executes a custom [ActionRunnable] and then updates the component view.
+ * Creates an [Action] that executes a custom [ActionRunnable] and then updates the component
+ * content.
  */
-public fun <T : ActionRunnable> updateContentAction(runnable: Class<T>): Action =
-    UpdateAction(runnable)
+public fun <T : ActionRunnable> actionUpdateContent(runnable: Class<T>): Action =
+    UpdateContentAction(runnable)
 
 @Suppress("MissingNullability") /* Shouldn't need to specify @NonNull. b/199284086 */
 /**
- * Creates an [Action] that executes a custom [ActionRunnable] and then updates the component view.
+ * Creates an [Action] that executes a custom [ActionRunnable] and then updates the component
+ * content.
  */
 // TODO(b/201418282): Add the UI update path
-public inline fun <reified T : ActionRunnable> updateContentAction(): Action =
-    updateContentAction(T::class.java)
+public inline fun <reified T : ActionRunnable> actionUpdateContent(): Action =
+    actionUpdateContent(T::class.java)
diff --git a/glance/glance/src/test/kotlin/androidx/glance/action/ActionTest.kt b/glance/glance/src/test/kotlin/androidx/glance/action/ActionTest.kt
index 7729138..4edbaa2 100644
--- a/glance/glance/src/test/kotlin/androidx/glance/action/ActionTest.kt
+++ b/glance/glance/src/test/kotlin/androidx/glance/action/ActionTest.kt
@@ -46,23 +46,23 @@
 
     @Test
     fun testLaunchActivity() {
-        val modifiers = Modifier.clickable(launchActivityAction(TestActivity::class.java))
+        val modifiers = Modifier.clickable(actionLaunchActivity(TestActivity::class.java))
         val modifier = checkNotNull(modifiers.findModifier<ActionModifier>())
         assertIs<LaunchActivityClassAction>(modifier.action)
     }
 
     @Test
     fun testUpdate() {
-        val modifiers = Modifier.clickable(updateContentAction<TestRunnable>())
+        val modifiers = Modifier.clickable(actionUpdateContent<TestRunnable>())
         val modifier = checkNotNull(modifiers.findModifier<ActionModifier>())
-        assertIs<UpdateAction>(modifier.action)
+        assertIs<UpdateContentAction>(modifier.action)
     }
 
     @Test
     fun testLaunchFromComponent() = fakeCoroutineScope.runBlockingTest {
         val c = ComponentName("androidx.glance.action", "androidx.glance.action.TestActivity")
 
-        val modifiers = Modifier.clickable(launchActivityAction(c))
+        val modifiers = Modifier.clickable(actionLaunchActivity(c))
         val modifier = checkNotNull(modifiers.findModifier<ActionModifier>())
         val action = assertIs<LaunchActivityComponentAction>(modifier.action)
         val component = assertNotNull(action.componentName)
@@ -74,7 +74,7 @@
     fun testLaunchFromComponentWithContext() = fakeCoroutineScope.runBlockingTest {
         val c = ComponentName(context, "androidx.glance.action.TestActivity")
 
-        val modifiers = Modifier.clickable(launchActivityAction(c))
+        val modifiers = Modifier.clickable(actionLaunchActivity(c))
         val modifier = checkNotNull(modifiers.findModifier<ActionModifier>())
         val action = assertIs<LaunchActivityComponentAction>(modifier.action)
         val component = assertNotNull(action.componentName)
diff --git a/glance/glance/src/test/kotlin/androidx/glance/layout/ButtonTest.kt b/glance/glance/src/test/kotlin/androidx/glance/layout/ButtonTest.kt
index db37f7b..f43aa10 100644
--- a/glance/glance/src/test/kotlin/androidx/glance/layout/ButtonTest.kt
+++ b/glance/glance/src/test/kotlin/androidx/glance/layout/ButtonTest.kt
@@ -18,7 +18,7 @@
 import android.app.Activity
 import androidx.glance.action.ActionModifier
 import androidx.glance.action.LaunchActivityAction
-import androidx.glance.action.launchActivityAction
+import androidx.glance.action.actionLaunchActivity
 import androidx.glance.findModifier
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -40,7 +40,7 @@
     @Test
     fun createComposableButton() = fakeCoroutineScope.runBlockingTest {
         val root = runTestingComposition {
-            Button(text = "button", onClick = launchActivityAction<Activity>(), enabled = true)
+            Button(text = "button", onClick = actionLaunchActivity<Activity>(), enabled = true)
         }
 
         assertThat(root.children).hasSize(1)
@@ -53,7 +53,7 @@
     @Test
     fun createDisabledButton() = fakeCoroutineScope.runBlockingTest {
         val root = runTestingComposition {
-            Button(text = "button", onClick = launchActivityAction<Activity>(), enabled = false)
+            Button(text = "button", onClick = actionLaunchActivity<Activity>(), enabled = false)
         }
 
         assertThat(root.children).hasSize(1)
diff --git a/navigation/navigation-common-ktx/api/2.4.0-beta01.txt b/navigation/navigation-common-ktx/api/2.4.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/navigation/navigation-common-ktx/api/2.4.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/navigation/navigation-common-ktx/api/public_plus_experimental_2.4.0-beta01.txt b/navigation/navigation-common-ktx/api/public_plus_experimental_2.4.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/navigation/navigation-common-ktx/api/public_plus_experimental_2.4.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/navigation/navigation-common-ktx/api/res-2.4.0-beta01.txt b/navigation/navigation-common-ktx/api/res-2.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/navigation-common-ktx/api/res-2.4.0-beta01.txt
diff --git a/navigation/navigation-common-ktx/api/restricted_2.4.0-beta01.txt b/navigation/navigation-common-ktx/api/restricted_2.4.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/navigation/navigation-common-ktx/api/restricted_2.4.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/navigation/navigation-common/api/2.4.0-beta01.txt b/navigation/navigation-common/api/2.4.0-beta01.txt
new file mode 100644
index 0000000..a169ec7
--- /dev/null
+++ b/navigation/navigation-common/api/2.4.0-beta01.txt
@@ -0,0 +1,522 @@
+// Signature format: 4.0
+package androidx.navigation {
+
+  public final class ActionOnlyNavDirections implements androidx.navigation.NavDirections {
+    ctor public ActionOnlyNavDirections(int actionId);
+    method public int component1();
+    method public androidx.navigation.ActionOnlyNavDirections copy(int actionId);
+    method public int getActionId();
+    method public android.os.Bundle getArguments();
+    property public int actionId;
+    property public android.os.Bundle arguments;
+  }
+
+  @androidx.navigation.NavOptionsDsl public final class AnimBuilder {
+    ctor public AnimBuilder();
+    method public int getEnter();
+    method public int getExit();
+    method public int getPopEnter();
+    method public int getPopExit();
+    method public void setEnter(int enter);
+    method public void setExit(int exit);
+    method public void setPopEnter(int popEnter);
+    method public void setPopExit(int popExit);
+    property public final int enter;
+    property public final int exit;
+    property public final int popEnter;
+    property public final int popExit;
+  }
+
+  public interface FloatingWindow {
+  }
+
+  public final class NamedNavArgument {
+    method public operator String component1();
+    method public operator androidx.navigation.NavArgument component2();
+    method public androidx.navigation.NavArgument getArgument();
+    method public String getName();
+    property public final androidx.navigation.NavArgument argument;
+    property public final String name;
+  }
+
+  public final class NamedNavArgumentKt {
+    method public static androidx.navigation.NamedNavArgument navArgument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> builder);
+  }
+
+  public final class NavAction {
+    ctor public NavAction(@IdRes int destinationId, optional androidx.navigation.NavOptions? navOptions, optional android.os.Bundle? defaultArguments);
+    ctor public NavAction(@IdRes int destinationId, optional androidx.navigation.NavOptions? navOptions);
+    ctor public NavAction(@IdRes int destinationId);
+    method public android.os.Bundle? getDefaultArguments();
+    method public int getDestinationId();
+    method public androidx.navigation.NavOptions? getNavOptions();
+    method public void setDefaultArguments(android.os.Bundle? defaultArguments);
+    method public void setNavOptions(androidx.navigation.NavOptions? navOptions);
+    property public final android.os.Bundle? defaultArguments;
+    property public final int destinationId;
+    property public final androidx.navigation.NavOptions? navOptions;
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class NavActionBuilder {
+    ctor public NavActionBuilder();
+    method public java.util.Map<java.lang.String,java.lang.Object> getDefaultArguments();
+    method public int getDestinationId();
+    method public void navOptions(kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> optionsBuilder);
+    method public void setDestinationId(int destinationId);
+    property public final java.util.Map<java.lang.String,java.lang.Object> defaultArguments;
+    property public final int destinationId;
+  }
+
+  public interface NavArgs {
+  }
+
+  public final class NavArgsLazy<Args extends androidx.navigation.NavArgs> implements kotlin.Lazy<Args> {
+    ctor public NavArgsLazy(kotlin.reflect.KClass<Args> navArgsClass, kotlin.jvm.functions.Function0<android.os.Bundle> argumentProducer);
+    method public Args getValue();
+    method public boolean isInitialized();
+    property public Args value;
+  }
+
+  public final class NavArgsLazyKt {
+  }
+
+  public final class NavArgument {
+    method public Object? getDefaultValue();
+    method public androidx.navigation.NavType<java.lang.Object> getType();
+    method public boolean isDefaultValuePresent();
+    method public boolean isNullable();
+    property public final Object? defaultValue;
+    property public final boolean isDefaultValuePresent;
+    property public final boolean isNullable;
+    property public final androidx.navigation.NavType<java.lang.Object> type;
+  }
+
+  public static final class NavArgument.Builder {
+    ctor public NavArgument.Builder();
+    method public androidx.navigation.NavArgument build();
+    method public androidx.navigation.NavArgument.Builder setDefaultValue(Object? defaultValue);
+    method public androidx.navigation.NavArgument.Builder setIsNullable(boolean isNullable);
+    method public <T> androidx.navigation.NavArgument.Builder setType(androidx.navigation.NavType<T> type);
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class NavArgumentBuilder {
+    ctor public NavArgumentBuilder();
+    method public androidx.navigation.NavArgument build();
+    method public Object? getDefaultValue();
+    method public boolean getNullable();
+    method public androidx.navigation.NavType<?> getType();
+    method public void setDefaultValue(Object? value);
+    method public void setNullable(boolean value);
+    method public void setType(androidx.navigation.NavType<?> value);
+    property public final Object? defaultValue;
+    property public final boolean nullable;
+    property public final androidx.navigation.NavType<?> type;
+  }
+
+  public final class NavBackStackEntry implements androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
+    method public android.os.Bundle? getArguments();
+    method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
+    method public androidx.navigation.NavDestination getDestination();
+    method public String getId();
+    method public androidx.lifecycle.Lifecycle getLifecycle();
+    method public androidx.lifecycle.SavedStateHandle getSavedStateHandle();
+    method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+    method public androidx.lifecycle.ViewModelStore getViewModelStore();
+    property public final android.os.Bundle? arguments;
+    property public final androidx.navigation.NavDestination destination;
+    property public final String id;
+    property public final androidx.lifecycle.SavedStateHandle savedStateHandle;
+  }
+
+  public final class NavDeepLink {
+    method public String? getAction();
+    method public String? getMimeType();
+    method public String? getUriPattern();
+    property public final String? action;
+    property public final String? mimeType;
+    property public final String? uriPattern;
+  }
+
+  public static final class NavDeepLink.Builder {
+    method public androidx.navigation.NavDeepLink build();
+    method public static androidx.navigation.NavDeepLink.Builder fromAction(String action);
+    method public static androidx.navigation.NavDeepLink.Builder fromMimeType(String mimeType);
+    method public static androidx.navigation.NavDeepLink.Builder fromUriPattern(String uriPattern);
+    method public androidx.navigation.NavDeepLink.Builder setAction(String action);
+    method public androidx.navigation.NavDeepLink.Builder setMimeType(String mimeType);
+    method public androidx.navigation.NavDeepLink.Builder setUriPattern(String uriPattern);
+  }
+
+  @kotlin.DslMarker public @interface NavDeepLinkDsl {
+  }
+
+  @androidx.navigation.NavDeepLinkDsl public final class NavDeepLinkDslBuilder {
+    ctor public NavDeepLinkDslBuilder();
+    method public String? getAction();
+    method public String? getMimeType();
+    method public String? getUriPattern();
+    method public void setAction(String? p);
+    method public void setMimeType(String? mimeType);
+    method public void setUriPattern(String? uriPattern);
+    property public final String? action;
+    property public final String? mimeType;
+    property public final String? uriPattern;
+  }
+
+  public final class NavDeepLinkDslBuilderKt {
+    method public static androidx.navigation.NavDeepLink navDeepLink(kotlin.jvm.functions.Function1<? super androidx.navigation.NavDeepLinkDslBuilder,kotlin.Unit> deepLinkBuilder);
+  }
+
+  public class NavDeepLinkRequest {
+    method public String? getAction();
+    method public String? getMimeType();
+    method public android.net.Uri? getUri();
+    property public String? action;
+    property public String? mimeType;
+    property public android.net.Uri? uri;
+  }
+
+  public static final class NavDeepLinkRequest.Builder {
+    method public androidx.navigation.NavDeepLinkRequest build();
+    method public static androidx.navigation.NavDeepLinkRequest.Builder fromAction(String action);
+    method public static androidx.navigation.NavDeepLinkRequest.Builder fromMimeType(String mimeType);
+    method public static androidx.navigation.NavDeepLinkRequest.Builder fromUri(android.net.Uri uri);
+    method public androidx.navigation.NavDeepLinkRequest.Builder setAction(String action);
+    method public androidx.navigation.NavDeepLinkRequest.Builder setMimeType(String mimeType);
+    method public androidx.navigation.NavDeepLinkRequest.Builder setUri(android.net.Uri uri);
+    field public static final androidx.navigation.NavDeepLinkRequest.Builder.Companion Companion;
+  }
+
+  public static final class NavDeepLinkRequest.Builder.Companion {
+    method public androidx.navigation.NavDeepLinkRequest.Builder fromAction(String action);
+    method public androidx.navigation.NavDeepLinkRequest.Builder fromMimeType(String mimeType);
+    method public androidx.navigation.NavDeepLinkRequest.Builder fromUri(android.net.Uri uri);
+  }
+
+  public class NavDestination {
+    ctor public NavDestination(String navigatorName);
+    ctor public NavDestination(androidx.navigation.Navigator<? extends androidx.navigation.NavDestination> navigator);
+    method public final void addArgument(String argumentName, androidx.navigation.NavArgument argument);
+    method public final void addDeepLink(String uriPattern);
+    method public final void addDeepLink(androidx.navigation.NavDeepLink navDeepLink);
+    method public final androidx.navigation.NavAction? getAction(@IdRes int id);
+    method public final java.util.Map<java.lang.String,androidx.navigation.NavArgument> getArguments();
+    method public static final kotlin.sequences.Sequence<androidx.navigation.NavDestination> getHierarchy(androidx.navigation.NavDestination);
+    method @IdRes public final int getId();
+    method public final CharSequence? getLabel();
+    method public final String getNavigatorName();
+    method public final androidx.navigation.NavGraph? getParent();
+    method public final String? getRoute();
+    method public boolean hasDeepLink(android.net.Uri deepLink);
+    method public boolean hasDeepLink(androidx.navigation.NavDeepLinkRequest deepLinkRequest);
+    method @CallSuper public void onInflate(android.content.Context context, android.util.AttributeSet attrs);
+    method protected static final <C> Class<? extends C> parseClassFromName(android.content.Context context, String name, Class<? extends C> expectedClassType);
+    method public final void putAction(@IdRes int actionId, @IdRes int destId);
+    method public final void putAction(@IdRes int actionId, androidx.navigation.NavAction action);
+    method public final void removeAction(@IdRes int actionId);
+    method public final void removeArgument(String argumentName);
+    method public final void setId(@IdRes int id);
+    method public final void setLabel(CharSequence? label);
+    method public final void setRoute(String? route);
+    property public final java.util.Map<java.lang.String,androidx.navigation.NavArgument> arguments;
+    property @IdRes public final int id;
+    property public final CharSequence? label;
+    property public final String navigatorName;
+    property public final androidx.navigation.NavGraph? parent;
+    property public final String? route;
+    field public static final androidx.navigation.NavDestination.Companion Companion;
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public static @interface NavDestination.ClassType {
+    method public abstract kotlin.reflect.KClass<?> value();
+    property public abstract kotlin.reflect.KClass<?> value;
+  }
+
+  public static final class NavDestination.Companion {
+    method public kotlin.sequences.Sequence<androidx.navigation.NavDestination> getHierarchy(androidx.navigation.NavDestination);
+    method protected <C> Class<? extends C> parseClassFromName(android.content.Context context, String name, Class<? extends C> expectedClassType);
+    property public final kotlin.sequences.Sequence<androidx.navigation.NavDestination> hierarchy;
+  }
+
+  @androidx.navigation.NavDestinationDsl public class NavDestinationBuilder<D extends androidx.navigation.NavDestination> {
+    ctor @Deprecated public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, @IdRes int id);
+    ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, String? route);
+    method @Deprecated public final void action(int actionId, kotlin.jvm.functions.Function1<? super androidx.navigation.NavActionBuilder,kotlin.Unit> actionBuilder);
+    method public final void argument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> argumentBuilder);
+    method public D build();
+    method public final void deepLink(String uriPattern);
+    method public final void deepLink(kotlin.jvm.functions.Function1<? super androidx.navigation.NavDeepLinkDslBuilder,kotlin.Unit> navDeepLink);
+    method public final int getId();
+    method public final CharSequence? getLabel();
+    method protected final androidx.navigation.Navigator<? extends D> getNavigator();
+    method public final String? getRoute();
+    method public final void setLabel(CharSequence? label);
+    property public final int id;
+    property public final CharSequence? label;
+    property protected final androidx.navigation.Navigator<? extends D> navigator;
+    property public final String? route;
+  }
+
+  @kotlin.DslMarker public @interface NavDestinationDsl {
+  }
+
+  public interface NavDirections {
+    method @IdRes public int getActionId();
+    method public android.os.Bundle getArguments();
+    property @IdRes public abstract int actionId;
+    property public abstract android.os.Bundle arguments;
+  }
+
+  public class NavGraph extends androidx.navigation.NavDestination implements java.lang.Iterable<androidx.navigation.NavDestination> kotlin.jvm.internal.markers.KMappedMarker {
+    ctor public NavGraph(androidx.navigation.Navigator<? extends androidx.navigation.NavGraph> navGraphNavigator);
+    method public final void addAll(androidx.navigation.NavGraph other);
+    method public final void addDestination(androidx.navigation.NavDestination node);
+    method public final void addDestinations(java.util.Collection<? extends androidx.navigation.NavDestination> nodes);
+    method public final void addDestinations(androidx.navigation.NavDestination... nodes);
+    method public final void clear();
+    method public final androidx.navigation.NavDestination? findNode(@IdRes int resId);
+    method public final androidx.navigation.NavDestination? findNode(String? route);
+    method public static final androidx.navigation.NavDestination findStartDestination(androidx.navigation.NavGraph);
+    method @Deprecated @IdRes public final int getStartDestination();
+    method @IdRes public final int getStartDestinationId();
+    method public final String? getStartDestinationRoute();
+    method public final java.util.Iterator<androidx.navigation.NavDestination> iterator();
+    method public final void remove(androidx.navigation.NavDestination node);
+    method public final void setStartDestination(int startDestId);
+    method public final void setStartDestination(String startDestRoute);
+    property @IdRes public final int startDestinationId;
+    property public final String? startDestinationRoute;
+    field public static final androidx.navigation.NavGraph.Companion Companion;
+  }
+
+  public static final class NavGraph.Companion {
+    method public androidx.navigation.NavDestination findStartDestination(androidx.navigation.NavGraph);
+  }
+
+  @androidx.navigation.NavDestinationDsl public class NavGraphBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.NavGraph> {
+    ctor @Deprecated public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, @IdRes int id, @IdRes int startDestination);
+    ctor public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, String startDestination, String? route);
+    method public final void addDestination(androidx.navigation.NavDestination destination);
+    method public androidx.navigation.NavGraph build();
+    method public final <D extends androidx.navigation.NavDestination> void destination(androidx.navigation.NavDestinationBuilder<? extends D> navDestination);
+    method public final androidx.navigation.NavigatorProvider getProvider();
+    method public final operator void unaryPlus(androidx.navigation.NavDestination);
+    property public final androidx.navigation.NavigatorProvider provider;
+  }
+
+  public final class NavGraphBuilderKt {
+    method @Deprecated public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method @Deprecated public static inline void navigation(androidx.navigation.NavGraphBuilder, @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method public static inline void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+  }
+
+  public final class NavGraphKt {
+    method public static operator boolean contains(androidx.navigation.NavGraph, @IdRes int id);
+    method public static operator boolean contains(androidx.navigation.NavGraph, String route);
+    method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, @IdRes int id);
+    method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, String route);
+    method public static inline operator void minusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
+    method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
+    method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavGraph other);
+  }
+
+  @androidx.navigation.Navigator.Name("navigation") public class NavGraphNavigator extends androidx.navigation.Navigator<androidx.navigation.NavGraph> {
+    ctor public NavGraphNavigator(androidx.navigation.NavigatorProvider navigatorProvider);
+    method public androidx.navigation.NavGraph createDestination();
+  }
+
+  public final class NavOptions {
+    method @AnimRes @AnimatorRes public int getEnterAnim();
+    method @AnimRes @AnimatorRes public int getExitAnim();
+    method @AnimRes @AnimatorRes public int getPopEnterAnim();
+    method @AnimRes @AnimatorRes public int getPopExitAnim();
+    method @Deprecated @IdRes public int getPopUpTo();
+    method @IdRes public int getPopUpToId();
+    method public String? getPopUpToRoute();
+    method public boolean isPopUpToInclusive();
+    method public boolean shouldLaunchSingleTop();
+    method public boolean shouldPopUpToSaveState();
+    method public boolean shouldRestoreState();
+    property @AnimRes @AnimatorRes public final int enterAnim;
+    property @AnimRes @AnimatorRes public final int exitAnim;
+    property @AnimRes @AnimatorRes public final int popEnterAnim;
+    property @AnimRes @AnimatorRes public final int popExitAnim;
+    property @IdRes public final int popUpToId;
+    property public final String? popUpToRoute;
+  }
+
+  public static final class NavOptions.Builder {
+    ctor public NavOptions.Builder();
+    method public androidx.navigation.NavOptions build();
+    method public androidx.navigation.NavOptions.Builder setEnterAnim(@AnimRes @AnimatorRes int enterAnim);
+    method public androidx.navigation.NavOptions.Builder setExitAnim(@AnimRes @AnimatorRes int exitAnim);
+    method public androidx.navigation.NavOptions.Builder setLaunchSingleTop(boolean singleTop);
+    method public androidx.navigation.NavOptions.Builder setPopEnterAnim(@AnimRes @AnimatorRes int popEnterAnim);
+    method public androidx.navigation.NavOptions.Builder setPopExitAnim(@AnimRes @AnimatorRes int popExitAnim);
+    method public androidx.navigation.NavOptions.Builder setPopUpTo(@IdRes int destinationId, boolean inclusive, optional boolean saveState);
+    method public androidx.navigation.NavOptions.Builder setPopUpTo(@IdRes int destinationId, boolean inclusive);
+    method public androidx.navigation.NavOptions.Builder setPopUpTo(String? route, boolean inclusive, optional boolean saveState);
+    method public androidx.navigation.NavOptions.Builder setPopUpTo(String? route, boolean inclusive);
+    method public androidx.navigation.NavOptions.Builder setRestoreState(boolean restoreState);
+  }
+
+  @androidx.navigation.NavOptionsDsl public final class NavOptionsBuilder {
+    ctor public NavOptionsBuilder();
+    method public void anim(kotlin.jvm.functions.Function1<? super androidx.navigation.AnimBuilder,kotlin.Unit> animBuilder);
+    method public boolean getLaunchSingleTop();
+    method @Deprecated public int getPopUpTo();
+    method public int getPopUpToId();
+    method public String? getPopUpToRoute();
+    method public boolean getRestoreState();
+    method public void popUpTo(@IdRes int id, optional kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
+    method public void popUpTo(String route, optional kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
+    method public void setLaunchSingleTop(boolean launchSingleTop);
+    method @Deprecated public void setPopUpTo(int value);
+    method public void setRestoreState(boolean restoreState);
+    property public final boolean launchSingleTop;
+    property @Deprecated public final int popUpTo;
+    property public final int popUpToId;
+    property public final String? popUpToRoute;
+    property public final boolean restoreState;
+  }
+
+  public final class NavOptionsBuilderKt {
+    method public static androidx.navigation.NavOptions navOptions(kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> optionsBuilder);
+  }
+
+  @kotlin.DslMarker public @interface NavOptionsDsl {
+  }
+
+  public abstract class NavType<T> {
+    ctor public NavType(boolean isNullableAllowed);
+    method public static androidx.navigation.NavType<?> fromArgType(String? type, String? packageName);
+    method public abstract operator T? get(android.os.Bundle bundle, String key);
+    method public String getName();
+    method public boolean isNullableAllowed();
+    method public abstract T! parseValue(String value);
+    method public abstract void put(android.os.Bundle bundle, String key, T? value);
+    property public boolean isNullableAllowed;
+    property public String name;
+    field public static final androidx.navigation.NavType<boolean[]> BoolArrayType;
+    field public static final androidx.navigation.NavType<java.lang.Boolean> BoolType;
+    field public static final androidx.navigation.NavType.Companion Companion;
+    field public static final androidx.navigation.NavType<float[]> FloatArrayType;
+    field public static final androidx.navigation.NavType<java.lang.Float> FloatType;
+    field public static final androidx.navigation.NavType<int[]> IntArrayType;
+    field public static final androidx.navigation.NavType<java.lang.Integer> IntType;
+    field public static final androidx.navigation.NavType<long[]> LongArrayType;
+    field public static final androidx.navigation.NavType<java.lang.Long> LongType;
+    field public static final androidx.navigation.NavType<java.lang.Integer> ReferenceType;
+    field public static final androidx.navigation.NavType<java.lang.String[]> StringArrayType;
+    field public static final androidx.navigation.NavType<java.lang.String> StringType;
+  }
+
+  public static final class NavType.Companion {
+    method public androidx.navigation.NavType<?> fromArgType(String? type, String? packageName);
+  }
+
+  public static final class NavType.EnumType<D extends java.lang.Enum<?>> extends androidx.navigation.NavType.SerializableType<D> {
+    ctor public NavType.EnumType(Class<D> type);
+    property public String name;
+  }
+
+  public static final class NavType.ParcelableArrayType<D extends android.os.Parcelable> extends androidx.navigation.NavType<D[]> {
+    ctor public NavType.ParcelableArrayType(Class<D> type);
+    method public D![]? get(android.os.Bundle bundle, String key);
+    method public D![] parseValue(String value);
+    method public void put(android.os.Bundle bundle, String key, D![]? value);
+    property public String name;
+  }
+
+  public static final class NavType.ParcelableType<D> extends androidx.navigation.NavType<D> {
+    ctor public NavType.ParcelableType(Class<D> type);
+    method public D? get(android.os.Bundle bundle, String key);
+    method public D! parseValue(String value);
+    method public void put(android.os.Bundle bundle, String key, D? value);
+    property public String name;
+  }
+
+  public static final class NavType.SerializableArrayType<D extends java.io.Serializable> extends androidx.navigation.NavType<D[]> {
+    ctor public NavType.SerializableArrayType(Class<D> type);
+    method public D![]? get(android.os.Bundle bundle, String key);
+    method public D![] parseValue(String value);
+    method public void put(android.os.Bundle bundle, String key, D![]? value);
+    property public String name;
+  }
+
+  public static class NavType.SerializableType<D extends java.io.Serializable> extends androidx.navigation.NavType<D> {
+    ctor public NavType.SerializableType(Class<D> type);
+    method public D? get(android.os.Bundle bundle, String key);
+    method public D parseValue(String value);
+    method public void put(android.os.Bundle bundle, String key, D value);
+    property public String name;
+  }
+
+  public abstract class Navigator<D extends androidx.navigation.NavDestination> {
+    ctor public Navigator();
+    method public abstract D createDestination();
+    method protected final androidx.navigation.NavigatorState getState();
+    method public final boolean isAttached();
+    method public void navigate(java.util.List<androidx.navigation.NavBackStackEntry> entries, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
+    method public androidx.navigation.NavDestination? navigate(D destination, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
+    method @CallSuper public void onAttach(androidx.navigation.NavigatorState state);
+    method public void onLaunchSingleTop(androidx.navigation.NavBackStackEntry backStackEntry);
+    method public void onRestoreState(android.os.Bundle savedState);
+    method public android.os.Bundle? onSaveState();
+    method public void popBackStack(androidx.navigation.NavBackStackEntry popUpTo, boolean savedState);
+    method public boolean popBackStack();
+    property public final boolean isAttached;
+    property protected final androidx.navigation.NavigatorState state;
+  }
+
+  public static interface Navigator.Extras {
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public static @interface Navigator.Name {
+    method public abstract String value();
+    property public abstract String value;
+  }
+
+  public class NavigatorProvider {
+    ctor public NavigatorProvider();
+    method public final androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>? addNavigator(androidx.navigation.Navigator<? extends androidx.navigation.NavDestination> navigator);
+    method @CallSuper public androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>? addNavigator(String name, androidx.navigation.Navigator<? extends androidx.navigation.NavDestination> navigator);
+    method public final <T extends androidx.navigation.Navigator<?>> T getNavigator(Class<T> navigatorClass);
+    method @CallSuper public <T extends androidx.navigation.Navigator<?>> T getNavigator(String name);
+  }
+
+  public final class NavigatorProviderKt {
+    method public static inline operator <T extends androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>> T get(androidx.navigation.NavigatorProvider, String name);
+    method public static inline operator <T extends androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>> T get(androidx.navigation.NavigatorProvider, kotlin.reflect.KClass<T> clazz);
+    method public static inline operator void plusAssign(androidx.navigation.NavigatorProvider, androidx.navigation.Navigator<? extends androidx.navigation.NavDestination> navigator);
+    method public static inline operator androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>? set(androidx.navigation.NavigatorProvider, String name, androidx.navigation.Navigator<? extends androidx.navigation.NavDestination> navigator);
+  }
+
+  public abstract class NavigatorState {
+    ctor public NavigatorState();
+    method public abstract androidx.navigation.NavBackStackEntry createBackStackEntry(androidx.navigation.NavDestination destination, android.os.Bundle? arguments);
+    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 @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);
+    method public void pushWithTransition(androidx.navigation.NavBackStackEntry backStackEntry);
+    property public final kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.navigation.NavBackStackEntry>> backStack;
+    property public final kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.navigation.NavBackStackEntry>> transitionsInProgress;
+  }
+
+  @androidx.navigation.NavOptionsDsl public final class PopUpToBuilder {
+    ctor public PopUpToBuilder();
+    method public boolean getInclusive();
+    method public boolean getSaveState();
+    method public void setInclusive(boolean inclusive);
+    method public void setSaveState(boolean saveState);
+    property public final boolean inclusive;
+    property public final boolean saveState;
+  }
+
+}
+
diff --git a/navigation/navigation-common/api/current.txt b/navigation/navigation-common/api/current.txt
index 9dea391..a169ec7 100644
--- a/navigation/navigation-common/api/current.txt
+++ b/navigation/navigation-common/api/current.txt
@@ -40,7 +40,7 @@
   }
 
   public final class NamedNavArgumentKt {
-    method @androidx.navigation.NavDestinationDsl public static androidx.navigation.NamedNavArgument navArgument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> builder);
+    method public static androidx.navigation.NamedNavArgument navArgument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> builder);
   }
 
   public final class NavAction {
diff --git a/navigation/navigation-common/api/public_plus_experimental_2.4.0-beta01.txt b/navigation/navigation-common/api/public_plus_experimental_2.4.0-beta01.txt
new file mode 100644
index 0000000..a169ec7
--- /dev/null
+++ b/navigation/navigation-common/api/public_plus_experimental_2.4.0-beta01.txt
@@ -0,0 +1,522 @@
+// Signature format: 4.0
+package androidx.navigation {
+
+  public final class ActionOnlyNavDirections implements androidx.navigation.NavDirections {
+    ctor public ActionOnlyNavDirections(int actionId);
+    method public int component1();
+    method public androidx.navigation.ActionOnlyNavDirections copy(int actionId);
+    method public int getActionId();
+    method public android.os.Bundle getArguments();
+    property public int actionId;
+    property public android.os.Bundle arguments;
+  }
+
+  @androidx.navigation.NavOptionsDsl public final class AnimBuilder {
+    ctor public AnimBuilder();
+    method public int getEnter();
+    method public int getExit();
+    method public int getPopEnter();
+    method public int getPopExit();
+    method public void setEnter(int enter);
+    method public void setExit(int exit);
+    method public void setPopEnter(int popEnter);
+    method public void setPopExit(int popExit);
+    property public final int enter;
+    property public final int exit;
+    property public final int popEnter;
+    property public final int popExit;
+  }
+
+  public interface FloatingWindow {
+  }
+
+  public final class NamedNavArgument {
+    method public operator String component1();
+    method public operator androidx.navigation.NavArgument component2();
+    method public androidx.navigation.NavArgument getArgument();
+    method public String getName();
+    property public final androidx.navigation.NavArgument argument;
+    property public final String name;
+  }
+
+  public final class NamedNavArgumentKt {
+    method public static androidx.navigation.NamedNavArgument navArgument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> builder);
+  }
+
+  public final class NavAction {
+    ctor public NavAction(@IdRes int destinationId, optional androidx.navigation.NavOptions? navOptions, optional android.os.Bundle? defaultArguments);
+    ctor public NavAction(@IdRes int destinationId, optional androidx.navigation.NavOptions? navOptions);
+    ctor public NavAction(@IdRes int destinationId);
+    method public android.os.Bundle? getDefaultArguments();
+    method public int getDestinationId();
+    method public androidx.navigation.NavOptions? getNavOptions();
+    method public void setDefaultArguments(android.os.Bundle? defaultArguments);
+    method public void setNavOptions(androidx.navigation.NavOptions? navOptions);
+    property public final android.os.Bundle? defaultArguments;
+    property public final int destinationId;
+    property public final androidx.navigation.NavOptions? navOptions;
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class NavActionBuilder {
+    ctor public NavActionBuilder();
+    method public java.util.Map<java.lang.String,java.lang.Object> getDefaultArguments();
+    method public int getDestinationId();
+    method public void navOptions(kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> optionsBuilder);
+    method public void setDestinationId(int destinationId);
+    property public final java.util.Map<java.lang.String,java.lang.Object> defaultArguments;
+    property public final int destinationId;
+  }
+
+  public interface NavArgs {
+  }
+
+  public final class NavArgsLazy<Args extends androidx.navigation.NavArgs> implements kotlin.Lazy<Args> {
+    ctor public NavArgsLazy(kotlin.reflect.KClass<Args> navArgsClass, kotlin.jvm.functions.Function0<android.os.Bundle> argumentProducer);
+    method public Args getValue();
+    method public boolean isInitialized();
+    property public Args value;
+  }
+
+  public final class NavArgsLazyKt {
+  }
+
+  public final class NavArgument {
+    method public Object? getDefaultValue();
+    method public androidx.navigation.NavType<java.lang.Object> getType();
+    method public boolean isDefaultValuePresent();
+    method public boolean isNullable();
+    property public final Object? defaultValue;
+    property public final boolean isDefaultValuePresent;
+    property public final boolean isNullable;
+    property public final androidx.navigation.NavType<java.lang.Object> type;
+  }
+
+  public static final class NavArgument.Builder {
+    ctor public NavArgument.Builder();
+    method public androidx.navigation.NavArgument build();
+    method public androidx.navigation.NavArgument.Builder setDefaultValue(Object? defaultValue);
+    method public androidx.navigation.NavArgument.Builder setIsNullable(boolean isNullable);
+    method public <T> androidx.navigation.NavArgument.Builder setType(androidx.navigation.NavType<T> type);
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class NavArgumentBuilder {
+    ctor public NavArgumentBuilder();
+    method public androidx.navigation.NavArgument build();
+    method public Object? getDefaultValue();
+    method public boolean getNullable();
+    method public androidx.navigation.NavType<?> getType();
+    method public void setDefaultValue(Object? value);
+    method public void setNullable(boolean value);
+    method public void setType(androidx.navigation.NavType<?> value);
+    property public final Object? defaultValue;
+    property public final boolean nullable;
+    property public final androidx.navigation.NavType<?> type;
+  }
+
+  public final class NavBackStackEntry implements androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
+    method public android.os.Bundle? getArguments();
+    method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
+    method public androidx.navigation.NavDestination getDestination();
+    method public String getId();
+    method public androidx.lifecycle.Lifecycle getLifecycle();
+    method public androidx.lifecycle.SavedStateHandle getSavedStateHandle();
+    method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+    method public androidx.lifecycle.ViewModelStore getViewModelStore();
+    property public final android.os.Bundle? arguments;
+    property public final androidx.navigation.NavDestination destination;
+    property public final String id;
+    property public final androidx.lifecycle.SavedStateHandle savedStateHandle;
+  }
+
+  public final class NavDeepLink {
+    method public String? getAction();
+    method public String? getMimeType();
+    method public String? getUriPattern();
+    property public final String? action;
+    property public final String? mimeType;
+    property public final String? uriPattern;
+  }
+
+  public static final class NavDeepLink.Builder {
+    method public androidx.navigation.NavDeepLink build();
+    method public static androidx.navigation.NavDeepLink.Builder fromAction(String action);
+    method public static androidx.navigation.NavDeepLink.Builder fromMimeType(String mimeType);
+    method public static androidx.navigation.NavDeepLink.Builder fromUriPattern(String uriPattern);
+    method public androidx.navigation.NavDeepLink.Builder setAction(String action);
+    method public androidx.navigation.NavDeepLink.Builder setMimeType(String mimeType);
+    method public androidx.navigation.NavDeepLink.Builder setUriPattern(String uriPattern);
+  }
+
+  @kotlin.DslMarker public @interface NavDeepLinkDsl {
+  }
+
+  @androidx.navigation.NavDeepLinkDsl public final class NavDeepLinkDslBuilder {
+    ctor public NavDeepLinkDslBuilder();
+    method public String? getAction();
+    method public String? getMimeType();
+    method public String? getUriPattern();
+    method public void setAction(String? p);
+    method public void setMimeType(String? mimeType);
+    method public void setUriPattern(String? uriPattern);
+    property public final String? action;
+    property public final String? mimeType;
+    property public final String? uriPattern;
+  }
+
+  public final class NavDeepLinkDslBuilderKt {
+    method public static androidx.navigation.NavDeepLink navDeepLink(kotlin.jvm.functions.Function1<? super androidx.navigation.NavDeepLinkDslBuilder,kotlin.Unit> deepLinkBuilder);
+  }
+
+  public class NavDeepLinkRequest {
+    method public String? getAction();
+    method public String? getMimeType();
+    method public android.net.Uri? getUri();
+    property public String? action;
+    property public String? mimeType;
+    property public android.net.Uri? uri;
+  }
+
+  public static final class NavDeepLinkRequest.Builder {
+    method public androidx.navigation.NavDeepLinkRequest build();
+    method public static androidx.navigation.NavDeepLinkRequest.Builder fromAction(String action);
+    method public static androidx.navigation.NavDeepLinkRequest.Builder fromMimeType(String mimeType);
+    method public static androidx.navigation.NavDeepLinkRequest.Builder fromUri(android.net.Uri uri);
+    method public androidx.navigation.NavDeepLinkRequest.Builder setAction(String action);
+    method public androidx.navigation.NavDeepLinkRequest.Builder setMimeType(String mimeType);
+    method public androidx.navigation.NavDeepLinkRequest.Builder setUri(android.net.Uri uri);
+    field public static final androidx.navigation.NavDeepLinkRequest.Builder.Companion Companion;
+  }
+
+  public static final class NavDeepLinkRequest.Builder.Companion {
+    method public androidx.navigation.NavDeepLinkRequest.Builder fromAction(String action);
+    method public androidx.navigation.NavDeepLinkRequest.Builder fromMimeType(String mimeType);
+    method public androidx.navigation.NavDeepLinkRequest.Builder fromUri(android.net.Uri uri);
+  }
+
+  public class NavDestination {
+    ctor public NavDestination(String navigatorName);
+    ctor public NavDestination(androidx.navigation.Navigator<? extends androidx.navigation.NavDestination> navigator);
+    method public final void addArgument(String argumentName, androidx.navigation.NavArgument argument);
+    method public final void addDeepLink(String uriPattern);
+    method public final void addDeepLink(androidx.navigation.NavDeepLink navDeepLink);
+    method public final androidx.navigation.NavAction? getAction(@IdRes int id);
+    method public final java.util.Map<java.lang.String,androidx.navigation.NavArgument> getArguments();
+    method public static final kotlin.sequences.Sequence<androidx.navigation.NavDestination> getHierarchy(androidx.navigation.NavDestination);
+    method @IdRes public final int getId();
+    method public final CharSequence? getLabel();
+    method public final String getNavigatorName();
+    method public final androidx.navigation.NavGraph? getParent();
+    method public final String? getRoute();
+    method public boolean hasDeepLink(android.net.Uri deepLink);
+    method public boolean hasDeepLink(androidx.navigation.NavDeepLinkRequest deepLinkRequest);
+    method @CallSuper public void onInflate(android.content.Context context, android.util.AttributeSet attrs);
+    method protected static final <C> Class<? extends C> parseClassFromName(android.content.Context context, String name, Class<? extends C> expectedClassType);
+    method public final void putAction(@IdRes int actionId, @IdRes int destId);
+    method public final void putAction(@IdRes int actionId, androidx.navigation.NavAction action);
+    method public final void removeAction(@IdRes int actionId);
+    method public final void removeArgument(String argumentName);
+    method public final void setId(@IdRes int id);
+    method public final void setLabel(CharSequence? label);
+    method public final void setRoute(String? route);
+    property public final java.util.Map<java.lang.String,androidx.navigation.NavArgument> arguments;
+    property @IdRes public final int id;
+    property public final CharSequence? label;
+    property public final String navigatorName;
+    property public final androidx.navigation.NavGraph? parent;
+    property public final String? route;
+    field public static final androidx.navigation.NavDestination.Companion Companion;
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public static @interface NavDestination.ClassType {
+    method public abstract kotlin.reflect.KClass<?> value();
+    property public abstract kotlin.reflect.KClass<?> value;
+  }
+
+  public static final class NavDestination.Companion {
+    method public kotlin.sequences.Sequence<androidx.navigation.NavDestination> getHierarchy(androidx.navigation.NavDestination);
+    method protected <C> Class<? extends C> parseClassFromName(android.content.Context context, String name, Class<? extends C> expectedClassType);
+    property public final kotlin.sequences.Sequence<androidx.navigation.NavDestination> hierarchy;
+  }
+
+  @androidx.navigation.NavDestinationDsl public class NavDestinationBuilder<D extends androidx.navigation.NavDestination> {
+    ctor @Deprecated public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, @IdRes int id);
+    ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, String? route);
+    method @Deprecated public final void action(int actionId, kotlin.jvm.functions.Function1<? super androidx.navigation.NavActionBuilder,kotlin.Unit> actionBuilder);
+    method public final void argument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> argumentBuilder);
+    method public D build();
+    method public final void deepLink(String uriPattern);
+    method public final void deepLink(kotlin.jvm.functions.Function1<? super androidx.navigation.NavDeepLinkDslBuilder,kotlin.Unit> navDeepLink);
+    method public final int getId();
+    method public final CharSequence? getLabel();
+    method protected final androidx.navigation.Navigator<? extends D> getNavigator();
+    method public final String? getRoute();
+    method public final void setLabel(CharSequence? label);
+    property public final int id;
+    property public final CharSequence? label;
+    property protected final androidx.navigation.Navigator<? extends D> navigator;
+    property public final String? route;
+  }
+
+  @kotlin.DslMarker public @interface NavDestinationDsl {
+  }
+
+  public interface NavDirections {
+    method @IdRes public int getActionId();
+    method public android.os.Bundle getArguments();
+    property @IdRes public abstract int actionId;
+    property public abstract android.os.Bundle arguments;
+  }
+
+  public class NavGraph extends androidx.navigation.NavDestination implements java.lang.Iterable<androidx.navigation.NavDestination> kotlin.jvm.internal.markers.KMappedMarker {
+    ctor public NavGraph(androidx.navigation.Navigator<? extends androidx.navigation.NavGraph> navGraphNavigator);
+    method public final void addAll(androidx.navigation.NavGraph other);
+    method public final void addDestination(androidx.navigation.NavDestination node);
+    method public final void addDestinations(java.util.Collection<? extends androidx.navigation.NavDestination> nodes);
+    method public final void addDestinations(androidx.navigation.NavDestination... nodes);
+    method public final void clear();
+    method public final androidx.navigation.NavDestination? findNode(@IdRes int resId);
+    method public final androidx.navigation.NavDestination? findNode(String? route);
+    method public static final androidx.navigation.NavDestination findStartDestination(androidx.navigation.NavGraph);
+    method @Deprecated @IdRes public final int getStartDestination();
+    method @IdRes public final int getStartDestinationId();
+    method public final String? getStartDestinationRoute();
+    method public final java.util.Iterator<androidx.navigation.NavDestination> iterator();
+    method public final void remove(androidx.navigation.NavDestination node);
+    method public final void setStartDestination(int startDestId);
+    method public final void setStartDestination(String startDestRoute);
+    property @IdRes public final int startDestinationId;
+    property public final String? startDestinationRoute;
+    field public static final androidx.navigation.NavGraph.Companion Companion;
+  }
+
+  public static final class NavGraph.Companion {
+    method public androidx.navigation.NavDestination findStartDestination(androidx.navigation.NavGraph);
+  }
+
+  @androidx.navigation.NavDestinationDsl public class NavGraphBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.NavGraph> {
+    ctor @Deprecated public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, @IdRes int id, @IdRes int startDestination);
+    ctor public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, String startDestination, String? route);
+    method public final void addDestination(androidx.navigation.NavDestination destination);
+    method public androidx.navigation.NavGraph build();
+    method public final <D extends androidx.navigation.NavDestination> void destination(androidx.navigation.NavDestinationBuilder<? extends D> navDestination);
+    method public final androidx.navigation.NavigatorProvider getProvider();
+    method public final operator void unaryPlus(androidx.navigation.NavDestination);
+    property public final androidx.navigation.NavigatorProvider provider;
+  }
+
+  public final class NavGraphBuilderKt {
+    method @Deprecated public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method @Deprecated public static inline void navigation(androidx.navigation.NavGraphBuilder, @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method public static inline void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+  }
+
+  public final class NavGraphKt {
+    method public static operator boolean contains(androidx.navigation.NavGraph, @IdRes int id);
+    method public static operator boolean contains(androidx.navigation.NavGraph, String route);
+    method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, @IdRes int id);
+    method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, String route);
+    method public static inline operator void minusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
+    method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
+    method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavGraph other);
+  }
+
+  @androidx.navigation.Navigator.Name("navigation") public class NavGraphNavigator extends androidx.navigation.Navigator<androidx.navigation.NavGraph> {
+    ctor public NavGraphNavigator(androidx.navigation.NavigatorProvider navigatorProvider);
+    method public androidx.navigation.NavGraph createDestination();
+  }
+
+  public final class NavOptions {
+    method @AnimRes @AnimatorRes public int getEnterAnim();
+    method @AnimRes @AnimatorRes public int getExitAnim();
+    method @AnimRes @AnimatorRes public int getPopEnterAnim();
+    method @AnimRes @AnimatorRes public int getPopExitAnim();
+    method @Deprecated @IdRes public int getPopUpTo();
+    method @IdRes public int getPopUpToId();
+    method public String? getPopUpToRoute();
+    method public boolean isPopUpToInclusive();
+    method public boolean shouldLaunchSingleTop();
+    method public boolean shouldPopUpToSaveState();
+    method public boolean shouldRestoreState();
+    property @AnimRes @AnimatorRes public final int enterAnim;
+    property @AnimRes @AnimatorRes public final int exitAnim;
+    property @AnimRes @AnimatorRes public final int popEnterAnim;
+    property @AnimRes @AnimatorRes public final int popExitAnim;
+    property @IdRes public final int popUpToId;
+    property public final String? popUpToRoute;
+  }
+
+  public static final class NavOptions.Builder {
+    ctor public NavOptions.Builder();
+    method public androidx.navigation.NavOptions build();
+    method public androidx.navigation.NavOptions.Builder setEnterAnim(@AnimRes @AnimatorRes int enterAnim);
+    method public androidx.navigation.NavOptions.Builder setExitAnim(@AnimRes @AnimatorRes int exitAnim);
+    method public androidx.navigation.NavOptions.Builder setLaunchSingleTop(boolean singleTop);
+    method public androidx.navigation.NavOptions.Builder setPopEnterAnim(@AnimRes @AnimatorRes int popEnterAnim);
+    method public androidx.navigation.NavOptions.Builder setPopExitAnim(@AnimRes @AnimatorRes int popExitAnim);
+    method public androidx.navigation.NavOptions.Builder setPopUpTo(@IdRes int destinationId, boolean inclusive, optional boolean saveState);
+    method public androidx.navigation.NavOptions.Builder setPopUpTo(@IdRes int destinationId, boolean inclusive);
+    method public androidx.navigation.NavOptions.Builder setPopUpTo(String? route, boolean inclusive, optional boolean saveState);
+    method public androidx.navigation.NavOptions.Builder setPopUpTo(String? route, boolean inclusive);
+    method public androidx.navigation.NavOptions.Builder setRestoreState(boolean restoreState);
+  }
+
+  @androidx.navigation.NavOptionsDsl public final class NavOptionsBuilder {
+    ctor public NavOptionsBuilder();
+    method public void anim(kotlin.jvm.functions.Function1<? super androidx.navigation.AnimBuilder,kotlin.Unit> animBuilder);
+    method public boolean getLaunchSingleTop();
+    method @Deprecated public int getPopUpTo();
+    method public int getPopUpToId();
+    method public String? getPopUpToRoute();
+    method public boolean getRestoreState();
+    method public void popUpTo(@IdRes int id, optional kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
+    method public void popUpTo(String route, optional kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
+    method public void setLaunchSingleTop(boolean launchSingleTop);
+    method @Deprecated public void setPopUpTo(int value);
+    method public void setRestoreState(boolean restoreState);
+    property public final boolean launchSingleTop;
+    property @Deprecated public final int popUpTo;
+    property public final int popUpToId;
+    property public final String? popUpToRoute;
+    property public final boolean restoreState;
+  }
+
+  public final class NavOptionsBuilderKt {
+    method public static androidx.navigation.NavOptions navOptions(kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> optionsBuilder);
+  }
+
+  @kotlin.DslMarker public @interface NavOptionsDsl {
+  }
+
+  public abstract class NavType<T> {
+    ctor public NavType(boolean isNullableAllowed);
+    method public static androidx.navigation.NavType<?> fromArgType(String? type, String? packageName);
+    method public abstract operator T? get(android.os.Bundle bundle, String key);
+    method public String getName();
+    method public boolean isNullableAllowed();
+    method public abstract T! parseValue(String value);
+    method public abstract void put(android.os.Bundle bundle, String key, T? value);
+    property public boolean isNullableAllowed;
+    property public String name;
+    field public static final androidx.navigation.NavType<boolean[]> BoolArrayType;
+    field public static final androidx.navigation.NavType<java.lang.Boolean> BoolType;
+    field public static final androidx.navigation.NavType.Companion Companion;
+    field public static final androidx.navigation.NavType<float[]> FloatArrayType;
+    field public static final androidx.navigation.NavType<java.lang.Float> FloatType;
+    field public static final androidx.navigation.NavType<int[]> IntArrayType;
+    field public static final androidx.navigation.NavType<java.lang.Integer> IntType;
+    field public static final androidx.navigation.NavType<long[]> LongArrayType;
+    field public static final androidx.navigation.NavType<java.lang.Long> LongType;
+    field public static final androidx.navigation.NavType<java.lang.Integer> ReferenceType;
+    field public static final androidx.navigation.NavType<java.lang.String[]> StringArrayType;
+    field public static final androidx.navigation.NavType<java.lang.String> StringType;
+  }
+
+  public static final class NavType.Companion {
+    method public androidx.navigation.NavType<?> fromArgType(String? type, String? packageName);
+  }
+
+  public static final class NavType.EnumType<D extends java.lang.Enum<?>> extends androidx.navigation.NavType.SerializableType<D> {
+    ctor public NavType.EnumType(Class<D> type);
+    property public String name;
+  }
+
+  public static final class NavType.ParcelableArrayType<D extends android.os.Parcelable> extends androidx.navigation.NavType<D[]> {
+    ctor public NavType.ParcelableArrayType(Class<D> type);
+    method public D![]? get(android.os.Bundle bundle, String key);
+    method public D![] parseValue(String value);
+    method public void put(android.os.Bundle bundle, String key, D![]? value);
+    property public String name;
+  }
+
+  public static final class NavType.ParcelableType<D> extends androidx.navigation.NavType<D> {
+    ctor public NavType.ParcelableType(Class<D> type);
+    method public D? get(android.os.Bundle bundle, String key);
+    method public D! parseValue(String value);
+    method public void put(android.os.Bundle bundle, String key, D? value);
+    property public String name;
+  }
+
+  public static final class NavType.SerializableArrayType<D extends java.io.Serializable> extends androidx.navigation.NavType<D[]> {
+    ctor public NavType.SerializableArrayType(Class<D> type);
+    method public D![]? get(android.os.Bundle bundle, String key);
+    method public D![] parseValue(String value);
+    method public void put(android.os.Bundle bundle, String key, D![]? value);
+    property public String name;
+  }
+
+  public static class NavType.SerializableType<D extends java.io.Serializable> extends androidx.navigation.NavType<D> {
+    ctor public NavType.SerializableType(Class<D> type);
+    method public D? get(android.os.Bundle bundle, String key);
+    method public D parseValue(String value);
+    method public void put(android.os.Bundle bundle, String key, D value);
+    property public String name;
+  }
+
+  public abstract class Navigator<D extends androidx.navigation.NavDestination> {
+    ctor public Navigator();
+    method public abstract D createDestination();
+    method protected final androidx.navigation.NavigatorState getState();
+    method public final boolean isAttached();
+    method public void navigate(java.util.List<androidx.navigation.NavBackStackEntry> entries, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
+    method public androidx.navigation.NavDestination? navigate(D destination, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
+    method @CallSuper public void onAttach(androidx.navigation.NavigatorState state);
+    method public void onLaunchSingleTop(androidx.navigation.NavBackStackEntry backStackEntry);
+    method public void onRestoreState(android.os.Bundle savedState);
+    method public android.os.Bundle? onSaveState();
+    method public void popBackStack(androidx.navigation.NavBackStackEntry popUpTo, boolean savedState);
+    method public boolean popBackStack();
+    property public final boolean isAttached;
+    property protected final androidx.navigation.NavigatorState state;
+  }
+
+  public static interface Navigator.Extras {
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public static @interface Navigator.Name {
+    method public abstract String value();
+    property public abstract String value;
+  }
+
+  public class NavigatorProvider {
+    ctor public NavigatorProvider();
+    method public final androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>? addNavigator(androidx.navigation.Navigator<? extends androidx.navigation.NavDestination> navigator);
+    method @CallSuper public androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>? addNavigator(String name, androidx.navigation.Navigator<? extends androidx.navigation.NavDestination> navigator);
+    method public final <T extends androidx.navigation.Navigator<?>> T getNavigator(Class<T> navigatorClass);
+    method @CallSuper public <T extends androidx.navigation.Navigator<?>> T getNavigator(String name);
+  }
+
+  public final class NavigatorProviderKt {
+    method public static inline operator <T extends androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>> T get(androidx.navigation.NavigatorProvider, String name);
+    method public static inline operator <T extends androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>> T get(androidx.navigation.NavigatorProvider, kotlin.reflect.KClass<T> clazz);
+    method public static inline operator void plusAssign(androidx.navigation.NavigatorProvider, androidx.navigation.Navigator<? extends androidx.navigation.NavDestination> navigator);
+    method public static inline operator androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>? set(androidx.navigation.NavigatorProvider, String name, androidx.navigation.Navigator<? extends androidx.navigation.NavDestination> navigator);
+  }
+
+  public abstract class NavigatorState {
+    ctor public NavigatorState();
+    method public abstract androidx.navigation.NavBackStackEntry createBackStackEntry(androidx.navigation.NavDestination destination, android.os.Bundle? arguments);
+    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 @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);
+    method public void pushWithTransition(androidx.navigation.NavBackStackEntry backStackEntry);
+    property public final kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.navigation.NavBackStackEntry>> backStack;
+    property public final kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.navigation.NavBackStackEntry>> transitionsInProgress;
+  }
+
+  @androidx.navigation.NavOptionsDsl public final class PopUpToBuilder {
+    ctor public PopUpToBuilder();
+    method public boolean getInclusive();
+    method public boolean getSaveState();
+    method public void setInclusive(boolean inclusive);
+    method public void setSaveState(boolean saveState);
+    property public final boolean inclusive;
+    property public final boolean saveState;
+  }
+
+}
+
diff --git a/navigation/navigation-common/api/public_plus_experimental_current.txt b/navigation/navigation-common/api/public_plus_experimental_current.txt
index 9dea391..a169ec7 100644
--- a/navigation/navigation-common/api/public_plus_experimental_current.txt
+++ b/navigation/navigation-common/api/public_plus_experimental_current.txt
@@ -40,7 +40,7 @@
   }
 
   public final class NamedNavArgumentKt {
-    method @androidx.navigation.NavDestinationDsl public static androidx.navigation.NamedNavArgument navArgument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> builder);
+    method public static androidx.navigation.NamedNavArgument navArgument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> builder);
   }
 
   public final class NavAction {
diff --git a/navigation/navigation-common/api/res-2.4.0-beta01.txt b/navigation/navigation-common/api/res-2.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/navigation-common/api/res-2.4.0-beta01.txt
diff --git a/navigation/navigation-common/api/restricted_2.4.0-beta01.txt b/navigation/navigation-common/api/restricted_2.4.0-beta01.txt
new file mode 100644
index 0000000..a169ec7
--- /dev/null
+++ b/navigation/navigation-common/api/restricted_2.4.0-beta01.txt
@@ -0,0 +1,522 @@
+// Signature format: 4.0
+package androidx.navigation {
+
+  public final class ActionOnlyNavDirections implements androidx.navigation.NavDirections {
+    ctor public ActionOnlyNavDirections(int actionId);
+    method public int component1();
+    method public androidx.navigation.ActionOnlyNavDirections copy(int actionId);
+    method public int getActionId();
+    method public android.os.Bundle getArguments();
+    property public int actionId;
+    property public android.os.Bundle arguments;
+  }
+
+  @androidx.navigation.NavOptionsDsl public final class AnimBuilder {
+    ctor public AnimBuilder();
+    method public int getEnter();
+    method public int getExit();
+    method public int getPopEnter();
+    method public int getPopExit();
+    method public void setEnter(int enter);
+    method public void setExit(int exit);
+    method public void setPopEnter(int popEnter);
+    method public void setPopExit(int popExit);
+    property public final int enter;
+    property public final int exit;
+    property public final int popEnter;
+    property public final int popExit;
+  }
+
+  public interface FloatingWindow {
+  }
+
+  public final class NamedNavArgument {
+    method public operator String component1();
+    method public operator androidx.navigation.NavArgument component2();
+    method public androidx.navigation.NavArgument getArgument();
+    method public String getName();
+    property public final androidx.navigation.NavArgument argument;
+    property public final String name;
+  }
+
+  public final class NamedNavArgumentKt {
+    method public static androidx.navigation.NamedNavArgument navArgument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> builder);
+  }
+
+  public final class NavAction {
+    ctor public NavAction(@IdRes int destinationId, optional androidx.navigation.NavOptions? navOptions, optional android.os.Bundle? defaultArguments);
+    ctor public NavAction(@IdRes int destinationId, optional androidx.navigation.NavOptions? navOptions);
+    ctor public NavAction(@IdRes int destinationId);
+    method public android.os.Bundle? getDefaultArguments();
+    method public int getDestinationId();
+    method public androidx.navigation.NavOptions? getNavOptions();
+    method public void setDefaultArguments(android.os.Bundle? defaultArguments);
+    method public void setNavOptions(androidx.navigation.NavOptions? navOptions);
+    property public final android.os.Bundle? defaultArguments;
+    property public final int destinationId;
+    property public final androidx.navigation.NavOptions? navOptions;
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class NavActionBuilder {
+    ctor public NavActionBuilder();
+    method public java.util.Map<java.lang.String,java.lang.Object> getDefaultArguments();
+    method public int getDestinationId();
+    method public void navOptions(kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> optionsBuilder);
+    method public void setDestinationId(int destinationId);
+    property public final java.util.Map<java.lang.String,java.lang.Object> defaultArguments;
+    property public final int destinationId;
+  }
+
+  public interface NavArgs {
+  }
+
+  public final class NavArgsLazy<Args extends androidx.navigation.NavArgs> implements kotlin.Lazy<Args> {
+    ctor public NavArgsLazy(kotlin.reflect.KClass<Args> navArgsClass, kotlin.jvm.functions.Function0<android.os.Bundle> argumentProducer);
+    method public Args getValue();
+    method public boolean isInitialized();
+    property public Args value;
+  }
+
+  public final class NavArgsLazyKt {
+  }
+
+  public final class NavArgument {
+    method public Object? getDefaultValue();
+    method public androidx.navigation.NavType<java.lang.Object> getType();
+    method public boolean isDefaultValuePresent();
+    method public boolean isNullable();
+    property public final Object? defaultValue;
+    property public final boolean isDefaultValuePresent;
+    property public final boolean isNullable;
+    property public final androidx.navigation.NavType<java.lang.Object> type;
+  }
+
+  public static final class NavArgument.Builder {
+    ctor public NavArgument.Builder();
+    method public androidx.navigation.NavArgument build();
+    method public androidx.navigation.NavArgument.Builder setDefaultValue(Object? defaultValue);
+    method public androidx.navigation.NavArgument.Builder setIsNullable(boolean isNullable);
+    method public <T> androidx.navigation.NavArgument.Builder setType(androidx.navigation.NavType<T> type);
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class NavArgumentBuilder {
+    ctor public NavArgumentBuilder();
+    method public androidx.navigation.NavArgument build();
+    method public Object? getDefaultValue();
+    method public boolean getNullable();
+    method public androidx.navigation.NavType<?> getType();
+    method public void setDefaultValue(Object? value);
+    method public void setNullable(boolean value);
+    method public void setType(androidx.navigation.NavType<?> value);
+    property public final Object? defaultValue;
+    property public final boolean nullable;
+    property public final androidx.navigation.NavType<?> type;
+  }
+
+  public final class NavBackStackEntry implements androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
+    method public android.os.Bundle? getArguments();
+    method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
+    method public androidx.navigation.NavDestination getDestination();
+    method public String getId();
+    method public androidx.lifecycle.Lifecycle getLifecycle();
+    method public androidx.lifecycle.SavedStateHandle getSavedStateHandle();
+    method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+    method public androidx.lifecycle.ViewModelStore getViewModelStore();
+    property public final android.os.Bundle? arguments;
+    property public final androidx.navigation.NavDestination destination;
+    property public final String id;
+    property public final androidx.lifecycle.SavedStateHandle savedStateHandle;
+  }
+
+  public final class NavDeepLink {
+    method public String? getAction();
+    method public String? getMimeType();
+    method public String? getUriPattern();
+    property public final String? action;
+    property public final String? mimeType;
+    property public final String? uriPattern;
+  }
+
+  public static final class NavDeepLink.Builder {
+    method public androidx.navigation.NavDeepLink build();
+    method public static androidx.navigation.NavDeepLink.Builder fromAction(String action);
+    method public static androidx.navigation.NavDeepLink.Builder fromMimeType(String mimeType);
+    method public static androidx.navigation.NavDeepLink.Builder fromUriPattern(String uriPattern);
+    method public androidx.navigation.NavDeepLink.Builder setAction(String action);
+    method public androidx.navigation.NavDeepLink.Builder setMimeType(String mimeType);
+    method public androidx.navigation.NavDeepLink.Builder setUriPattern(String uriPattern);
+  }
+
+  @kotlin.DslMarker public @interface NavDeepLinkDsl {
+  }
+
+  @androidx.navigation.NavDeepLinkDsl public final class NavDeepLinkDslBuilder {
+    ctor public NavDeepLinkDslBuilder();
+    method public String? getAction();
+    method public String? getMimeType();
+    method public String? getUriPattern();
+    method public void setAction(String? p);
+    method public void setMimeType(String? mimeType);
+    method public void setUriPattern(String? uriPattern);
+    property public final String? action;
+    property public final String? mimeType;
+    property public final String? uriPattern;
+  }
+
+  public final class NavDeepLinkDslBuilderKt {
+    method public static androidx.navigation.NavDeepLink navDeepLink(kotlin.jvm.functions.Function1<? super androidx.navigation.NavDeepLinkDslBuilder,kotlin.Unit> deepLinkBuilder);
+  }
+
+  public class NavDeepLinkRequest {
+    method public String? getAction();
+    method public String? getMimeType();
+    method public android.net.Uri? getUri();
+    property public String? action;
+    property public String? mimeType;
+    property public android.net.Uri? uri;
+  }
+
+  public static final class NavDeepLinkRequest.Builder {
+    method public androidx.navigation.NavDeepLinkRequest build();
+    method public static androidx.navigation.NavDeepLinkRequest.Builder fromAction(String action);
+    method public static androidx.navigation.NavDeepLinkRequest.Builder fromMimeType(String mimeType);
+    method public static androidx.navigation.NavDeepLinkRequest.Builder fromUri(android.net.Uri uri);
+    method public androidx.navigation.NavDeepLinkRequest.Builder setAction(String action);
+    method public androidx.navigation.NavDeepLinkRequest.Builder setMimeType(String mimeType);
+    method public androidx.navigation.NavDeepLinkRequest.Builder setUri(android.net.Uri uri);
+    field public static final androidx.navigation.NavDeepLinkRequest.Builder.Companion Companion;
+  }
+
+  public static final class NavDeepLinkRequest.Builder.Companion {
+    method public androidx.navigation.NavDeepLinkRequest.Builder fromAction(String action);
+    method public androidx.navigation.NavDeepLinkRequest.Builder fromMimeType(String mimeType);
+    method public androidx.navigation.NavDeepLinkRequest.Builder fromUri(android.net.Uri uri);
+  }
+
+  public class NavDestination {
+    ctor public NavDestination(String navigatorName);
+    ctor public NavDestination(androidx.navigation.Navigator<? extends androidx.navigation.NavDestination> navigator);
+    method public final void addArgument(String argumentName, androidx.navigation.NavArgument argument);
+    method public final void addDeepLink(String uriPattern);
+    method public final void addDeepLink(androidx.navigation.NavDeepLink navDeepLink);
+    method public final androidx.navigation.NavAction? getAction(@IdRes int id);
+    method public final java.util.Map<java.lang.String,androidx.navigation.NavArgument> getArguments();
+    method public static final kotlin.sequences.Sequence<androidx.navigation.NavDestination> getHierarchy(androidx.navigation.NavDestination);
+    method @IdRes public final int getId();
+    method public final CharSequence? getLabel();
+    method public final String getNavigatorName();
+    method public final androidx.navigation.NavGraph? getParent();
+    method public final String? getRoute();
+    method public boolean hasDeepLink(android.net.Uri deepLink);
+    method public boolean hasDeepLink(androidx.navigation.NavDeepLinkRequest deepLinkRequest);
+    method @CallSuper public void onInflate(android.content.Context context, android.util.AttributeSet attrs);
+    method protected static final <C> Class<? extends C> parseClassFromName(android.content.Context context, String name, Class<? extends C> expectedClassType);
+    method public final void putAction(@IdRes int actionId, @IdRes int destId);
+    method public final void putAction(@IdRes int actionId, androidx.navigation.NavAction action);
+    method public final void removeAction(@IdRes int actionId);
+    method public final void removeArgument(String argumentName);
+    method public final void setId(@IdRes int id);
+    method public final void setLabel(CharSequence? label);
+    method public final void setRoute(String? route);
+    property public final java.util.Map<java.lang.String,androidx.navigation.NavArgument> arguments;
+    property @IdRes public final int id;
+    property public final CharSequence? label;
+    property public final String navigatorName;
+    property public final androidx.navigation.NavGraph? parent;
+    property public final String? route;
+    field public static final androidx.navigation.NavDestination.Companion Companion;
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public static @interface NavDestination.ClassType {
+    method public abstract kotlin.reflect.KClass<?> value();
+    property public abstract kotlin.reflect.KClass<?> value;
+  }
+
+  public static final class NavDestination.Companion {
+    method public kotlin.sequences.Sequence<androidx.navigation.NavDestination> getHierarchy(androidx.navigation.NavDestination);
+    method protected <C> Class<? extends C> parseClassFromName(android.content.Context context, String name, Class<? extends C> expectedClassType);
+    property public final kotlin.sequences.Sequence<androidx.navigation.NavDestination> hierarchy;
+  }
+
+  @androidx.navigation.NavDestinationDsl public class NavDestinationBuilder<D extends androidx.navigation.NavDestination> {
+    ctor @Deprecated public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, @IdRes int id);
+    ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, String? route);
+    method @Deprecated public final void action(int actionId, kotlin.jvm.functions.Function1<? super androidx.navigation.NavActionBuilder,kotlin.Unit> actionBuilder);
+    method public final void argument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> argumentBuilder);
+    method public D build();
+    method public final void deepLink(String uriPattern);
+    method public final void deepLink(kotlin.jvm.functions.Function1<? super androidx.navigation.NavDeepLinkDslBuilder,kotlin.Unit> navDeepLink);
+    method public final int getId();
+    method public final CharSequence? getLabel();
+    method protected final androidx.navigation.Navigator<? extends D> getNavigator();
+    method public final String? getRoute();
+    method public final void setLabel(CharSequence? label);
+    property public final int id;
+    property public final CharSequence? label;
+    property protected final androidx.navigation.Navigator<? extends D> navigator;
+    property public final String? route;
+  }
+
+  @kotlin.DslMarker public @interface NavDestinationDsl {
+  }
+
+  public interface NavDirections {
+    method @IdRes public int getActionId();
+    method public android.os.Bundle getArguments();
+    property @IdRes public abstract int actionId;
+    property public abstract android.os.Bundle arguments;
+  }
+
+  public class NavGraph extends androidx.navigation.NavDestination implements java.lang.Iterable<androidx.navigation.NavDestination> kotlin.jvm.internal.markers.KMappedMarker {
+    ctor public NavGraph(androidx.navigation.Navigator<? extends androidx.navigation.NavGraph> navGraphNavigator);
+    method public final void addAll(androidx.navigation.NavGraph other);
+    method public final void addDestination(androidx.navigation.NavDestination node);
+    method public final void addDestinations(java.util.Collection<? extends androidx.navigation.NavDestination> nodes);
+    method public final void addDestinations(androidx.navigation.NavDestination... nodes);
+    method public final void clear();
+    method public final androidx.navigation.NavDestination? findNode(@IdRes int resId);
+    method public final androidx.navigation.NavDestination? findNode(String? route);
+    method public static final androidx.navigation.NavDestination findStartDestination(androidx.navigation.NavGraph);
+    method @Deprecated @IdRes public final int getStartDestination();
+    method @IdRes public final int getStartDestinationId();
+    method public final String? getStartDestinationRoute();
+    method public final java.util.Iterator<androidx.navigation.NavDestination> iterator();
+    method public final void remove(androidx.navigation.NavDestination node);
+    method public final void setStartDestination(int startDestId);
+    method public final void setStartDestination(String startDestRoute);
+    property @IdRes public final int startDestinationId;
+    property public final String? startDestinationRoute;
+    field public static final androidx.navigation.NavGraph.Companion Companion;
+  }
+
+  public static final class NavGraph.Companion {
+    method public androidx.navigation.NavDestination findStartDestination(androidx.navigation.NavGraph);
+  }
+
+  @androidx.navigation.NavDestinationDsl public class NavGraphBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.NavGraph> {
+    ctor @Deprecated public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, @IdRes int id, @IdRes int startDestination);
+    ctor public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, String startDestination, String? route);
+    method public final void addDestination(androidx.navigation.NavDestination destination);
+    method public androidx.navigation.NavGraph build();
+    method public final <D extends androidx.navigation.NavDestination> void destination(androidx.navigation.NavDestinationBuilder<? extends D> navDestination);
+    method public final androidx.navigation.NavigatorProvider getProvider();
+    method public final operator void unaryPlus(androidx.navigation.NavDestination);
+    property public final androidx.navigation.NavigatorProvider provider;
+  }
+
+  public final class NavGraphBuilderKt {
+    method @Deprecated public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method @Deprecated public static inline void navigation(androidx.navigation.NavGraphBuilder, @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method public static inline void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+  }
+
+  public final class NavGraphKt {
+    method public static operator boolean contains(androidx.navigation.NavGraph, @IdRes int id);
+    method public static operator boolean contains(androidx.navigation.NavGraph, String route);
+    method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, @IdRes int id);
+    method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, String route);
+    method public static inline operator void minusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
+    method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
+    method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavGraph other);
+  }
+
+  @androidx.navigation.Navigator.Name("navigation") public class NavGraphNavigator extends androidx.navigation.Navigator<androidx.navigation.NavGraph> {
+    ctor public NavGraphNavigator(androidx.navigation.NavigatorProvider navigatorProvider);
+    method public androidx.navigation.NavGraph createDestination();
+  }
+
+  public final class NavOptions {
+    method @AnimRes @AnimatorRes public int getEnterAnim();
+    method @AnimRes @AnimatorRes public int getExitAnim();
+    method @AnimRes @AnimatorRes public int getPopEnterAnim();
+    method @AnimRes @AnimatorRes public int getPopExitAnim();
+    method @Deprecated @IdRes public int getPopUpTo();
+    method @IdRes public int getPopUpToId();
+    method public String? getPopUpToRoute();
+    method public boolean isPopUpToInclusive();
+    method public boolean shouldLaunchSingleTop();
+    method public boolean shouldPopUpToSaveState();
+    method public boolean shouldRestoreState();
+    property @AnimRes @AnimatorRes public final int enterAnim;
+    property @AnimRes @AnimatorRes public final int exitAnim;
+    property @AnimRes @AnimatorRes public final int popEnterAnim;
+    property @AnimRes @AnimatorRes public final int popExitAnim;
+    property @IdRes public final int popUpToId;
+    property public final String? popUpToRoute;
+  }
+
+  public static final class NavOptions.Builder {
+    ctor public NavOptions.Builder();
+    method public androidx.navigation.NavOptions build();
+    method public androidx.navigation.NavOptions.Builder setEnterAnim(@AnimRes @AnimatorRes int enterAnim);
+    method public androidx.navigation.NavOptions.Builder setExitAnim(@AnimRes @AnimatorRes int exitAnim);
+    method public androidx.navigation.NavOptions.Builder setLaunchSingleTop(boolean singleTop);
+    method public androidx.navigation.NavOptions.Builder setPopEnterAnim(@AnimRes @AnimatorRes int popEnterAnim);
+    method public androidx.navigation.NavOptions.Builder setPopExitAnim(@AnimRes @AnimatorRes int popExitAnim);
+    method public androidx.navigation.NavOptions.Builder setPopUpTo(@IdRes int destinationId, boolean inclusive, optional boolean saveState);
+    method public androidx.navigation.NavOptions.Builder setPopUpTo(@IdRes int destinationId, boolean inclusive);
+    method public androidx.navigation.NavOptions.Builder setPopUpTo(String? route, boolean inclusive, optional boolean saveState);
+    method public androidx.navigation.NavOptions.Builder setPopUpTo(String? route, boolean inclusive);
+    method public androidx.navigation.NavOptions.Builder setRestoreState(boolean restoreState);
+  }
+
+  @androidx.navigation.NavOptionsDsl public final class NavOptionsBuilder {
+    ctor public NavOptionsBuilder();
+    method public void anim(kotlin.jvm.functions.Function1<? super androidx.navigation.AnimBuilder,kotlin.Unit> animBuilder);
+    method public boolean getLaunchSingleTop();
+    method @Deprecated public int getPopUpTo();
+    method public int getPopUpToId();
+    method public String? getPopUpToRoute();
+    method public boolean getRestoreState();
+    method public void popUpTo(@IdRes int id, optional kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
+    method public void popUpTo(String route, optional kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
+    method public void setLaunchSingleTop(boolean launchSingleTop);
+    method @Deprecated public void setPopUpTo(int value);
+    method public void setRestoreState(boolean restoreState);
+    property public final boolean launchSingleTop;
+    property @Deprecated public final int popUpTo;
+    property public final int popUpToId;
+    property public final String? popUpToRoute;
+    property public final boolean restoreState;
+  }
+
+  public final class NavOptionsBuilderKt {
+    method public static androidx.navigation.NavOptions navOptions(kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> optionsBuilder);
+  }
+
+  @kotlin.DslMarker public @interface NavOptionsDsl {
+  }
+
+  public abstract class NavType<T> {
+    ctor public NavType(boolean isNullableAllowed);
+    method public static androidx.navigation.NavType<?> fromArgType(String? type, String? packageName);
+    method public abstract operator T? get(android.os.Bundle bundle, String key);
+    method public String getName();
+    method public boolean isNullableAllowed();
+    method public abstract T! parseValue(String value);
+    method public abstract void put(android.os.Bundle bundle, String key, T? value);
+    property public boolean isNullableAllowed;
+    property public String name;
+    field public static final androidx.navigation.NavType<boolean[]> BoolArrayType;
+    field public static final androidx.navigation.NavType<java.lang.Boolean> BoolType;
+    field public static final androidx.navigation.NavType.Companion Companion;
+    field public static final androidx.navigation.NavType<float[]> FloatArrayType;
+    field public static final androidx.navigation.NavType<java.lang.Float> FloatType;
+    field public static final androidx.navigation.NavType<int[]> IntArrayType;
+    field public static final androidx.navigation.NavType<java.lang.Integer> IntType;
+    field public static final androidx.navigation.NavType<long[]> LongArrayType;
+    field public static final androidx.navigation.NavType<java.lang.Long> LongType;
+    field public static final androidx.navigation.NavType<java.lang.Integer> ReferenceType;
+    field public static final androidx.navigation.NavType<java.lang.String[]> StringArrayType;
+    field public static final androidx.navigation.NavType<java.lang.String> StringType;
+  }
+
+  public static final class NavType.Companion {
+    method public androidx.navigation.NavType<?> fromArgType(String? type, String? packageName);
+  }
+
+  public static final class NavType.EnumType<D extends java.lang.Enum<?>> extends androidx.navigation.NavType.SerializableType<D> {
+    ctor public NavType.EnumType(Class<D> type);
+    property public String name;
+  }
+
+  public static final class NavType.ParcelableArrayType<D extends android.os.Parcelable> extends androidx.navigation.NavType<D[]> {
+    ctor public NavType.ParcelableArrayType(Class<D> type);
+    method public D![]? get(android.os.Bundle bundle, String key);
+    method public D![] parseValue(String value);
+    method public void put(android.os.Bundle bundle, String key, D![]? value);
+    property public String name;
+  }
+
+  public static final class NavType.ParcelableType<D> extends androidx.navigation.NavType<D> {
+    ctor public NavType.ParcelableType(Class<D> type);
+    method public D? get(android.os.Bundle bundle, String key);
+    method public D! parseValue(String value);
+    method public void put(android.os.Bundle bundle, String key, D? value);
+    property public String name;
+  }
+
+  public static final class NavType.SerializableArrayType<D extends java.io.Serializable> extends androidx.navigation.NavType<D[]> {
+    ctor public NavType.SerializableArrayType(Class<D> type);
+    method public D![]? get(android.os.Bundle bundle, String key);
+    method public D![] parseValue(String value);
+    method public void put(android.os.Bundle bundle, String key, D![]? value);
+    property public String name;
+  }
+
+  public static class NavType.SerializableType<D extends java.io.Serializable> extends androidx.navigation.NavType<D> {
+    ctor public NavType.SerializableType(Class<D> type);
+    method public D? get(android.os.Bundle bundle, String key);
+    method public D parseValue(String value);
+    method public void put(android.os.Bundle bundle, String key, D value);
+    property public String name;
+  }
+
+  public abstract class Navigator<D extends androidx.navigation.NavDestination> {
+    ctor public Navigator();
+    method public abstract D createDestination();
+    method protected final androidx.navigation.NavigatorState getState();
+    method public final boolean isAttached();
+    method public void navigate(java.util.List<androidx.navigation.NavBackStackEntry> entries, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
+    method public androidx.navigation.NavDestination? navigate(D destination, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
+    method @CallSuper public void onAttach(androidx.navigation.NavigatorState state);
+    method public void onLaunchSingleTop(androidx.navigation.NavBackStackEntry backStackEntry);
+    method public void onRestoreState(android.os.Bundle savedState);
+    method public android.os.Bundle? onSaveState();
+    method public void popBackStack(androidx.navigation.NavBackStackEntry popUpTo, boolean savedState);
+    method public boolean popBackStack();
+    property public final boolean isAttached;
+    property protected final androidx.navigation.NavigatorState state;
+  }
+
+  public static interface Navigator.Extras {
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public static @interface Navigator.Name {
+    method public abstract String value();
+    property public abstract String value;
+  }
+
+  public class NavigatorProvider {
+    ctor public NavigatorProvider();
+    method public final androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>? addNavigator(androidx.navigation.Navigator<? extends androidx.navigation.NavDestination> navigator);
+    method @CallSuper public androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>? addNavigator(String name, androidx.navigation.Navigator<? extends androidx.navigation.NavDestination> navigator);
+    method public final <T extends androidx.navigation.Navigator<?>> T getNavigator(Class<T> navigatorClass);
+    method @CallSuper public <T extends androidx.navigation.Navigator<?>> T getNavigator(String name);
+  }
+
+  public final class NavigatorProviderKt {
+    method public static inline operator <T extends androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>> T get(androidx.navigation.NavigatorProvider, String name);
+    method public static inline operator <T extends androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>> T get(androidx.navigation.NavigatorProvider, kotlin.reflect.KClass<T> clazz);
+    method public static inline operator void plusAssign(androidx.navigation.NavigatorProvider, androidx.navigation.Navigator<? extends androidx.navigation.NavDestination> navigator);
+    method public static inline operator androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>? set(androidx.navigation.NavigatorProvider, String name, androidx.navigation.Navigator<? extends androidx.navigation.NavDestination> navigator);
+  }
+
+  public abstract class NavigatorState {
+    ctor public NavigatorState();
+    method public abstract androidx.navigation.NavBackStackEntry createBackStackEntry(androidx.navigation.NavDestination destination, android.os.Bundle? arguments);
+    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 @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);
+    method public void pushWithTransition(androidx.navigation.NavBackStackEntry backStackEntry);
+    property public final kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.navigation.NavBackStackEntry>> backStack;
+    property public final kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.navigation.NavBackStackEntry>> transitionsInProgress;
+  }
+
+  @androidx.navigation.NavOptionsDsl public final class PopUpToBuilder {
+    ctor public PopUpToBuilder();
+    method public boolean getInclusive();
+    method public boolean getSaveState();
+    method public void setInclusive(boolean inclusive);
+    method public void setSaveState(boolean saveState);
+    property public final boolean inclusive;
+    property public final boolean saveState;
+  }
+
+}
+
diff --git a/navigation/navigation-common/api/restricted_current.txt b/navigation/navigation-common/api/restricted_current.txt
index 9dea391..a169ec7 100644
--- a/navigation/navigation-common/api/restricted_current.txt
+++ b/navigation/navigation-common/api/restricted_current.txt
@@ -40,7 +40,7 @@
   }
 
   public final class NamedNavArgumentKt {
-    method @androidx.navigation.NavDestinationDsl public static androidx.navigation.NamedNavArgument navArgument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> builder);
+    method public static androidx.navigation.NamedNavArgument navArgument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> builder);
   }
 
   public final class NavAction {
diff --git a/navigation/navigation-common/src/main/baseline-prof.txt b/navigation/navigation-common/src/main/baseline-prof.txt
new file mode 100644
index 0000000..8a37b60
--- /dev/null
+++ b/navigation/navigation-common/src/main/baseline-prof.txt
@@ -0,0 +1,181 @@
+# Baseline Profiles for navigation-common
+
+HSPLandroidx/navigation/NavAction;-><init>(ILandroidx/navigation/NavOptions;Landroid/os/Bundle;)V
+HSPLandroidx/navigation/NavAction;-><init>(ILandroidx/navigation/NavOptions;Landroid/os/Bundle;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLandroidx/navigation/NavAction;->getDefaultArguments()Landroid/os/Bundle;
+HSPLandroidx/navigation/NavAction;->getDestinationId()I
+HSPLandroidx/navigation/NavAction;->getNavOptions()Landroidx/navigation/NavOptions;
+HSPLandroidx/navigation/NavAction;->setNavOptions(Landroidx/navigation/NavOptions;)V
+HSPLandroidx/navigation/NavArgument$Builder;-><init>()V
+HSPLandroidx/navigation/NavArgument$Builder;->build()Landroidx/navigation/NavArgument;
+HSPLandroidx/navigation/NavArgument$Builder;->setIsNullable(Z)Landroidx/navigation/NavArgument$Builder;
+HSPLandroidx/navigation/NavArgument$Builder;->setType(Landroidx/navigation/NavType;)Landroidx/navigation/NavArgument$Builder;
+HSPLandroidx/navigation/NavArgument;-><init>(Landroidx/navigation/NavType;ZLjava/lang/Object;Z)V
+HSPLandroidx/navigation/NavArgument;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/navigation/NavArgument;->hashCode()I
+HSPLandroidx/navigation/NavBackStackEntry$Companion;-><init>()V
+HSPLandroidx/navigation/NavBackStackEntry$Companion;-><init>(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLandroidx/navigation/NavBackStackEntry$Companion;->create$default(Landroidx/navigation/NavBackStackEntry$Companion;Landroid/content/Context;Landroidx/navigation/NavDestination;Landroid/os/Bundle;Landroidx/lifecycle/LifecycleOwner;Landroidx/navigation/NavViewModelStoreProvider;Ljava/lang/String;Landroid/os/Bundle;ILjava/lang/Object;)Landroidx/navigation/NavBackStackEntry;
+HSPLandroidx/navigation/NavBackStackEntry$Companion;->create(Landroid/content/Context;Landroidx/navigation/NavDestination;Landroid/os/Bundle;Landroidx/lifecycle/LifecycleOwner;Landroidx/navigation/NavViewModelStoreProvider;Ljava/lang/String;Landroid/os/Bundle;)Landroidx/navigation/NavBackStackEntry;
+HSPLandroidx/navigation/NavBackStackEntry$defaultFactory$2;-><init>(Landroidx/navigation/NavBackStackEntry;)V
+HSPLandroidx/navigation/NavBackStackEntry$savedStateHandle$2;-><init>(Landroidx/navigation/NavBackStackEntry;)V
+HSPLandroidx/navigation/NavBackStackEntry;-><clinit>()V
+HSPLandroidx/navigation/NavBackStackEntry;-><init>(Landroid/content/Context;Landroidx/navigation/NavDestination;Landroid/os/Bundle;Landroidx/lifecycle/LifecycleOwner;Landroidx/navigation/NavViewModelStoreProvider;Ljava/lang/String;Landroid/os/Bundle;)V
+HSPLandroidx/navigation/NavBackStackEntry;-><init>(Landroid/content/Context;Landroidx/navigation/NavDestination;Landroid/os/Bundle;Landroidx/lifecycle/LifecycleOwner;Landroidx/navigation/NavViewModelStoreProvider;Ljava/lang/String;Landroid/os/Bundle;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLandroidx/navigation/NavBackStackEntry;->getArguments()Landroid/os/Bundle;
+HSPLandroidx/navigation/NavBackStackEntry;->getDestination()Landroidx/navigation/NavDestination;
+HSPLandroidx/navigation/NavBackStackEntry;->getLifecycle()Landroidx/lifecycle/Lifecycle;
+HSPLandroidx/navigation/NavBackStackEntry;->getMaxLifecycle()Landroidx/lifecycle/Lifecycle$State;
+HSPLandroidx/navigation/NavBackStackEntry;->getSavedStateRegistry()Landroidx/savedstate/SavedStateRegistry;
+HSPLandroidx/navigation/NavBackStackEntry;->handleLifecycleEvent(Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/navigation/NavBackStackEntry;->hashCode()I
+HSPLandroidx/navigation/NavBackStackEntry;->setMaxLifecycle(Landroidx/lifecycle/Lifecycle$State;)V
+HSPLandroidx/navigation/NavBackStackEntry;->updateState()V
+HSPLandroidx/navigation/NavDeepLinkRequest;-><init>(Landroid/content/Intent;)V
+HSPLandroidx/navigation/NavDeepLinkRequest;-><init>(Landroid/net/Uri;Ljava/lang/String;Ljava/lang/String;)V
+HSPLandroidx/navigation/NavDestination$Companion;-><init>()V
+HSPLandroidx/navigation/NavDestination$Companion;-><init>(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLandroidx/navigation/NavDestination$Companion;->getDisplayName(Landroid/content/Context;I)Ljava/lang/String;
+HSPLandroidx/navigation/NavDestination;-><clinit>()V
+HSPLandroidx/navigation/NavDestination;-><init>(Landroidx/navigation/Navigator;)V
+HSPLandroidx/navigation/NavDestination;-><init>(Ljava/lang/String;)V
+HSPLandroidx/navigation/NavDestination;->addArgument(Ljava/lang/String;Landroidx/navigation/NavArgument;)V
+HSPLandroidx/navigation/NavDestination;->addInDefaultArgs(Landroid/os/Bundle;)Landroid/os/Bundle;
+HSPLandroidx/navigation/NavDestination;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/navigation/NavDestination;->getArguments()Ljava/util/Map;
+HSPLandroidx/navigation/NavDestination;->getId()I
+HSPLandroidx/navigation/NavDestination;->getNavigatorName()Ljava/lang/String;
+HSPLandroidx/navigation/NavDestination;->getParent()Landroidx/navigation/NavGraph;
+HSPLandroidx/navigation/NavDestination;->getRoute()Ljava/lang/String;
+HSPLandroidx/navigation/NavDestination;->hashCode()I
+HSPLandroidx/navigation/NavDestination;->matchDeepLink(Landroidx/navigation/NavDeepLinkRequest;)Landroidx/navigation/NavDestination$DeepLinkMatch;
+HSPLandroidx/navigation/NavDestination;->onInflate(Landroid/content/Context;Landroid/util/AttributeSet;)V
+HSPLandroidx/navigation/NavDestination;->putAction(ILandroidx/navigation/NavAction;)V
+HSPLandroidx/navigation/NavDestination;->setId(I)V
+HSPLandroidx/navigation/NavDestination;->setLabel(Ljava/lang/CharSequence;)V
+HSPLandroidx/navigation/NavDestination;->setParent(Landroidx/navigation/NavGraph;)V
+HSPLandroidx/navigation/NavDestination;->setRoute(Ljava/lang/String;)V
+HSPLandroidx/navigation/NavDestination;->supportsActions()Z
+HSPLandroidx/navigation/NavGraph$Companion;-><init>()V
+HSPLandroidx/navigation/NavGraph$Companion;-><init>(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLandroidx/navigation/NavGraph$iterator$1;-><init>(Landroidx/navigation/NavGraph;)V
+HSPLandroidx/navigation/NavGraph$iterator$1;->hasNext()Z
+HSPLandroidx/navigation/NavGraph$iterator$1;->next()Landroidx/navigation/NavDestination;
+HSPLandroidx/navigation/NavGraph$iterator$1;->next()Ljava/lang/Object;
+HSPLandroidx/navigation/NavGraph;-><clinit>()V
+HSPLandroidx/navigation/NavGraph;-><init>(Landroidx/navigation/Navigator;)V
+HSPLandroidx/navigation/NavGraph;->addDestination(Landroidx/navigation/NavDestination;)V
+HSPLandroidx/navigation/NavGraph;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/navigation/NavGraph;->findNode(IZ)Landroidx/navigation/NavDestination;
+HSPLandroidx/navigation/NavGraph;->getNodes()Landroidx/collection/SparseArrayCompat;
+HSPLandroidx/navigation/NavGraph;->getStartDestinationId()I
+HSPLandroidx/navigation/NavGraph;->getStartDestinationRoute()Ljava/lang/String;
+HSPLandroidx/navigation/NavGraph;->hashCode()I
+HSPLandroidx/navigation/NavGraph;->iterator()Ljava/util/Iterator;
+HSPLandroidx/navigation/NavGraph;->matchDeepLink(Landroidx/navigation/NavDeepLinkRequest;)Landroidx/navigation/NavDestination$DeepLinkMatch;
+HSPLandroidx/navigation/NavGraph;->onInflate(Landroid/content/Context;Landroid/util/AttributeSet;)V
+HSPLandroidx/navigation/NavGraph;->setStartDestinationId(I)V
+HSPLandroidx/navigation/NavGraphNavigator;-><init>(Landroidx/navigation/NavigatorProvider;)V
+HSPLandroidx/navigation/NavGraphNavigator;->createDestination()Landroidx/navigation/NavDestination;
+HSPLandroidx/navigation/NavGraphNavigator;->createDestination()Landroidx/navigation/NavGraph;
+HSPLandroidx/navigation/NavGraphNavigator;->navigate(Landroidx/navigation/NavBackStackEntry;Landroidx/navigation/NavOptions;Landroidx/navigation/Navigator$Extras;)V
+HSPLandroidx/navigation/NavGraphNavigator;->navigate(Ljava/util/List;Landroidx/navigation/NavOptions;Landroidx/navigation/Navigator$Extras;)V
+HSPLandroidx/navigation/NavOptions$Builder;-><init>()V
+HSPLandroidx/navigation/NavOptions$Builder;->build()Landroidx/navigation/NavOptions;
+HSPLandroidx/navigation/NavOptions$Builder;->setEnterAnim(I)Landroidx/navigation/NavOptions$Builder;
+HSPLandroidx/navigation/NavOptions$Builder;->setExitAnim(I)Landroidx/navigation/NavOptions$Builder;
+HSPLandroidx/navigation/NavOptions$Builder;->setLaunchSingleTop(Z)Landroidx/navigation/NavOptions$Builder;
+HSPLandroidx/navigation/NavOptions$Builder;->setPopEnterAnim(I)Landroidx/navigation/NavOptions$Builder;
+HSPLandroidx/navigation/NavOptions$Builder;->setPopExitAnim(I)Landroidx/navigation/NavOptions$Builder;
+HSPLandroidx/navigation/NavOptions$Builder;->setPopUpTo(IZZ)Landroidx/navigation/NavOptions$Builder;
+HSPLandroidx/navigation/NavOptions$Builder;->setRestoreState(Z)Landroidx/navigation/NavOptions$Builder;
+HSPLandroidx/navigation/NavOptions;-><init>(ZZIZZIIII)V
+HSPLandroidx/navigation/NavOptions;->hashCode()I
+HSPLandroidx/navigation/NavOptions;->isPopUpToInclusive()Z
+HSPLandroidx/navigation/NavOptions;->shouldLaunchSingleTop()Z
+HSPLandroidx/navigation/NavOptions;->shouldPopUpToSaveState()Z
+HSPLandroidx/navigation/NavOptions;->shouldRestoreState()Z
+HSPLandroidx/navigation/NavType$Companion$BoolArrayType$1;-><init>()V
+HSPLandroidx/navigation/NavType$Companion$BoolArrayType$1;->getName()Ljava/lang/String;
+HSPLandroidx/navigation/NavType$Companion$BoolType$1;-><init>()V
+HSPLandroidx/navigation/NavType$Companion$BoolType$1;->getName()Ljava/lang/String;
+HSPLandroidx/navigation/NavType$Companion$FloatArrayType$1;-><init>()V
+HSPLandroidx/navigation/NavType$Companion$FloatType$1;-><init>()V
+HSPLandroidx/navigation/NavType$Companion$IntArrayType$1;-><init>()V
+HSPLandroidx/navigation/NavType$Companion$IntArrayType$1;->getName()Ljava/lang/String;
+HSPLandroidx/navigation/NavType$Companion$IntType$1;-><init>()V
+HSPLandroidx/navigation/NavType$Companion$IntType$1;->getName()Ljava/lang/String;
+HSPLandroidx/navigation/NavType$Companion$LongArrayType$1;-><init>()V
+HSPLandroidx/navigation/NavType$Companion$LongArrayType$1;->getName()Ljava/lang/String;
+HSPLandroidx/navigation/NavType$Companion$LongType$1;-><init>()V
+HSPLandroidx/navigation/NavType$Companion$LongType$1;->getName()Ljava/lang/String;
+HSPLandroidx/navigation/NavType$Companion$ReferenceType$1;-><init>()V
+HSPLandroidx/navigation/NavType$Companion$StringArrayType$1;-><init>()V
+HSPLandroidx/navigation/NavType$Companion$StringType$1;-><init>()V
+HSPLandroidx/navigation/NavType$Companion$StringType$1;->getName()Ljava/lang/String;
+HSPLandroidx/navigation/NavType$Companion;-><init>()V
+HSPLandroidx/navigation/NavType$Companion;-><init>(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLandroidx/navigation/NavType$Companion;->fromArgType(Ljava/lang/String;Ljava/lang/String;)Landroidx/navigation/NavType;
+HSPLandroidx/navigation/NavType;-><clinit>()V
+HSPLandroidx/navigation/NavType;-><init>(Z)V
+HSPLandroidx/navigation/NavType;->isNullableAllowed()Z
+HSPLandroidx/navigation/Navigator;-><init>()V
+HSPLandroidx/navigation/Navigator;->getState()Landroidx/navigation/NavigatorState;
+HSPLandroidx/navigation/Navigator;->isAttached()Z
+HSPLandroidx/navigation/Navigator;->onAttach(Landroidx/navigation/NavigatorState;)V
+HSPLandroidx/navigation/NavigatorProvider$Companion;-><init>()V
+HSPLandroidx/navigation/NavigatorProvider$Companion;-><init>(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLandroidx/navigation/NavigatorProvider$Companion;->getNameForNavigator$navigation_common_release(Ljava/lang/Class;)Ljava/lang/String;
+HSPLandroidx/navigation/NavigatorProvider$Companion;->validateName$navigation_common_release(Ljava/lang/String;)Z
+HSPLandroidx/navigation/NavigatorProvider;-><clinit>()V
+HSPLandroidx/navigation/NavigatorProvider;-><init>()V
+HSPLandroidx/navigation/NavigatorProvider;->access$getAnnotationNames$cp()Ljava/util/Map;
+HSPLandroidx/navigation/NavigatorProvider;->addNavigator(Landroidx/navigation/Navigator;)Landroidx/navigation/Navigator;
+HSPLandroidx/navigation/NavigatorProvider;->addNavigator(Ljava/lang/String;Landroidx/navigation/Navigator;)Landroidx/navigation/Navigator;
+HSPLandroidx/navigation/NavigatorProvider;->getNavigator(Ljava/lang/String;)Landroidx/navigation/Navigator;
+HSPLandroidx/navigation/NavigatorProvider;->getNavigators()Ljava/util/Map;
+HSPLandroidx/navigation/NavigatorState;-><init>()V
+HSPLandroidx/navigation/NavigatorState;->getBackStack()Lkotlinx/coroutines/flow/StateFlow;
+HSPLandroidx/navigation/NavigatorState;->getTransitionsInProgress()Lkotlinx/coroutines/flow/StateFlow;
+HSPLandroidx/navigation/NavigatorState;->push(Landroidx/navigation/NavBackStackEntry;)V
+HSPLandroidx/navigation/NavigatorState;->setNavigating(Z)V
+Landroidx/navigation/FloatingWindow;
+Landroidx/navigation/NavAction;
+Landroidx/navigation/NavArgument$Builder;
+Landroidx/navigation/NavArgument;
+Landroidx/navigation/NavBackStackEntry$Companion;
+Landroidx/navigation/NavBackStackEntry$defaultFactory$2;
+Landroidx/navigation/NavBackStackEntry$savedStateHandle$2;
+Landroidx/navigation/NavBackStackEntry;
+Landroidx/navigation/NavDeepLinkRequest;
+Landroidx/navigation/NavDestination$Companion;
+Landroidx/navigation/NavDestination$DeepLinkMatch;
+Landroidx/navigation/NavDestination;
+Landroidx/navigation/NavGraph$Companion;
+Landroidx/navigation/NavGraph$iterator$1;
+Landroidx/navigation/NavGraph;
+Landroidx/navigation/NavGraphNavigator;
+Landroidx/navigation/NavOptions$Builder;
+Landroidx/navigation/NavOptions;
+Landroidx/navigation/NavType$Companion$BoolArrayType$1;
+Landroidx/navigation/NavType$Companion$BoolType$1;
+Landroidx/navigation/NavType$Companion$FloatArrayType$1;
+Landroidx/navigation/NavType$Companion$FloatType$1;
+Landroidx/navigation/NavType$Companion$IntArrayType$1;
+Landroidx/navigation/NavType$Companion$IntType$1;
+Landroidx/navigation/NavType$Companion$LongArrayType$1;
+Landroidx/navigation/NavType$Companion$LongType$1;
+Landroidx/navigation/NavType$Companion$ReferenceType$1;
+Landroidx/navigation/NavType$Companion$StringArrayType$1;
+Landroidx/navigation/NavType$Companion$StringType$1;
+Landroidx/navigation/NavType$Companion;
+Landroidx/navigation/NavType;
+Landroidx/navigation/NavViewModelStoreProvider;
+Landroidx/navigation/Navigator$Extras;
+Landroidx/navigation/Navigator$Name;
+Landroidx/navigation/Navigator;
+Landroidx/navigation/NavigatorProvider$Companion;
+Landroidx/navigation/NavigatorProvider;
+Landroidx/navigation/NavigatorState;
+HSPLandroidx/navigation/common/R$styleable;-><clinit>()V
+Landroidx/navigation/common/R$styleable;
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NamedNavArgument.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NamedNavArgument.kt
index 0e8e34f..8032df6 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NamedNavArgument.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NamedNavArgument.kt
@@ -19,7 +19,6 @@
 /**
  * Construct a new [NavArgument]
  */
-@NavDestinationDsl
 public fun navArgument(
     name: String,
     builder: NavArgumentBuilder.() -> Unit
diff --git a/navigation/navigation-compose/api/2.4.0-beta01.txt b/navigation/navigation-compose/api/2.4.0-beta01.txt
new file mode 100644
index 0000000..d46757e
--- /dev/null
+++ b/navigation/navigation-compose/api/2.4.0-beta01.txt
@@ -0,0 +1,47 @@
+// Signature format: 4.0
+package androidx.navigation.compose {
+
+  @androidx.navigation.Navigator.Name("composable") public final class ComposeNavigator extends androidx.navigation.Navigator<androidx.navigation.compose.ComposeNavigator.Destination> {
+    ctor public ComposeNavigator();
+    method public androidx.navigation.compose.ComposeNavigator.Destination createDestination();
+  }
+
+  @androidx.navigation.NavDestination.ClassType(Composable::class) public static final class ComposeNavigator.Destination extends androidx.navigation.NavDestination {
+    ctor public ComposeNavigator.Destination(androidx.navigation.compose.ComposeNavigator navigator, kotlin.jvm.functions.Function1<? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
+  }
+
+  public final class DialogHostKt {
+    method @androidx.compose.runtime.Composable public static void DialogHost(androidx.navigation.compose.DialogNavigator dialogNavigator);
+  }
+
+  @androidx.navigation.Navigator.Name("dialog") public final class DialogNavigator extends androidx.navigation.Navigator<androidx.navigation.compose.DialogNavigator.Destination> {
+    ctor public DialogNavigator();
+    method public androidx.navigation.compose.DialogNavigator.Destination createDestination();
+  }
+
+  @androidx.navigation.NavDestination.ClassType(Composable::class) public static final class DialogNavigator.Destination extends androidx.navigation.NavDestination implements androidx.navigation.FloatingWindow {
+    ctor public DialogNavigator.Destination(androidx.navigation.compose.DialogNavigator navigator, optional androidx.compose.ui.window.DialogProperties dialogProperties, kotlin.jvm.functions.Function1<? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
+  }
+
+  public final class NavBackStackEntryProviderKt {
+    method @androidx.compose.runtime.Composable public static void LocalOwnersProvider(androidx.navigation.NavBackStackEntry, androidx.compose.runtime.saveable.SaveableStateHolder saveableStateHolder, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
+  public final class NavGraphBuilderKt {
+    method public static void composable(androidx.navigation.NavGraphBuilder, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, kotlin.jvm.functions.Function1<? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
+    method public static void dialog(androidx.navigation.NavGraphBuilder, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, optional androidx.compose.ui.window.DialogProperties dialogProperties, kotlin.jvm.functions.Function1<? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
+    method public static void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+  }
+
+  public final class NavHostControllerKt {
+    method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.navigation.NavBackStackEntry> currentBackStackEntryAsState(androidx.navigation.NavController);
+    method @androidx.compose.runtime.Composable public static androidx.navigation.NavHostController rememberNavController(androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>... navigators);
+  }
+
+  public final class NavHostKt {
+    method @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, String startDestination, optional androidx.compose.ui.Modifier modifier, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, androidx.navigation.NavGraph graph, optional androidx.compose.ui.Modifier modifier);
+  }
+
+}
+
diff --git a/navigation/navigation-compose/api/public_plus_experimental_2.4.0-beta01.txt b/navigation/navigation-compose/api/public_plus_experimental_2.4.0-beta01.txt
new file mode 100644
index 0000000..d46757e
--- /dev/null
+++ b/navigation/navigation-compose/api/public_plus_experimental_2.4.0-beta01.txt
@@ -0,0 +1,47 @@
+// Signature format: 4.0
+package androidx.navigation.compose {
+
+  @androidx.navigation.Navigator.Name("composable") public final class ComposeNavigator extends androidx.navigation.Navigator<androidx.navigation.compose.ComposeNavigator.Destination> {
+    ctor public ComposeNavigator();
+    method public androidx.navigation.compose.ComposeNavigator.Destination createDestination();
+  }
+
+  @androidx.navigation.NavDestination.ClassType(Composable::class) public static final class ComposeNavigator.Destination extends androidx.navigation.NavDestination {
+    ctor public ComposeNavigator.Destination(androidx.navigation.compose.ComposeNavigator navigator, kotlin.jvm.functions.Function1<? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
+  }
+
+  public final class DialogHostKt {
+    method @androidx.compose.runtime.Composable public static void DialogHost(androidx.navigation.compose.DialogNavigator dialogNavigator);
+  }
+
+  @androidx.navigation.Navigator.Name("dialog") public final class DialogNavigator extends androidx.navigation.Navigator<androidx.navigation.compose.DialogNavigator.Destination> {
+    ctor public DialogNavigator();
+    method public androidx.navigation.compose.DialogNavigator.Destination createDestination();
+  }
+
+  @androidx.navigation.NavDestination.ClassType(Composable::class) public static final class DialogNavigator.Destination extends androidx.navigation.NavDestination implements androidx.navigation.FloatingWindow {
+    ctor public DialogNavigator.Destination(androidx.navigation.compose.DialogNavigator navigator, optional androidx.compose.ui.window.DialogProperties dialogProperties, kotlin.jvm.functions.Function1<? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
+  }
+
+  public final class NavBackStackEntryProviderKt {
+    method @androidx.compose.runtime.Composable public static void LocalOwnersProvider(androidx.navigation.NavBackStackEntry, androidx.compose.runtime.saveable.SaveableStateHolder saveableStateHolder, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
+  public final class NavGraphBuilderKt {
+    method public static void composable(androidx.navigation.NavGraphBuilder, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, kotlin.jvm.functions.Function1<? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
+    method public static void dialog(androidx.navigation.NavGraphBuilder, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, optional androidx.compose.ui.window.DialogProperties dialogProperties, kotlin.jvm.functions.Function1<? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
+    method public static void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+  }
+
+  public final class NavHostControllerKt {
+    method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.navigation.NavBackStackEntry> currentBackStackEntryAsState(androidx.navigation.NavController);
+    method @androidx.compose.runtime.Composable public static androidx.navigation.NavHostController rememberNavController(androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>... navigators);
+  }
+
+  public final class NavHostKt {
+    method @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, String startDestination, optional androidx.compose.ui.Modifier modifier, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, androidx.navigation.NavGraph graph, optional androidx.compose.ui.Modifier modifier);
+  }
+
+}
+
diff --git a/navigation/navigation-compose/api/res-2.4.0-beta01.txt b/navigation/navigation-compose/api/res-2.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/navigation-compose/api/res-2.4.0-beta01.txt
diff --git a/navigation/navigation-compose/api/restricted_2.4.0-beta01.txt b/navigation/navigation-compose/api/restricted_2.4.0-beta01.txt
new file mode 100644
index 0000000..d46757e
--- /dev/null
+++ b/navigation/navigation-compose/api/restricted_2.4.0-beta01.txt
@@ -0,0 +1,47 @@
+// Signature format: 4.0
+package androidx.navigation.compose {
+
+  @androidx.navigation.Navigator.Name("composable") public final class ComposeNavigator extends androidx.navigation.Navigator<androidx.navigation.compose.ComposeNavigator.Destination> {
+    ctor public ComposeNavigator();
+    method public androidx.navigation.compose.ComposeNavigator.Destination createDestination();
+  }
+
+  @androidx.navigation.NavDestination.ClassType(Composable::class) public static final class ComposeNavigator.Destination extends androidx.navigation.NavDestination {
+    ctor public ComposeNavigator.Destination(androidx.navigation.compose.ComposeNavigator navigator, kotlin.jvm.functions.Function1<? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
+  }
+
+  public final class DialogHostKt {
+    method @androidx.compose.runtime.Composable public static void DialogHost(androidx.navigation.compose.DialogNavigator dialogNavigator);
+  }
+
+  @androidx.navigation.Navigator.Name("dialog") public final class DialogNavigator extends androidx.navigation.Navigator<androidx.navigation.compose.DialogNavigator.Destination> {
+    ctor public DialogNavigator();
+    method public androidx.navigation.compose.DialogNavigator.Destination createDestination();
+  }
+
+  @androidx.navigation.NavDestination.ClassType(Composable::class) public static final class DialogNavigator.Destination extends androidx.navigation.NavDestination implements androidx.navigation.FloatingWindow {
+    ctor public DialogNavigator.Destination(androidx.navigation.compose.DialogNavigator navigator, optional androidx.compose.ui.window.DialogProperties dialogProperties, kotlin.jvm.functions.Function1<? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
+  }
+
+  public final class NavBackStackEntryProviderKt {
+    method @androidx.compose.runtime.Composable public static void LocalOwnersProvider(androidx.navigation.NavBackStackEntry, androidx.compose.runtime.saveable.SaveableStateHolder saveableStateHolder, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
+  public final class NavGraphBuilderKt {
+    method public static void composable(androidx.navigation.NavGraphBuilder, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, kotlin.jvm.functions.Function1<? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
+    method public static void dialog(androidx.navigation.NavGraphBuilder, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, optional androidx.compose.ui.window.DialogProperties dialogProperties, kotlin.jvm.functions.Function1<? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
+    method public static void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+  }
+
+  public final class NavHostControllerKt {
+    method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.navigation.NavBackStackEntry> currentBackStackEntryAsState(androidx.navigation.NavController);
+    method @androidx.compose.runtime.Composable public static androidx.navigation.NavHostController rememberNavController(androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>... navigators);
+  }
+
+  public final class NavHostKt {
+    method @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, String startDestination, optional androidx.compose.ui.Modifier modifier, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, androidx.navigation.NavGraph graph, optional androidx.compose.ui.Modifier modifier);
+  }
+
+}
+
diff --git a/navigation/navigation-dynamic-features-fragment/api/2.4.0-beta01.txt b/navigation/navigation-dynamic-features-fragment/api/2.4.0-beta01.txt
new file mode 100644
index 0000000..9271a05
--- /dev/null
+++ b/navigation/navigation-dynamic-features-fragment/api/2.4.0-beta01.txt
@@ -0,0 +1,69 @@
+// Signature format: 4.0
+package androidx.navigation.dynamicfeatures.fragment {
+
+  @androidx.navigation.Navigator.Name("fragment") public final class DynamicFragmentNavigator extends androidx.navigation.fragment.FragmentNavigator {
+    ctor public DynamicFragmentNavigator(android.content.Context context, androidx.fragment.app.FragmentManager manager, int containerId, androidx.navigation.dynamicfeatures.DynamicInstallManager installManager);
+    method public androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigator.Destination createDestination();
+  }
+
+  public static final class DynamicFragmentNavigator.Destination extends androidx.navigation.fragment.FragmentNavigator.Destination {
+    ctor public DynamicFragmentNavigator.Destination(androidx.navigation.NavigatorProvider navigatorProvider);
+    ctor public DynamicFragmentNavigator.Destination(androidx.navigation.Navigator<? extends androidx.navigation.fragment.FragmentNavigator.Destination> fragmentNavigator);
+    method public String? getModuleName();
+    method public void setModuleName(String? moduleName);
+    property public final String? moduleName;
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class DynamicFragmentNavigatorDestinationBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.fragment.FragmentNavigator.Destination> {
+    ctor @Deprecated public DynamicFragmentNavigatorDestinationBuilder(androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigator navigator, @IdRes int id, String fragmentClassName);
+    ctor public DynamicFragmentNavigatorDestinationBuilder(androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigator navigator, String route, String fragmentClassName);
+    method public androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigator.Destination build();
+    method public String? getModuleName();
+    method public void setModuleName(String? moduleName);
+    property public final String? moduleName;
+  }
+
+  public final class DynamicFragmentNavigatorDestinationBuilderKt {
+    method @Deprecated public static inline void fragment(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, @IdRes int id, String fragmentClassName, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigatorDestinationBuilder,kotlin.Unit> builder);
+    method public static inline void fragment(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, String route, String fragmentClassName, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigatorDestinationBuilder,kotlin.Unit> builder);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, @IdRes int id);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, @IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigatorDestinationBuilder,? extends kotlin.Unit> builder);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, String route);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigatorDestinationBuilder,? extends kotlin.Unit> builder);
+  }
+
+  public class DynamicNavHostFragment extends androidx.navigation.fragment.NavHostFragment {
+    ctor public DynamicNavHostFragment();
+    method public static final androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment create(@NavigationRes int graphResId, optional android.os.Bundle? startDestinationArgs);
+    method public static final androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment create(@NavigationRes int graphResId);
+    method protected com.google.android.play.core.splitinstall.SplitInstallManager createSplitInstallManager();
+    field public static final androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment.Companion Companion;
+  }
+
+  public static final class DynamicNavHostFragment.Companion {
+    method public androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment create(@NavigationRes int graphResId, optional android.os.Bundle? startDestinationArgs);
+    method public androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment create(@NavigationRes int graphResId);
+  }
+
+}
+
+package androidx.navigation.dynamicfeatures.fragment.ui {
+
+  public abstract class AbstractProgressFragment extends androidx.fragment.app.Fragment {
+    ctor public AbstractProgressFragment();
+    ctor public AbstractProgressFragment(int contentLayoutId);
+    method protected abstract void onCancelled();
+    method protected abstract void onFailed(@com.google.android.play.core.splitinstall.model.SplitInstallErrorCode int errorCode);
+    method protected void onInstalled();
+    method protected abstract void onProgress(@com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus int status, long bytesDownloaded, long bytesTotal);
+  }
+
+  public final class DefaultProgressFragment extends androidx.navigation.dynamicfeatures.fragment.ui.AbstractProgressFragment {
+    ctor public DefaultProgressFragment();
+    method protected void onCancelled();
+    method protected void onFailed(@com.google.android.play.core.splitinstall.model.SplitInstallErrorCode int errorCode);
+    method protected void onProgress(int status, long bytesDownloaded, long bytesTotal);
+  }
+
+}
+
diff --git a/navigation/navigation-dynamic-features-fragment/api/public_plus_experimental_2.4.0-beta01.txt b/navigation/navigation-dynamic-features-fragment/api/public_plus_experimental_2.4.0-beta01.txt
new file mode 100644
index 0000000..9271a05
--- /dev/null
+++ b/navigation/navigation-dynamic-features-fragment/api/public_plus_experimental_2.4.0-beta01.txt
@@ -0,0 +1,69 @@
+// Signature format: 4.0
+package androidx.navigation.dynamicfeatures.fragment {
+
+  @androidx.navigation.Navigator.Name("fragment") public final class DynamicFragmentNavigator extends androidx.navigation.fragment.FragmentNavigator {
+    ctor public DynamicFragmentNavigator(android.content.Context context, androidx.fragment.app.FragmentManager manager, int containerId, androidx.navigation.dynamicfeatures.DynamicInstallManager installManager);
+    method public androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigator.Destination createDestination();
+  }
+
+  public static final class DynamicFragmentNavigator.Destination extends androidx.navigation.fragment.FragmentNavigator.Destination {
+    ctor public DynamicFragmentNavigator.Destination(androidx.navigation.NavigatorProvider navigatorProvider);
+    ctor public DynamicFragmentNavigator.Destination(androidx.navigation.Navigator<? extends androidx.navigation.fragment.FragmentNavigator.Destination> fragmentNavigator);
+    method public String? getModuleName();
+    method public void setModuleName(String? moduleName);
+    property public final String? moduleName;
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class DynamicFragmentNavigatorDestinationBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.fragment.FragmentNavigator.Destination> {
+    ctor @Deprecated public DynamicFragmentNavigatorDestinationBuilder(androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigator navigator, @IdRes int id, String fragmentClassName);
+    ctor public DynamicFragmentNavigatorDestinationBuilder(androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigator navigator, String route, String fragmentClassName);
+    method public androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigator.Destination build();
+    method public String? getModuleName();
+    method public void setModuleName(String? moduleName);
+    property public final String? moduleName;
+  }
+
+  public final class DynamicFragmentNavigatorDestinationBuilderKt {
+    method @Deprecated public static inline void fragment(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, @IdRes int id, String fragmentClassName, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigatorDestinationBuilder,kotlin.Unit> builder);
+    method public static inline void fragment(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, String route, String fragmentClassName, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigatorDestinationBuilder,kotlin.Unit> builder);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, @IdRes int id);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, @IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigatorDestinationBuilder,? extends kotlin.Unit> builder);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, String route);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigatorDestinationBuilder,? extends kotlin.Unit> builder);
+  }
+
+  public class DynamicNavHostFragment extends androidx.navigation.fragment.NavHostFragment {
+    ctor public DynamicNavHostFragment();
+    method public static final androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment create(@NavigationRes int graphResId, optional android.os.Bundle? startDestinationArgs);
+    method public static final androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment create(@NavigationRes int graphResId);
+    method protected com.google.android.play.core.splitinstall.SplitInstallManager createSplitInstallManager();
+    field public static final androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment.Companion Companion;
+  }
+
+  public static final class DynamicNavHostFragment.Companion {
+    method public androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment create(@NavigationRes int graphResId, optional android.os.Bundle? startDestinationArgs);
+    method public androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment create(@NavigationRes int graphResId);
+  }
+
+}
+
+package androidx.navigation.dynamicfeatures.fragment.ui {
+
+  public abstract class AbstractProgressFragment extends androidx.fragment.app.Fragment {
+    ctor public AbstractProgressFragment();
+    ctor public AbstractProgressFragment(int contentLayoutId);
+    method protected abstract void onCancelled();
+    method protected abstract void onFailed(@com.google.android.play.core.splitinstall.model.SplitInstallErrorCode int errorCode);
+    method protected void onInstalled();
+    method protected abstract void onProgress(@com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus int status, long bytesDownloaded, long bytesTotal);
+  }
+
+  public final class DefaultProgressFragment extends androidx.navigation.dynamicfeatures.fragment.ui.AbstractProgressFragment {
+    ctor public DefaultProgressFragment();
+    method protected void onCancelled();
+    method protected void onFailed(@com.google.android.play.core.splitinstall.model.SplitInstallErrorCode int errorCode);
+    method protected void onProgress(int status, long bytesDownloaded, long bytesTotal);
+  }
+
+}
+
diff --git a/navigation/navigation-dynamic-features-fragment/api/res-2.4.0-beta01.txt b/navigation/navigation-dynamic-features-fragment/api/res-2.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/navigation-dynamic-features-fragment/api/res-2.4.0-beta01.txt
diff --git a/navigation/navigation-dynamic-features-fragment/api/restricted_2.4.0-beta01.txt b/navigation/navigation-dynamic-features-fragment/api/restricted_2.4.0-beta01.txt
new file mode 100644
index 0000000..9271a05
--- /dev/null
+++ b/navigation/navigation-dynamic-features-fragment/api/restricted_2.4.0-beta01.txt
@@ -0,0 +1,69 @@
+// Signature format: 4.0
+package androidx.navigation.dynamicfeatures.fragment {
+
+  @androidx.navigation.Navigator.Name("fragment") public final class DynamicFragmentNavigator extends androidx.navigation.fragment.FragmentNavigator {
+    ctor public DynamicFragmentNavigator(android.content.Context context, androidx.fragment.app.FragmentManager manager, int containerId, androidx.navigation.dynamicfeatures.DynamicInstallManager installManager);
+    method public androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigator.Destination createDestination();
+  }
+
+  public static final class DynamicFragmentNavigator.Destination extends androidx.navigation.fragment.FragmentNavigator.Destination {
+    ctor public DynamicFragmentNavigator.Destination(androidx.navigation.NavigatorProvider navigatorProvider);
+    ctor public DynamicFragmentNavigator.Destination(androidx.navigation.Navigator<? extends androidx.navigation.fragment.FragmentNavigator.Destination> fragmentNavigator);
+    method public String? getModuleName();
+    method public void setModuleName(String? moduleName);
+    property public final String? moduleName;
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class DynamicFragmentNavigatorDestinationBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.fragment.FragmentNavigator.Destination> {
+    ctor @Deprecated public DynamicFragmentNavigatorDestinationBuilder(androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigator navigator, @IdRes int id, String fragmentClassName);
+    ctor public DynamicFragmentNavigatorDestinationBuilder(androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigator navigator, String route, String fragmentClassName);
+    method public androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigator.Destination build();
+    method public String? getModuleName();
+    method public void setModuleName(String? moduleName);
+    property public final String? moduleName;
+  }
+
+  public final class DynamicFragmentNavigatorDestinationBuilderKt {
+    method @Deprecated public static inline void fragment(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, @IdRes int id, String fragmentClassName, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigatorDestinationBuilder,kotlin.Unit> builder);
+    method public static inline void fragment(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, String route, String fragmentClassName, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigatorDestinationBuilder,kotlin.Unit> builder);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, @IdRes int id);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, @IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigatorDestinationBuilder,? extends kotlin.Unit> builder);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, String route);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigatorDestinationBuilder,? extends kotlin.Unit> builder);
+  }
+
+  public class DynamicNavHostFragment extends androidx.navigation.fragment.NavHostFragment {
+    ctor public DynamicNavHostFragment();
+    method public static final androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment create(@NavigationRes int graphResId, optional android.os.Bundle? startDestinationArgs);
+    method public static final androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment create(@NavigationRes int graphResId);
+    method protected com.google.android.play.core.splitinstall.SplitInstallManager createSplitInstallManager();
+    field public static final androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment.Companion Companion;
+  }
+
+  public static final class DynamicNavHostFragment.Companion {
+    method public androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment create(@NavigationRes int graphResId, optional android.os.Bundle? startDestinationArgs);
+    method public androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment create(@NavigationRes int graphResId);
+  }
+
+}
+
+package androidx.navigation.dynamicfeatures.fragment.ui {
+
+  public abstract class AbstractProgressFragment extends androidx.fragment.app.Fragment {
+    ctor public AbstractProgressFragment();
+    ctor public AbstractProgressFragment(int contentLayoutId);
+    method protected abstract void onCancelled();
+    method protected abstract void onFailed(@com.google.android.play.core.splitinstall.model.SplitInstallErrorCode int errorCode);
+    method protected void onInstalled();
+    method protected abstract void onProgress(@com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus int status, long bytesDownloaded, long bytesTotal);
+  }
+
+  public final class DefaultProgressFragment extends androidx.navigation.dynamicfeatures.fragment.ui.AbstractProgressFragment {
+    ctor public DefaultProgressFragment();
+    method protected void onCancelled();
+    method protected void onFailed(@com.google.android.play.core.splitinstall.model.SplitInstallErrorCode int errorCode);
+    method protected void onProgress(int status, long bytesDownloaded, long bytesTotal);
+  }
+
+}
+
diff --git a/navigation/navigation-dynamic-features-runtime/api/2.4.0-beta01.txt b/navigation/navigation-dynamic-features-runtime/api/2.4.0-beta01.txt
new file mode 100644
index 0000000..842d76f
--- /dev/null
+++ b/navigation/navigation-dynamic-features-runtime/api/2.4.0-beta01.txt
@@ -0,0 +1,153 @@
+// Signature format: 4.0
+package androidx.navigation.dynamicfeatures {
+
+  @androidx.navigation.Navigator.Name("activity") public final class DynamicActivityNavigator extends androidx.navigation.ActivityNavigator {
+    ctor public DynamicActivityNavigator(android.content.Context context, androidx.navigation.dynamicfeatures.DynamicInstallManager installManager);
+    method public androidx.navigation.dynamicfeatures.DynamicActivityNavigator.Destination createDestination();
+  }
+
+  public static final class DynamicActivityNavigator.Destination extends androidx.navigation.ActivityNavigator.Destination {
+    ctor public DynamicActivityNavigator.Destination(androidx.navigation.NavigatorProvider navigatorProvider);
+    ctor public DynamicActivityNavigator.Destination(androidx.navigation.Navigator<? extends androidx.navigation.ActivityNavigator.Destination> activityNavigator);
+    method public String? getModuleName();
+    method public void setModuleName(String? moduleName);
+    property public final String? moduleName;
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class DynamicActivityNavigatorDestinationBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.ActivityNavigator.Destination> {
+    ctor @Deprecated public DynamicActivityNavigatorDestinationBuilder(androidx.navigation.dynamicfeatures.DynamicActivityNavigator activityNavigator, @IdRes int id);
+    ctor public DynamicActivityNavigatorDestinationBuilder(androidx.navigation.dynamicfeatures.DynamicActivityNavigator activityNavigator, String route);
+    method public androidx.navigation.dynamicfeatures.DynamicActivityNavigator.Destination build();
+    method public String? getAction();
+    method public String? getActivityClassName();
+    method public android.net.Uri? getData();
+    method public String? getDataPattern();
+    method public String? getModuleName();
+    method public String? getTargetPackage();
+    method public void setAction(String? action);
+    method public void setActivityClassName(String? activityClassName);
+    method public void setData(android.net.Uri? data);
+    method public void setDataPattern(String? dataPattern);
+    method public void setModuleName(String? moduleName);
+    method public void setTargetPackage(String? targetPackage);
+    property public final String? action;
+    property public final String? activityClassName;
+    property public final android.net.Uri? data;
+    property public final String? dataPattern;
+    property public final String? moduleName;
+    property public final String? targetPackage;
+  }
+
+  public final class DynamicActivityNavigatorDestinationBuilderKt {
+    method @Deprecated public static inline void activity(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, @IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder,kotlin.Unit> builder);
+    method public static inline void activity(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder,kotlin.Unit> builder);
+  }
+
+  public final class DynamicExtras implements androidx.navigation.Navigator.Extras {
+    ctor public DynamicExtras(optional androidx.navigation.dynamicfeatures.DynamicInstallMonitor? installMonitor, optional androidx.navigation.Navigator.Extras? destinationExtras);
+    ctor public DynamicExtras(optional androidx.navigation.dynamicfeatures.DynamicInstallMonitor? installMonitor);
+    method public androidx.navigation.Navigator.Extras? getDestinationExtras();
+    method public androidx.navigation.dynamicfeatures.DynamicInstallMonitor? getInstallMonitor();
+    property public final androidx.navigation.Navigator.Extras? destinationExtras;
+    property public final androidx.navigation.dynamicfeatures.DynamicInstallMonitor? installMonitor;
+  }
+
+  @androidx.navigation.Navigator.Name("navigation") public final class DynamicGraphNavigator extends androidx.navigation.NavGraphNavigator {
+    ctor public DynamicGraphNavigator(androidx.navigation.NavigatorProvider navigatorProvider, androidx.navigation.dynamicfeatures.DynamicInstallManager installManager);
+    method public androidx.navigation.dynamicfeatures.DynamicGraphNavigator.DynamicNavGraph createDestination();
+    method public void installDefaultProgressDestination(kotlin.jvm.functions.Function0<? extends androidx.navigation.NavDestination> progressDestinationSupplier);
+  }
+
+  public static final class DynamicGraphNavigator.DynamicNavGraph extends androidx.navigation.NavGraph {
+    ctor public DynamicGraphNavigator.DynamicNavGraph(androidx.navigation.dynamicfeatures.DynamicGraphNavigator navGraphNavigator, androidx.navigation.NavigatorProvider navigatorProvider);
+    method public String? getModuleName();
+    method public int getProgressDestination();
+    method public void setModuleName(String? moduleName);
+    method public void setProgressDestination(int progressDestination);
+    property public final String? moduleName;
+    property public final int progressDestination;
+  }
+
+  @androidx.navigation.Navigator.Name("include-dynamic") public final class DynamicIncludeGraphNavigator extends androidx.navigation.Navigator<androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.DynamicIncludeNavGraph> {
+    ctor public DynamicIncludeGraphNavigator(android.content.Context context, androidx.navigation.NavigatorProvider navigatorProvider, androidx.navigation.NavInflater navInflater, androidx.navigation.dynamicfeatures.DynamicInstallManager installManager);
+    method public androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.DynamicIncludeNavGraph createDestination();
+  }
+
+  public static final class DynamicIncludeGraphNavigator.DynamicIncludeNavGraph extends androidx.navigation.NavDestination {
+    method public String? getGraphPackage();
+    method public String? getGraphResourceName();
+    method public String? getModuleName();
+    method public void setGraphPackage(String? graphPackage);
+    method public void setGraphResourceName(String? graphResourceName);
+    method public void setModuleName(String? moduleName);
+    property public final String? graphPackage;
+    property public final String? graphResourceName;
+    property public final String? moduleName;
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class DynamicIncludeNavGraphBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.DynamicIncludeNavGraph> {
+    ctor @Deprecated public DynamicIncludeNavGraphBuilder(androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator dynamicIncludeGraphNavigator, @IdRes int id, String moduleName, String graphResourceName);
+    ctor public DynamicIncludeNavGraphBuilder(androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator dynamicIncludeGraphNavigator, String route, String moduleName, String graphResourceName);
+    method public androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.DynamicIncludeNavGraph build();
+    method public String? getGraphPackage();
+    method public void setGraphPackage(String? graphPackage);
+    property public final String? graphPackage;
+  }
+
+  public final class DynamicIncludeNavGraphBuilderKt {
+    method @Deprecated public static inline void includeDynamic(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, @IdRes int id, String moduleName, String graphResourceName);
+    method @Deprecated public static inline void includeDynamic(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, @IdRes int id, String moduleName, String graphResourceName, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicIncludeNavGraphBuilder,kotlin.Unit> builder);
+    method public static inline void includeDynamic(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, String route, String moduleName, String graphResourceName);
+    method public static inline void includeDynamic(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, String route, String moduleName, String graphResourceName, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicIncludeNavGraphBuilder,kotlin.Unit> builder);
+  }
+
+  public class DynamicInstallManager {
+    ctor public DynamicInstallManager(android.content.Context context, com.google.android.play.core.splitinstall.SplitInstallManager splitInstallManager);
+  }
+
+  public final class DynamicInstallMonitor {
+    ctor public DynamicInstallMonitor();
+    method public void cancelInstall();
+    method public Exception? getException();
+    method public int getSessionId();
+    method public androidx.lifecycle.LiveData<com.google.android.play.core.splitinstall.SplitInstallSessionState> getStatus();
+    method public boolean isInstallRequired();
+    property public final Exception? exception;
+    property public final boolean isInstallRequired;
+    property public final int sessionId;
+    property public final androidx.lifecycle.LiveData<com.google.android.play.core.splitinstall.SplitInstallSessionState> status;
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class DynamicNavGraphBuilder extends androidx.navigation.NavGraphBuilder {
+    ctor @Deprecated public DynamicNavGraphBuilder(androidx.navigation.NavigatorProvider provider, @IdRes int id, @IdRes int startDestination);
+    ctor public DynamicNavGraphBuilder(androidx.navigation.NavigatorProvider provider, String startDestination, optional String? route);
+    method public String? getModuleName();
+    method public int getProgressDestination();
+    method public String? getProgressDestinationRoute();
+    method public void setModuleName(String? moduleName);
+    method public void setProgressDestination(int p);
+    method public void setProgressDestinationRoute(String? progDestRoute);
+    property public final String? moduleName;
+    property public final int progressDestination;
+    property public final String? progressDestinationRoute;
+  }
+
+  public final class DynamicNavGraphBuilderKt {
+    method @Deprecated public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder,kotlin.Unit> builder);
+    method @Deprecated public static inline void navigation(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder,kotlin.Unit> builder);
+    method public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder,kotlin.Unit> builder);
+    method public static inline void navigation(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, String startDestination, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder,kotlin.Unit> builder);
+  }
+
+  public final class NavControllerKt {
+    method @Deprecated public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder,kotlin.Unit> builder);
+    method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder,kotlin.Unit> builder);
+  }
+
+  public final class NavHostKt {
+    method @Deprecated public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavHost, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder,kotlin.Unit> builder);
+    method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavHost, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder,kotlin.Unit> builder);
+  }
+
+}
+
diff --git a/navigation/navigation-dynamic-features-runtime/api/public_plus_experimental_2.4.0-beta01.txt b/navigation/navigation-dynamic-features-runtime/api/public_plus_experimental_2.4.0-beta01.txt
new file mode 100644
index 0000000..842d76f
--- /dev/null
+++ b/navigation/navigation-dynamic-features-runtime/api/public_plus_experimental_2.4.0-beta01.txt
@@ -0,0 +1,153 @@
+// Signature format: 4.0
+package androidx.navigation.dynamicfeatures {
+
+  @androidx.navigation.Navigator.Name("activity") public final class DynamicActivityNavigator extends androidx.navigation.ActivityNavigator {
+    ctor public DynamicActivityNavigator(android.content.Context context, androidx.navigation.dynamicfeatures.DynamicInstallManager installManager);
+    method public androidx.navigation.dynamicfeatures.DynamicActivityNavigator.Destination createDestination();
+  }
+
+  public static final class DynamicActivityNavigator.Destination extends androidx.navigation.ActivityNavigator.Destination {
+    ctor public DynamicActivityNavigator.Destination(androidx.navigation.NavigatorProvider navigatorProvider);
+    ctor public DynamicActivityNavigator.Destination(androidx.navigation.Navigator<? extends androidx.navigation.ActivityNavigator.Destination> activityNavigator);
+    method public String? getModuleName();
+    method public void setModuleName(String? moduleName);
+    property public final String? moduleName;
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class DynamicActivityNavigatorDestinationBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.ActivityNavigator.Destination> {
+    ctor @Deprecated public DynamicActivityNavigatorDestinationBuilder(androidx.navigation.dynamicfeatures.DynamicActivityNavigator activityNavigator, @IdRes int id);
+    ctor public DynamicActivityNavigatorDestinationBuilder(androidx.navigation.dynamicfeatures.DynamicActivityNavigator activityNavigator, String route);
+    method public androidx.navigation.dynamicfeatures.DynamicActivityNavigator.Destination build();
+    method public String? getAction();
+    method public String? getActivityClassName();
+    method public android.net.Uri? getData();
+    method public String? getDataPattern();
+    method public String? getModuleName();
+    method public String? getTargetPackage();
+    method public void setAction(String? action);
+    method public void setActivityClassName(String? activityClassName);
+    method public void setData(android.net.Uri? data);
+    method public void setDataPattern(String? dataPattern);
+    method public void setModuleName(String? moduleName);
+    method public void setTargetPackage(String? targetPackage);
+    property public final String? action;
+    property public final String? activityClassName;
+    property public final android.net.Uri? data;
+    property public final String? dataPattern;
+    property public final String? moduleName;
+    property public final String? targetPackage;
+  }
+
+  public final class DynamicActivityNavigatorDestinationBuilderKt {
+    method @Deprecated public static inline void activity(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, @IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder,kotlin.Unit> builder);
+    method public static inline void activity(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder,kotlin.Unit> builder);
+  }
+
+  public final class DynamicExtras implements androidx.navigation.Navigator.Extras {
+    ctor public DynamicExtras(optional androidx.navigation.dynamicfeatures.DynamicInstallMonitor? installMonitor, optional androidx.navigation.Navigator.Extras? destinationExtras);
+    ctor public DynamicExtras(optional androidx.navigation.dynamicfeatures.DynamicInstallMonitor? installMonitor);
+    method public androidx.navigation.Navigator.Extras? getDestinationExtras();
+    method public androidx.navigation.dynamicfeatures.DynamicInstallMonitor? getInstallMonitor();
+    property public final androidx.navigation.Navigator.Extras? destinationExtras;
+    property public final androidx.navigation.dynamicfeatures.DynamicInstallMonitor? installMonitor;
+  }
+
+  @androidx.navigation.Navigator.Name("navigation") public final class DynamicGraphNavigator extends androidx.navigation.NavGraphNavigator {
+    ctor public DynamicGraphNavigator(androidx.navigation.NavigatorProvider navigatorProvider, androidx.navigation.dynamicfeatures.DynamicInstallManager installManager);
+    method public androidx.navigation.dynamicfeatures.DynamicGraphNavigator.DynamicNavGraph createDestination();
+    method public void installDefaultProgressDestination(kotlin.jvm.functions.Function0<? extends androidx.navigation.NavDestination> progressDestinationSupplier);
+  }
+
+  public static final class DynamicGraphNavigator.DynamicNavGraph extends androidx.navigation.NavGraph {
+    ctor public DynamicGraphNavigator.DynamicNavGraph(androidx.navigation.dynamicfeatures.DynamicGraphNavigator navGraphNavigator, androidx.navigation.NavigatorProvider navigatorProvider);
+    method public String? getModuleName();
+    method public int getProgressDestination();
+    method public void setModuleName(String? moduleName);
+    method public void setProgressDestination(int progressDestination);
+    property public final String? moduleName;
+    property public final int progressDestination;
+  }
+
+  @androidx.navigation.Navigator.Name("include-dynamic") public final class DynamicIncludeGraphNavigator extends androidx.navigation.Navigator<androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.DynamicIncludeNavGraph> {
+    ctor public DynamicIncludeGraphNavigator(android.content.Context context, androidx.navigation.NavigatorProvider navigatorProvider, androidx.navigation.NavInflater navInflater, androidx.navigation.dynamicfeatures.DynamicInstallManager installManager);
+    method public androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.DynamicIncludeNavGraph createDestination();
+  }
+
+  public static final class DynamicIncludeGraphNavigator.DynamicIncludeNavGraph extends androidx.navigation.NavDestination {
+    method public String? getGraphPackage();
+    method public String? getGraphResourceName();
+    method public String? getModuleName();
+    method public void setGraphPackage(String? graphPackage);
+    method public void setGraphResourceName(String? graphResourceName);
+    method public void setModuleName(String? moduleName);
+    property public final String? graphPackage;
+    property public final String? graphResourceName;
+    property public final String? moduleName;
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class DynamicIncludeNavGraphBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.DynamicIncludeNavGraph> {
+    ctor @Deprecated public DynamicIncludeNavGraphBuilder(androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator dynamicIncludeGraphNavigator, @IdRes int id, String moduleName, String graphResourceName);
+    ctor public DynamicIncludeNavGraphBuilder(androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator dynamicIncludeGraphNavigator, String route, String moduleName, String graphResourceName);
+    method public androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.DynamicIncludeNavGraph build();
+    method public String? getGraphPackage();
+    method public void setGraphPackage(String? graphPackage);
+    property public final String? graphPackage;
+  }
+
+  public final class DynamicIncludeNavGraphBuilderKt {
+    method @Deprecated public static inline void includeDynamic(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, @IdRes int id, String moduleName, String graphResourceName);
+    method @Deprecated public static inline void includeDynamic(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, @IdRes int id, String moduleName, String graphResourceName, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicIncludeNavGraphBuilder,kotlin.Unit> builder);
+    method public static inline void includeDynamic(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, String route, String moduleName, String graphResourceName);
+    method public static inline void includeDynamic(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, String route, String moduleName, String graphResourceName, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicIncludeNavGraphBuilder,kotlin.Unit> builder);
+  }
+
+  public class DynamicInstallManager {
+    ctor public DynamicInstallManager(android.content.Context context, com.google.android.play.core.splitinstall.SplitInstallManager splitInstallManager);
+  }
+
+  public final class DynamicInstallMonitor {
+    ctor public DynamicInstallMonitor();
+    method public void cancelInstall();
+    method public Exception? getException();
+    method public int getSessionId();
+    method public androidx.lifecycle.LiveData<com.google.android.play.core.splitinstall.SplitInstallSessionState> getStatus();
+    method public boolean isInstallRequired();
+    property public final Exception? exception;
+    property public final boolean isInstallRequired;
+    property public final int sessionId;
+    property public final androidx.lifecycle.LiveData<com.google.android.play.core.splitinstall.SplitInstallSessionState> status;
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class DynamicNavGraphBuilder extends androidx.navigation.NavGraphBuilder {
+    ctor @Deprecated public DynamicNavGraphBuilder(androidx.navigation.NavigatorProvider provider, @IdRes int id, @IdRes int startDestination);
+    ctor public DynamicNavGraphBuilder(androidx.navigation.NavigatorProvider provider, String startDestination, optional String? route);
+    method public String? getModuleName();
+    method public int getProgressDestination();
+    method public String? getProgressDestinationRoute();
+    method public void setModuleName(String? moduleName);
+    method public void setProgressDestination(int p);
+    method public void setProgressDestinationRoute(String? progDestRoute);
+    property public final String? moduleName;
+    property public final int progressDestination;
+    property public final String? progressDestinationRoute;
+  }
+
+  public final class DynamicNavGraphBuilderKt {
+    method @Deprecated public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder,kotlin.Unit> builder);
+    method @Deprecated public static inline void navigation(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder,kotlin.Unit> builder);
+    method public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder,kotlin.Unit> builder);
+    method public static inline void navigation(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, String startDestination, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder,kotlin.Unit> builder);
+  }
+
+  public final class NavControllerKt {
+    method @Deprecated public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder,kotlin.Unit> builder);
+    method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder,kotlin.Unit> builder);
+  }
+
+  public final class NavHostKt {
+    method @Deprecated public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavHost, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder,kotlin.Unit> builder);
+    method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavHost, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder,kotlin.Unit> builder);
+  }
+
+}
+
diff --git a/navigation/navigation-dynamic-features-runtime/api/res-2.4.0-beta01.txt b/navigation/navigation-dynamic-features-runtime/api/res-2.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/navigation-dynamic-features-runtime/api/res-2.4.0-beta01.txt
diff --git a/navigation/navigation-dynamic-features-runtime/api/restricted_2.4.0-beta01.txt b/navigation/navigation-dynamic-features-runtime/api/restricted_2.4.0-beta01.txt
new file mode 100644
index 0000000..842d76f
--- /dev/null
+++ b/navigation/navigation-dynamic-features-runtime/api/restricted_2.4.0-beta01.txt
@@ -0,0 +1,153 @@
+// Signature format: 4.0
+package androidx.navigation.dynamicfeatures {
+
+  @androidx.navigation.Navigator.Name("activity") public final class DynamicActivityNavigator extends androidx.navigation.ActivityNavigator {
+    ctor public DynamicActivityNavigator(android.content.Context context, androidx.navigation.dynamicfeatures.DynamicInstallManager installManager);
+    method public androidx.navigation.dynamicfeatures.DynamicActivityNavigator.Destination createDestination();
+  }
+
+  public static final class DynamicActivityNavigator.Destination extends androidx.navigation.ActivityNavigator.Destination {
+    ctor public DynamicActivityNavigator.Destination(androidx.navigation.NavigatorProvider navigatorProvider);
+    ctor public DynamicActivityNavigator.Destination(androidx.navigation.Navigator<? extends androidx.navigation.ActivityNavigator.Destination> activityNavigator);
+    method public String? getModuleName();
+    method public void setModuleName(String? moduleName);
+    property public final String? moduleName;
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class DynamicActivityNavigatorDestinationBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.ActivityNavigator.Destination> {
+    ctor @Deprecated public DynamicActivityNavigatorDestinationBuilder(androidx.navigation.dynamicfeatures.DynamicActivityNavigator activityNavigator, @IdRes int id);
+    ctor public DynamicActivityNavigatorDestinationBuilder(androidx.navigation.dynamicfeatures.DynamicActivityNavigator activityNavigator, String route);
+    method public androidx.navigation.dynamicfeatures.DynamicActivityNavigator.Destination build();
+    method public String? getAction();
+    method public String? getActivityClassName();
+    method public android.net.Uri? getData();
+    method public String? getDataPattern();
+    method public String? getModuleName();
+    method public String? getTargetPackage();
+    method public void setAction(String? action);
+    method public void setActivityClassName(String? activityClassName);
+    method public void setData(android.net.Uri? data);
+    method public void setDataPattern(String? dataPattern);
+    method public void setModuleName(String? moduleName);
+    method public void setTargetPackage(String? targetPackage);
+    property public final String? action;
+    property public final String? activityClassName;
+    property public final android.net.Uri? data;
+    property public final String? dataPattern;
+    property public final String? moduleName;
+    property public final String? targetPackage;
+  }
+
+  public final class DynamicActivityNavigatorDestinationBuilderKt {
+    method @Deprecated public static inline void activity(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, @IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder,kotlin.Unit> builder);
+    method public static inline void activity(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder,kotlin.Unit> builder);
+  }
+
+  public final class DynamicExtras implements androidx.navigation.Navigator.Extras {
+    ctor public DynamicExtras(optional androidx.navigation.dynamicfeatures.DynamicInstallMonitor? installMonitor, optional androidx.navigation.Navigator.Extras? destinationExtras);
+    ctor public DynamicExtras(optional androidx.navigation.dynamicfeatures.DynamicInstallMonitor? installMonitor);
+    method public androidx.navigation.Navigator.Extras? getDestinationExtras();
+    method public androidx.navigation.dynamicfeatures.DynamicInstallMonitor? getInstallMonitor();
+    property public final androidx.navigation.Navigator.Extras? destinationExtras;
+    property public final androidx.navigation.dynamicfeatures.DynamicInstallMonitor? installMonitor;
+  }
+
+  @androidx.navigation.Navigator.Name("navigation") public final class DynamicGraphNavigator extends androidx.navigation.NavGraphNavigator {
+    ctor public DynamicGraphNavigator(androidx.navigation.NavigatorProvider navigatorProvider, androidx.navigation.dynamicfeatures.DynamicInstallManager installManager);
+    method public androidx.navigation.dynamicfeatures.DynamicGraphNavigator.DynamicNavGraph createDestination();
+    method public void installDefaultProgressDestination(kotlin.jvm.functions.Function0<? extends androidx.navigation.NavDestination> progressDestinationSupplier);
+  }
+
+  public static final class DynamicGraphNavigator.DynamicNavGraph extends androidx.navigation.NavGraph {
+    ctor public DynamicGraphNavigator.DynamicNavGraph(androidx.navigation.dynamicfeatures.DynamicGraphNavigator navGraphNavigator, androidx.navigation.NavigatorProvider navigatorProvider);
+    method public String? getModuleName();
+    method public int getProgressDestination();
+    method public void setModuleName(String? moduleName);
+    method public void setProgressDestination(int progressDestination);
+    property public final String? moduleName;
+    property public final int progressDestination;
+  }
+
+  @androidx.navigation.Navigator.Name("include-dynamic") public final class DynamicIncludeGraphNavigator extends androidx.navigation.Navigator<androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.DynamicIncludeNavGraph> {
+    ctor public DynamicIncludeGraphNavigator(android.content.Context context, androidx.navigation.NavigatorProvider navigatorProvider, androidx.navigation.NavInflater navInflater, androidx.navigation.dynamicfeatures.DynamicInstallManager installManager);
+    method public androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.DynamicIncludeNavGraph createDestination();
+  }
+
+  public static final class DynamicIncludeGraphNavigator.DynamicIncludeNavGraph extends androidx.navigation.NavDestination {
+    method public String? getGraphPackage();
+    method public String? getGraphResourceName();
+    method public String? getModuleName();
+    method public void setGraphPackage(String? graphPackage);
+    method public void setGraphResourceName(String? graphResourceName);
+    method public void setModuleName(String? moduleName);
+    property public final String? graphPackage;
+    property public final String? graphResourceName;
+    property public final String? moduleName;
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class DynamicIncludeNavGraphBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.DynamicIncludeNavGraph> {
+    ctor @Deprecated public DynamicIncludeNavGraphBuilder(androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator dynamicIncludeGraphNavigator, @IdRes int id, String moduleName, String graphResourceName);
+    ctor public DynamicIncludeNavGraphBuilder(androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator dynamicIncludeGraphNavigator, String route, String moduleName, String graphResourceName);
+    method public androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.DynamicIncludeNavGraph build();
+    method public String? getGraphPackage();
+    method public void setGraphPackage(String? graphPackage);
+    property public final String? graphPackage;
+  }
+
+  public final class DynamicIncludeNavGraphBuilderKt {
+    method @Deprecated public static inline void includeDynamic(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, @IdRes int id, String moduleName, String graphResourceName);
+    method @Deprecated public static inline void includeDynamic(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, @IdRes int id, String moduleName, String graphResourceName, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicIncludeNavGraphBuilder,kotlin.Unit> builder);
+    method public static inline void includeDynamic(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, String route, String moduleName, String graphResourceName);
+    method public static inline void includeDynamic(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, String route, String moduleName, String graphResourceName, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicIncludeNavGraphBuilder,kotlin.Unit> builder);
+  }
+
+  public class DynamicInstallManager {
+    ctor public DynamicInstallManager(android.content.Context context, com.google.android.play.core.splitinstall.SplitInstallManager splitInstallManager);
+  }
+
+  public final class DynamicInstallMonitor {
+    ctor public DynamicInstallMonitor();
+    method public void cancelInstall();
+    method public Exception? getException();
+    method public int getSessionId();
+    method public androidx.lifecycle.LiveData<com.google.android.play.core.splitinstall.SplitInstallSessionState> getStatus();
+    method public boolean isInstallRequired();
+    property public final Exception? exception;
+    property public final boolean isInstallRequired;
+    property public final int sessionId;
+    property public final androidx.lifecycle.LiveData<com.google.android.play.core.splitinstall.SplitInstallSessionState> status;
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class DynamicNavGraphBuilder extends androidx.navigation.NavGraphBuilder {
+    ctor @Deprecated public DynamicNavGraphBuilder(androidx.navigation.NavigatorProvider provider, @IdRes int id, @IdRes int startDestination);
+    ctor public DynamicNavGraphBuilder(androidx.navigation.NavigatorProvider provider, String startDestination, optional String? route);
+    method public String? getModuleName();
+    method public int getProgressDestination();
+    method public String? getProgressDestinationRoute();
+    method public void setModuleName(String? moduleName);
+    method public void setProgressDestination(int p);
+    method public void setProgressDestinationRoute(String? progDestRoute);
+    property public final String? moduleName;
+    property public final int progressDestination;
+    property public final String? progressDestinationRoute;
+  }
+
+  public final class DynamicNavGraphBuilderKt {
+    method @Deprecated public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder,kotlin.Unit> builder);
+    method @Deprecated public static inline void navigation(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder,kotlin.Unit> builder);
+    method public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder,kotlin.Unit> builder);
+    method public static inline void navigation(androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder, String startDestination, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder,kotlin.Unit> builder);
+  }
+
+  public final class NavControllerKt {
+    method @Deprecated public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder,kotlin.Unit> builder);
+    method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder,kotlin.Unit> builder);
+  }
+
+  public final class NavHostKt {
+    method @Deprecated public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavHost, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder,kotlin.Unit> builder);
+    method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavHost, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder,kotlin.Unit> builder);
+  }
+
+}
+
diff --git a/navigation/navigation-fragment-ktx/api/2.4.0-beta01.txt b/navigation/navigation-fragment-ktx/api/2.4.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/navigation/navigation-fragment-ktx/api/2.4.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/navigation/navigation-fragment-ktx/api/public_plus_experimental_2.4.0-beta01.txt b/navigation/navigation-fragment-ktx/api/public_plus_experimental_2.4.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/navigation/navigation-fragment-ktx/api/public_plus_experimental_2.4.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/navigation/navigation-fragment-ktx/api/res-2.4.0-beta01.txt b/navigation/navigation-fragment-ktx/api/res-2.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/navigation-fragment-ktx/api/res-2.4.0-beta01.txt
diff --git a/navigation/navigation-fragment-ktx/api/restricted_2.4.0-beta01.txt b/navigation/navigation-fragment-ktx/api/restricted_2.4.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/navigation/navigation-fragment-ktx/api/restricted_2.4.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/navigation/navigation-fragment/api/2.4.0-beta01.txt b/navigation/navigation-fragment/api/2.4.0-beta01.txt
new file mode 100644
index 0000000..f2297ab
--- /dev/null
+++ b/navigation/navigation-fragment/api/2.4.0-beta01.txt
@@ -0,0 +1,123 @@
+// Signature format: 4.0
+package androidx.navigation {
+
+  public final class NavGraphViewModelLazyKt {
+    method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<? extends VM>! navGraphViewModels(androidx.fragment.app.Fragment, @IdRes int navGraphId, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+    method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<? extends VM>! navGraphViewModels(androidx.fragment.app.Fragment, String navGraphRoute, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+  }
+
+}
+
+package androidx.navigation.fragment {
+
+  public abstract class AbstractListDetailFragment extends androidx.fragment.app.Fragment {
+    ctor public AbstractListDetailFragment();
+    method public final androidx.navigation.fragment.NavHostFragment getDetailPaneNavHostFragment();
+    method public final androidx.slidingpanelayout.widget.SlidingPaneLayout getSlidingPaneLayout();
+    method public androidx.navigation.fragment.NavHostFragment onCreateDetailPaneNavHostFragment();
+    method public abstract android.view.View onCreateListPaneView(android.view.LayoutInflater inflater, android.view.ViewGroup? container, android.os.Bundle? savedInstanceState);
+    method @CallSuper public final android.view.View onCreateView(android.view.LayoutInflater inflater, android.view.ViewGroup? container, android.os.Bundle? savedInstanceState);
+    method public void onListPaneViewCreated(android.view.View view, android.os.Bundle? savedInstanceState);
+    method @CallSuper public final void onViewCreated(android.view.View view, android.os.Bundle? savedInstanceState);
+    property public final androidx.navigation.fragment.NavHostFragment detailPaneNavHostFragment;
+    property public final androidx.slidingpanelayout.widget.SlidingPaneLayout slidingPaneLayout;
+  }
+
+  @androidx.navigation.Navigator.Name("dialog") public final class DialogFragmentNavigator extends androidx.navigation.Navigator<androidx.navigation.fragment.DialogFragmentNavigator.Destination> {
+    ctor public DialogFragmentNavigator(android.content.Context context, androidx.fragment.app.FragmentManager fragmentManager);
+    method public androidx.navigation.fragment.DialogFragmentNavigator.Destination createDestination();
+  }
+
+  @androidx.navigation.NavDestination.ClassType(DialogFragment::class) public static class DialogFragmentNavigator.Destination extends androidx.navigation.NavDestination implements androidx.navigation.FloatingWindow {
+    ctor public DialogFragmentNavigator.Destination(androidx.navigation.Navigator<? extends androidx.navigation.fragment.DialogFragmentNavigator.Destination> fragmentNavigator);
+    ctor public DialogFragmentNavigator.Destination(androidx.navigation.NavigatorProvider navigatorProvider);
+    method public final String getClassName();
+    method public final androidx.navigation.fragment.DialogFragmentNavigator.Destination setClassName(String className);
+    property public final String className;
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class DialogFragmentNavigatorDestinationBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.fragment.DialogFragmentNavigator.Destination> {
+    ctor @Deprecated public DialogFragmentNavigatorDestinationBuilder(androidx.navigation.fragment.DialogFragmentNavigator navigator, @IdRes int id, kotlin.reflect.KClass<? extends androidx.fragment.app.DialogFragment> fragmentClass);
+    ctor public DialogFragmentNavigatorDestinationBuilder(androidx.navigation.fragment.DialogFragmentNavigator navigator, String route, kotlin.reflect.KClass<? extends androidx.fragment.app.DialogFragment> fragmentClass);
+    method public androidx.navigation.fragment.DialogFragmentNavigator.Destination build();
+  }
+
+  public final class DialogFragmentNavigatorDestinationBuilderKt {
+    method public static inline <reified F extends androidx.fragment.app.DialogFragment> void dialog(androidx.navigation.NavGraphBuilder, @IdRes int id);
+    method public static inline <reified F extends androidx.fragment.app.DialogFragment> void dialog(androidx.navigation.NavGraphBuilder, @IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.fragment.DialogFragmentNavigatorDestinationBuilder,? extends kotlin.Unit> builder);
+    method public static inline <reified F extends androidx.fragment.app.DialogFragment> void dialog(androidx.navigation.NavGraphBuilder, String route);
+    method public static inline <reified F extends androidx.fragment.app.DialogFragment> void dialog(androidx.navigation.NavGraphBuilder, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.fragment.DialogFragmentNavigatorDestinationBuilder,? extends kotlin.Unit> builder);
+  }
+
+  public final class FragmentKt {
+    method public static androidx.navigation.NavController findNavController(androidx.fragment.app.Fragment);
+  }
+
+  public final class FragmentNavArgsLazyKt {
+    method @MainThread public static inline <reified Args extends androidx.navigation.NavArgs> androidx.navigation.NavArgsLazy<Args>! navArgs(androidx.fragment.app.Fragment);
+  }
+
+  @androidx.navigation.Navigator.Name("fragment") public class FragmentNavigator extends androidx.navigation.Navigator<androidx.navigation.fragment.FragmentNavigator.Destination> {
+    ctor public FragmentNavigator(android.content.Context context, androidx.fragment.app.FragmentManager fragmentManager, int containerId);
+    method public androidx.navigation.fragment.FragmentNavigator.Destination createDestination();
+    method @Deprecated public androidx.fragment.app.Fragment instantiateFragment(android.content.Context context, androidx.fragment.app.FragmentManager fragmentManager, String className, android.os.Bundle? args);
+  }
+
+  @androidx.navigation.NavDestination.ClassType(Fragment::class) public static class FragmentNavigator.Destination extends androidx.navigation.NavDestination {
+    ctor public FragmentNavigator.Destination(androidx.navigation.Navigator<? extends androidx.navigation.fragment.FragmentNavigator.Destination> fragmentNavigator);
+    ctor public FragmentNavigator.Destination(androidx.navigation.NavigatorProvider navigatorProvider);
+    method public final String getClassName();
+    method public final androidx.navigation.fragment.FragmentNavigator.Destination setClassName(String className);
+    property public final String className;
+  }
+
+  public static final class FragmentNavigator.Extras implements androidx.navigation.Navigator.Extras {
+    method public java.util.Map<android.view.View,java.lang.String> getSharedElements();
+    property public final java.util.Map<android.view.View,java.lang.String> sharedElements;
+  }
+
+  public static final class FragmentNavigator.Extras.Builder {
+    ctor public FragmentNavigator.Extras.Builder();
+    method public androidx.navigation.fragment.FragmentNavigator.Extras.Builder addSharedElement(android.view.View sharedElement, String name);
+    method public androidx.navigation.fragment.FragmentNavigator.Extras.Builder addSharedElements(java.util.Map<android.view.View,java.lang.String> sharedElements);
+    method public androidx.navigation.fragment.FragmentNavigator.Extras build();
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class FragmentNavigatorDestinationBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.fragment.FragmentNavigator.Destination> {
+    ctor @Deprecated public FragmentNavigatorDestinationBuilder(androidx.navigation.fragment.FragmentNavigator navigator, @IdRes int id, kotlin.reflect.KClass<? extends androidx.fragment.app.Fragment> fragmentClass);
+    ctor public FragmentNavigatorDestinationBuilder(androidx.navigation.fragment.FragmentNavigator navigator, String route, kotlin.reflect.KClass<? extends androidx.fragment.app.Fragment> fragmentClass);
+    method public androidx.navigation.fragment.FragmentNavigator.Destination build();
+  }
+
+  public final class FragmentNavigatorDestinationBuilderKt {
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.NavGraphBuilder, @IdRes int id);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.NavGraphBuilder, @IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.fragment.FragmentNavigatorDestinationBuilder,? extends kotlin.Unit> builder);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.NavGraphBuilder, String route);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.NavGraphBuilder, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.fragment.FragmentNavigatorDestinationBuilder,? extends kotlin.Unit> builder);
+  }
+
+  public final class FragmentNavigatorExtrasKt {
+    method public static androidx.navigation.fragment.FragmentNavigator.Extras FragmentNavigatorExtras(kotlin.Pair<? extends android.view.View,java.lang.String>... sharedElements);
+  }
+
+  public class NavHostFragment extends androidx.fragment.app.Fragment implements androidx.navigation.NavHost {
+    ctor public NavHostFragment();
+    method public static final androidx.navigation.fragment.NavHostFragment create(@NavigationRes int graphResId, optional android.os.Bundle? startDestinationArgs);
+    method public static final androidx.navigation.fragment.NavHostFragment create(@NavigationRes int graphResId);
+    method @Deprecated protected androidx.navigation.Navigator<? extends androidx.navigation.fragment.FragmentNavigator.Destination> createFragmentNavigator();
+    method public static final androidx.navigation.NavController findNavController(androidx.fragment.app.Fragment fragment);
+    method public final androidx.navigation.NavController getNavController();
+    method @Deprecated @CallSuper protected void onCreateNavController(androidx.navigation.NavController navController);
+    method @CallSuper protected void onCreateNavHostController(androidx.navigation.NavHostController navHostController);
+    property public final androidx.navigation.NavController navController;
+    field public static final androidx.navigation.fragment.NavHostFragment.Companion Companion;
+  }
+
+  public static final class NavHostFragment.Companion {
+    method public androidx.navigation.fragment.NavHostFragment create(@NavigationRes int graphResId, optional android.os.Bundle? startDestinationArgs);
+    method public androidx.navigation.fragment.NavHostFragment create(@NavigationRes int graphResId);
+    method public androidx.navigation.NavController findNavController(androidx.fragment.app.Fragment fragment);
+  }
+
+}
+
diff --git a/navigation/navigation-fragment/api/public_plus_experimental_2.4.0-beta01.txt b/navigation/navigation-fragment/api/public_plus_experimental_2.4.0-beta01.txt
new file mode 100644
index 0000000..f2297ab
--- /dev/null
+++ b/navigation/navigation-fragment/api/public_plus_experimental_2.4.0-beta01.txt
@@ -0,0 +1,123 @@
+// Signature format: 4.0
+package androidx.navigation {
+
+  public final class NavGraphViewModelLazyKt {
+    method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<? extends VM>! navGraphViewModels(androidx.fragment.app.Fragment, @IdRes int navGraphId, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+    method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<? extends VM>! navGraphViewModels(androidx.fragment.app.Fragment, String navGraphRoute, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+  }
+
+}
+
+package androidx.navigation.fragment {
+
+  public abstract class AbstractListDetailFragment extends androidx.fragment.app.Fragment {
+    ctor public AbstractListDetailFragment();
+    method public final androidx.navigation.fragment.NavHostFragment getDetailPaneNavHostFragment();
+    method public final androidx.slidingpanelayout.widget.SlidingPaneLayout getSlidingPaneLayout();
+    method public androidx.navigation.fragment.NavHostFragment onCreateDetailPaneNavHostFragment();
+    method public abstract android.view.View onCreateListPaneView(android.view.LayoutInflater inflater, android.view.ViewGroup? container, android.os.Bundle? savedInstanceState);
+    method @CallSuper public final android.view.View onCreateView(android.view.LayoutInflater inflater, android.view.ViewGroup? container, android.os.Bundle? savedInstanceState);
+    method public void onListPaneViewCreated(android.view.View view, android.os.Bundle? savedInstanceState);
+    method @CallSuper public final void onViewCreated(android.view.View view, android.os.Bundle? savedInstanceState);
+    property public final androidx.navigation.fragment.NavHostFragment detailPaneNavHostFragment;
+    property public final androidx.slidingpanelayout.widget.SlidingPaneLayout slidingPaneLayout;
+  }
+
+  @androidx.navigation.Navigator.Name("dialog") public final class DialogFragmentNavigator extends androidx.navigation.Navigator<androidx.navigation.fragment.DialogFragmentNavigator.Destination> {
+    ctor public DialogFragmentNavigator(android.content.Context context, androidx.fragment.app.FragmentManager fragmentManager);
+    method public androidx.navigation.fragment.DialogFragmentNavigator.Destination createDestination();
+  }
+
+  @androidx.navigation.NavDestination.ClassType(DialogFragment::class) public static class DialogFragmentNavigator.Destination extends androidx.navigation.NavDestination implements androidx.navigation.FloatingWindow {
+    ctor public DialogFragmentNavigator.Destination(androidx.navigation.Navigator<? extends androidx.navigation.fragment.DialogFragmentNavigator.Destination> fragmentNavigator);
+    ctor public DialogFragmentNavigator.Destination(androidx.navigation.NavigatorProvider navigatorProvider);
+    method public final String getClassName();
+    method public final androidx.navigation.fragment.DialogFragmentNavigator.Destination setClassName(String className);
+    property public final String className;
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class DialogFragmentNavigatorDestinationBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.fragment.DialogFragmentNavigator.Destination> {
+    ctor @Deprecated public DialogFragmentNavigatorDestinationBuilder(androidx.navigation.fragment.DialogFragmentNavigator navigator, @IdRes int id, kotlin.reflect.KClass<? extends androidx.fragment.app.DialogFragment> fragmentClass);
+    ctor public DialogFragmentNavigatorDestinationBuilder(androidx.navigation.fragment.DialogFragmentNavigator navigator, String route, kotlin.reflect.KClass<? extends androidx.fragment.app.DialogFragment> fragmentClass);
+    method public androidx.navigation.fragment.DialogFragmentNavigator.Destination build();
+  }
+
+  public final class DialogFragmentNavigatorDestinationBuilderKt {
+    method public static inline <reified F extends androidx.fragment.app.DialogFragment> void dialog(androidx.navigation.NavGraphBuilder, @IdRes int id);
+    method public static inline <reified F extends androidx.fragment.app.DialogFragment> void dialog(androidx.navigation.NavGraphBuilder, @IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.fragment.DialogFragmentNavigatorDestinationBuilder,? extends kotlin.Unit> builder);
+    method public static inline <reified F extends androidx.fragment.app.DialogFragment> void dialog(androidx.navigation.NavGraphBuilder, String route);
+    method public static inline <reified F extends androidx.fragment.app.DialogFragment> void dialog(androidx.navigation.NavGraphBuilder, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.fragment.DialogFragmentNavigatorDestinationBuilder,? extends kotlin.Unit> builder);
+  }
+
+  public final class FragmentKt {
+    method public static androidx.navigation.NavController findNavController(androidx.fragment.app.Fragment);
+  }
+
+  public final class FragmentNavArgsLazyKt {
+    method @MainThread public static inline <reified Args extends androidx.navigation.NavArgs> androidx.navigation.NavArgsLazy<Args>! navArgs(androidx.fragment.app.Fragment);
+  }
+
+  @androidx.navigation.Navigator.Name("fragment") public class FragmentNavigator extends androidx.navigation.Navigator<androidx.navigation.fragment.FragmentNavigator.Destination> {
+    ctor public FragmentNavigator(android.content.Context context, androidx.fragment.app.FragmentManager fragmentManager, int containerId);
+    method public androidx.navigation.fragment.FragmentNavigator.Destination createDestination();
+    method @Deprecated public androidx.fragment.app.Fragment instantiateFragment(android.content.Context context, androidx.fragment.app.FragmentManager fragmentManager, String className, android.os.Bundle? args);
+  }
+
+  @androidx.navigation.NavDestination.ClassType(Fragment::class) public static class FragmentNavigator.Destination extends androidx.navigation.NavDestination {
+    ctor public FragmentNavigator.Destination(androidx.navigation.Navigator<? extends androidx.navigation.fragment.FragmentNavigator.Destination> fragmentNavigator);
+    ctor public FragmentNavigator.Destination(androidx.navigation.NavigatorProvider navigatorProvider);
+    method public final String getClassName();
+    method public final androidx.navigation.fragment.FragmentNavigator.Destination setClassName(String className);
+    property public final String className;
+  }
+
+  public static final class FragmentNavigator.Extras implements androidx.navigation.Navigator.Extras {
+    method public java.util.Map<android.view.View,java.lang.String> getSharedElements();
+    property public final java.util.Map<android.view.View,java.lang.String> sharedElements;
+  }
+
+  public static final class FragmentNavigator.Extras.Builder {
+    ctor public FragmentNavigator.Extras.Builder();
+    method public androidx.navigation.fragment.FragmentNavigator.Extras.Builder addSharedElement(android.view.View sharedElement, String name);
+    method public androidx.navigation.fragment.FragmentNavigator.Extras.Builder addSharedElements(java.util.Map<android.view.View,java.lang.String> sharedElements);
+    method public androidx.navigation.fragment.FragmentNavigator.Extras build();
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class FragmentNavigatorDestinationBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.fragment.FragmentNavigator.Destination> {
+    ctor @Deprecated public FragmentNavigatorDestinationBuilder(androidx.navigation.fragment.FragmentNavigator navigator, @IdRes int id, kotlin.reflect.KClass<? extends androidx.fragment.app.Fragment> fragmentClass);
+    ctor public FragmentNavigatorDestinationBuilder(androidx.navigation.fragment.FragmentNavigator navigator, String route, kotlin.reflect.KClass<? extends androidx.fragment.app.Fragment> fragmentClass);
+    method public androidx.navigation.fragment.FragmentNavigator.Destination build();
+  }
+
+  public final class FragmentNavigatorDestinationBuilderKt {
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.NavGraphBuilder, @IdRes int id);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.NavGraphBuilder, @IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.fragment.FragmentNavigatorDestinationBuilder,? extends kotlin.Unit> builder);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.NavGraphBuilder, String route);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.NavGraphBuilder, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.fragment.FragmentNavigatorDestinationBuilder,? extends kotlin.Unit> builder);
+  }
+
+  public final class FragmentNavigatorExtrasKt {
+    method public static androidx.navigation.fragment.FragmentNavigator.Extras FragmentNavigatorExtras(kotlin.Pair<? extends android.view.View,java.lang.String>... sharedElements);
+  }
+
+  public class NavHostFragment extends androidx.fragment.app.Fragment implements androidx.navigation.NavHost {
+    ctor public NavHostFragment();
+    method public static final androidx.navigation.fragment.NavHostFragment create(@NavigationRes int graphResId, optional android.os.Bundle? startDestinationArgs);
+    method public static final androidx.navigation.fragment.NavHostFragment create(@NavigationRes int graphResId);
+    method @Deprecated protected androidx.navigation.Navigator<? extends androidx.navigation.fragment.FragmentNavigator.Destination> createFragmentNavigator();
+    method public static final androidx.navigation.NavController findNavController(androidx.fragment.app.Fragment fragment);
+    method public final androidx.navigation.NavController getNavController();
+    method @Deprecated @CallSuper protected void onCreateNavController(androidx.navigation.NavController navController);
+    method @CallSuper protected void onCreateNavHostController(androidx.navigation.NavHostController navHostController);
+    property public final androidx.navigation.NavController navController;
+    field public static final androidx.navigation.fragment.NavHostFragment.Companion Companion;
+  }
+
+  public static final class NavHostFragment.Companion {
+    method public androidx.navigation.fragment.NavHostFragment create(@NavigationRes int graphResId, optional android.os.Bundle? startDestinationArgs);
+    method public androidx.navigation.fragment.NavHostFragment create(@NavigationRes int graphResId);
+    method public androidx.navigation.NavController findNavController(androidx.fragment.app.Fragment fragment);
+  }
+
+}
+
diff --git a/navigation/navigation-fragment/api/res-2.4.0-beta01.txt b/navigation/navigation-fragment/api/res-2.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/navigation-fragment/api/res-2.4.0-beta01.txt
diff --git a/navigation/navigation-fragment/api/restricted_2.4.0-beta01.txt b/navigation/navigation-fragment/api/restricted_2.4.0-beta01.txt
new file mode 100644
index 0000000..f2297ab
--- /dev/null
+++ b/navigation/navigation-fragment/api/restricted_2.4.0-beta01.txt
@@ -0,0 +1,123 @@
+// Signature format: 4.0
+package androidx.navigation {
+
+  public final class NavGraphViewModelLazyKt {
+    method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<? extends VM>! navGraphViewModels(androidx.fragment.app.Fragment, @IdRes int navGraphId, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+    method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<? extends VM>! navGraphViewModels(androidx.fragment.app.Fragment, String navGraphRoute, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
+  }
+
+}
+
+package androidx.navigation.fragment {
+
+  public abstract class AbstractListDetailFragment extends androidx.fragment.app.Fragment {
+    ctor public AbstractListDetailFragment();
+    method public final androidx.navigation.fragment.NavHostFragment getDetailPaneNavHostFragment();
+    method public final androidx.slidingpanelayout.widget.SlidingPaneLayout getSlidingPaneLayout();
+    method public androidx.navigation.fragment.NavHostFragment onCreateDetailPaneNavHostFragment();
+    method public abstract android.view.View onCreateListPaneView(android.view.LayoutInflater inflater, android.view.ViewGroup? container, android.os.Bundle? savedInstanceState);
+    method @CallSuper public final android.view.View onCreateView(android.view.LayoutInflater inflater, android.view.ViewGroup? container, android.os.Bundle? savedInstanceState);
+    method public void onListPaneViewCreated(android.view.View view, android.os.Bundle? savedInstanceState);
+    method @CallSuper public final void onViewCreated(android.view.View view, android.os.Bundle? savedInstanceState);
+    property public final androidx.navigation.fragment.NavHostFragment detailPaneNavHostFragment;
+    property public final androidx.slidingpanelayout.widget.SlidingPaneLayout slidingPaneLayout;
+  }
+
+  @androidx.navigation.Navigator.Name("dialog") public final class DialogFragmentNavigator extends androidx.navigation.Navigator<androidx.navigation.fragment.DialogFragmentNavigator.Destination> {
+    ctor public DialogFragmentNavigator(android.content.Context context, androidx.fragment.app.FragmentManager fragmentManager);
+    method public androidx.navigation.fragment.DialogFragmentNavigator.Destination createDestination();
+  }
+
+  @androidx.navigation.NavDestination.ClassType(DialogFragment::class) public static class DialogFragmentNavigator.Destination extends androidx.navigation.NavDestination implements androidx.navigation.FloatingWindow {
+    ctor public DialogFragmentNavigator.Destination(androidx.navigation.Navigator<? extends androidx.navigation.fragment.DialogFragmentNavigator.Destination> fragmentNavigator);
+    ctor public DialogFragmentNavigator.Destination(androidx.navigation.NavigatorProvider navigatorProvider);
+    method public final String getClassName();
+    method public final androidx.navigation.fragment.DialogFragmentNavigator.Destination setClassName(String className);
+    property public final String className;
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class DialogFragmentNavigatorDestinationBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.fragment.DialogFragmentNavigator.Destination> {
+    ctor @Deprecated public DialogFragmentNavigatorDestinationBuilder(androidx.navigation.fragment.DialogFragmentNavigator navigator, @IdRes int id, kotlin.reflect.KClass<? extends androidx.fragment.app.DialogFragment> fragmentClass);
+    ctor public DialogFragmentNavigatorDestinationBuilder(androidx.navigation.fragment.DialogFragmentNavigator navigator, String route, kotlin.reflect.KClass<? extends androidx.fragment.app.DialogFragment> fragmentClass);
+    method public androidx.navigation.fragment.DialogFragmentNavigator.Destination build();
+  }
+
+  public final class DialogFragmentNavigatorDestinationBuilderKt {
+    method public static inline <reified F extends androidx.fragment.app.DialogFragment> void dialog(androidx.navigation.NavGraphBuilder, @IdRes int id);
+    method public static inline <reified F extends androidx.fragment.app.DialogFragment> void dialog(androidx.navigation.NavGraphBuilder, @IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.fragment.DialogFragmentNavigatorDestinationBuilder,? extends kotlin.Unit> builder);
+    method public static inline <reified F extends androidx.fragment.app.DialogFragment> void dialog(androidx.navigation.NavGraphBuilder, String route);
+    method public static inline <reified F extends androidx.fragment.app.DialogFragment> void dialog(androidx.navigation.NavGraphBuilder, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.fragment.DialogFragmentNavigatorDestinationBuilder,? extends kotlin.Unit> builder);
+  }
+
+  public final class FragmentKt {
+    method public static androidx.navigation.NavController findNavController(androidx.fragment.app.Fragment);
+  }
+
+  public final class FragmentNavArgsLazyKt {
+    method @MainThread public static inline <reified Args extends androidx.navigation.NavArgs> androidx.navigation.NavArgsLazy<Args>! navArgs(androidx.fragment.app.Fragment);
+  }
+
+  @androidx.navigation.Navigator.Name("fragment") public class FragmentNavigator extends androidx.navigation.Navigator<androidx.navigation.fragment.FragmentNavigator.Destination> {
+    ctor public FragmentNavigator(android.content.Context context, androidx.fragment.app.FragmentManager fragmentManager, int containerId);
+    method public androidx.navigation.fragment.FragmentNavigator.Destination createDestination();
+    method @Deprecated public androidx.fragment.app.Fragment instantiateFragment(android.content.Context context, androidx.fragment.app.FragmentManager fragmentManager, String className, android.os.Bundle? args);
+  }
+
+  @androidx.navigation.NavDestination.ClassType(Fragment::class) public static class FragmentNavigator.Destination extends androidx.navigation.NavDestination {
+    ctor public FragmentNavigator.Destination(androidx.navigation.Navigator<? extends androidx.navigation.fragment.FragmentNavigator.Destination> fragmentNavigator);
+    ctor public FragmentNavigator.Destination(androidx.navigation.NavigatorProvider navigatorProvider);
+    method public final String getClassName();
+    method public final androidx.navigation.fragment.FragmentNavigator.Destination setClassName(String className);
+    property public final String className;
+  }
+
+  public static final class FragmentNavigator.Extras implements androidx.navigation.Navigator.Extras {
+    method public java.util.Map<android.view.View,java.lang.String> getSharedElements();
+    property public final java.util.Map<android.view.View,java.lang.String> sharedElements;
+  }
+
+  public static final class FragmentNavigator.Extras.Builder {
+    ctor public FragmentNavigator.Extras.Builder();
+    method public androidx.navigation.fragment.FragmentNavigator.Extras.Builder addSharedElement(android.view.View sharedElement, String name);
+    method public androidx.navigation.fragment.FragmentNavigator.Extras.Builder addSharedElements(java.util.Map<android.view.View,java.lang.String> sharedElements);
+    method public androidx.navigation.fragment.FragmentNavigator.Extras build();
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class FragmentNavigatorDestinationBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.fragment.FragmentNavigator.Destination> {
+    ctor @Deprecated public FragmentNavigatorDestinationBuilder(androidx.navigation.fragment.FragmentNavigator navigator, @IdRes int id, kotlin.reflect.KClass<? extends androidx.fragment.app.Fragment> fragmentClass);
+    ctor public FragmentNavigatorDestinationBuilder(androidx.navigation.fragment.FragmentNavigator navigator, String route, kotlin.reflect.KClass<? extends androidx.fragment.app.Fragment> fragmentClass);
+    method public androidx.navigation.fragment.FragmentNavigator.Destination build();
+  }
+
+  public final class FragmentNavigatorDestinationBuilderKt {
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.NavGraphBuilder, @IdRes int id);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.NavGraphBuilder, @IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.fragment.FragmentNavigatorDestinationBuilder,? extends kotlin.Unit> builder);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.NavGraphBuilder, String route);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.NavGraphBuilder, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.fragment.FragmentNavigatorDestinationBuilder,? extends kotlin.Unit> builder);
+  }
+
+  public final class FragmentNavigatorExtrasKt {
+    method public static androidx.navigation.fragment.FragmentNavigator.Extras FragmentNavigatorExtras(kotlin.Pair<? extends android.view.View,java.lang.String>... sharedElements);
+  }
+
+  public class NavHostFragment extends androidx.fragment.app.Fragment implements androidx.navigation.NavHost {
+    ctor public NavHostFragment();
+    method public static final androidx.navigation.fragment.NavHostFragment create(@NavigationRes int graphResId, optional android.os.Bundle? startDestinationArgs);
+    method public static final androidx.navigation.fragment.NavHostFragment create(@NavigationRes int graphResId);
+    method @Deprecated protected androidx.navigation.Navigator<? extends androidx.navigation.fragment.FragmentNavigator.Destination> createFragmentNavigator();
+    method public static final androidx.navigation.NavController findNavController(androidx.fragment.app.Fragment fragment);
+    method public final androidx.navigation.NavController getNavController();
+    method @Deprecated @CallSuper protected void onCreateNavController(androidx.navigation.NavController navController);
+    method @CallSuper protected void onCreateNavHostController(androidx.navigation.NavHostController navHostController);
+    property public final androidx.navigation.NavController navController;
+    field public static final androidx.navigation.fragment.NavHostFragment.Companion Companion;
+  }
+
+  public static final class NavHostFragment.Companion {
+    method public androidx.navigation.fragment.NavHostFragment create(@NavigationRes int graphResId, optional android.os.Bundle? startDestinationArgs);
+    method public androidx.navigation.fragment.NavHostFragment create(@NavigationRes int graphResId);
+    method public androidx.navigation.NavController findNavController(androidx.fragment.app.Fragment fragment);
+  }
+
+}
+
diff --git a/navigation/navigation-fragment/src/main/baseline-prof.txt b/navigation/navigation-fragment/src/main/baseline-prof.txt
new file mode 100644
index 0000000..d91776d
--- /dev/null
+++ b/navigation/navigation-fragment/src/main/baseline-prof.txt
@@ -0,0 +1,52 @@
+# Baseline Profiles for navigation-fragment
+
+HSPLandroidx/navigation/fragment/DialogFragmentNavigator$Companion;-><init>()V
+HSPLandroidx/navigation/fragment/DialogFragmentNavigator$Companion;-><init>(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLandroidx/navigation/fragment/DialogFragmentNavigator$observer$1;-><init>(Landroidx/navigation/fragment/DialogFragmentNavigator;)V
+HSPLandroidx/navigation/fragment/DialogFragmentNavigator$onAttach$1;-><init>(Landroidx/navigation/fragment/DialogFragmentNavigator;)V
+HSPLandroidx/navigation/fragment/DialogFragmentNavigator$onAttach$1;->onAttachFragment(Landroidx/fragment/app/FragmentManager;Landroidx/fragment/app/Fragment;)V
+HSPLandroidx/navigation/fragment/DialogFragmentNavigator;-><clinit>()V
+HSPLandroidx/navigation/fragment/DialogFragmentNavigator;-><init>(Landroid/content/Context;Landroidx/fragment/app/FragmentManager;)V
+HSPLandroidx/navigation/fragment/DialogFragmentNavigator;->access$getRestoredTagsAwaitingAttach$p(Landroidx/navigation/fragment/DialogFragmentNavigator;)Ljava/util/Set;
+HSPLandroidx/navigation/fragment/DialogFragmentNavigator;->onAttach(Landroidx/navigation/NavigatorState;)V
+HSPLandroidx/navigation/fragment/FragmentNavigator$Companion;-><init>()V
+HSPLandroidx/navigation/fragment/FragmentNavigator$Companion;-><init>(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLandroidx/navigation/fragment/FragmentNavigator$Destination;-><init>(Landroidx/navigation/Navigator;)V
+HSPLandroidx/navigation/fragment/FragmentNavigator$Destination;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/navigation/fragment/FragmentNavigator$Destination;->getClassName()Ljava/lang/String;
+HSPLandroidx/navigation/fragment/FragmentNavigator$Destination;->hashCode()I
+HSPLandroidx/navigation/fragment/FragmentNavigator$Destination;->onInflate(Landroid/content/Context;Landroid/util/AttributeSet;)V
+HSPLandroidx/navigation/fragment/FragmentNavigator$Destination;->setClassName(Ljava/lang/String;)Landroidx/navigation/fragment/FragmentNavigator$Destination;
+HSPLandroidx/navigation/fragment/FragmentNavigator;-><clinit>()V
+HSPLandroidx/navigation/fragment/FragmentNavigator;-><init>(Landroid/content/Context;Landroidx/fragment/app/FragmentManager;I)V
+HSPLandroidx/navigation/fragment/FragmentNavigator;->createDestination()Landroidx/navigation/NavDestination;
+HSPLandroidx/navigation/fragment/FragmentNavigator;->createDestination()Landroidx/navigation/fragment/FragmentNavigator$Destination;
+HSPLandroidx/navigation/fragment/FragmentNavigator;->navigate(Landroidx/navigation/NavBackStackEntry;Landroidx/navigation/NavOptions;Landroidx/navigation/Navigator$Extras;)V
+HSPLandroidx/navigation/fragment/FragmentNavigator;->navigate(Ljava/util/List;Landroidx/navigation/NavOptions;Landroidx/navigation/Navigator$Extras;)V
+HSPLandroidx/navigation/fragment/NavHostFragment$Companion;-><init>()V
+HSPLandroidx/navigation/fragment/NavHostFragment$Companion;-><init>(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLandroidx/navigation/fragment/NavHostFragment;-><clinit>()V
+HSPLandroidx/navigation/fragment/NavHostFragment;-><init>()V
+HSPLandroidx/navigation/fragment/NavHostFragment;->createFragmentNavigator()Landroidx/navigation/Navigator;
+HSPLandroidx/navigation/fragment/NavHostFragment;->getContainerId()I
+HSPLandroidx/navigation/fragment/NavHostFragment;->onAttach(Landroid/content/Context;)V
+HSPLandroidx/navigation/fragment/NavHostFragment;->onCreate(Landroid/os/Bundle;)V
+HSPLandroidx/navigation/fragment/NavHostFragment;->onCreateNavController(Landroidx/navigation/NavController;)V
+HSPLandroidx/navigation/fragment/NavHostFragment;->onCreateNavHostController(Landroidx/navigation/NavHostController;)V
+HSPLandroidx/navigation/fragment/NavHostFragment;->onCreateView(Landroid/view/LayoutInflater;Landroid/view/ViewGroup;Landroid/os/Bundle;)Landroid/view/View;
+HSPLandroidx/navigation/fragment/NavHostFragment;->onInflate(Landroid/content/Context;Landroid/util/AttributeSet;Landroid/os/Bundle;)V
+HSPLandroidx/navigation/fragment/NavHostFragment;->onPrimaryNavigationFragmentChanged(Z)V
+HSPLandroidx/navigation/fragment/NavHostFragment;->onViewCreated(Landroid/view/View;Landroid/os/Bundle;)V
+Landroidx/navigation/fragment/DialogFragmentNavigator$Companion;
+Landroidx/navigation/fragment/DialogFragmentNavigator$observer$1;
+Landroidx/navigation/fragment/DialogFragmentNavigator$onAttach$1;
+Landroidx/navigation/fragment/DialogFragmentNavigator;
+Landroidx/navigation/fragment/FragmentNavigator$Companion;
+Landroidx/navigation/fragment/FragmentNavigator$Destination;
+Landroidx/navigation/fragment/FragmentNavigator$Extras;
+Landroidx/navigation/fragment/FragmentNavigator;
+Landroidx/navigation/fragment/NavHostFragment$Companion;
+Landroidx/navigation/fragment/NavHostFragment;
+PLandroidx/navigation/fragment/NavHostFragment;->onDestroyView()V
+HSPLandroidx/navigation/fragment/R$styleable;-><clinit>()V
+Landroidx/navigation/fragment/R$styleable;
diff --git a/navigation/navigation-fragment/src/main/java/androidx/navigation/NavGraphViewModelLazy.kt b/navigation/navigation-fragment/src/main/java/androidx/navigation/NavGraphViewModelLazy.kt
index 53c5c67..38be5e1 100644
--- a/navigation/navigation-fragment/src/main/java/androidx/navigation/NavGraphViewModelLazy.kt
+++ b/navigation/navigation-fragment/src/main/java/androidx/navigation/NavGraphViewModelLazy.kt
@@ -86,7 +86,9 @@
  * This property can be accessed only after this NavGraph is on the NavController back stack,
  * and an attempt access prior to that will result in an IllegalArgumentException.
  *
- * @param navGraphRoute route of a NavGraph that exists on the [NavController] back stack
+ * @param navGraphRoute [NavDestination.route] of a NavGraph that exists on the [NavController]
+ * back stack. If a [NavDestination] with the given route does not exist on the back stack, an
+ * [IllegalArgumentException] will be thrown.
  */
 @MainThread
 public inline fun <reified VM : ViewModel> Fragment.navGraphViewModels(
diff --git a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/AbstractListDetailFragment.kt b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/AbstractListDetailFragment.kt
index 2da2527..44692c6 100644
--- a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/AbstractListDetailFragment.kt
+++ b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/AbstractListDetailFragment.kt
@@ -23,8 +23,6 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import android.widget.FrameLayout
 import androidx.activity.OnBackPressedCallback
 import androidx.annotation.CallSuper
 import androidx.core.content.res.use
@@ -145,17 +143,10 @@
             id = R.id.sliding_pane_layout
         }
 
-        // Set up the list container
-        val listContainer = FrameLayout(inflater.context).apply {
-            id = R.id.sliding_pane_list_container
-        }
-        val listLayoutParams = SlidingPaneLayout.LayoutParams(WRAP_CONTENT, MATCH_PARENT)
-        slidingPaneLayout.addView(listContainer, listLayoutParams)
-
-        // Now create and add the list pane itself
-        val listPaneView = onCreateListPaneView(inflater, listContainer, savedInstanceState)
-        if (listPaneView.parent != listContainer) {
-            listContainer.addView(listPaneView)
+        // Create and add the list pane
+        val listPaneView = onCreateListPaneView(inflater, slidingPaneLayout, savedInstanceState)
+        if (listPaneView != slidingPaneLayout && listPaneView.parent != slidingPaneLayout) {
+            slidingPaneLayout.addView(listPaneView)
         }
 
         // Set up the detail container
@@ -235,8 +226,7 @@
     @CallSuper
     final override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
-        val listContainer = view.findViewById<FrameLayout>(R.id.sliding_pane_list_container)
-        val listPaneView = listContainer.getChildAt(0)
+        val listPaneView = slidingPaneLayout.getChildAt(0)
         onListPaneViewCreated(listPaneView, savedInstanceState)
     }
 
diff --git a/navigation/navigation-fragment/src/main/res/values/ids.xml b/navigation/navigation-fragment/src/main/res/values/ids.xml
index 8ab2900..7c23e2c 100644
--- a/navigation/navigation-fragment/src/main/res/values/ids.xml
+++ b/navigation/navigation-fragment/src/main/res/values/ids.xml
@@ -17,6 +17,5 @@
 <resources>
     <item type="id" name="nav_host_fragment_container" />
     <item type="id" name="sliding_pane_layout" />
-    <item type="id" name="sliding_pane_list_container" />
     <item type="id" name="sliding_pane_detail_container" />
 </resources>
diff --git a/navigation/navigation-runtime-ktx/api/2.4.0-beta01.txt b/navigation/navigation-runtime-ktx/api/2.4.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/navigation/navigation-runtime-ktx/api/2.4.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/navigation/navigation-runtime-ktx/api/public_plus_experimental_2.4.0-beta01.txt b/navigation/navigation-runtime-ktx/api/public_plus_experimental_2.4.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/navigation/navigation-runtime-ktx/api/public_plus_experimental_2.4.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/navigation/navigation-runtime-ktx/api/res-2.4.0-beta01.txt b/navigation/navigation-runtime-ktx/api/res-2.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/navigation-runtime-ktx/api/res-2.4.0-beta01.txt
diff --git a/navigation/navigation-runtime-ktx/api/restricted_2.4.0-beta01.txt b/navigation/navigation-runtime-ktx/api/restricted_2.4.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/navigation/navigation-runtime-ktx/api/restricted_2.4.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/navigation/navigation-runtime/api/2.4.0-beta01.txt b/navigation/navigation-runtime/api/2.4.0-beta01.txt
new file mode 100644
index 0000000..1b18046
--- /dev/null
+++ b/navigation/navigation-runtime/api/2.4.0-beta01.txt
@@ -0,0 +1,224 @@
+// Signature format: 4.0
+package androidx.navigation {
+
+  public final class ActivityKt {
+    method public static androidx.navigation.NavController findNavController(android.app.Activity, @IdRes int viewId);
+  }
+
+  public final class ActivityNavArgsLazyKt {
+    method @MainThread public static inline <reified Args extends androidx.navigation.NavArgs> androidx.navigation.NavArgsLazy<Args>! navArgs(android.app.Activity);
+  }
+
+  @androidx.navigation.Navigator.Name("activity") public class ActivityNavigator extends androidx.navigation.Navigator<androidx.navigation.ActivityNavigator.Destination> {
+    ctor public ActivityNavigator(android.content.Context context);
+    method public static final void applyPopAnimationsToPendingTransition(android.app.Activity activity);
+    method public androidx.navigation.ActivityNavigator.Destination createDestination();
+    method public androidx.navigation.NavDestination? navigate(androidx.navigation.ActivityNavigator.Destination destination, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
+    field public static final androidx.navigation.ActivityNavigator.Companion Companion;
+  }
+
+  public static final class ActivityNavigator.Companion {
+    method public void applyPopAnimationsToPendingTransition(android.app.Activity activity);
+  }
+
+  @androidx.navigation.NavDestination.ClassType(Activity::class) public static class ActivityNavigator.Destination extends androidx.navigation.NavDestination {
+    ctor public ActivityNavigator.Destination(androidx.navigation.Navigator<? extends androidx.navigation.ActivityNavigator.Destination> activityNavigator);
+    ctor public ActivityNavigator.Destination(androidx.navigation.NavigatorProvider navigatorProvider);
+    method public final String? getAction();
+    method public final android.content.ComponentName? getComponent();
+    method public final android.net.Uri? getData();
+    method public final String? getDataPattern();
+    method public final android.content.Intent? getIntent();
+    method public final String? getTargetPackage();
+    method public final androidx.navigation.ActivityNavigator.Destination setAction(String? action);
+    method public final androidx.navigation.ActivityNavigator.Destination setComponentName(android.content.ComponentName? name);
+    method public final androidx.navigation.ActivityNavigator.Destination setData(android.net.Uri? data);
+    method public final androidx.navigation.ActivityNavigator.Destination setDataPattern(String? dataPattern);
+    method public final androidx.navigation.ActivityNavigator.Destination setIntent(android.content.Intent? intent);
+    method public final androidx.navigation.ActivityNavigator.Destination setTargetPackage(String? packageName);
+    property public final String? action;
+    property public final android.content.ComponentName? component;
+    property public final android.net.Uri? data;
+    property public final String? dataPattern;
+    property public final android.content.Intent? intent;
+    property public final String? targetPackage;
+  }
+
+  public static final class ActivityNavigator.Extras implements androidx.navigation.Navigator.Extras {
+    method public androidx.core.app.ActivityOptionsCompat? getActivityOptions();
+    method public int getFlags();
+    property public final androidx.core.app.ActivityOptionsCompat? activityOptions;
+    property public final int flags;
+  }
+
+  public static final class ActivityNavigator.Extras.Builder {
+    ctor public ActivityNavigator.Extras.Builder();
+    method public androidx.navigation.ActivityNavigator.Extras.Builder addFlags(int flags);
+    method public androidx.navigation.ActivityNavigator.Extras build();
+    method public androidx.navigation.ActivityNavigator.Extras.Builder setActivityOptions(androidx.core.app.ActivityOptionsCompat activityOptions);
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class ActivityNavigatorDestinationBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.ActivityNavigator.Destination> {
+    ctor @Deprecated public ActivityNavigatorDestinationBuilder(androidx.navigation.ActivityNavigator navigator, @IdRes int id);
+    ctor public ActivityNavigatorDestinationBuilder(androidx.navigation.ActivityNavigator navigator, String route);
+    method public androidx.navigation.ActivityNavigator.Destination build();
+    method public String? getAction();
+    method public kotlin.reflect.KClass<? extends android.app.Activity>? getActivityClass();
+    method public android.net.Uri? getData();
+    method public String? getDataPattern();
+    method public String? getTargetPackage();
+    method public void setAction(String? action);
+    method public void setActivityClass(kotlin.reflect.KClass<? extends android.app.Activity>? activityClass);
+    method public void setData(android.net.Uri? data);
+    method public void setDataPattern(String? dataPattern);
+    method public void setTargetPackage(String? targetPackage);
+    property public final String? action;
+    property public final kotlin.reflect.KClass<? extends android.app.Activity>? activityClass;
+    property public final android.net.Uri? data;
+    property public final String? dataPattern;
+    property public final String? targetPackage;
+  }
+
+  public final class ActivityNavigatorDestinationBuilderKt {
+    method @Deprecated public static inline void activity(androidx.navigation.NavGraphBuilder, @IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.ActivityNavigatorDestinationBuilder,kotlin.Unit> builder);
+    method public static inline void activity(androidx.navigation.NavGraphBuilder, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.ActivityNavigatorDestinationBuilder,kotlin.Unit> builder);
+  }
+
+  public final class ActivityNavigatorExtrasKt {
+    method public static androidx.navigation.ActivityNavigator.Extras ActivityNavigatorExtras(optional androidx.core.app.ActivityOptionsCompat? activityOptions, optional int flags);
+  }
+
+  public class NavController {
+    ctor public NavController(android.content.Context context);
+    method public void addOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener listener);
+    method @MainThread public final boolean clearBackStack(String route);
+    method @MainThread public final boolean clearBackStack(@IdRes int destinationId);
+    method public androidx.navigation.NavDeepLinkBuilder createDeepLink();
+    method public androidx.navigation.NavBackStackEntry getBackStackEntry(@IdRes int destinationId);
+    method public final androidx.navigation.NavBackStackEntry getBackStackEntry(String route);
+    method public androidx.navigation.NavBackStackEntry? getCurrentBackStackEntry();
+    method public final kotlinx.coroutines.flow.Flow<androidx.navigation.NavBackStackEntry> getCurrentBackStackEntryFlow();
+    method public androidx.navigation.NavDestination? getCurrentDestination();
+    method @MainThread public androidx.navigation.NavGraph getGraph();
+    method public androidx.navigation.NavInflater getNavInflater();
+    method public androidx.navigation.NavigatorProvider getNavigatorProvider();
+    method public androidx.navigation.NavBackStackEntry? getPreviousBackStackEntry();
+    method public androidx.lifecycle.ViewModelStoreOwner getViewModelStoreOwner(@IdRes int navGraphId);
+    method public final kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.navigation.NavBackStackEntry>> getVisibleEntries();
+    method @MainThread public boolean handleDeepLink(android.content.Intent? intent);
+    method @MainThread public void navigate(@IdRes int resId);
+    method @MainThread public void navigate(@IdRes int resId, android.os.Bundle? args);
+    method @MainThread public void navigate(@IdRes int resId, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions);
+    method @MainThread public void navigate(@IdRes int resId, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
+    method @MainThread public void navigate(android.net.Uri deepLink);
+    method @MainThread public void navigate(android.net.Uri deepLink, androidx.navigation.NavOptions? navOptions);
+    method @MainThread public void navigate(android.net.Uri deepLink, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
+    method @MainThread public void navigate(androidx.navigation.NavDeepLinkRequest request);
+    method @MainThread public void navigate(androidx.navigation.NavDeepLinkRequest request, androidx.navigation.NavOptions? navOptions);
+    method @MainThread public void navigate(androidx.navigation.NavDeepLinkRequest request, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
+    method @MainThread public void navigate(androidx.navigation.NavDirections directions);
+    method @MainThread public void navigate(androidx.navigation.NavDirections directions, androidx.navigation.NavOptions? navOptions);
+    method @MainThread public void navigate(androidx.navigation.NavDirections directions, androidx.navigation.Navigator.Extras navigatorExtras);
+    method public final void navigate(String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> builder);
+    method public final void navigate(String route, optional androidx.navigation.NavOptions? navOptions, optional androidx.navigation.Navigator.Extras? navigatorExtras);
+    method public final void navigate(String route, optional androidx.navigation.NavOptions? navOptions);
+    method public final void navigate(String route);
+    method @MainThread public boolean navigateUp();
+    method @MainThread public boolean popBackStack();
+    method @MainThread public boolean popBackStack(@IdRes int destinationId, boolean inclusive);
+    method @MainThread public boolean popBackStack(@IdRes int destinationId, boolean inclusive, boolean saveState);
+    method @MainThread public final boolean popBackStack(String route, boolean inclusive, optional boolean saveState);
+    method @MainThread public final boolean popBackStack(String route, boolean inclusive);
+    method public void removeOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener listener);
+    method @CallSuper public void restoreState(android.os.Bundle? navState);
+    method @CallSuper public android.os.Bundle? saveState();
+    method @CallSuper @MainThread public void setGraph(androidx.navigation.NavGraph graph);
+    method @CallSuper @MainThread public void setGraph(@NavigationRes int graphResId);
+    method @CallSuper @MainThread public void setGraph(@NavigationRes int graphResId, android.os.Bundle? startDestinationArgs);
+    method @CallSuper @MainThread public void setGraph(androidx.navigation.NavGraph graph, android.os.Bundle? startDestinationArgs);
+    property public androidx.navigation.NavBackStackEntry? currentBackStackEntry;
+    property public final kotlinx.coroutines.flow.Flow<androidx.navigation.NavBackStackEntry> currentBackStackEntryFlow;
+    property public androidx.navigation.NavDestination? currentDestination;
+    property @MainThread public androidx.navigation.NavGraph graph;
+    property public androidx.navigation.NavInflater navInflater;
+    property public androidx.navigation.NavigatorProvider navigatorProvider;
+    property public androidx.navigation.NavBackStackEntry? previousBackStackEntry;
+    property public final kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.navigation.NavBackStackEntry>> visibleEntries;
+    field public static final androidx.navigation.NavController.Companion Companion;
+    field public static final String KEY_DEEP_LINK_INTENT = "android-support-nav:controller:deepLinkIntent";
+  }
+
+  public static final class NavController.Companion {
+  }
+
+  public static fun interface NavController.OnDestinationChangedListener {
+    method public void onDestinationChanged(androidx.navigation.NavController controller, androidx.navigation.NavDestination destination, android.os.Bundle? arguments);
+  }
+
+  public final class NavControllerKt {
+    method @Deprecated public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+  }
+
+  public final class NavDeepLinkBuilder {
+    ctor public NavDeepLinkBuilder(android.content.Context context);
+    method public androidx.navigation.NavDeepLinkBuilder addDestination(@IdRes int destId, optional android.os.Bundle? args);
+    method public androidx.navigation.NavDeepLinkBuilder addDestination(@IdRes int destId);
+    method public androidx.navigation.NavDeepLinkBuilder addDestination(String route, optional android.os.Bundle? args);
+    method public androidx.navigation.NavDeepLinkBuilder addDestination(String route);
+    method public android.app.PendingIntent createPendingIntent();
+    method public androidx.core.app.TaskStackBuilder createTaskStackBuilder();
+    method public androidx.navigation.NavDeepLinkBuilder setArguments(android.os.Bundle? args);
+    method public androidx.navigation.NavDeepLinkBuilder setComponentName(Class<? extends android.app.Activity> activityClass);
+    method public androidx.navigation.NavDeepLinkBuilder setComponentName(android.content.ComponentName componentName);
+    method public androidx.navigation.NavDeepLinkBuilder setDestination(@IdRes int destId, optional android.os.Bundle? args);
+    method public androidx.navigation.NavDeepLinkBuilder setDestination(@IdRes int destId);
+    method public androidx.navigation.NavDeepLinkBuilder setDestination(String destRoute, optional android.os.Bundle? args);
+    method public androidx.navigation.NavDeepLinkBuilder setDestination(String destRoute);
+    method public androidx.navigation.NavDeepLinkBuilder setGraph(@NavigationRes int navGraphId);
+    method public androidx.navigation.NavDeepLinkBuilder setGraph(androidx.navigation.NavGraph navGraph);
+  }
+
+  public interface NavHost {
+    method public androidx.navigation.NavController getNavController();
+    property public abstract androidx.navigation.NavController navController;
+  }
+
+  public class NavHostController extends androidx.navigation.NavController {
+    ctor public NavHostController(android.content.Context context);
+    method public final void enableOnBackPressed(boolean enabled);
+    method public final void setLifecycleOwner(androidx.lifecycle.LifecycleOwner owner);
+    method public final void setOnBackPressedDispatcher(androidx.activity.OnBackPressedDispatcher dispatcher);
+    method public final void setViewModelStore(androidx.lifecycle.ViewModelStore viewModelStore);
+  }
+
+  public final class NavHostKt {
+    method @Deprecated public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavHost, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavHost, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+  }
+
+  public final class NavInflater {
+    ctor public NavInflater(android.content.Context context, androidx.navigation.NavigatorProvider navigatorProvider);
+    method public androidx.navigation.NavGraph inflate(@NavigationRes int graphResId);
+    field public static final androidx.navigation.NavInflater.Companion Companion;
+  }
+
+  public static final class NavInflater.Companion {
+  }
+
+  public final class Navigation {
+    method public static android.view.View.OnClickListener createNavigateOnClickListener(@IdRes int resId, optional android.os.Bundle? args);
+    method public static android.view.View.OnClickListener createNavigateOnClickListener(@IdRes int resId);
+    method public static android.view.View.OnClickListener createNavigateOnClickListener(androidx.navigation.NavDirections directions);
+    method public static androidx.navigation.NavController findNavController(android.app.Activity activity, @IdRes int viewId);
+    method public static androidx.navigation.NavController findNavController(android.view.View view);
+    method public static void setViewNavController(android.view.View view, androidx.navigation.NavController? controller);
+    field public static final androidx.navigation.Navigation INSTANCE;
+  }
+
+  public final class ViewKt {
+    method public static androidx.navigation.NavController findNavController(android.view.View);
+  }
+
+}
+
diff --git a/navigation/navigation-runtime/api/public_plus_experimental_2.4.0-beta01.txt b/navigation/navigation-runtime/api/public_plus_experimental_2.4.0-beta01.txt
new file mode 100644
index 0000000..c46a8bf
--- /dev/null
+++ b/navigation/navigation-runtime/api/public_plus_experimental_2.4.0-beta01.txt
@@ -0,0 +1,227 @@
+// Signature format: 4.0
+package androidx.navigation {
+
+  public final class ActivityKt {
+    method public static androidx.navigation.NavController findNavController(android.app.Activity, @IdRes int viewId);
+  }
+
+  public final class ActivityNavArgsLazyKt {
+    method @MainThread public static inline <reified Args extends androidx.navigation.NavArgs> androidx.navigation.NavArgsLazy<Args>! navArgs(android.app.Activity);
+  }
+
+  @androidx.navigation.Navigator.Name("activity") public class ActivityNavigator extends androidx.navigation.Navigator<androidx.navigation.ActivityNavigator.Destination> {
+    ctor public ActivityNavigator(android.content.Context context);
+    method public static final void applyPopAnimationsToPendingTransition(android.app.Activity activity);
+    method public androidx.navigation.ActivityNavigator.Destination createDestination();
+    method public androidx.navigation.NavDestination? navigate(androidx.navigation.ActivityNavigator.Destination destination, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
+    field public static final androidx.navigation.ActivityNavigator.Companion Companion;
+  }
+
+  public static final class ActivityNavigator.Companion {
+    method public void applyPopAnimationsToPendingTransition(android.app.Activity activity);
+  }
+
+  @androidx.navigation.NavDestination.ClassType(Activity::class) public static class ActivityNavigator.Destination extends androidx.navigation.NavDestination {
+    ctor public ActivityNavigator.Destination(androidx.navigation.Navigator<? extends androidx.navigation.ActivityNavigator.Destination> activityNavigator);
+    ctor public ActivityNavigator.Destination(androidx.navigation.NavigatorProvider navigatorProvider);
+    method public final String? getAction();
+    method public final android.content.ComponentName? getComponent();
+    method public final android.net.Uri? getData();
+    method public final String? getDataPattern();
+    method public final android.content.Intent? getIntent();
+    method public final String? getTargetPackage();
+    method public final androidx.navigation.ActivityNavigator.Destination setAction(String? action);
+    method public final androidx.navigation.ActivityNavigator.Destination setComponentName(android.content.ComponentName? name);
+    method public final androidx.navigation.ActivityNavigator.Destination setData(android.net.Uri? data);
+    method public final androidx.navigation.ActivityNavigator.Destination setDataPattern(String? dataPattern);
+    method public final androidx.navigation.ActivityNavigator.Destination setIntent(android.content.Intent? intent);
+    method public final androidx.navigation.ActivityNavigator.Destination setTargetPackage(String? packageName);
+    property public final String? action;
+    property public final android.content.ComponentName? component;
+    property public final android.net.Uri? data;
+    property public final String? dataPattern;
+    property public final android.content.Intent? intent;
+    property public final String? targetPackage;
+  }
+
+  public static final class ActivityNavigator.Extras implements androidx.navigation.Navigator.Extras {
+    method public androidx.core.app.ActivityOptionsCompat? getActivityOptions();
+    method public int getFlags();
+    property public final androidx.core.app.ActivityOptionsCompat? activityOptions;
+    property public final int flags;
+  }
+
+  public static final class ActivityNavigator.Extras.Builder {
+    ctor public ActivityNavigator.Extras.Builder();
+    method public androidx.navigation.ActivityNavigator.Extras.Builder addFlags(int flags);
+    method public androidx.navigation.ActivityNavigator.Extras build();
+    method public androidx.navigation.ActivityNavigator.Extras.Builder setActivityOptions(androidx.core.app.ActivityOptionsCompat activityOptions);
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class ActivityNavigatorDestinationBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.ActivityNavigator.Destination> {
+    ctor @Deprecated public ActivityNavigatorDestinationBuilder(androidx.navigation.ActivityNavigator navigator, @IdRes int id);
+    ctor public ActivityNavigatorDestinationBuilder(androidx.navigation.ActivityNavigator navigator, String route);
+    method public androidx.navigation.ActivityNavigator.Destination build();
+    method public String? getAction();
+    method public kotlin.reflect.KClass<? extends android.app.Activity>? getActivityClass();
+    method public android.net.Uri? getData();
+    method public String? getDataPattern();
+    method public String? getTargetPackage();
+    method public void setAction(String? action);
+    method public void setActivityClass(kotlin.reflect.KClass<? extends android.app.Activity>? activityClass);
+    method public void setData(android.net.Uri? data);
+    method public void setDataPattern(String? dataPattern);
+    method public void setTargetPackage(String? targetPackage);
+    property public final String? action;
+    property public final kotlin.reflect.KClass<? extends android.app.Activity>? activityClass;
+    property public final android.net.Uri? data;
+    property public final String? dataPattern;
+    property public final String? targetPackage;
+  }
+
+  public final class ActivityNavigatorDestinationBuilderKt {
+    method @Deprecated public static inline void activity(androidx.navigation.NavGraphBuilder, @IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.ActivityNavigatorDestinationBuilder,kotlin.Unit> builder);
+    method public static inline void activity(androidx.navigation.NavGraphBuilder, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.ActivityNavigatorDestinationBuilder,kotlin.Unit> builder);
+  }
+
+  public final class ActivityNavigatorExtrasKt {
+    method public static androidx.navigation.ActivityNavigator.Extras ActivityNavigatorExtras(optional androidx.core.app.ActivityOptionsCompat? activityOptions, optional int flags);
+  }
+
+  public class NavController {
+    ctor public NavController(android.content.Context context);
+    method public void addOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener listener);
+    method @MainThread public final boolean clearBackStack(String route);
+    method @MainThread public final boolean clearBackStack(@IdRes int destinationId);
+    method public androidx.navigation.NavDeepLinkBuilder createDeepLink();
+    method public androidx.navigation.NavBackStackEntry getBackStackEntry(@IdRes int destinationId);
+    method public final androidx.navigation.NavBackStackEntry getBackStackEntry(String route);
+    method public androidx.navigation.NavBackStackEntry? getCurrentBackStackEntry();
+    method public final kotlinx.coroutines.flow.Flow<androidx.navigation.NavBackStackEntry> getCurrentBackStackEntryFlow();
+    method public androidx.navigation.NavDestination? getCurrentDestination();
+    method @MainThread public androidx.navigation.NavGraph getGraph();
+    method public androidx.navigation.NavInflater getNavInflater();
+    method public androidx.navigation.NavigatorProvider getNavigatorProvider();
+    method public androidx.navigation.NavBackStackEntry? getPreviousBackStackEntry();
+    method public androidx.lifecycle.ViewModelStoreOwner getViewModelStoreOwner(@IdRes int navGraphId);
+    method public final kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.navigation.NavBackStackEntry>> getVisibleEntries();
+    method @MainThread public boolean handleDeepLink(android.content.Intent? intent);
+    method @MainThread public void navigate(@IdRes int resId);
+    method @MainThread public void navigate(@IdRes int resId, android.os.Bundle? args);
+    method @MainThread public void navigate(@IdRes int resId, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions);
+    method @MainThread public void navigate(@IdRes int resId, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
+    method @MainThread public void navigate(android.net.Uri deepLink);
+    method @MainThread public void navigate(android.net.Uri deepLink, androidx.navigation.NavOptions? navOptions);
+    method @MainThread public void navigate(android.net.Uri deepLink, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
+    method @MainThread public void navigate(androidx.navigation.NavDeepLinkRequest request);
+    method @MainThread public void navigate(androidx.navigation.NavDeepLinkRequest request, androidx.navigation.NavOptions? navOptions);
+    method @MainThread public void navigate(androidx.navigation.NavDeepLinkRequest request, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
+    method @MainThread public void navigate(androidx.navigation.NavDirections directions);
+    method @MainThread public void navigate(androidx.navigation.NavDirections directions, androidx.navigation.NavOptions? navOptions);
+    method @MainThread public void navigate(androidx.navigation.NavDirections directions, androidx.navigation.Navigator.Extras navigatorExtras);
+    method public final void navigate(String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> builder);
+    method public final void navigate(String route, optional androidx.navigation.NavOptions? navOptions, optional androidx.navigation.Navigator.Extras? navigatorExtras);
+    method public final void navigate(String route, optional androidx.navigation.NavOptions? navOptions);
+    method public final void navigate(String route);
+    method @MainThread public boolean navigateUp();
+    method @MainThread public boolean popBackStack();
+    method @MainThread public boolean popBackStack(@IdRes int destinationId, boolean inclusive);
+    method @MainThread public boolean popBackStack(@IdRes int destinationId, boolean inclusive, boolean saveState);
+    method @MainThread public final boolean popBackStack(String route, boolean inclusive, optional boolean saveState);
+    method @MainThread public final boolean popBackStack(String route, boolean inclusive);
+    method public void removeOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener listener);
+    method @CallSuper public void restoreState(android.os.Bundle? navState);
+    method @CallSuper public android.os.Bundle? saveState();
+    method @CallSuper @MainThread public void setGraph(androidx.navigation.NavGraph graph);
+    method @CallSuper @MainThread public void setGraph(@NavigationRes int graphResId);
+    method @CallSuper @MainThread public void setGraph(@NavigationRes int graphResId, android.os.Bundle? startDestinationArgs);
+    method @CallSuper @MainThread public void setGraph(androidx.navigation.NavGraph graph, android.os.Bundle? startDestinationArgs);
+    property public androidx.navigation.NavBackStackEntry? currentBackStackEntry;
+    property public final kotlinx.coroutines.flow.Flow<androidx.navigation.NavBackStackEntry> currentBackStackEntryFlow;
+    property public androidx.navigation.NavDestination? currentDestination;
+    property @MainThread public androidx.navigation.NavGraph graph;
+    property public androidx.navigation.NavInflater navInflater;
+    property public androidx.navigation.NavigatorProvider navigatorProvider;
+    property public androidx.navigation.NavBackStackEntry? previousBackStackEntry;
+    property public final kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.navigation.NavBackStackEntry>> visibleEntries;
+    field public static final androidx.navigation.NavController.Companion Companion;
+    field public static final String KEY_DEEP_LINK_INTENT = "android-support-nav:controller:deepLinkIntent";
+  }
+
+  public static final class NavController.Companion {
+  }
+
+  public static fun interface NavController.OnDestinationChangedListener {
+    method public void onDestinationChanged(androidx.navigation.NavController controller, androidx.navigation.NavDestination destination, android.os.Bundle? arguments);
+  }
+
+  public final class NavControllerKt {
+    method @Deprecated public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+  }
+
+  @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level) @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets=kotlin.annotation.AnnotationTarget) public @interface NavControllerVisibleEntries {
+  }
+
+  public final class NavDeepLinkBuilder {
+    ctor public NavDeepLinkBuilder(android.content.Context context);
+    method public androidx.navigation.NavDeepLinkBuilder addDestination(@IdRes int destId, optional android.os.Bundle? args);
+    method public androidx.navigation.NavDeepLinkBuilder addDestination(@IdRes int destId);
+    method public androidx.navigation.NavDeepLinkBuilder addDestination(String route, optional android.os.Bundle? args);
+    method public androidx.navigation.NavDeepLinkBuilder addDestination(String route);
+    method public android.app.PendingIntent createPendingIntent();
+    method public androidx.core.app.TaskStackBuilder createTaskStackBuilder();
+    method public androidx.navigation.NavDeepLinkBuilder setArguments(android.os.Bundle? args);
+    method public androidx.navigation.NavDeepLinkBuilder setComponentName(Class<? extends android.app.Activity> activityClass);
+    method public androidx.navigation.NavDeepLinkBuilder setComponentName(android.content.ComponentName componentName);
+    method public androidx.navigation.NavDeepLinkBuilder setDestination(@IdRes int destId, optional android.os.Bundle? args);
+    method public androidx.navigation.NavDeepLinkBuilder setDestination(@IdRes int destId);
+    method public androidx.navigation.NavDeepLinkBuilder setDestination(String destRoute, optional android.os.Bundle? args);
+    method public androidx.navigation.NavDeepLinkBuilder setDestination(String destRoute);
+    method public androidx.navigation.NavDeepLinkBuilder setGraph(@NavigationRes int navGraphId);
+    method public androidx.navigation.NavDeepLinkBuilder setGraph(androidx.navigation.NavGraph navGraph);
+  }
+
+  public interface NavHost {
+    method public androidx.navigation.NavController getNavController();
+    property public abstract androidx.navigation.NavController navController;
+  }
+
+  public class NavHostController extends androidx.navigation.NavController {
+    ctor public NavHostController(android.content.Context context);
+    method public final void enableOnBackPressed(boolean enabled);
+    method public final void setLifecycleOwner(androidx.lifecycle.LifecycleOwner owner);
+    method public final void setOnBackPressedDispatcher(androidx.activity.OnBackPressedDispatcher dispatcher);
+    method public final void setViewModelStore(androidx.lifecycle.ViewModelStore viewModelStore);
+  }
+
+  public final class NavHostKt {
+    method @Deprecated public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavHost, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavHost, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+  }
+
+  public final class NavInflater {
+    ctor public NavInflater(android.content.Context context, androidx.navigation.NavigatorProvider navigatorProvider);
+    method public androidx.navigation.NavGraph inflate(@NavigationRes int graphResId);
+    field public static final androidx.navigation.NavInflater.Companion Companion;
+  }
+
+  public static final class NavInflater.Companion {
+  }
+
+  public final class Navigation {
+    method public static android.view.View.OnClickListener createNavigateOnClickListener(@IdRes int resId, optional android.os.Bundle? args);
+    method public static android.view.View.OnClickListener createNavigateOnClickListener(@IdRes int resId);
+    method public static android.view.View.OnClickListener createNavigateOnClickListener(androidx.navigation.NavDirections directions);
+    method public static androidx.navigation.NavController findNavController(android.app.Activity activity, @IdRes int viewId);
+    method public static androidx.navigation.NavController findNavController(android.view.View view);
+    method public static void setViewNavController(android.view.View view, androidx.navigation.NavController? controller);
+    field public static final androidx.navigation.Navigation INSTANCE;
+  }
+
+  public final class ViewKt {
+    method public static androidx.navigation.NavController findNavController(android.view.View);
+  }
+
+}
+
diff --git a/navigation/navigation-runtime/api/res-2.4.0-beta01.txt b/navigation/navigation-runtime/api/res-2.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/navigation-runtime/api/res-2.4.0-beta01.txt
diff --git a/navigation/navigation-runtime/api/restricted_2.4.0-beta01.txt b/navigation/navigation-runtime/api/restricted_2.4.0-beta01.txt
new file mode 100644
index 0000000..1b18046
--- /dev/null
+++ b/navigation/navigation-runtime/api/restricted_2.4.0-beta01.txt
@@ -0,0 +1,224 @@
+// Signature format: 4.0
+package androidx.navigation {
+
+  public final class ActivityKt {
+    method public static androidx.navigation.NavController findNavController(android.app.Activity, @IdRes int viewId);
+  }
+
+  public final class ActivityNavArgsLazyKt {
+    method @MainThread public static inline <reified Args extends androidx.navigation.NavArgs> androidx.navigation.NavArgsLazy<Args>! navArgs(android.app.Activity);
+  }
+
+  @androidx.navigation.Navigator.Name("activity") public class ActivityNavigator extends androidx.navigation.Navigator<androidx.navigation.ActivityNavigator.Destination> {
+    ctor public ActivityNavigator(android.content.Context context);
+    method public static final void applyPopAnimationsToPendingTransition(android.app.Activity activity);
+    method public androidx.navigation.ActivityNavigator.Destination createDestination();
+    method public androidx.navigation.NavDestination? navigate(androidx.navigation.ActivityNavigator.Destination destination, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
+    field public static final androidx.navigation.ActivityNavigator.Companion Companion;
+  }
+
+  public static final class ActivityNavigator.Companion {
+    method public void applyPopAnimationsToPendingTransition(android.app.Activity activity);
+  }
+
+  @androidx.navigation.NavDestination.ClassType(Activity::class) public static class ActivityNavigator.Destination extends androidx.navigation.NavDestination {
+    ctor public ActivityNavigator.Destination(androidx.navigation.Navigator<? extends androidx.navigation.ActivityNavigator.Destination> activityNavigator);
+    ctor public ActivityNavigator.Destination(androidx.navigation.NavigatorProvider navigatorProvider);
+    method public final String? getAction();
+    method public final android.content.ComponentName? getComponent();
+    method public final android.net.Uri? getData();
+    method public final String? getDataPattern();
+    method public final android.content.Intent? getIntent();
+    method public final String? getTargetPackage();
+    method public final androidx.navigation.ActivityNavigator.Destination setAction(String? action);
+    method public final androidx.navigation.ActivityNavigator.Destination setComponentName(android.content.ComponentName? name);
+    method public final androidx.navigation.ActivityNavigator.Destination setData(android.net.Uri? data);
+    method public final androidx.navigation.ActivityNavigator.Destination setDataPattern(String? dataPattern);
+    method public final androidx.navigation.ActivityNavigator.Destination setIntent(android.content.Intent? intent);
+    method public final androidx.navigation.ActivityNavigator.Destination setTargetPackage(String? packageName);
+    property public final String? action;
+    property public final android.content.ComponentName? component;
+    property public final android.net.Uri? data;
+    property public final String? dataPattern;
+    property public final android.content.Intent? intent;
+    property public final String? targetPackage;
+  }
+
+  public static final class ActivityNavigator.Extras implements androidx.navigation.Navigator.Extras {
+    method public androidx.core.app.ActivityOptionsCompat? getActivityOptions();
+    method public int getFlags();
+    property public final androidx.core.app.ActivityOptionsCompat? activityOptions;
+    property public final int flags;
+  }
+
+  public static final class ActivityNavigator.Extras.Builder {
+    ctor public ActivityNavigator.Extras.Builder();
+    method public androidx.navigation.ActivityNavigator.Extras.Builder addFlags(int flags);
+    method public androidx.navigation.ActivityNavigator.Extras build();
+    method public androidx.navigation.ActivityNavigator.Extras.Builder setActivityOptions(androidx.core.app.ActivityOptionsCompat activityOptions);
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class ActivityNavigatorDestinationBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.ActivityNavigator.Destination> {
+    ctor @Deprecated public ActivityNavigatorDestinationBuilder(androidx.navigation.ActivityNavigator navigator, @IdRes int id);
+    ctor public ActivityNavigatorDestinationBuilder(androidx.navigation.ActivityNavigator navigator, String route);
+    method public androidx.navigation.ActivityNavigator.Destination build();
+    method public String? getAction();
+    method public kotlin.reflect.KClass<? extends android.app.Activity>? getActivityClass();
+    method public android.net.Uri? getData();
+    method public String? getDataPattern();
+    method public String? getTargetPackage();
+    method public void setAction(String? action);
+    method public void setActivityClass(kotlin.reflect.KClass<? extends android.app.Activity>? activityClass);
+    method public void setData(android.net.Uri? data);
+    method public void setDataPattern(String? dataPattern);
+    method public void setTargetPackage(String? targetPackage);
+    property public final String? action;
+    property public final kotlin.reflect.KClass<? extends android.app.Activity>? activityClass;
+    property public final android.net.Uri? data;
+    property public final String? dataPattern;
+    property public final String? targetPackage;
+  }
+
+  public final class ActivityNavigatorDestinationBuilderKt {
+    method @Deprecated public static inline void activity(androidx.navigation.NavGraphBuilder, @IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.ActivityNavigatorDestinationBuilder,kotlin.Unit> builder);
+    method public static inline void activity(androidx.navigation.NavGraphBuilder, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.ActivityNavigatorDestinationBuilder,kotlin.Unit> builder);
+  }
+
+  public final class ActivityNavigatorExtrasKt {
+    method public static androidx.navigation.ActivityNavigator.Extras ActivityNavigatorExtras(optional androidx.core.app.ActivityOptionsCompat? activityOptions, optional int flags);
+  }
+
+  public class NavController {
+    ctor public NavController(android.content.Context context);
+    method public void addOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener listener);
+    method @MainThread public final boolean clearBackStack(String route);
+    method @MainThread public final boolean clearBackStack(@IdRes int destinationId);
+    method public androidx.navigation.NavDeepLinkBuilder createDeepLink();
+    method public androidx.navigation.NavBackStackEntry getBackStackEntry(@IdRes int destinationId);
+    method public final androidx.navigation.NavBackStackEntry getBackStackEntry(String route);
+    method public androidx.navigation.NavBackStackEntry? getCurrentBackStackEntry();
+    method public final kotlinx.coroutines.flow.Flow<androidx.navigation.NavBackStackEntry> getCurrentBackStackEntryFlow();
+    method public androidx.navigation.NavDestination? getCurrentDestination();
+    method @MainThread public androidx.navigation.NavGraph getGraph();
+    method public androidx.navigation.NavInflater getNavInflater();
+    method public androidx.navigation.NavigatorProvider getNavigatorProvider();
+    method public androidx.navigation.NavBackStackEntry? getPreviousBackStackEntry();
+    method public androidx.lifecycle.ViewModelStoreOwner getViewModelStoreOwner(@IdRes int navGraphId);
+    method public final kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.navigation.NavBackStackEntry>> getVisibleEntries();
+    method @MainThread public boolean handleDeepLink(android.content.Intent? intent);
+    method @MainThread public void navigate(@IdRes int resId);
+    method @MainThread public void navigate(@IdRes int resId, android.os.Bundle? args);
+    method @MainThread public void navigate(@IdRes int resId, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions);
+    method @MainThread public void navigate(@IdRes int resId, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
+    method @MainThread public void navigate(android.net.Uri deepLink);
+    method @MainThread public void navigate(android.net.Uri deepLink, androidx.navigation.NavOptions? navOptions);
+    method @MainThread public void navigate(android.net.Uri deepLink, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
+    method @MainThread public void navigate(androidx.navigation.NavDeepLinkRequest request);
+    method @MainThread public void navigate(androidx.navigation.NavDeepLinkRequest request, androidx.navigation.NavOptions? navOptions);
+    method @MainThread public void navigate(androidx.navigation.NavDeepLinkRequest request, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
+    method @MainThread public void navigate(androidx.navigation.NavDirections directions);
+    method @MainThread public void navigate(androidx.navigation.NavDirections directions, androidx.navigation.NavOptions? navOptions);
+    method @MainThread public void navigate(androidx.navigation.NavDirections directions, androidx.navigation.Navigator.Extras navigatorExtras);
+    method public final void navigate(String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> builder);
+    method public final void navigate(String route, optional androidx.navigation.NavOptions? navOptions, optional androidx.navigation.Navigator.Extras? navigatorExtras);
+    method public final void navigate(String route, optional androidx.navigation.NavOptions? navOptions);
+    method public final void navigate(String route);
+    method @MainThread public boolean navigateUp();
+    method @MainThread public boolean popBackStack();
+    method @MainThread public boolean popBackStack(@IdRes int destinationId, boolean inclusive);
+    method @MainThread public boolean popBackStack(@IdRes int destinationId, boolean inclusive, boolean saveState);
+    method @MainThread public final boolean popBackStack(String route, boolean inclusive, optional boolean saveState);
+    method @MainThread public final boolean popBackStack(String route, boolean inclusive);
+    method public void removeOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener listener);
+    method @CallSuper public void restoreState(android.os.Bundle? navState);
+    method @CallSuper public android.os.Bundle? saveState();
+    method @CallSuper @MainThread public void setGraph(androidx.navigation.NavGraph graph);
+    method @CallSuper @MainThread public void setGraph(@NavigationRes int graphResId);
+    method @CallSuper @MainThread public void setGraph(@NavigationRes int graphResId, android.os.Bundle? startDestinationArgs);
+    method @CallSuper @MainThread public void setGraph(androidx.navigation.NavGraph graph, android.os.Bundle? startDestinationArgs);
+    property public androidx.navigation.NavBackStackEntry? currentBackStackEntry;
+    property public final kotlinx.coroutines.flow.Flow<androidx.navigation.NavBackStackEntry> currentBackStackEntryFlow;
+    property public androidx.navigation.NavDestination? currentDestination;
+    property @MainThread public androidx.navigation.NavGraph graph;
+    property public androidx.navigation.NavInflater navInflater;
+    property public androidx.navigation.NavigatorProvider navigatorProvider;
+    property public androidx.navigation.NavBackStackEntry? previousBackStackEntry;
+    property public final kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.navigation.NavBackStackEntry>> visibleEntries;
+    field public static final androidx.navigation.NavController.Companion Companion;
+    field public static final String KEY_DEEP_LINK_INTENT = "android-support-nav:controller:deepLinkIntent";
+  }
+
+  public static final class NavController.Companion {
+  }
+
+  public static fun interface NavController.OnDestinationChangedListener {
+    method public void onDestinationChanged(androidx.navigation.NavController controller, androidx.navigation.NavDestination destination, android.os.Bundle? arguments);
+  }
+
+  public final class NavControllerKt {
+    method @Deprecated public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+  }
+
+  public final class NavDeepLinkBuilder {
+    ctor public NavDeepLinkBuilder(android.content.Context context);
+    method public androidx.navigation.NavDeepLinkBuilder addDestination(@IdRes int destId, optional android.os.Bundle? args);
+    method public androidx.navigation.NavDeepLinkBuilder addDestination(@IdRes int destId);
+    method public androidx.navigation.NavDeepLinkBuilder addDestination(String route, optional android.os.Bundle? args);
+    method public androidx.navigation.NavDeepLinkBuilder addDestination(String route);
+    method public android.app.PendingIntent createPendingIntent();
+    method public androidx.core.app.TaskStackBuilder createTaskStackBuilder();
+    method public androidx.navigation.NavDeepLinkBuilder setArguments(android.os.Bundle? args);
+    method public androidx.navigation.NavDeepLinkBuilder setComponentName(Class<? extends android.app.Activity> activityClass);
+    method public androidx.navigation.NavDeepLinkBuilder setComponentName(android.content.ComponentName componentName);
+    method public androidx.navigation.NavDeepLinkBuilder setDestination(@IdRes int destId, optional android.os.Bundle? args);
+    method public androidx.navigation.NavDeepLinkBuilder setDestination(@IdRes int destId);
+    method public androidx.navigation.NavDeepLinkBuilder setDestination(String destRoute, optional android.os.Bundle? args);
+    method public androidx.navigation.NavDeepLinkBuilder setDestination(String destRoute);
+    method public androidx.navigation.NavDeepLinkBuilder setGraph(@NavigationRes int navGraphId);
+    method public androidx.navigation.NavDeepLinkBuilder setGraph(androidx.navigation.NavGraph navGraph);
+  }
+
+  public interface NavHost {
+    method public androidx.navigation.NavController getNavController();
+    property public abstract androidx.navigation.NavController navController;
+  }
+
+  public class NavHostController extends androidx.navigation.NavController {
+    ctor public NavHostController(android.content.Context context);
+    method public final void enableOnBackPressed(boolean enabled);
+    method public final void setLifecycleOwner(androidx.lifecycle.LifecycleOwner owner);
+    method public final void setOnBackPressedDispatcher(androidx.activity.OnBackPressedDispatcher dispatcher);
+    method public final void setViewModelStore(androidx.lifecycle.ViewModelStore viewModelStore);
+  }
+
+  public final class NavHostKt {
+    method @Deprecated public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavHost, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavHost, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+  }
+
+  public final class NavInflater {
+    ctor public NavInflater(android.content.Context context, androidx.navigation.NavigatorProvider navigatorProvider);
+    method public androidx.navigation.NavGraph inflate(@NavigationRes int graphResId);
+    field public static final androidx.navigation.NavInflater.Companion Companion;
+  }
+
+  public static final class NavInflater.Companion {
+  }
+
+  public final class Navigation {
+    method public static android.view.View.OnClickListener createNavigateOnClickListener(@IdRes int resId, optional android.os.Bundle? args);
+    method public static android.view.View.OnClickListener createNavigateOnClickListener(@IdRes int resId);
+    method public static android.view.View.OnClickListener createNavigateOnClickListener(androidx.navigation.NavDirections directions);
+    method public static androidx.navigation.NavController findNavController(android.app.Activity activity, @IdRes int viewId);
+    method public static androidx.navigation.NavController findNavController(android.view.View view);
+    method public static void setViewNavController(android.view.View view, androidx.navigation.NavController? controller);
+    field public static final androidx.navigation.Navigation INSTANCE;
+  }
+
+  public final class ViewKt {
+    method public static androidx.navigation.NavController findNavController(android.view.View);
+  }
+
+}
+
diff --git a/navigation/navigation-runtime/src/main/baseline-prof.txt b/navigation/navigation-runtime/src/main/baseline-prof.txt
new file mode 100644
index 0000000..c719d9a
--- /dev/null
+++ b/navigation/navigation-runtime/src/main/baseline-prof.txt
@@ -0,0 +1,106 @@
+# Baseline Profiles for navigation-runtime
+
+HSPLandroidx/navigation/ActivityNavigator$Companion;-><init>()V
+HSPLandroidx/navigation/ActivityNavigator$Companion;-><init>(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLandroidx/navigation/ActivityNavigator$hostActivity$1;-><clinit>()V
+HSPLandroidx/navigation/ActivityNavigator$hostActivity$1;-><init>()V
+HSPLandroidx/navigation/ActivityNavigator;-><clinit>()V
+HSPLandroidx/navigation/ActivityNavigator;-><init>(Landroid/content/Context;)V
+HSPLandroidx/navigation/NavController$Companion;-><init>()V
+HSPLandroidx/navigation/NavController$Companion;-><init>(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLandroidx/navigation/NavController$NavControllerNavigatorState;-><init>(Landroidx/navigation/NavController;Landroidx/navigation/Navigator;)V
+HSPLandroidx/navigation/NavController$NavControllerNavigatorState;->addInternal(Landroidx/navigation/NavBackStackEntry;)V
+HSPLandroidx/navigation/NavController$NavControllerNavigatorState;->createBackStackEntry(Landroidx/navigation/NavDestination;Landroid/os/Bundle;)Landroidx/navigation/NavBackStackEntry;
+HSPLandroidx/navigation/NavController$NavControllerNavigatorState;->push(Landroidx/navigation/NavBackStackEntry;)V
+HSPLandroidx/navigation/NavController$activity$1;-><clinit>()V
+HSPLandroidx/navigation/NavController$activity$1;-><init>()V
+HSPLandroidx/navigation/NavController$lifecycleObserver$1;-><init>(Landroidx/navigation/NavController;)V
+HSPLandroidx/navigation/NavController$lifecycleObserver$1;->onStateChanged(Landroidx/lifecycle/LifecycleOwner;Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/navigation/NavController$navInflater$2;-><init>(Landroidx/navigation/NavController;)V
+HSPLandroidx/navigation/NavController$navInflater$2;->invoke()Landroidx/navigation/NavInflater;
+HSPLandroidx/navigation/NavController$navInflater$2;->invoke()Ljava/lang/Object;
+HSPLandroidx/navigation/NavController$navigate$4;-><init>(Lkotlin/jvm/internal/Ref$BooleanRef;Landroidx/navigation/NavController;Landroidx/navigation/NavDestination;Landroid/os/Bundle;)V
+HSPLandroidx/navigation/NavController$navigate$4;->invoke(Landroidx/navigation/NavBackStackEntry;)V
+HSPLandroidx/navigation/NavController$navigate$4;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/navigation/NavController$onBackPressedCallback$1;-><init>(Landroidx/navigation/NavController;)V
+HSPLandroidx/navigation/NavController;-><clinit>()V
+HSPLandroidx/navigation/NavController;-><init>(Landroid/content/Context;)V
+HSPLandroidx/navigation/NavController;->access$getAddToBackStackHandler$p(Landroidx/navigation/NavController;)Lkotlin/jvm/functions/Function1;
+HSPLandroidx/navigation/NavController;->access$getInflater$p(Landroidx/navigation/NavController;)Landroidx/navigation/NavInflater;
+HSPLandroidx/navigation/NavController;->access$getLifecycleOwner$p(Landroidx/navigation/NavController;)Landroidx/lifecycle/LifecycleOwner;
+HSPLandroidx/navigation/NavController;->access$getViewModel$p(Landroidx/navigation/NavController;)Landroidx/navigation/NavControllerViewModel;
+HSPLandroidx/navigation/NavController;->access$get_graph$p(Landroidx/navigation/NavController;)Landroidx/navigation/NavGraph;
+HSPLandroidx/navigation/NavController;->access$get_navigatorProvider$p(Landroidx/navigation/NavController;)Landroidx/navigation/NavigatorProvider;
+HSPLandroidx/navigation/NavController;->addEntryToBackStack$default(Landroidx/navigation/NavController;Landroidx/navigation/NavDestination;Landroid/os/Bundle;Landroidx/navigation/NavBackStackEntry;Ljava/util/List;ILjava/lang/Object;)V
+HSPLandroidx/navigation/NavController;->addEntryToBackStack(Landroidx/navigation/NavDestination;Landroid/os/Bundle;Landroidx/navigation/NavBackStackEntry;Ljava/util/List;)V
+HSPLandroidx/navigation/NavController;->dispatchOnDestinationChanged()Z
+HSPLandroidx/navigation/NavController;->enableOnBackPressed(Z)V
+HSPLandroidx/navigation/NavController;->findDestination(I)Landroidx/navigation/NavDestination;
+HSPLandroidx/navigation/NavController;->getBackQueue()Lkotlin/collections/ArrayDeque;
+HSPLandroidx/navigation/NavController;->getBackStackEntry(I)Landroidx/navigation/NavBackStackEntry;
+HSPLandroidx/navigation/NavController;->getContext()Landroid/content/Context;
+HSPLandroidx/navigation/NavController;->getCurrentBackStackEntry()Landroidx/navigation/NavBackStackEntry;
+HSPLandroidx/navigation/NavController;->getDestinationCountOnBackStack()I
+HSPLandroidx/navigation/NavController;->getNavInflater()Landroidx/navigation/NavInflater;
+HSPLandroidx/navigation/NavController;->getNavigatorProvider()Landroidx/navigation/NavigatorProvider;
+HSPLandroidx/navigation/NavController;->handleDeepLink(Landroid/content/Intent;)Z
+HSPLandroidx/navigation/NavController;->linkChildToParent(Landroidx/navigation/NavBackStackEntry;Landroidx/navigation/NavBackStackEntry;)V
+HSPLandroidx/navigation/NavController;->navigate(Landroidx/navigation/NavDestination;Landroid/os/Bundle;Landroidx/navigation/NavOptions;Landroidx/navigation/Navigator$Extras;)V
+HSPLandroidx/navigation/NavController;->navigateInternal(Landroidx/navigation/Navigator;Ljava/util/List;Landroidx/navigation/NavOptions;Landroidx/navigation/Navigator$Extras;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/navigation/NavController;->onGraphCreated(Landroid/os/Bundle;)V
+HSPLandroidx/navigation/NavController;->populateVisibleEntries$navigation_runtime_release()Ljava/util/List;
+HSPLandroidx/navigation/NavController;->setGraph(I)V
+HSPLandroidx/navigation/NavController;->setGraph(Landroidx/navigation/NavGraph;Landroid/os/Bundle;)V
+HSPLandroidx/navigation/NavController;->setLifecycleOwner(Landroidx/lifecycle/LifecycleOwner;)V
+HSPLandroidx/navigation/NavController;->setOnBackPressedDispatcher(Landroidx/activity/OnBackPressedDispatcher;)V
+HSPLandroidx/navigation/NavController;->setViewModelStore(Landroidx/lifecycle/ViewModelStore;)V
+HSPLandroidx/navigation/NavController;->updateBackStackLifecycle$navigation_runtime_release()V
+HSPLandroidx/navigation/NavController;->updateOnBackPressedCallbackEnabled()V
+HSPLandroidx/navigation/NavControllerViewModel$Companion$FACTORY$1;-><init>()V
+HSPLandroidx/navigation/NavControllerViewModel$Companion$FACTORY$1;->create(Ljava/lang/Class;)Landroidx/lifecycle/ViewModel;
+HSPLandroidx/navigation/NavControllerViewModel$Companion;-><init>()V
+HSPLandroidx/navigation/NavControllerViewModel$Companion;-><init>(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLandroidx/navigation/NavControllerViewModel$Companion;->getInstance(Landroidx/lifecycle/ViewModelStore;)Landroidx/navigation/NavControllerViewModel;
+HSPLandroidx/navigation/NavControllerViewModel;-><clinit>()V
+HSPLandroidx/navigation/NavControllerViewModel;-><init>()V
+HSPLandroidx/navigation/NavControllerViewModel;->access$getFACTORY$cp()Landroidx/lifecycle/ViewModelProvider$Factory;
+HSPLandroidx/navigation/NavHostController;-><init>(Landroid/content/Context;)V
+HSPLandroidx/navigation/NavHostController;->enableOnBackPressed(Z)V
+HSPLandroidx/navigation/NavHostController;->setLifecycleOwner(Landroidx/lifecycle/LifecycleOwner;)V
+HSPLandroidx/navigation/NavHostController;->setOnBackPressedDispatcher(Landroidx/activity/OnBackPressedDispatcher;)V
+HSPLandroidx/navigation/NavHostController;->setViewModelStore(Landroidx/lifecycle/ViewModelStore;)V
+HSPLandroidx/navigation/NavInflater$Companion;-><init>()V
+HSPLandroidx/navigation/NavInflater$Companion;-><init>(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLandroidx/navigation/NavInflater;-><clinit>()V
+HSPLandroidx/navigation/NavInflater;-><init>(Landroid/content/Context;Landroidx/navigation/NavigatorProvider;)V
+HSPLandroidx/navigation/NavInflater;->inflate(I)Landroidx/navigation/NavGraph;
+HSPLandroidx/navigation/NavInflater;->inflate(Landroid/content/res/Resources;Landroid/content/res/XmlResourceParser;Landroid/util/AttributeSet;I)Landroidx/navigation/NavDestination;
+HSPLandroidx/navigation/NavInflater;->inflateAction(Landroid/content/res/Resources;Landroidx/navigation/NavDestination;Landroid/util/AttributeSet;Landroid/content/res/XmlResourceParser;I)V
+HSPLandroidx/navigation/NavInflater;->inflateArgument(Landroid/content/res/TypedArray;Landroid/content/res/Resources;I)Landroidx/navigation/NavArgument;
+HSPLandroidx/navigation/NavInflater;->inflateArgumentForDestination(Landroid/content/res/Resources;Landroidx/navigation/NavDestination;Landroid/util/AttributeSet;I)V
+HSPLandroidx/navigation/Navigation;-><clinit>()V
+HSPLandroidx/navigation/Navigation;-><init>()V
+HSPLandroidx/navigation/Navigation;->setViewNavController(Landroid/view/View;Landroidx/navigation/NavController;)V
+Landroidx/navigation/ActivityNavigator$Companion;
+Landroidx/navigation/ActivityNavigator$hostActivity$1;
+Landroidx/navigation/ActivityNavigator;
+Landroidx/navigation/NavController$Companion;
+Landroidx/navigation/NavController$NavControllerNavigatorState;
+Landroidx/navigation/NavController$activity$1;
+Landroidx/navigation/NavController$lifecycleObserver$1;
+Landroidx/navigation/NavController$navInflater$2;
+Landroidx/navigation/NavController$navigate$4;
+Landroidx/navigation/NavController$onBackPressedCallback$1;
+Landroidx/navigation/NavController;
+Landroidx/navigation/NavControllerViewModel$Companion$FACTORY$1;
+Landroidx/navigation/NavControllerViewModel$Companion;
+Landroidx/navigation/NavControllerViewModel;
+Landroidx/navigation/NavHost;
+Landroidx/navigation/NavHostController;
+Landroidx/navigation/NavInflater$Companion;
+Landroidx/navigation/NavInflater;
+Landroidx/navigation/Navigation;
+PLandroidx/navigation/NavControllerViewModel;->onCleared()V
+HSPLandroidx/navigation/R$styleable;-><clinit>()V
+Landroidx/navigation/R$id;
+Landroidx/navigation/R$styleable;
diff --git a/navigation/navigation-testing/api/2.4.0-beta01.txt b/navigation/navigation-testing/api/2.4.0-beta01.txt
new file mode 100644
index 0000000..8fc24e8
--- /dev/null
+++ b/navigation/navigation-testing/api/2.4.0-beta01.txt
@@ -0,0 +1,22 @@
+// Signature format: 4.0
+package androidx.navigation.testing {
+
+  public final class TestNavHostController extends androidx.navigation.NavHostController {
+    ctor public TestNavHostController(android.content.Context context);
+    method public java.util.List<androidx.navigation.NavBackStackEntry> getBackStack();
+    method public void setCurrentDestination(@IdRes int destId, optional android.os.Bundle args);
+    method public void setCurrentDestination(@IdRes int destId);
+    method public void setCurrentDestination(String destRoute, optional android.os.Bundle args);
+    method public void setCurrentDestination(String destRoute);
+    property public final java.util.List<androidx.navigation.NavBackStackEntry> backStack;
+  }
+
+  public final class TestNavigatorState extends androidx.navigation.NavigatorState {
+    ctor public TestNavigatorState(optional android.content.Context? context, optional kotlinx.coroutines.CoroutineDispatcher coroutineDispatcher);
+    ctor public TestNavigatorState(optional android.content.Context? context);
+    method public androidx.navigation.NavBackStackEntry createBackStackEntry(androidx.navigation.NavDestination destination, android.os.Bundle? arguments);
+    method public androidx.navigation.NavBackStackEntry restoreBackStackEntry(androidx.navigation.NavBackStackEntry previouslySavedEntry);
+  }
+
+}
+
diff --git a/navigation/navigation-testing/api/public_plus_experimental_2.4.0-beta01.txt b/navigation/navigation-testing/api/public_plus_experimental_2.4.0-beta01.txt
new file mode 100644
index 0000000..8fc24e8
--- /dev/null
+++ b/navigation/navigation-testing/api/public_plus_experimental_2.4.0-beta01.txt
@@ -0,0 +1,22 @@
+// Signature format: 4.0
+package androidx.navigation.testing {
+
+  public final class TestNavHostController extends androidx.navigation.NavHostController {
+    ctor public TestNavHostController(android.content.Context context);
+    method public java.util.List<androidx.navigation.NavBackStackEntry> getBackStack();
+    method public void setCurrentDestination(@IdRes int destId, optional android.os.Bundle args);
+    method public void setCurrentDestination(@IdRes int destId);
+    method public void setCurrentDestination(String destRoute, optional android.os.Bundle args);
+    method public void setCurrentDestination(String destRoute);
+    property public final java.util.List<androidx.navigation.NavBackStackEntry> backStack;
+  }
+
+  public final class TestNavigatorState extends androidx.navigation.NavigatorState {
+    ctor public TestNavigatorState(optional android.content.Context? context, optional kotlinx.coroutines.CoroutineDispatcher coroutineDispatcher);
+    ctor public TestNavigatorState(optional android.content.Context? context);
+    method public androidx.navigation.NavBackStackEntry createBackStackEntry(androidx.navigation.NavDestination destination, android.os.Bundle? arguments);
+    method public androidx.navigation.NavBackStackEntry restoreBackStackEntry(androidx.navigation.NavBackStackEntry previouslySavedEntry);
+  }
+
+}
+
diff --git a/navigation/navigation-testing/api/res-2.4.0-beta01.txt b/navigation/navigation-testing/api/res-2.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/navigation-testing/api/res-2.4.0-beta01.txt
diff --git a/navigation/navigation-testing/api/restricted_2.4.0-beta01.txt b/navigation/navigation-testing/api/restricted_2.4.0-beta01.txt
new file mode 100644
index 0000000..8fc24e8
--- /dev/null
+++ b/navigation/navigation-testing/api/restricted_2.4.0-beta01.txt
@@ -0,0 +1,22 @@
+// Signature format: 4.0
+package androidx.navigation.testing {
+
+  public final class TestNavHostController extends androidx.navigation.NavHostController {
+    ctor public TestNavHostController(android.content.Context context);
+    method public java.util.List<androidx.navigation.NavBackStackEntry> getBackStack();
+    method public void setCurrentDestination(@IdRes int destId, optional android.os.Bundle args);
+    method public void setCurrentDestination(@IdRes int destId);
+    method public void setCurrentDestination(String destRoute, optional android.os.Bundle args);
+    method public void setCurrentDestination(String destRoute);
+    property public final java.util.List<androidx.navigation.NavBackStackEntry> backStack;
+  }
+
+  public final class TestNavigatorState extends androidx.navigation.NavigatorState {
+    ctor public TestNavigatorState(optional android.content.Context? context, optional kotlinx.coroutines.CoroutineDispatcher coroutineDispatcher);
+    ctor public TestNavigatorState(optional android.content.Context? context);
+    method public androidx.navigation.NavBackStackEntry createBackStackEntry(androidx.navigation.NavDestination destination, android.os.Bundle? arguments);
+    method public androidx.navigation.NavBackStackEntry restoreBackStackEntry(androidx.navigation.NavBackStackEntry previouslySavedEntry);
+  }
+
+}
+
diff --git a/navigation/navigation-ui-ktx/api/2.4.0-beta01.txt b/navigation/navigation-ui-ktx/api/2.4.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/navigation/navigation-ui-ktx/api/2.4.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/navigation/navigation-ui-ktx/api/public_plus_experimental_2.4.0-beta01.txt b/navigation/navigation-ui-ktx/api/public_plus_experimental_2.4.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/navigation/navigation-ui-ktx/api/public_plus_experimental_2.4.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/navigation/navigation-ui-ktx/api/res-2.4.0-beta01.txt b/navigation/navigation-ui-ktx/api/res-2.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/navigation-ui-ktx/api/res-2.4.0-beta01.txt
diff --git a/navigation/navigation-ui-ktx/api/restricted_2.4.0-beta01.txt b/navigation/navigation-ui-ktx/api/restricted_2.4.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/navigation/navigation-ui-ktx/api/restricted_2.4.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/navigation/navigation-ui/api/2.4.0-beta01.txt b/navigation/navigation-ui/api/2.4.0-beta01.txt
new file mode 100644
index 0000000..9551da2
--- /dev/null
+++ b/navigation/navigation-ui/api/2.4.0-beta01.txt
@@ -0,0 +1,87 @@
+// Signature format: 4.0
+package androidx.navigation.ui {
+
+  public final class ActivityKt {
+    method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController navController, androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+    method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController navController, optional androidx.navigation.ui.AppBarConfiguration configuration);
+  }
+
+  public final class AppBarConfiguration {
+    method @Deprecated public androidx.drawerlayout.widget.DrawerLayout? getDrawerLayout();
+    method public androidx.navigation.ui.AppBarConfiguration.OnNavigateUpListener? getFallbackOnNavigateUpListener();
+    method public androidx.customview.widget.Openable? getOpenableLayout();
+    method public java.util.Set<java.lang.Integer> getTopLevelDestinations();
+    property @Deprecated public final androidx.drawerlayout.widget.DrawerLayout? drawerLayout;
+    property public final androidx.navigation.ui.AppBarConfiguration.OnNavigateUpListener? fallbackOnNavigateUpListener;
+    property public final androidx.customview.widget.Openable? openableLayout;
+    property public final java.util.Set<java.lang.Integer> topLevelDestinations;
+  }
+
+  public static final class AppBarConfiguration.Builder {
+    ctor public AppBarConfiguration.Builder(androidx.navigation.NavGraph navGraph);
+    ctor public AppBarConfiguration.Builder(android.view.Menu topLevelMenu);
+    ctor public AppBarConfiguration.Builder(int... topLevelDestinationIds);
+    ctor public AppBarConfiguration.Builder(java.util.Set<java.lang.Integer> topLevelDestinationIds);
+    method public androidx.navigation.ui.AppBarConfiguration build();
+    method @Deprecated public androidx.navigation.ui.AppBarConfiguration.Builder setDrawerLayout(androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+    method public androidx.navigation.ui.AppBarConfiguration.Builder setFallbackOnNavigateUpListener(androidx.navigation.ui.AppBarConfiguration.OnNavigateUpListener? fallbackOnNavigateUpListener);
+    method public androidx.navigation.ui.AppBarConfiguration.Builder setOpenableLayout(androidx.customview.widget.Openable? openableLayout);
+  }
+
+  public static fun interface AppBarConfiguration.OnNavigateUpListener {
+    method public boolean onNavigateUp();
+  }
+
+  public final class AppBarConfigurationKt {
+    method public static inline androidx.navigation.ui.AppBarConfiguration AppBarConfiguration(androidx.navigation.NavGraph navGraph, optional androidx.customview.widget.Openable? drawerLayout, optional kotlin.jvm.functions.Function0<java.lang.Boolean> fallbackOnNavigateUpListener);
+    method public static inline androidx.navigation.ui.AppBarConfiguration AppBarConfiguration(android.view.Menu topLevelMenu, optional androidx.customview.widget.Openable? drawerLayout, optional kotlin.jvm.functions.Function0<java.lang.Boolean> fallbackOnNavigateUpListener);
+    method public static inline androidx.navigation.ui.AppBarConfiguration AppBarConfiguration(java.util.Set<java.lang.Integer> topLevelDestinationIds, optional androidx.customview.widget.Openable? drawerLayout, optional kotlin.jvm.functions.Function0<java.lang.Boolean> fallbackOnNavigateUpListener);
+  }
+
+  public final class BottomNavigationViewKt {
+    method public static void setupWithNavController(com.google.android.material.navigation.NavigationBarView, androidx.navigation.NavController navController);
+  }
+
+  public final class CollapsingToolbarLayoutKt {
+    method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+    method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, optional androidx.navigation.ui.AppBarConfiguration configuration);
+  }
+
+  public final class MenuItemKt {
+    method public static boolean onNavDestinationSelected(android.view.MenuItem, androidx.navigation.NavController navController);
+  }
+
+  public final class NavControllerKt {
+    method public static boolean navigateUp(androidx.navigation.NavController, androidx.customview.widget.Openable? drawerLayout);
+    method public static boolean navigateUp(androidx.navigation.NavController, androidx.navigation.ui.AppBarConfiguration appBarConfiguration);
+  }
+
+  public final class NavigationUI {
+    method public static boolean navigateUp(androidx.navigation.NavController navController, androidx.customview.widget.Openable? openableLayout);
+    method public static boolean navigateUp(androidx.navigation.NavController navController, androidx.navigation.ui.AppBarConfiguration configuration);
+    method public static boolean onNavDestinationSelected(android.view.MenuItem item, androidx.navigation.NavController navController);
+    method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity activity, androidx.navigation.NavController navController, androidx.customview.widget.Openable? openableLayout);
+    method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity activity, androidx.navigation.NavController navController, optional androidx.navigation.ui.AppBarConfiguration configuration);
+    method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity activity, androidx.navigation.NavController navController);
+    method public static void setupWithNavController(androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, androidx.customview.widget.Openable? openableLayout);
+    method public static void setupWithNavController(androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, optional androidx.navigation.ui.AppBarConfiguration configuration);
+    method public static void setupWithNavController(androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController);
+    method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout collapsingToolbarLayout, androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, androidx.customview.widget.Openable? openableLayout);
+    method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout collapsingToolbarLayout, androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, optional androidx.navigation.ui.AppBarConfiguration configuration);
+    method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout collapsingToolbarLayout, androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController);
+    method public static void setupWithNavController(com.google.android.material.navigation.NavigationView navigationView, androidx.navigation.NavController navController);
+    method public static void setupWithNavController(com.google.android.material.navigation.NavigationBarView navigationBarView, androidx.navigation.NavController navController);
+    field public static final androidx.navigation.ui.NavigationUI INSTANCE;
+  }
+
+  public final class NavigationViewKt {
+    method public static void setupWithNavController(com.google.android.material.navigation.NavigationView, androidx.navigation.NavController navController);
+  }
+
+  public final class ToolbarKt {
+    method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController navController, androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+    method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController navController, optional androidx.navigation.ui.AppBarConfiguration configuration);
+  }
+
+}
+
diff --git a/navigation/navigation-ui/api/public_plus_experimental_2.4.0-beta01.txt b/navigation/navigation-ui/api/public_plus_experimental_2.4.0-beta01.txt
new file mode 100644
index 0000000..ce4e91b
--- /dev/null
+++ b/navigation/navigation-ui/api/public_plus_experimental_2.4.0-beta01.txt
@@ -0,0 +1,93 @@
+// Signature format: 4.0
+package androidx.navigation.ui {
+
+  public final class ActivityKt {
+    method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController navController, androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+    method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController navController, optional androidx.navigation.ui.AppBarConfiguration configuration);
+  }
+
+  public final class AppBarConfiguration {
+    method @Deprecated public androidx.drawerlayout.widget.DrawerLayout? getDrawerLayout();
+    method public androidx.navigation.ui.AppBarConfiguration.OnNavigateUpListener? getFallbackOnNavigateUpListener();
+    method public androidx.customview.widget.Openable? getOpenableLayout();
+    method public java.util.Set<java.lang.Integer> getTopLevelDestinations();
+    property @Deprecated public final androidx.drawerlayout.widget.DrawerLayout? drawerLayout;
+    property public final androidx.navigation.ui.AppBarConfiguration.OnNavigateUpListener? fallbackOnNavigateUpListener;
+    property public final androidx.customview.widget.Openable? openableLayout;
+    property public final java.util.Set<java.lang.Integer> topLevelDestinations;
+  }
+
+  public static final class AppBarConfiguration.Builder {
+    ctor public AppBarConfiguration.Builder(androidx.navigation.NavGraph navGraph);
+    ctor public AppBarConfiguration.Builder(android.view.Menu topLevelMenu);
+    ctor public AppBarConfiguration.Builder(int... topLevelDestinationIds);
+    ctor public AppBarConfiguration.Builder(java.util.Set<java.lang.Integer> topLevelDestinationIds);
+    method public androidx.navigation.ui.AppBarConfiguration build();
+    method @Deprecated public androidx.navigation.ui.AppBarConfiguration.Builder setDrawerLayout(androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+    method public androidx.navigation.ui.AppBarConfiguration.Builder setFallbackOnNavigateUpListener(androidx.navigation.ui.AppBarConfiguration.OnNavigateUpListener? fallbackOnNavigateUpListener);
+    method public androidx.navigation.ui.AppBarConfiguration.Builder setOpenableLayout(androidx.customview.widget.Openable? openableLayout);
+  }
+
+  public static fun interface AppBarConfiguration.OnNavigateUpListener {
+    method public boolean onNavigateUp();
+  }
+
+  public final class AppBarConfigurationKt {
+    method public static inline androidx.navigation.ui.AppBarConfiguration AppBarConfiguration(androidx.navigation.NavGraph navGraph, optional androidx.customview.widget.Openable? drawerLayout, optional kotlin.jvm.functions.Function0<java.lang.Boolean> fallbackOnNavigateUpListener);
+    method public static inline androidx.navigation.ui.AppBarConfiguration AppBarConfiguration(android.view.Menu topLevelMenu, optional androidx.customview.widget.Openable? drawerLayout, optional kotlin.jvm.functions.Function0<java.lang.Boolean> fallbackOnNavigateUpListener);
+    method public static inline androidx.navigation.ui.AppBarConfiguration AppBarConfiguration(java.util.Set<java.lang.Integer> topLevelDestinationIds, optional androidx.customview.widget.Openable? drawerLayout, optional kotlin.jvm.functions.Function0<java.lang.Boolean> fallbackOnNavigateUpListener);
+  }
+
+  public final class BottomNavigationViewKt {
+    method public static void setupWithNavController(com.google.android.material.navigation.NavigationBarView, androidx.navigation.NavController navController);
+  }
+
+  public final class CollapsingToolbarLayoutKt {
+    method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+    method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, optional androidx.navigation.ui.AppBarConfiguration configuration);
+  }
+
+  public final class MenuItemKt {
+    method public static boolean onNavDestinationSelected(android.view.MenuItem, androidx.navigation.NavController navController);
+  }
+
+  public final class NavControllerKt {
+    method public static boolean navigateUp(androidx.navigation.NavController, androidx.customview.widget.Openable? drawerLayout);
+    method public static boolean navigateUp(androidx.navigation.NavController, androidx.navigation.ui.AppBarConfiguration appBarConfiguration);
+  }
+
+  public final class NavigationUI {
+    method public static boolean navigateUp(androidx.navigation.NavController navController, androidx.customview.widget.Openable? openableLayout);
+    method public static boolean navigateUp(androidx.navigation.NavController navController, androidx.navigation.ui.AppBarConfiguration configuration);
+    method public static boolean onNavDestinationSelected(android.view.MenuItem item, androidx.navigation.NavController navController);
+    method @androidx.navigation.ui.NavigationUiSaveStateControl public static boolean onNavDestinationSelected(android.view.MenuItem item, androidx.navigation.NavController navController, boolean saveState);
+    method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity activity, androidx.navigation.NavController navController, androidx.customview.widget.Openable? openableLayout);
+    method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity activity, androidx.navigation.NavController navController, optional androidx.navigation.ui.AppBarConfiguration configuration);
+    method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity activity, androidx.navigation.NavController navController);
+    method public static void setupWithNavController(androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, androidx.customview.widget.Openable? openableLayout);
+    method public static void setupWithNavController(androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, optional androidx.navigation.ui.AppBarConfiguration configuration);
+    method public static void setupWithNavController(androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController);
+    method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout collapsingToolbarLayout, androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, androidx.customview.widget.Openable? openableLayout);
+    method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout collapsingToolbarLayout, androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, optional androidx.navigation.ui.AppBarConfiguration configuration);
+    method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout collapsingToolbarLayout, androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController);
+    method public static void setupWithNavController(com.google.android.material.navigation.NavigationView navigationView, androidx.navigation.NavController navController);
+    method @androidx.navigation.ui.NavigationUiSaveStateControl public static void setupWithNavController(com.google.android.material.navigation.NavigationView navigationView, androidx.navigation.NavController navController, boolean saveState);
+    method public static void setupWithNavController(com.google.android.material.navigation.NavigationBarView navigationBarView, androidx.navigation.NavController navController);
+    method @androidx.navigation.ui.NavigationUiSaveStateControl public static void setupWithNavController(com.google.android.material.navigation.NavigationBarView navigationBarView, androidx.navigation.NavController navController, boolean saveState);
+    field public static final androidx.navigation.ui.NavigationUI INSTANCE;
+  }
+
+  @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level) @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets=kotlin.annotation.AnnotationTarget) public @interface NavigationUiSaveStateControl {
+  }
+
+  public final class NavigationViewKt {
+    method public static void setupWithNavController(com.google.android.material.navigation.NavigationView, androidx.navigation.NavController navController);
+  }
+
+  public final class ToolbarKt {
+    method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController navController, androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+    method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController navController, optional androidx.navigation.ui.AppBarConfiguration configuration);
+  }
+
+}
+
diff --git a/navigation/navigation-ui/api/res-2.4.0-beta01.txt b/navigation/navigation-ui/api/res-2.4.0-beta01.txt
new file mode 100644
index 0000000..e65fdbe
--- /dev/null
+++ b/navigation/navigation-ui/api/res-2.4.0-beta01.txt
@@ -0,0 +1,8 @@
+anim nav_default_enter_anim
+anim nav_default_exit_anim
+anim nav_default_pop_enter_anim
+anim nav_default_pop_exit_anim
+animator nav_default_enter_anim
+animator nav_default_exit_anim
+animator nav_default_pop_enter_anim
+animator nav_default_pop_exit_anim
diff --git a/navigation/navigation-ui/api/restricted_2.4.0-beta01.txt b/navigation/navigation-ui/api/restricted_2.4.0-beta01.txt
new file mode 100644
index 0000000..9551da2
--- /dev/null
+++ b/navigation/navigation-ui/api/restricted_2.4.0-beta01.txt
@@ -0,0 +1,87 @@
+// Signature format: 4.0
+package androidx.navigation.ui {
+
+  public final class ActivityKt {
+    method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController navController, androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+    method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController navController, optional androidx.navigation.ui.AppBarConfiguration configuration);
+  }
+
+  public final class AppBarConfiguration {
+    method @Deprecated public androidx.drawerlayout.widget.DrawerLayout? getDrawerLayout();
+    method public androidx.navigation.ui.AppBarConfiguration.OnNavigateUpListener? getFallbackOnNavigateUpListener();
+    method public androidx.customview.widget.Openable? getOpenableLayout();
+    method public java.util.Set<java.lang.Integer> getTopLevelDestinations();
+    property @Deprecated public final androidx.drawerlayout.widget.DrawerLayout? drawerLayout;
+    property public final androidx.navigation.ui.AppBarConfiguration.OnNavigateUpListener? fallbackOnNavigateUpListener;
+    property public final androidx.customview.widget.Openable? openableLayout;
+    property public final java.util.Set<java.lang.Integer> topLevelDestinations;
+  }
+
+  public static final class AppBarConfiguration.Builder {
+    ctor public AppBarConfiguration.Builder(androidx.navigation.NavGraph navGraph);
+    ctor public AppBarConfiguration.Builder(android.view.Menu topLevelMenu);
+    ctor public AppBarConfiguration.Builder(int... topLevelDestinationIds);
+    ctor public AppBarConfiguration.Builder(java.util.Set<java.lang.Integer> topLevelDestinationIds);
+    method public androidx.navigation.ui.AppBarConfiguration build();
+    method @Deprecated public androidx.navigation.ui.AppBarConfiguration.Builder setDrawerLayout(androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+    method public androidx.navigation.ui.AppBarConfiguration.Builder setFallbackOnNavigateUpListener(androidx.navigation.ui.AppBarConfiguration.OnNavigateUpListener? fallbackOnNavigateUpListener);
+    method public androidx.navigation.ui.AppBarConfiguration.Builder setOpenableLayout(androidx.customview.widget.Openable? openableLayout);
+  }
+
+  public static fun interface AppBarConfiguration.OnNavigateUpListener {
+    method public boolean onNavigateUp();
+  }
+
+  public final class AppBarConfigurationKt {
+    method public static inline androidx.navigation.ui.AppBarConfiguration AppBarConfiguration(androidx.navigation.NavGraph navGraph, optional androidx.customview.widget.Openable? drawerLayout, optional kotlin.jvm.functions.Function0<java.lang.Boolean> fallbackOnNavigateUpListener);
+    method public static inline androidx.navigation.ui.AppBarConfiguration AppBarConfiguration(android.view.Menu topLevelMenu, optional androidx.customview.widget.Openable? drawerLayout, optional kotlin.jvm.functions.Function0<java.lang.Boolean> fallbackOnNavigateUpListener);
+    method public static inline androidx.navigation.ui.AppBarConfiguration AppBarConfiguration(java.util.Set<java.lang.Integer> topLevelDestinationIds, optional androidx.customview.widget.Openable? drawerLayout, optional kotlin.jvm.functions.Function0<java.lang.Boolean> fallbackOnNavigateUpListener);
+  }
+
+  public final class BottomNavigationViewKt {
+    method public static void setupWithNavController(com.google.android.material.navigation.NavigationBarView, androidx.navigation.NavController navController);
+  }
+
+  public final class CollapsingToolbarLayoutKt {
+    method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+    method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, optional androidx.navigation.ui.AppBarConfiguration configuration);
+  }
+
+  public final class MenuItemKt {
+    method public static boolean onNavDestinationSelected(android.view.MenuItem, androidx.navigation.NavController navController);
+  }
+
+  public final class NavControllerKt {
+    method public static boolean navigateUp(androidx.navigation.NavController, androidx.customview.widget.Openable? drawerLayout);
+    method public static boolean navigateUp(androidx.navigation.NavController, androidx.navigation.ui.AppBarConfiguration appBarConfiguration);
+  }
+
+  public final class NavigationUI {
+    method public static boolean navigateUp(androidx.navigation.NavController navController, androidx.customview.widget.Openable? openableLayout);
+    method public static boolean navigateUp(androidx.navigation.NavController navController, androidx.navigation.ui.AppBarConfiguration configuration);
+    method public static boolean onNavDestinationSelected(android.view.MenuItem item, androidx.navigation.NavController navController);
+    method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity activity, androidx.navigation.NavController navController, androidx.customview.widget.Openable? openableLayout);
+    method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity activity, androidx.navigation.NavController navController, optional androidx.navigation.ui.AppBarConfiguration configuration);
+    method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity activity, androidx.navigation.NavController navController);
+    method public static void setupWithNavController(androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, androidx.customview.widget.Openable? openableLayout);
+    method public static void setupWithNavController(androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, optional androidx.navigation.ui.AppBarConfiguration configuration);
+    method public static void setupWithNavController(androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController);
+    method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout collapsingToolbarLayout, androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, androidx.customview.widget.Openable? openableLayout);
+    method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout collapsingToolbarLayout, androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, optional androidx.navigation.ui.AppBarConfiguration configuration);
+    method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout collapsingToolbarLayout, androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController);
+    method public static void setupWithNavController(com.google.android.material.navigation.NavigationView navigationView, androidx.navigation.NavController navController);
+    method public static void setupWithNavController(com.google.android.material.navigation.NavigationBarView navigationBarView, androidx.navigation.NavController navController);
+    field public static final androidx.navigation.ui.NavigationUI INSTANCE;
+  }
+
+  public final class NavigationViewKt {
+    method public static void setupWithNavController(com.google.android.material.navigation.NavigationView, androidx.navigation.NavController navController);
+  }
+
+  public final class ToolbarKt {
+    method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController navController, androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+    method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController navController, optional androidx.navigation.ui.AppBarConfiguration configuration);
+  }
+
+}
+
diff --git a/navigation/navigation-ui/src/main/java/androidx/navigation/ui/AbstractAppBarOnDestinationChangedListener.kt b/navigation/navigation-ui/src/main/java/androidx/navigation/ui/AbstractAppBarOnDestinationChangedListener.kt
index 66ebd17..e70048a 100644
--- a/navigation/navigation-ui/src/main/java/androidx/navigation/ui/AbstractAppBarOnDestinationChangedListener.kt
+++ b/navigation/navigation-ui/src/main/java/androidx/navigation/ui/AbstractAppBarOnDestinationChangedListener.kt
@@ -79,7 +79,7 @@
                     title.append(arguments[argName].toString())
                 } else {
                     throw IllegalArgumentException(
-                        "Could not find $argName in $arguments to fill label $label"
+                        "Could not find \"$argName\" in $arguments to fill label \"$label\""
                     )
                 }
             }
diff --git a/preference/preference/api/current.txt b/preference/preference/api/current.txt
index f8e20c5..4cc0b65 100644
--- a/preference/preference/api/current.txt
+++ b/preference/preference/api/current.txt
@@ -414,6 +414,16 @@
     method public int getPreferenceAdapterPosition(androidx.preference.Preference!);
   }
 
+  public abstract class PreferenceHeaderFragmentCompat extends androidx.fragment.app.Fragment implements androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
+    ctor public PreferenceHeaderFragmentCompat();
+    method public final androidx.slidingpanelayout.widget.SlidingPaneLayout getSlidingPaneLayout();
+    method public androidx.fragment.app.Fragment? onCreateInitialDetailFragment();
+    method public abstract androidx.preference.PreferenceFragmentCompat onCreatePreferenceHeader();
+    method @CallSuper public boolean onPreferenceStartFragment(androidx.preference.PreferenceFragmentCompat caller, androidx.preference.Preference pref);
+    method public final void openPreference(androidx.preference.Preference header);
+    property public final androidx.slidingpanelayout.widget.SlidingPaneLayout slidingPaneLayout;
+  }
+
   public class PreferenceManager {
     method public androidx.preference.PreferenceScreen! createPreferenceScreen(android.content.Context!);
     method public <T extends androidx.preference.Preference> T? findPreference(CharSequence);
diff --git a/preference/preference/api/public_plus_experimental_current.txt b/preference/preference/api/public_plus_experimental_current.txt
index f8e20c5..4cc0b65 100644
--- a/preference/preference/api/public_plus_experimental_current.txt
+++ b/preference/preference/api/public_plus_experimental_current.txt
@@ -414,6 +414,16 @@
     method public int getPreferenceAdapterPosition(androidx.preference.Preference!);
   }
 
+  public abstract class PreferenceHeaderFragmentCompat extends androidx.fragment.app.Fragment implements androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
+    ctor public PreferenceHeaderFragmentCompat();
+    method public final androidx.slidingpanelayout.widget.SlidingPaneLayout getSlidingPaneLayout();
+    method public androidx.fragment.app.Fragment? onCreateInitialDetailFragment();
+    method public abstract androidx.preference.PreferenceFragmentCompat onCreatePreferenceHeader();
+    method @CallSuper public boolean onPreferenceStartFragment(androidx.preference.PreferenceFragmentCompat caller, androidx.preference.Preference pref);
+    method public final void openPreference(androidx.preference.Preference header);
+    property public final androidx.slidingpanelayout.widget.SlidingPaneLayout slidingPaneLayout;
+  }
+
   public class PreferenceManager {
     method public androidx.preference.PreferenceScreen! createPreferenceScreen(android.content.Context!);
     method public <T extends androidx.preference.Preference> T? findPreference(CharSequence);
diff --git a/preference/preference/api/restricted_current.txt b/preference/preference/api/restricted_current.txt
index 4251683..1ca2328 100644
--- a/preference/preference/api/restricted_current.txt
+++ b/preference/preference/api/restricted_current.txt
@@ -439,6 +439,16 @@
     method public void onPreferenceVisibilityChange(androidx.preference.Preference!);
   }
 
+  public abstract class PreferenceHeaderFragmentCompat extends androidx.fragment.app.Fragment implements androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
+    ctor public PreferenceHeaderFragmentCompat();
+    method public final androidx.slidingpanelayout.widget.SlidingPaneLayout getSlidingPaneLayout();
+    method public androidx.fragment.app.Fragment? onCreateInitialDetailFragment();
+    method public abstract androidx.preference.PreferenceFragmentCompat onCreatePreferenceHeader();
+    method @CallSuper public boolean onPreferenceStartFragment(androidx.preference.PreferenceFragmentCompat caller, androidx.preference.Preference pref);
+    method public final void openPreference(androidx.preference.Preference header);
+    property public final androidx.slidingpanelayout.widget.SlidingPaneLayout slidingPaneLayout;
+  }
+
   public class PreferenceManager {
     ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public PreferenceManager(android.content.Context!);
     method public androidx.preference.PreferenceScreen! createPreferenceScreen(android.content.Context!);
diff --git a/preference/preference/build.gradle b/preference/preference/build.gradle
index 65b82b9..6f18810 100644
--- a/preference/preference/build.gradle
+++ b/preference/preference/build.gradle
@@ -29,8 +29,9 @@
     // Use the latest version of core library for verifying insets visibility
     api("androidx.core:core:1.6.0-rc01")
     implementation("androidx.collection:collection:1.0.0")
-    api("androidx.fragment:fragment:1.2.4")
+    api("androidx.fragment:fragment-ktx:1.3.6")
     api("androidx.recyclerview:recyclerview:1.0.0")
+    api("androidx.slidingpanelayout:slidingpanelayout:1.2.0-beta01")
 
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testCore)
diff --git a/preference/preference/res/values/dimens.xml b/preference/preference/res/values/dimens.xml
index 403523e..52efb82 100644
--- a/preference/preference/res/values/dimens.xml
+++ b/preference/preference/res/values/dimens.xml
@@ -13,4 +13,6 @@
     <!-- The padding at the start of the dropdown menu within a DropDownPreference
     TODO: Pending UX discussion in b/110975540 - keeping old behaviour for now.-->
     <dimen name="preference_dropdown_padding_start">0dp</dimen>
+    <dimen name="preferences_header_width">384dp</dimen>
+    <dimen name="preferences_detail_width">300dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/preference/preference/res/values/ids.xml b/preference/preference/res/values/ids.xml
new file mode 100644
index 0000000..33aaec7
--- /dev/null
+++ b/preference/preference/res/values/ids.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.
+  -->
+
+<resources>
+    <item name="preferences_sliding_pane_layout" type="id" />
+    <item name="preferences_header" type="id" />
+    <item name="preferences_detail" type="id" />
+</resources>
\ No newline at end of file
diff --git a/preference/preference/res/values/integers.xml b/preference/preference/res/values/integers.xml
new file mode 100644
index 0000000..172ff6f
--- /dev/null
+++ b/preference/preference/res/values/integers.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.
+  -->
+
+<resources>
+    <integer name="preferences_header_pane_weight">0</integer>
+    <integer name="preferences_detail_pane_weight">1</integer>
+</resources>
\ No newline at end of file
diff --git a/preference/preference/src/androidTest/java/androidx/preference/tests/EditTextPreferenceDialogFragmentCompatTest.java b/preference/preference/src/androidTest/java/androidx/preference/tests/EditTextPreferenceDialogFragmentCompatTest.java
index 03a8eb0..dce02bd 100644
--- a/preference/preference/src/androidTest/java/androidx/preference/tests/EditTextPreferenceDialogFragmentCompatTest.java
+++ b/preference/preference/src/androidTest/java/androidx/preference/tests/EditTextPreferenceDialogFragmentCompatTest.java
@@ -92,6 +92,7 @@
 
         mActivityRule.getScenario().onActivity(
                 new ActivityScenario.ActivityAction<PreferenceTestHelperActivity>() {
+                    @SuppressWarnings("deprecation")
                     @Override
                     public void perform(PreferenceTestHelperActivity activity) {
                         mTargetPreference = activity.setupPreferenceHierarchy(
diff --git a/preference/preference/src/androidTest/java/androidx/preference/tests/PreferenceDialogFragmentCompatTest.kt b/preference/preference/src/androidTest/java/androidx/preference/tests/PreferenceDialogFragmentCompatTest.kt
index 4d4d373..2b7b3aa7 100644
--- a/preference/preference/src/androidTest/java/androidx/preference/tests/PreferenceDialogFragmentCompatTest.kt
+++ b/preference/preference/src/androidTest/java/androidx/preference/tests/PreferenceDialogFragmentCompatTest.kt
@@ -90,6 +90,7 @@
         setPreferencesFromResource(xmlLayoutId, rootKey)
     }
 
+    @Suppress("DEPRECATION")
     override fun onDisplayPreferenceDialog(preference: Preference) {
         dialogFragment = DialogFragment(preference.key)
             .also { it.setTargetFragment(this, 0) }
diff --git a/preference/preference/src/androidTest/java/androidx/preference/tests/PreferenceFragmentCompatInterfaceTest.kt b/preference/preference/src/androidTest/java/androidx/preference/tests/PreferenceFragmentCompatInterfaceTest.kt
index 4f60ae0..3b5abce 100644
--- a/preference/preference/src/androidTest/java/androidx/preference/tests/PreferenceFragmentCompatInterfaceTest.kt
+++ b/preference/preference/src/androidTest/java/androidx/preference/tests/PreferenceFragmentCompatInterfaceTest.kt
@@ -20,11 +20,21 @@
 import android.content.ContextWrapper
 import android.content.Intent
 import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.widget.FrameLayout
 import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentContainerView
+import androidx.fragment.app.commit
 import androidx.preference.EditTextPreference
 import androidx.preference.Preference
 import androidx.preference.PreferenceFragmentCompat
 import androidx.preference.PreferenceScreen
+import androidx.preference.test.R
 import androidx.test.espresso.Espresso.onView
 import androidx.test.espresso.action.ViewActions.click
 import androidx.test.espresso.assertion.ViewAssertions.matches
@@ -32,14 +42,15 @@
 import androidx.test.espresso.matcher.ViewMatchers.withText
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
+import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
 import org.junit.Assert.assertEquals
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
 /**
- * Test for [PreferenceFragmentCompat] interfaces that can be implemented via both [Context] and
- * [android.app.Activity], to ensure that they are called, and only once.
+ * Test for [PreferenceFragmentCompat] interfaces that can be implemented via subclasses,
+ * [Context] and [android.app.Activity], to ensure that they are called, and only once.
  *
  * This test doesn't test the paths including [PreferenceFragmentCompat.getCallbackFragment], as
  * this API is @RestrictTo and we don't expect developers to be using it.
@@ -101,6 +112,16 @@
     }
 
     @Test
+    fun onPreferenceStartFragmentTest_FragmentCallback_childHandlesCall() {
+        verifyCallsWithNestedFragment(booleanArrayOf(true, false))
+    }
+
+    @Test
+    fun onPreferenceStartFragmentTest_FragmentCallback_childDoesNotHandleCall() {
+        verifyCallsWithNestedFragment(booleanArrayOf(true, true))
+    }
+
+    @Test
     fun onPreferenceStartScreenTest_contextCallback() {
         verifyCallsWithContext {
             preferenceManager.createPreferenceScreen(context).apply {
@@ -301,13 +322,67 @@
             assertEquals(expectedActivityCount, activityCount)
         }
     }
+
+    /**
+     * Verify the callback is dispatched to the parent fragment via the fragment hierarchy. Use
+     * [fragmentHandlesCall] to indicates whether or not the
+     * corresponding fragment handles the callback.
+     *
+     * @param fragmentHandlesCall A boolean array indicates whether or not the corresponding
+     * child fragment handles the callback. The first element in the array represents the
+     * parent fragment, and the second element is the child fragment.
+     */
+    private fun verifyCallsWithNestedFragment(
+        fragmentHandlesCall: BooleanArray
+    ) {
+        var parentCount = 0
+        val incrementParentCount: () -> Boolean = {
+            Log.i("DEBUG_TEST", "parent get called ")
+            parentCount++
+            fragmentHandlesCall[0]
+        }
+
+        var childCount = 0
+        val incrementChildCount: () -> Boolean = {
+            Log.i("DEBUG_TEST", "child get called ")
+            childCount++
+            fragmentHandlesCall[1]
+        }
+
+        noInterfaceActivityRule.launchActivity(Intent())
+        noInterfaceActivityRule.run {
+            runOnUiThread {
+                activity.displayPreferenceFragment(
+                    WithInterfaceParentFragment(
+                        incrementParentCount,
+                        incrementChildCount
+                    )
+                )
+            }
+        }
+
+        TestFragment.assertPreferenceIsDisplayed()
+
+        noInterfaceActivityRule.runOnUiThread {
+            assertEquals(0, parentCount)
+            assertEquals(0, childCount)
+        }
+
+        TestFragment.clickOnPreference()
+        noInterfaceActivityRule.runOnUiThread {
+            assertEquals(1, childCount)
+            // If the child fragment doesn't handle the callback, the callback is dispatched to
+            // parent fragment.
+            assertEquals(if (!fragmentHandlesCall[1]) 1 else 0, parentCount)
+        }
+    }
 }
 
 open class NoInterfaceTestActivity : AppCompatActivity() {
     /**
      * Displays the given [fragment] by adding it to a FragmentTransaction
      */
-    fun displayPreferenceFragment(fragment: PreferenceFragmentCompat) {
+    fun displayPreferenceFragment(fragment: Fragment) {
         supportFragmentManager
             .beginTransaction()
             .replace(android.R.id.content, fragment)
@@ -423,3 +498,83 @@
         }
     }
 }
+
+/**
+ * Testing a nested fragment that implements the interface [PreferenceFragmentCompat
+ * .OnPreferenceStartFragmentCallback].
+ *
+ * @property parentTestCallback invoked when an interface method from [PreferenceFragmentCompat
+ * .OnPreferenceStartFragmentCallback] is invoked on parent fragment. Returns true if it handles
+ * the event, false if not.
+ * @property childTestCallback
+ */
+class WithInterfaceParentFragment(
+    private val parentTestCallback: () -> Boolean,
+    private val childTestCallback: () -> Boolean
+) :
+    Fragment(),
+    PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
+
+    class ChildFragment(private val callback: () -> Boolean) :
+        Fragment(),
+        PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
+
+        override fun onCreateView(
+            inflater: LayoutInflater,
+            container: ViewGroup?,
+            savedInstanceState: Bundle?
+        ): View? {
+            val contentView = FrameLayout(inflater.context)
+            contentView.addView(
+                FragmentContainerView(inflater.context).apply {
+                    id = R.id.child_fragment
+                },
+                ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+            )
+
+            if (childFragmentManager.findFragmentById(R.id.child_fragment) == null) {
+                childFragmentManager.commit {
+                    setReorderingAllowed(true)
+                    add(
+                        R.id.child_fragment,
+                        TestFragment(
+                            { Preference(context).apply { fragment = "preference.fragment" } },
+                            { false }
+                        )
+                    )
+                }
+            }
+            return contentView
+        }
+
+        override fun onPreferenceStartFragment(
+            caller: PreferenceFragmentCompat?,
+            pref: Preference?
+        ) = callback()
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        val contentView = FrameLayout(inflater.context)
+        contentView.addView(
+            FragmentContainerView(inflater.context).apply { id = R.id.fragment_container_view },
+            ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+        )
+
+        if (childFragmentManager.findFragmentById(R.id.fragment_container_view) == null) {
+            childFragmentManager.commit {
+                setReorderingAllowed(true)
+                add(R.id.fragment_container_view, ChildFragment(childTestCallback))
+            }
+        }
+        return contentView
+    }
+
+    override fun onPreferenceStartFragment(
+        caller: PreferenceFragmentCompat?,
+        pref: Preference?
+    ) = parentTestCallback()
+}
diff --git a/preference/preference/src/main/java/androidx/preference/PreferenceDialogFragmentCompat.java b/preference/preference/src/main/java/androidx/preference/PreferenceDialogFragmentCompat.java
index 450fbba..fbc7b7f 100644
--- a/preference/preference/src/main/java/androidx/preference/PreferenceDialogFragmentCompat.java
+++ b/preference/preference/src/main/java/androidx/preference/PreferenceDialogFragmentCompat.java
@@ -74,6 +74,7 @@
     /** Which button was clicked. */
     private int mWhichButtonClicked;
 
+    @SuppressWarnings("deprecation")
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -170,6 +171,7 @@
      *
      * @return The {@link DialogPreference} associated with this dialog
      */
+    @SuppressWarnings("deprecation")
     public DialogPreference getPreference() {
         if (mPreference == null) {
             final String key = getArguments().getString(ARG_KEY);
diff --git a/preference/preference/src/main/java/androidx/preference/PreferenceFragmentCompat.java b/preference/preference/src/main/java/androidx/preference/PreferenceFragmentCompat.java
index 5922dfa..cda4118 100644
--- a/preference/preference/src/main/java/androidx/preference/PreferenceFragmentCompat.java
+++ b/preference/preference/src/main/java/androidx/preference/PreferenceFragmentCompat.java
@@ -395,6 +395,7 @@
     /**
      * {@inheritDoc}
      */
+    @SuppressWarnings("deprecation")
     @Override
     public boolean onPreferenceTreeClick(Preference preference) {
         if (preference.getFragment() != null) {
@@ -403,6 +404,17 @@
                 handled = ((OnPreferenceStartFragmentCallback) getCallbackFragment())
                         .onPreferenceStartFragment(this, preference);
             }
+            //  If the callback fragment doesn't handle OnPreferenceStartFragmentCallback, looks up
+            //  its parent fragment in the hierarchy that implements the callback until the first
+            //  one that returns true
+            Fragment callbackFragment = this;
+            while (!handled && callbackFragment != null) {
+                if (callbackFragment instanceof OnPreferenceStartFragmentCallback) {
+                    handled = ((OnPreferenceStartFragmentCallback) callbackFragment)
+                            .onPreferenceStartFragment(this, preference);
+                }
+                callbackFragment = callbackFragment.getParentFragment();
+            }
             if (!handled && getContext() instanceof OnPreferenceStartFragmentCallback) {
                 handled = ((OnPreferenceStartFragmentCallback) getContext())
                         .onPreferenceStartFragment(this, preference);
@@ -587,6 +599,7 @@
      *
      * @param preference The {@link Preference} object requesting the dialog
      */
+    @SuppressWarnings("deprecation")
     @Override
     public void onDisplayPreferenceDialog(Preference preference) {
 
diff --git a/preference/preference/src/main/java/androidx/preference/PreferenceHeaderFragmentCompat.kt b/preference/preference/src/main/java/androidx/preference/PreferenceHeaderFragmentCompat.kt
new file mode 100644
index 0000000..54220cac
--- /dev/null
+++ b/preference/preference/src/main/java/androidx/preference/PreferenceHeaderFragmentCompat.kt
@@ -0,0 +1,314 @@
+/*
+ * 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.preference
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import androidx.activity.OnBackPressedCallback
+import androidx.activity.OnBackPressedDispatcherOwner
+import androidx.annotation.CallSuper
+import androidx.core.view.doOnLayout
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentContainerView
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.FragmentTransaction
+import androidx.fragment.app.commit
+import androidx.slidingpanelayout.widget.SlidingPaneLayout
+
+/**
+ * [PreferenceHeaderFragmentCompat] implements a two-pane fragment for preferences. The list
+ * pane is a container of preference headers. Tapping on a preference header swaps out the fragment
+ * shown in the detail pane. Subclasses are expected to implement [onCreatePreferenceHeader] to
+ * provide your own [PreferenceFragmentCompat] in the list pane. The preference header hierarchy
+ * is defined by either providing an XML resource or build in code through
+ * [PreferenceFragmentCompat]. In both cases, users need to use a [PreferenceScreen] as the root
+ * component in the hierarchy.
+ *
+ * Usage:
+ *
+ * ```
+ * class TwoPanePreference : PreferenceHeaderFragmentCompat() {
+ *     override fun onCreatePreferenceHeader(): PreferenceFragmentCompat {
+ *         return PreferenceHeader()
+ *     }
+ * }
+ * ```
+ *
+ * [PreferenceHeaderFragmentCompat] handles the fragment transaction when users defines a
+ * fragment or intent associated with the preference header. By default, the initial state fragment
+ * for the detail pane is set to the associated fragment that first found in preference
+ * headers. You can override [onCreateInitialDetailFragment] to provide the custom empty state
+ * fragment for the detail pane.
+ */
+abstract class PreferenceHeaderFragmentCompat :
+    Fragment(),
+    PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
+    private var onBackPressedCallback: OnBackPressedCallback? = null
+
+    /**
+     * Return the [SlidingPaneLayout] this fragment is currently controlling.
+     *
+     * @throws IllegalStateException if the SlidingPaneLayout has not been created by [onCreateView]
+     */
+    val slidingPaneLayout: SlidingPaneLayout
+        get() = requireView() as SlidingPaneLayout
+
+    @CallSuper
+    override fun onPreferenceStartFragment(
+        caller: PreferenceFragmentCompat,
+        pref: Preference
+    ): Boolean {
+        if (caller.id == R.id.preferences_header) {
+            // Opens the preference header.
+            openPreference(pref)
+            return true
+        }
+        if (caller.id == R.id.preferences_detail) {
+            // Opens an preference in detail pane.
+            val frag = childFragmentManager.fragmentFactory.instantiate(
+                requireContext().classLoader,
+                pref.fragment
+            )
+            frag.arguments = pref.extras
+
+            childFragmentManager.commit {
+                setReorderingAllowed(true)
+                replace(R.id.preferences_detail, frag)
+                setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
+                addToBackStack(null)
+            }
+            return true
+        }
+        return false
+    }
+
+    private class InnerOnBackPressedCallback(
+        private val caller: PreferenceHeaderFragmentCompat
+    ) :
+        OnBackPressedCallback(true),
+        SlidingPaneLayout.PanelSlideListener {
+
+        init {
+            caller.slidingPaneLayout.addPanelSlideListener(this)
+        }
+
+        override fun handleOnBackPressed() {
+            caller.slidingPaneLayout.closePane()
+        }
+
+        override fun onPanelSlide(panel: View, slideOffset: Float) {}
+
+        override fun onPanelOpened(panel: View) {
+            // Intercept the system back button when the detail pane becomes visible.
+            isEnabled = true
+        }
+
+        override fun onPanelClosed(panel: View) {
+            // Disable intercepting the system back button when the user returns to the list pane.
+            isEnabled = false
+        }
+    }
+
+    @CallSuper
+    override fun onAttach(context: Context) {
+        super.onAttach(context)
+        parentFragmentManager.commit {
+            setPrimaryNavigationFragment(this@PreferenceHeaderFragmentCompat)
+        }
+    }
+
+    @CallSuper
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        val slidingPaneLayout = buildContentView(inflater)
+        // Now create the header fragment
+        val existingHeaderFragment = childFragmentManager.findFragmentById(
+            R.id.preferences_header
+        )
+        if (existingHeaderFragment == null) {
+            onCreatePreferenceHeader().also { newHeaderFragment ->
+                childFragmentManager.commit {
+                    setReorderingAllowed(true)
+                    add(R.id.preferences_header, newHeaderFragment)
+                }
+            }
+        }
+        slidingPaneLayout.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED
+        return slidingPaneLayout
+    }
+
+    private fun buildContentView(inflater: LayoutInflater): SlidingPaneLayout {
+        val slidingPaneLayout = SlidingPaneLayout(inflater.context).apply {
+            id = R.id.preferences_sliding_pane_layout
+        }
+        // Add Preference Header Pane
+        val headerContainer = FragmentContainerView(inflater.context).apply {
+            id = R.id.preferences_header
+        }
+        val headerLayoutParams = SlidingPaneLayout.LayoutParams(
+            resources.getDimensionPixelSize(R.dimen.preferences_header_width),
+            MATCH_PARENT
+        ).apply {
+            weight = resources.getInteger(R.integer.preferences_header_pane_weight).toFloat()
+        }
+        slidingPaneLayout.addView(
+            headerContainer,
+            headerLayoutParams
+        )
+
+        // Add Preference Detail Pane
+        val detailContainer = FragmentContainerView(inflater.context).apply {
+            id = R.id.preferences_detail
+        }
+        val detailLayoutParams = SlidingPaneLayout.LayoutParams(
+            resources.getDimensionPixelSize(R.dimen.preferences_detail_width),
+            MATCH_PARENT
+        ).apply {
+            weight = resources.getInteger(R.integer.preferences_detail_pane_weight).toFloat()
+        }
+        slidingPaneLayout.addView(
+            detailContainer,
+            detailLayoutParams
+        )
+        return slidingPaneLayout
+    }
+
+    /**
+     * Called to supply the preference header for this fragment. The subclasses are expected
+     * to call [setPreferenceScreen(PreferenceScreen)] either directly or via helper methods
+     * such as [setPreferenceFromResource(int)] to set headers.
+     */
+    abstract fun onCreatePreferenceHeader(): PreferenceFragmentCompat
+
+    @CallSuper
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        onBackPressedCallback = InnerOnBackPressedCallback(this)
+        slidingPaneLayout.doOnLayout {
+            onBackPressedCallback!!.isEnabled =
+                slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen
+        }
+        childFragmentManager.addOnBackStackChangedListener {
+            onBackPressedCallback!!.isEnabled = childFragmentManager.backStackEntryCount == 0
+        }
+        val onBackPressedDispatcherOwner = requireContext() as? OnBackPressedDispatcherOwner
+        onBackPressedDispatcherOwner?.let {
+            it.onBackPressedDispatcher.addCallback(
+                viewLifecycleOwner,
+                onBackPressedCallback!!
+            )
+        }
+    }
+
+    override fun onViewStateRestored(savedInstanceState: Bundle?) {
+        super.onViewStateRestored(savedInstanceState)
+        if (savedInstanceState == null) {
+            onCreateInitialDetailFragment()?.let {
+                childFragmentManager.commit {
+                    setReorderingAllowed(true)
+                    replace(R.id.preferences_detail, it)
+                }
+            }
+        }
+    }
+
+    /**
+     * Override this method to set initial detail fragment that to be shown. The default
+     * implementation returns the first preference that has a fragment defined on
+     * it.
+     *
+     * @return Fragment The first fragment that found in the list of preference headers.
+     */
+    open fun onCreateInitialDetailFragment(): Fragment? {
+        val headerFragment = childFragmentManager.findFragmentById(R.id.preferences_header)
+            as PreferenceFragmentCompat
+        if (headerFragment.preferenceScreen.preferenceCount <= 0) {
+            return null
+        }
+        for (index in 0 until headerFragment.preferenceScreen.preferenceCount) {
+            val header = headerFragment.preferenceScreen.getPreference(index)
+            if (header.fragment == null) {
+                continue
+            }
+            val fragment = header.fragment?.let {
+                childFragmentManager.fragmentFactory.instantiate(
+                    requireContext().classLoader,
+                    it
+                )
+            }
+            return fragment
+        }
+        return null
+    }
+
+    /**
+     * Swaps out the fragment that associated with preference header. If associated fragment is
+     * unspecified, open the preference with the given intent instead.
+     *
+     * @param header The preference header that selected
+     */
+    fun openPreference(header: Preference) {
+        if (header.fragment == null) {
+            openPreference(header.intent)
+            return
+        }
+        val fragment = header.fragment?.let {
+            childFragmentManager.fragmentFactory.instantiate(
+                requireContext().classLoader,
+                it
+            )
+        }
+
+        fragment?.apply {
+            arguments = header.extras
+        }
+
+        // Clear back stack
+        if (childFragmentManager.backStackEntryCount > 0) {
+            val entry = childFragmentManager.getBackStackEntryAt(0)
+            childFragmentManager.popBackStack(entry.id, FragmentManager.POP_BACK_STACK_INCLUSIVE)
+        }
+
+        childFragmentManager.commit {
+            setReorderingAllowed(true)
+            replace(R.id.preferences_detail, fragment!!)
+            if (slidingPaneLayout.isOpen) {
+                setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
+            }
+            slidingPaneLayout.openPane()
+        }
+    }
+
+    /**
+     * Open preference with the given intent
+     *
+     * @param intent The intent that associated with preference header
+     */
+    private fun openPreference(intent: Intent?) {
+        if (intent == null) return
+        // TODO: Change to use WindowManager ActivityView API
+        startActivity(intent)
+    }
+}
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XAnnotation.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XAnnotation.kt
index 1787f38..7a4dc1b 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XAnnotation.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XAnnotation.kt
@@ -47,40 +47,92 @@
      */
     val type: XType
 
-    /**
-     * All values declared in the annotation class.
-     */
+    /** All values declared in the annotation class. */
     val annotationValues: List<XAnnotationValue>
 
-    /**
-     * Returns the value of the given [methodName] as a type reference.
-     */
-    fun getAsType(methodName: String): XType = get(methodName)
+    /** Returns the value of the given [methodName] as a type reference. */
+    fun getAsType(methodName: String): XType = getAnnotationValue(methodName).asType()
 
-    /**
-     * Returns the value of the given [methodName] as a list of type references.
-     */
-    fun getAsTypeList(methodName: String): List<XType> = get(methodName)
+    /** Returns the value of the given [methodName] as a list of type references. */
+    fun getAsTypeList(methodName: String): List<XType> = getAnnotationValue(methodName).asTypeList()
 
-    /**
-     * Returns the value of the given [methodName] as another [XAnnotation].
-     */
-    fun getAsAnnotation(methodName: String): XAnnotation = get(methodName)
+    /** Returns the value of the given [methodName] as another [XAnnotation]. */
+    fun getAsAnnotation(methodName: String): XAnnotation =
+        getAnnotationValue(methodName).asAnnotation()
 
-    /**
-     * Returns the value of the given [methodName] as a list of [XAnnotation].
-     */
-    fun getAsAnnotationList(methodName: String): List<XAnnotation> = get(methodName)
+    /** Returns the value of the given [methodName] as a list of [XAnnotation]. */
+    fun getAsAnnotationList(methodName: String): List<XAnnotation> =
+        getAnnotationValue(methodName).asAnnotationList()
 
-    /**
-     * Returns the value of the given [methodName] as a [XEnumEntry].
-     */
-    fun getAsEnum(methodName: String): XEnumEntry = get(methodName)
+    /** Returns the value of the given [methodName] as a [XEnumEntry]. */
+    fun getAsEnum(methodName: String): XEnumEntry = getAnnotationValue(methodName).asEnum()
 
-    /**
-     * Returns the value of the given [methodName] as a list of [XEnumEntry].
-     */
-    fun getAsEnumList(methodName: String): List<XEnumEntry> = get(methodName)
+    /** Returns the value of the given [methodName] as a list of [XEnumEntry]. */
+    fun getAsEnumList(methodName: String): List<XEnumEntry> =
+        getAnnotationValue(methodName).asEnumList()
+
+    /** Returns the value of the given [methodName] as a [Boolean]. */
+    fun getAsBoolean(methodName: String): Boolean = getAnnotationValue(methodName).asBoolean()
+
+    /** Returns the value of the given [methodName] as a list of [Boolean]. */
+    fun getAsBooleanList(methodName: String): List<Boolean> =
+        getAnnotationValue(methodName).asBooleanList()
+
+    /** Returns the value of the given [methodName] as a [String]. */
+    fun getAsString(methodName: String): String = getAnnotationValue(methodName).asString()
+
+    /** Returns the value of the given [methodName] as a list of [String]. */
+    fun getAsStringList(methodName: String): List<String> =
+        getAnnotationValue(methodName).asStringList()
+
+    /** Returns the value of the given [methodName] as a [Int]. */
+    fun getAsInt(methodName: String): Int = getAnnotationValue(methodName).asInt()
+
+    /** Returns the value of the given [methodName] as a list of [Int]. */
+    fun getAsIntList(methodName: String): List<Int> = getAnnotationValue(methodName).asIntList()
+
+    /** Returns the value of the given [methodName] as a [Long]. */
+    fun getAsLong(methodName: String): Long = getAnnotationValue(methodName).asLong()
+
+    /** Returns the value of the given [methodName] as a list of [Long]. */
+    fun getAsLongList(methodName: String): List<Long> = getAnnotationValue(methodName).asLongList()
+
+    /** Returns the value of the given [methodName] as a [Short]. */
+    fun getAsShort(methodName: String): Short = getAnnotationValue(methodName).asShort()
+
+    /** Returns the value of the given [methodName] as a list of [Short]. */
+    fun getAsShortList(methodName: String): List<Short> =
+        getAnnotationValue(methodName).asShortList()
+
+    /** Returns the value of the given [methodName] as a [Float]. */
+    fun getAsFloat(methodName: String): Float = getAnnotationValue(methodName).asFloat()
+
+    /** Returns the value of the given [methodName] as a list of [Float]. */
+    fun getAsFloatList(methodName: String): List<Float> =
+        getAnnotationValue(methodName).asFloatList()
+
+    /** Returns the value of the given [methodName] as a [Double]. */
+    fun getAsDouble(methodName: String): Double = getAnnotationValue(methodName).asDouble()
+
+    /** Returns the value of the given [methodName] as a list of [Double]. */
+    fun getAsDoubleList(methodName: String): List<Double> =
+        getAnnotationValue(methodName).asDoubleList()
+
+    /** Returns the value of the given [methodName] as a [Byte]. */
+    fun getAsByte(methodName: String): Byte = getAnnotationValue(methodName).asByte()
+
+    /** Returns the value of the given [methodName] as a list of [Byte]. */
+    fun getAsByteList(methodName: String): List<Byte> = getAnnotationValue(methodName).asByteList()
+
+    /** Returns the value of the given [methodName] as a list of [Byte]. */
+    fun getAsAnnotationValueList(methodName: String): List<XAnnotationValue> =
+        getAnnotationValue(methodName).asAnnotationValueList()
+
+    /**Returns the value of the given [methodName] as a [XAnnotationValue]. */
+    fun getAnnotationValue(methodName: String): XAnnotationValue {
+        return annotationValues.firstOrNull { it.name == methodName }
+            ?: error("No property named $methodName was found in annotation $name")
+    }
 }
 
 /**
@@ -95,15 +147,9 @@
  *
  * For convenience, wrapper functions are provided for these types, eg [XAnnotation.getAsType]
  */
-inline fun <reified T> XAnnotation.get(methodName: String): T {
-    val argument = annotationValues.firstOrNull { it.name == methodName }
-        ?: error("No property named $methodName was found in annotation $name")
-
-    return argument.value as? T ?: error(
-        "Value of $methodName of type ${argument.value?.javaClass} " +
-            "cannot be cast to ${T::class.java}"
-    )
-}
+// TODO: Consider deprecating this method for getAs*() methods
+@Suppress("DEPRECATION")
+inline fun <reified T> XAnnotation.get(methodName: String): T = get(methodName, T::class.java)
 
 /**
  * Returns the value of the given [methodName], throwing an exception if the method is not
@@ -119,16 +165,23 @@
  *
  * For convenience, wrapper functions are provided for these types, eg [XAnnotation.getAsType]
  */
+@Deprecated("Use one of the getAs*() methods instead, e.g. getAsBoolean().")
 fun <T> XAnnotation.get(methodName: String, clazz: Class<T>): T {
-    val argument = annotationValues.firstOrNull { it.name == methodName }
-        ?: error("No property named $methodName was found in annotation $name")
+    val argument = getAnnotationValue(methodName)
 
-    if (!clazz.isInstance(argument.value)) {
-        error("Value of $methodName of type ${argument.value?.javaClass} cannot be cast to $clazz")
+    val value = if (argument.value is List<*>) {
+        // If the argument is for a list, unwrap each item in the list
+        (argument.value as List<*>).map { (it as XAnnotationValue).value }
+    } else {
+        argument.value
+    }
+
+    if (!clazz.isInstance(value)) {
+        error("Value of $methodName of type ${value?.javaClass} cannot be cast to $clazz")
     }
 
     @Suppress("UNCHECKED_CAST")
-    return argument.value as T
+    return value as T
 }
 
 /**
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XAnnotationValue.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XAnnotationValue.kt
index c3c6d34..ffd039b 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XAnnotationValue.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XAnnotationValue.kt
@@ -34,7 +34,77 @@
      * - XEnumEntry
      * - XAnnotation
      * - XType
-     * - List of any of the above
+     * - List of [XAnnotationValue]
      */
     val value: Any?
-}
+
+    /** Returns the value as a [XType]. */
+    fun asType(): XType = value as XType
+
+    /** Returns the value as a list of [XType]. */
+    fun asTypeList(): List<XType> = asAnnotationValueList().map { it.asType() }
+
+    /** Returns the value as another [XAnnotation]. */
+    fun asAnnotation(): XAnnotation = value as XAnnotation
+
+    /** Returns the value as a list of [XAnnotation]. */
+    fun asAnnotationList(): List<XAnnotation> = asAnnotationValueList().map { it.asAnnotation() }
+
+    /** Returns the value as a [XEnumEntry]. */
+    fun asEnum(): XEnumEntry = value as XEnumEntry
+
+    /** Returns the value as a list of [XEnumEntry]. */
+    fun asEnumList(): List<XEnumEntry> = asAnnotationValueList().map { it.asEnum() }
+
+    /** Returns the value as a [Boolean]. */
+    fun asBoolean(): Boolean = value as Boolean
+
+    /** Returns the value as a list of [Boolean]. */
+    fun asBooleanList(): List<Boolean> = asAnnotationValueList().map { it.asBoolean() }
+
+    /** Returns the value as a [String]. */
+    fun asString(): String = value as String
+
+    /** Returns the value as a list of [String]. */
+    fun asStringList(): List<String> = asAnnotationValueList().map { it.asString() }
+
+    /** Returns the value as a [Int]. */
+    fun asInt(): Int = value as Int
+
+    /** Returns the value as a list of [Int]. */
+    fun asIntList(): List<Int> = asAnnotationValueList().map { it.asInt() }
+
+    /** Returns the value as a [Long]. */
+    fun asLong(): Long = value as Long
+
+    /** Returns the value as a list of [Long]. */
+    fun asLongList(): List<Long> = asAnnotationValueList().map { it.asLong() }
+
+    /** Returns the value as a [Short]. */
+    fun asShort(): Short = value as Short
+
+    /** Returns the value as a list of [Short]. */
+    fun asShortList(): List<Short> = asAnnotationValueList().map { it.asShort() }
+
+    /** Returns the value as a [Float]. */
+    fun asFloat(): Float = value as Float
+
+    /** Returns the value as a list of [Float]. */
+    fun asFloatList(): List<Float> = asAnnotationValueList().map { it.value as Float }
+
+    /** Returns the value as a [Double]. */
+    fun asDouble(): Double = value as Double
+
+    /** Returns the value as a list of [Double]. */
+    fun asDoubleList(): List<Double> = asAnnotationValueList().map { it.asDouble() }
+
+    /** Returns the value as a [Byte]. */
+    fun asByte(): Byte = value as Byte
+
+    /** Returns the value as a list of [Byte]. */
+    fun asByteList(): List<Byte> = asAnnotationValueList().map { it.asByte() }
+
+    /**Returns the value a list of [XAnnotationValue]. */
+    @Suppress("UNCHECKED_CAST") // Values in a list are always wrapped in XAnnotationValue
+    fun asAnnotationValueList(): List<XAnnotationValue> = value as List<XAnnotationValue>
+}
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacAnnotationValue.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacAnnotationValue.kt
index 8767264..7a3d2b2 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacAnnotationValue.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacAnnotationValue.kt
@@ -30,55 +30,60 @@
 
 internal class JavacAnnotationValue(
     val env: JavacProcessingEnv,
-    val element: ExecutableElement,
-    val annotationValue: AnnotationValue
+    val method: ExecutableElement,
+    val annotationValue: AnnotationValue,
+    private val valueProvider: () -> Any? = {
+        UNWRAP_VISITOR.visit(annotationValue, VisitorData(env, method))
+    }
 ) : XAnnotationValue {
     override val name: String
-        get() = element.simpleName.toString()
+        get() = method.simpleName.toString()
 
-    override val value: Any? by lazy { UNWRAP_VISITOR.visit(annotationValue, env) }
+    override val value: Any? by lazy { valueProvider.invoke() }
 }
 
-private val UNWRAP_VISITOR = object : AbstractAnnotationValueVisitor8<Any?, JavacProcessingEnv>() {
-    override fun visitBoolean(b: Boolean, env: JavacProcessingEnv) = b
-    override fun visitByte(b: Byte, env: JavacProcessingEnv) = b
-    override fun visitChar(c: Char, env: JavacProcessingEnv) = c
-    override fun visitDouble(d: Double, env: JavacProcessingEnv) = d
-    override fun visitFloat(f: Float, env: JavacProcessingEnv) = f
-    override fun visitInt(i: Int, env: JavacProcessingEnv) = i
-    override fun visitLong(i: Long, env: JavacProcessingEnv) = i
-    override fun visitShort(s: Short, env: JavacProcessingEnv) = s
+private data class VisitorData(val env: JavacProcessingEnv, val method: ExecutableElement)
 
-    override fun visitString(s: String?, env: JavacProcessingEnv) =
+private val UNWRAP_VISITOR = object : AbstractAnnotationValueVisitor8<Any?, VisitorData>() {
+    override fun visitBoolean(b: Boolean, data: VisitorData) = b
+    override fun visitByte(b: Byte, data: VisitorData) = b
+    override fun visitChar(c: Char, data: VisitorData) = c
+    override fun visitDouble(d: Double, data: VisitorData) = d
+    override fun visitFloat(f: Float, data: VisitorData) = f
+    override fun visitInt(i: Int, data: VisitorData) = i
+    override fun visitLong(i: Long, data: VisitorData) = i
+    override fun visitShort(s: Short, data: VisitorData) = s
+
+    override fun visitString(s: String?, data: VisitorData) =
         if (s == "<error>") {
             throw TypeNotPresentException(s, null)
         } else {
             s
         }
 
-    override fun visitType(t: TypeMirror, env: JavacProcessingEnv): JavacType {
+    override fun visitType(t: TypeMirror, data: VisitorData): JavacType {
         if (t.kind == TypeKind.ERROR) {
             throw TypeNotPresentException(t.toString(), null)
         }
-        return env.wrap(t, kotlinType = null, XNullability.NONNULL)
+        return data.env.wrap(t, kotlinType = null, XNullability.NONNULL)
     }
 
-    override fun visitEnumConstant(c: VariableElement, env: JavacProcessingEnv): JavacEnumEntry {
+    override fun visitEnumConstant(c: VariableElement, data: VisitorData): JavacEnumEntry {
         val type = c.asType()
         if (type.kind == TypeKind.ERROR) {
             throw TypeNotPresentException(type.toString(), null)
         }
         val enumTypeElement = MoreTypes.asTypeElement(type)
         return JavacEnumEntry(
-            env = env,
+            env = data.env,
             entryElement = c,
-            enumTypeElement = JavacTypeElement.create(env, enumTypeElement) as XEnumTypeElement
+            enumTypeElement = JavacTypeElement.create(data.env, enumTypeElement) as XEnumTypeElement
         )
     }
 
-    override fun visitAnnotation(a: AnnotationMirror, env: JavacProcessingEnv) =
-        JavacAnnotation(env, a)
+    override fun visitAnnotation(a: AnnotationMirror, data: VisitorData) =
+        JavacAnnotation(data.env, a)
 
-    override fun visitArray(vals: MutableList<out AnnotationValue>, env: JavacProcessingEnv) =
-        vals.map { it.accept(this, env) }
+    override fun visitArray(vals: MutableList<out AnnotationValue>, data: VisitorData) =
+        vals.map { JavacAnnotationValue(data.env, data.method, it) { it.accept(this, data) } }
 }
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotation.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotation.kt
index 122b1b7..bb707eb 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotation.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotation.kt
@@ -20,10 +20,7 @@
 import androidx.room.compiler.processing.XAnnotationBox
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.XAnnotationValue
-import androidx.room.compiler.processing.isArray
-import com.google.devtools.ksp.getConstructors
 import com.google.devtools.ksp.symbol.KSAnnotation
-import com.google.devtools.ksp.symbol.KSClassDeclaration
 import com.google.devtools.ksp.symbol.KSType
 
 internal class KspAnnotation(
@@ -44,18 +41,7 @@
     }
 
     override val annotationValues: List<XAnnotationValue> by lazy {
-        ksAnnotated.arguments.map { arg ->
-            KspAnnotationValue(
-                env, arg,
-                isListType = {
-                    (ksType.declaration as KSClassDeclaration).getConstructors()
-                        .singleOrNull()
-                        ?.parameters
-                        ?.firstOrNull { it.name == arg.name }
-                        ?.let { env.wrap(it.type).isArray() } == true
-                }
-            )
-        }
+        ksAnnotated.arguments.map { KspAnnotationValue(env, this, it) }
     }
 
     override fun <T : Annotation> asAnnotationBox(annotationClass: Class<T>): XAnnotationBox<T> {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotationValue.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotationValue.kt
index 7fd29c2..9959811 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotationValue.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotationValue.kt
@@ -17,7 +17,9 @@
 package androidx.room.compiler.processing.ksp
 
 import androidx.room.compiler.processing.XAnnotationValue
+import androidx.room.compiler.processing.isArray
 import com.google.devtools.ksp.getClassDeclarationByName
+import com.google.devtools.ksp.getConstructors
 import com.google.devtools.ksp.symbol.ClassKind
 import com.google.devtools.ksp.symbol.KSAnnotation
 import com.google.devtools.ksp.symbol.KSClassDeclaration
@@ -26,64 +28,80 @@
 
 internal class KspAnnotationValue(
     val env: KspProcessingEnv,
+    private val owner: KspAnnotation,
     val valueArgument: KSValueArgument,
-    val isListType: () -> Boolean,
+    private val valueProvider: () -> Any? = { owner.unwrap(valueArgument) },
 ) : XAnnotationValue {
 
     override val name: String
         get() = valueArgument.name?.asString()
             ?: error("Value argument $this does not have a name.")
 
-    override val value: Any? by lazy { valueArgument.unwrap() }
+    override val value: Any? by lazy { valueProvider.invoke() }
+}
 
-    private fun KSValueArgument.unwrap(): Any? {
-        fun unwrap(value: Any?): Any? {
-            return when (value) {
-                is KSType -> {
-                    val declaration = value.declaration
-                    // Wrap enum entries in enum specific type elements
-                    if (declaration is KSClassDeclaration &&
-                        declaration.classKind == ClassKind.ENUM_ENTRY
-                    ) {
-                        KspEnumEntry.create(env, declaration)
-                    } else {
-                        // And otherwise represent class types as generic XType
-                        env.wrap(value, allowPrimitives = true)
-                    }
+internal fun KspAnnotation.unwrap(valueArgument: KSValueArgument): Any? {
+    fun unwrap(value: Any?): Any? {
+        return when (value) {
+            is KSType -> {
+                val declaration = value.declaration
+                // Wrap enum entries in enum specific type elements
+                if (declaration is KSClassDeclaration &&
+                    declaration.classKind == ClassKind.ENUM_ENTRY
+                ) {
+                    KspEnumEntry.create(env, declaration)
+                } else {
+                    // And otherwise represent class types as generic XType
+                    env.wrap(value, allowPrimitives = true)
                 }
-                is KSAnnotation -> KspAnnotation(env, value)
-                // The List implementation further wraps each value as a AnnotationValue.
-                // We don't use arrays because we don't have a reified type to instantiate the array
-                // with, and using "Any" prevents the array from being cast to the correct
-                // type later on.
-                is List<*> -> value.map { unwrap(it) }
-                // TODO: https://github.com/google/ksp/issues/429
-                // If the enum value is from compiled code KSP gives us the actual value an not
-                // the KSType, so we wrap it as KspEnumEntry for consistency.
-                is Enum<*> -> {
-                    val declaration =
-                        env.resolver.getClassDeclarationByName(value::class.java.canonicalName)
-                            ?: error("Cannot find KSClassDeclaration for Enum '$value'.")
-                    val valueDeclaration = declaration.declarations
-                        .filterIsInstance<KSClassDeclaration>()
-                        .filter { it.classKind == ClassKind.ENUM_ENTRY }
-                        .firstOrNull() { it.simpleName.getShortName() == value.name }
-                        ?: error("Cannot find ENUM_ENTRY '$value' in '$declaration'.")
-                    KspEnumEntry.create(env, valueDeclaration)
-                }
-                else -> value
             }
-        }
-        return unwrap(value).let { result ->
-            // TODO: 5/24/21 KSP does not wrap a single item in a list, even though the
-            // return type should be Class<?>[] (only in sources).
-            // https://github.com/google/ksp/issues/172
-            // https://github.com/google/ksp/issues/214
-            if (result !is List<*> && isListType()) {
-                listOf(result)
-            } else {
-                result
+            is KSAnnotation -> KspAnnotation(env, value)
+            // The List implementation further wraps each value as a AnnotationValue.
+            // We don't use arrays because we don't have a reified type to instantiate the array
+            // with, and using "Any" prevents the array from being cast to the correct
+            // type later on.
+            is List<*> -> value.map { unwrap(it) }
+            // TODO: https://github.com/google/ksp/issues/429
+            // If the enum value is from compiled code KSP gives us the actual value an not
+            // the KSType, so we wrap it as KspEnumEntry for consistency.
+            is Enum<*> -> {
+                val declaration =
+                    env.resolver.getClassDeclarationByName(value::class.java.canonicalName)
+                        ?: error("Cannot find KSClassDeclaration for Enum '$value'.")
+                val valueDeclaration = declaration.declarations
+                    .filterIsInstance<KSClassDeclaration>()
+                    .filter { it.classKind == ClassKind.ENUM_ENTRY }
+                    .firstOrNull() { it.simpleName.getShortName() == value.name }
+                    ?: error("Cannot find ENUM_ENTRY '$value' in '$declaration'.")
+                KspEnumEntry.create(env, valueDeclaration)
             }
+            else -> value
         }
     }
+    return unwrap(valueArgument.value).let { result ->
+        when {
+            result is List<*> -> {
+                // For lists, wrap each item in a KSPAnnotationValue. This models things similar to
+                // javac, and allows us to report errors on each individual item rather than just
+                // the list itself.
+                result.map { KspAnnotationValue(env, this, valueArgument) { it } }
+            }
+            isArrayType(valueArgument) -> {
+                // TODO: 5/24/21 KSP does not wrap a single item in a list, even though the
+                // return type should be Class<?>[] (only in sources).
+                // https://github.com/google/ksp/issues/172
+                // https://github.com/google/ksp/issues/214
+                listOf(KspAnnotationValue(env, this, valueArgument) { result })
+            }
+            else -> result
+        }
+    }
+}
+
+private fun KspAnnotation.isArrayType(arg: KSValueArgument): Boolean {
+    return (ksType.declaration as KSClassDeclaration).getConstructors()
+        .singleOrNull()
+        ?.parameters
+        ?.firstOrNull { it.name == arg.name }
+        ?.let { env.wrap(it.type).isArray() } == true
 }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMessager.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMessager.kt
index c03ee6a..d5d6147 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMessager.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMessager.kt
@@ -51,6 +51,11 @@
             // pick first node with a location, if possible
             it.location != NonExistLocation
         } ?: nodes.firstOrNull() // fallback to the first non-null argument
+
+        // TODO: 10/8/21 Consider checking if the KspAnnotationValue is for an item in a value's
+        //  list and adding that information to the error message if so (currently, we have to
+        //  report an error on the list itself, so the information about the particular item is
+        //  lost). https://github.com/google/ksp/issues/656
         if (ksNode == null || ksNode.location == NonExistLocation) {
             internalPrintMessage(kind, "$msg - ${element.fallbackLocationText}", ksNode)
         } else {
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
index 6fd9043..de97611 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
@@ -163,10 +163,10 @@
             val element = invocation.processingEnv.requireTypeElement("foo.bar.Baz")
             val annotation = element.requireAnnotation<TestSuppressWarnings>()
 
-            val argument = annotation.annotationValues.single()
+            val argument = annotation.getAnnotationValue("value")
             assertThat(argument.name).isEqualTo("value")
             assertThat(
-                argument.value
+                argument.asStringList()
             ).isEqualTo(
                 listOf("warning1", "warning 2")
             )
@@ -192,10 +192,10 @@
             val annotation =
                 element.requireAnnotation(ClassName.get(TestSuppressWarnings::class.java))
 
-            val argument = annotation.annotationValues.single()
+            val argument = annotation.getAnnotationValue("value")
             assertThat(argument.name).isEqualTo("value")
             assertThat(
-                argument.value
+                argument.asStringList()
             ).isEqualTo(
                 listOf("warning1", "warning 2")
             )
@@ -857,10 +857,12 @@
             val annotation =
                 element.requireAnnotation(ClassName.get(JavaAnnotationWithDefaults::class.java))
 
-            // Even though not necessary for calling from Kotlin, use the version that passes
-            // in a Class to test it.
-            assertThat(annotation.get("stringVal", String::class.java)).isEqualTo("test")
-            assertThat(annotation.get("intVal", Int::class.javaObjectType)).isEqualTo(3)
+            assertThat(annotation.get<String>("stringVal")).isEqualTo("test")
+            assertThat(annotation.get<Int>("intVal")).isEqualTo(3)
+
+            // Also test reading theses values through getAs*() methods
+            assertThat(annotation.getAsString("stringVal")).isEqualTo("test")
+            assertThat(annotation.getAsInt("intVal")).isEqualTo(3)
         }
     }
 
diff --git a/samples/SupportPreferenceDemos/build.gradle b/samples/SupportPreferenceDemos/build.gradle
index 14670ed..3c25b24 100644
--- a/samples/SupportPreferenceDemos/build.gradle
+++ b/samples/SupportPreferenceDemos/build.gradle
@@ -1,12 +1,16 @@
 plugins {
     id("AndroidXPlugin")
     id("com.android.application")
+    id("kotlin-android")
 }
 
 dependencies {
+    implementation(libs.kotlinStdlib)
+    implementation("androidx.core:core-ktx:1.5.0")
     implementation(project(":appcompat:appcompat"))
     implementation(project(":preference:preference"))
+    implementation(project(":preference:preference-ktx"))
     implementation(project(":recyclerview:recyclerview"))
     implementation(project(":leanback:leanback"))
     implementation(project(":leanback:leanback-preference"))
-}
+}
\ No newline at end of file
diff --git a/samples/SupportPreferenceDemos/src/main/AndroidManifest.xml b/samples/SupportPreferenceDemos/src/main/AndroidManifest.xml
index f25a041..1c0a55da 100644
--- a/samples/SupportPreferenceDemos/src/main/AndroidManifest.xml
+++ b/samples/SupportPreferenceDemos/src/main/AndroidManifest.xml
@@ -67,5 +67,15 @@
             </intent-filter>
         </activity>
 
+        <activity
+            android:name="TwoPanePreferences"
+            android:label="@string/twopane_preferences"
+            android:theme="@style/PreferenceTheme"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="com.example.androidx.preference.SAMPLE_CODE"/>
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
diff --git a/samples/SupportPreferenceDemos/src/main/java/com/example/androidx/preference/MainActivity.java b/samples/SupportPreferenceDemos/src/main/java/com/example/androidx/preference/MainActivity.java
index e6aba68..05aaf32 100644
--- a/samples/SupportPreferenceDemos/src/main/java/com/example/androidx/preference/MainActivity.java
+++ b/samples/SupportPreferenceDemos/src/main/java/com/example/androidx/preference/MainActivity.java
@@ -24,6 +24,8 @@
 import android.widget.ListView;
 import android.widget.SimpleAdapter;
 
+import androidx.annotation.NonNull;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -39,7 +41,7 @@
     private static final String NAME = "name";
 
     @Override
-    public void onCreate(Bundle savedInstanceState) {
+    public void onCreate(@NonNull Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         SimpleAdapter adapter = new SimpleAdapter(this, getActivityList(),
                 android.R.layout.simple_list_item_1, new String[]{NAME},
@@ -49,12 +51,13 @@
 
     @Override
     @SuppressWarnings("unchecked")
-    protected void onListItemClick(ListView l, View v, int position, long id) {
+    protected void onListItemClick(@NonNull ListView l, @NonNull View v, int position, long id) {
         Map<String, Object> map = (Map<String, Object>) l.getItemAtPosition(position);
         Intent intent = (Intent) map.get(INTENT);
         startActivity(intent);
     }
 
+    @NonNull
     protected List<Map<String, Object>> getActivityList() {
         List<Map<String, Object>> activityList = new ArrayList<>();
 
@@ -63,7 +66,6 @@
 
         PackageManager pm = getPackageManager();
         List<ResolveInfo> list = pm.queryIntentActivities(mainIntent, 0);
-
         for (int i = 0; i < list.size(); i++) {
             ResolveInfo info = list.get(i);
             String label = info.loadLabel(pm).toString();
diff --git a/samples/SupportPreferenceDemos/src/main/java/com/example/androidx/preference/TwoPanePreferences.kt b/samples/SupportPreferenceDemos/src/main/java/com/example/androidx/preference/TwoPanePreferences.kt
new file mode 100644
index 0000000..724783e
--- /dev/null
+++ b/samples/SupportPreferenceDemos/src/main/java/com/example/androidx/preference/TwoPanePreferences.kt
@@ -0,0 +1,80 @@
+/*
+ * 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 com.example.androidx.preference
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.PreferenceHeaderFragmentCompat
+
+class TwoPanePreferences : AppCompatActivity() {
+
+    class PreferenceHeaderFragmentCompatImpl : PreferenceHeaderFragmentCompat() {
+
+        class PreferenceHeader : PreferenceFragmentCompat() {
+            override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+                setPreferencesFromResource(R.xml.preference_headers, rootKey)
+            }
+        }
+
+        override fun onCreatePreferenceHeader(): PreferenceFragmentCompat {
+            return PreferenceHeader()
+        }
+    }
+
+    class BasicPreferences : PreferenceFragmentCompat() {
+        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+            setPreferencesFromResource(R.xml.preferences_basic, rootKey)
+        }
+    }
+
+    class WidgetPreferences : PreferenceFragmentCompat() {
+        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+            setPreferencesFromResource(R.xml.preferences_widget, rootKey)
+        }
+    }
+
+    class DialogPreferences : PreferenceFragmentCompat() {
+        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+            setPreferencesFromResource(R.xml.preferences_dialog, rootKey)
+        }
+    }
+
+    class AdvancedPreferences : PreferenceFragmentCompat() {
+        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+            setPreferencesFromResource(R.xml.preferences_advanced, rootKey)
+        }
+    }
+
+    class MultiPreferences : PreferenceFragmentCompat() {
+        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+            setPreferencesFromResource(R.xml.preferences_advanced_next, rootKey)
+        }
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        // Display preference header fragment as the main content
+        if (savedInstanceState == null) {
+            supportFragmentManager.beginTransaction().replace(
+                android.R.id.content,
+                PreferenceHeaderFragmentCompatImpl()
+            ).commit()
+        }
+    }
+}
\ No newline at end of file
diff --git a/samples/SupportPreferenceDemos/src/main/res/values/strings.xml b/samples/SupportPreferenceDemos/src/main/res/values/strings.xml
index e4b9708..acfe9b8 100644
--- a/samples/SupportPreferenceDemos/src/main/res/values/strings.xml
+++ b/samples/SupportPreferenceDemos/src/main/res/values/strings.xml
@@ -22,6 +22,7 @@
     <!--Fragment titles-->
     <string name="preferences">Preferences</string>
     <string name="leanback_preferences">Leanback Preferences</string>
+    <string name="twopane_preferences">TwoPane Preferences</string>
 
     <!--This section is for basic attributes -->
     <string name="basic_preferences">Basic attributes</string>
@@ -85,4 +86,18 @@
 
     <string name="title_copyable_preference">Copyable preference</string>
     <string name="summary_copyable_preference">Long press on this preference to copy its summary</string>
+    <string name="summary_dialogs">Dialog preferences</string>
+    <string name="summary_basic_preferences">Basic preferences</string>
+    <string name="summary_widgets">widgets</string>
+    <string name="summary_advanced_attributes">Advanced attributes of preferences</string>
+
+    <string name="multi_preference_screen">Multi Preference Screens</string>
+    <string name="title_multi_preference_screen">Next Preference Screen</string>
+    <string name="summary_multi_preference_screen">Click to launch another preference screen</string>
+
+    <string name="title_next_preference_screen">Next Preference Screen</string>
+    <string name="title_information">Basic Information</string>
+    <string name="summary_information">Basic Information about the demo app</string>
+    <string name="title_details">Details</string>
+    <string name="summary_details">Detailed Information about the demo app</string>
 </resources>
diff --git a/samples/SupportPreferenceDemos/src/main/res/xml/preference_headers.xml b/samples/SupportPreferenceDemos/src/main/res/xml/preference_headers.xml
new file mode 100644
index 0000000..bd37919
--- /dev/null
+++ b/samples/SupportPreferenceDemos/src/main/res/xml/preference_headers.xml
@@ -0,0 +1,51 @@
+<?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.
+  -->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <Preference
+        android:key="basic"
+        android:fragment="com.example.androidx.preference.TwoPanePreferences$BasicPreferences"
+        android:summary="@string/summary_basic_preferences"
+        android:title="@string/basic_preferences"
+        android:icon="@android:drawable/sym_def_app_icon"/>
+    <SwitchPreferenceCompat
+        android:key="switch"
+        android:title="@string/title_switch_preference"
+        android:summary="@string/summary_switch_preference"/>
+    <Preference
+        android:key="widgets"
+        android:fragment="com.example.androidx.preference.TwoPanePreferences$WidgetPreferences"
+        android:summary="@string/summary_widgets"
+        android:title="@string/widgets"/>
+    <Preference
+        android:key="dialogs"
+        android:fragment="com.example.androidx.preference.TwoPanePreferences$DialogPreferences"
+        android:summary="@string/summary_dialogs"
+        android:title="@string/dialogs" />
+    <Preference
+        android:key="advanced"
+        android:fragment="com.example.androidx.preference.TwoPanePreferences$AdvancedPreferences"
+        android:summary="@string/summary_advanced_attributes"
+        android:title="@string/advanced_attributes" />
+    <Preference
+        android:key="intent"
+        android:title="@string/title_intent_preference"
+        android:summary="@string/summary_intent_preference">
+        <intent android:action="android.intent.action.VIEW"
+            android:data="http://www.android.com"/>
+    </Preference>
+</PreferenceScreen>
\ No newline at end of file
diff --git a/samples/SupportPreferenceDemos/src/main/res/xml/preferences_advanced.xml b/samples/SupportPreferenceDemos/src/main/res/xml/preferences_advanced.xml
new file mode 100644
index 0000000..a16bb93
--- /dev/null
+++ b/samples/SupportPreferenceDemos/src/main/res/xml/preferences_advanced.xml
@@ -0,0 +1,70 @@
+<?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.
+  -->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <PreferenceCategory
+        android:key="advanced"
+        android:title="@string/advanced_attributes"
+        app:initialExpandedChildrenCount="1">
+
+        <Preference
+            android:key="expandable"
+            android:title="@string/title_expandable_preference"
+            android:summary="@string/summary_expandable_preference"/>
+
+        <Preference
+            android:title="@string/title_intent_preference"
+            android:summary="@string/summary_intent_preference">
+
+            <intent android:action="android.intent.action.VIEW"
+                android:data="http://www.android.com"/>
+
+        </Preference>
+
+        <SwitchPreferenceCompat
+            android:key="parent"
+            android:title="@string/title_parent_preference"
+            android:summary="@string/summary_parent_preference"/>
+
+        <SwitchPreferenceCompat
+            android:key="child"
+            android:dependency="parent"
+            android:title="@string/title_child_preference"
+            android:summary="@string/summary_child_preference"/>
+
+        <SwitchPreferenceCompat
+            android:key="toggle_summary"
+            android:title="@string/title_toggle_summary_preference"
+            android:summaryOn="@string/summary_on_toggle_summary_preference"
+            android:summaryOff="@string/summary_off_toggle_summary_preference"/>
+
+        <Preference
+            android:key="copyable"
+            android:title="@string/title_copyable_preference"
+            android:summary="@string/summary_copyable_preference"
+            android:selectable="false"
+            app:enableCopying="true"/>
+    </PreferenceCategory>
+    <PreferenceCategory
+        android:key="multi-screen"
+        android:title="@string/multi_preference_screen">
+        <Preference
+            android:title="@string/title_multi_preference_screen"
+            android:summary="@string/summary_multi_preference_screen"
+            android:fragment="com.example.androidx.preference.TwoPanePreferences$MultiPreferences"/>
+    </PreferenceCategory>
+</PreferenceScreen>
\ No newline at end of file
diff --git a/samples/SupportPreferenceDemos/src/main/res/xml/preferences_advanced_next.xml b/samples/SupportPreferenceDemos/src/main/res/xml/preferences_advanced_next.xml
new file mode 100644
index 0000000..07562e8
--- /dev/null
+++ b/samples/SupportPreferenceDemos/src/main/res/xml/preferences_advanced_next.xml
@@ -0,0 +1,28 @@
+<?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.
+  -->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory android:title="@string/title_next_preference_screen">
+        <Preference
+            android:key="Information"
+            android:summary="@string/summary_information"
+            android:title="@string/title_information" />
+        <Preference
+            android:key="stylized"
+            android:summary="@string/summary_details"
+            android:title="@string/title_details" />
+    </PreferenceCategory>
+</PreferenceScreen>
\ No newline at end of file
diff --git a/samples/SupportPreferenceDemos/src/main/res/xml/preferences_basic.xml b/samples/SupportPreferenceDemos/src/main/res/xml/preferences_basic.xml
new file mode 100644
index 0000000..ba3be40
--- /dev/null
+++ b/samples/SupportPreferenceDemos/src/main/res/xml/preferences_basic.xml
@@ -0,0 +1,39 @@
+<?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.
+  -->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <PreferenceCategory android:title="@string/basic_preferences">
+        <Preference
+            android:key="preference"
+            android:summary="@string/summary_basic_preference"
+            android:title="@string/title_basic_preference" />
+        <Preference
+            android:key="stylized"
+            android:summary="@string/summary_stylish_preference"
+            android:title="@string/title_stylish_preference" />
+        <Preference
+            android:icon="@android:drawable/ic_menu_camera"
+            android:key="icon"
+            android:summary="@string/summary_icon_preference"
+            android:title="@string/title_icon_preference" />
+        <Preference
+            android:key="single_line_title"
+            android:summary="@string/summary_single_line_title_preference"
+            android:title="@string/title_single_line_title_preference"
+            app:singleLineTitle="true" />
+    </PreferenceCategory>
+</PreferenceScreen>
\ No newline at end of file
diff --git a/samples/SupportPreferenceDemos/src/main/res/xml/preferences_dialog.xml b/samples/SupportPreferenceDemos/src/main/res/xml/preferences_dialog.xml
new file mode 100644
index 0000000..c39432c
--- /dev/null
+++ b/samples/SupportPreferenceDemos/src/main/res/xml/preferences_dialog.xml
@@ -0,0 +1,44 @@
+<?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.
+  -->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <PreferenceCategory
+        android:title="@string/dialogs">
+
+        <EditTextPreference
+            android:key="edittext"
+            android:title="@string/title_edittext_preference"
+            app:useSimpleSummaryProvider="true"
+            android:dialogTitle="@string/dialog_title_edittext_preference"/>
+
+        <ListPreference
+            android:key="list"
+            android:title="@string/title_list_preference"
+            app:useSimpleSummaryProvider="true"
+            android:entries="@array/entries"
+            android:entryValues="@array/entry_values"
+            android:dialogTitle="@string/dialog_title_list_preference"/>
+
+        <MultiSelectListPreference
+            android:key="multi_select_list"
+            android:title="@string/title_multi_list_preference"
+            android:summary="@string/summary_multi_list_preference"
+            android:entries="@array/entries"
+            android:entryValues="@array/entry_values"
+            android:dialogTitle="@string/dialog_title_multi_list_preference"/>
+    </PreferenceCategory>
+</PreferenceScreen>
\ No newline at end of file
diff --git a/samples/SupportPreferenceDemos/src/main/res/xml/preferences_widget.xml b/samples/SupportPreferenceDemos/src/main/res/xml/preferences_widget.xml
new file mode 100644
index 0000000..6813927
--- /dev/null
+++ b/samples/SupportPreferenceDemos/src/main/res/xml/preferences_widget.xml
@@ -0,0 +1,45 @@
+<?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.
+  -->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <PreferenceCategory
+        android:title="@string/widgets">
+
+        <CheckBoxPreference
+            android:key="checkbox"
+            android:title="@string/title_checkbox_preference"
+            android:summary="@string/summary_checkbox_preference"/>
+
+        <SwitchPreferenceCompat
+            android:key="switch"
+            android:title="@string/title_switch_preference"
+            android:summary="@string/summary_switch_preference"/>
+
+        <DropDownPreference
+            android:key="dropdown"
+            android:title="@string/title_dropdown_preference"
+            android:entries="@array/entries"
+            app:useSimpleSummaryProvider="true"
+            android:entryValues="@array/entry_values"/>
+
+        <SeekBarPreference
+            android:key="seekbar"
+            android:title="@string/title_seekbar_preference"
+            android:max="10"
+            android:defaultValue="5"/>
+    </PreferenceCategory>
+</PreferenceScreen>
\ No newline at end of file
diff --git a/wear/compose/compose-navigation/src/main/java/androidx/wear/compose/navigation/NavGraphBuilder.kt b/wear/compose/compose-navigation/src/main/java/androidx/wear/compose/navigation/NavGraphBuilder.kt
index 869a89f..2f007ab 100644
--- a/wear/compose/compose-navigation/src/main/java/androidx/wear/compose/navigation/NavGraphBuilder.kt
+++ b/wear/compose/compose-navigation/src/main/java/androidx/wear/compose/navigation/NavGraphBuilder.kt
@@ -24,7 +24,9 @@
 import androidx.navigation.get
 
 /**
- * Add the [Composable] to the [NavGraphBuilder]
+ * Utility function for building Wear Compose navigation graphs.
+ *
+ * Adds the content composable to the [NavGraphBuilder] as a [WearNavigator.Destination].
  *
  * @param route route for the destination
  * @param arguments list of arguments to associate with destination
diff --git a/wear/compose/compose-navigation/src/main/java/androidx/wear/compose/navigation/SwipeDismissableNavHost.kt b/wear/compose/compose-navigation/src/main/java/androidx/wear/compose/navigation/SwipeDismissableNavHost.kt
index ea19da0..c2ff968 100644
--- a/wear/compose/compose-navigation/src/main/java/androidx/wear/compose/navigation/SwipeDismissableNavHost.kt
+++ b/wear/compose/compose-navigation/src/main/java/androidx/wear/compose/navigation/SwipeDismissableNavHost.kt
@@ -96,6 +96,8 @@
  * @param navController [NavHostController] for this host
  * @param graph Graph for this host
  * @param modifier [Modifier] to be applied to the layout.
+ *
+ * @throws IllegalArgumentException if no WearNavigation.Destination is on the navigation backstack.
  */
 @ExperimentalWearMaterialApi
 @Composable
@@ -106,7 +108,8 @@
 ) {
     val lifecycleOwner = LocalLifecycleOwner.current
     val viewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
-        "NavHost requires a ViewModelStoreOwner to be provided via LocalViewModelStoreOwner"
+        "SwipeDismissableNavHost requires a ViewModelStoreOwner to be provided " +
+            "via LocalViewModelStoreOwner"
     }
 
     // Setup the navController with proper owners
@@ -125,7 +128,10 @@
     ) as? WearNavigator ?: return
     val backStack by composeNavigator.backStack.collectAsState()
     val previous = if (backStack.size <= 1) null else backStack[backStack.lastIndex - 1]
-    val current = if (backStack.isEmpty()) null else backStack.last()
+    val current = if (backStack.isNotEmpty()) backStack.last() else throw IllegalArgumentException(
+        "No WearNavigation.Destination has been added to the WearNavigator in this NavGraph. " +
+            "For convenience, build NavGraph using androidx.wear.compose.navigation.composable."
+    )
 
     val state = rememberSwipeToDismissBoxState()
     LaunchedEffect(state.currentValue) {
@@ -140,7 +146,7 @@
         modifier = Modifier,
         hasBackground = previous != null,
         backgroundKey = previous?.id ?: SwipeToDismissBoxDefaults.BackgroundKey,
-        contentKey = current?.id ?: SwipeToDismissBoxDefaults.ContentKey,
+        contentKey = current.id,
         content = { isBackground ->
             BoxedStackEntryContent(if (isBackground) previous else current, stateHolder, modifier)
         }
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
index ee44604..c3b16a6 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
@@ -451,6 +451,12 @@
         // available programmatically. The value below is the default but it could be overridden
         // by OEMs.
         internal const val INITIAL_LOW_BATTERY_THRESHOLD = 15.0f
+
+        // Number of milliseconds before the target draw time for the delayed task to run and post a
+        // choreographer frame. This is necessary when rendering at less than 60 fps to make sure we
+        // post the choreographer frame in time to for us to render in the desired frame.
+        // NOTE this value must be less than 16 or we'll render too early.
+        internal const val POST_CHOREOGRAPHER_FRAME_MILLIS_BEFORE_DEADLINE = 10
     }
 
     private val defaultRenderParametersForDrawMode: HashMap<DrawMode, RenderParameters> =
@@ -496,7 +502,7 @@
 
     // True if 'Do Not Disturb' mode is on.
     private var muteMode = false
-    private var nextDrawTimeMillis: Long = 0
+    internal var nextDrawTimeMillis: Long = 0
 
     private val pendingUpdateTime: CancellableUniqueTask =
         CancellableUniqueTask(watchFaceHostApi.getUiThreadHandler())
@@ -556,17 +562,6 @@
     private var inOnSetStyle = false
     internal var initComplete = false
 
-    private fun ambient() {
-        TraceEvent("WatchFaceImpl.ambient").use {
-            // It's not safe to draw until initComplete because the ComplicationSlotManager init
-            // may not have completed.
-            if (initComplete) {
-                onDraw()
-            }
-            scheduleDraw()
-        }
-    }
-
     private fun interruptionFilter(it: Int) {
         // We are in mute mode in any of the following modes. The specific mode depends on the
         // device's implementation of "Do Not Disturb".
@@ -635,7 +630,14 @@
 
         mainScope.launch {
             watchState.isAmbient.collect {
-                ambient()
+                TraceEvent("WatchFaceImpl.ambient").use {
+                    // It's not safe to draw until initComplete because the ComplicationSlotManager
+                    // init may not have completed.
+                    if (initComplete) {
+                        onDraw()
+                    }
+                    scheduleDraw()
+                }
             }
         }
 
@@ -832,14 +834,26 @@
     /** @hide */
     @UiThread
     internal fun onDraw() {
+        val startTime = getZonedDateTime()
+        val startTimeMillis = startTime.toInstant().toEpochMilli()
         maybeUpdateDrawMode()
-        renderer.renderInternal(getZonedDateTime())
+        renderer.renderInternal(startTime)
 
-        val currentTimeMillis = systemTimeProvider.getSystemTimeMillis()
         if (renderer.shouldAnimate()) {
-            val delayMillis = computeDelayTillNextFrame(nextDrawTimeMillis, currentTimeMillis)
-            nextDrawTimeMillis = currentTimeMillis + delayMillis
-            pendingUpdateTime.postDelayedUnique(delayMillis) { watchFaceHostApi.invalidate() }
+            val currentTimeMillis = systemTimeProvider.getSystemTimeMillis()
+            var delay = computeDelayTillNextFrame(startTimeMillis, currentTimeMillis)
+            nextDrawTimeMillis = currentTimeMillis + delay
+
+            // We want to post our delayed task to post the choreographer frame a bit earlier than
+            // the deadline because if we post it too close to the deadline we'll miss it. If we're
+            // close to the deadline we post the choreographer frame immediately.
+            delay -= POST_CHOREOGRAPHER_FRAME_MILLIS_BEFORE_DEADLINE
+
+            if (delay <= 0) {
+                watchFaceHostApi.invalidate()
+            } else {
+                pendingUpdateTime.postDelayedUnique(delay) { watchFaceHostApi.invalidate() }
+            }
         }
     }
 
@@ -851,7 +865,7 @@
     /** @hide */
     @UiThread
     internal fun computeDelayTillNextFrame(
-        beginFrameTimeMillis: Long,
+        startTimeMillis: Long,
         currentTimeMillis: Long
     ): Long {
         // Limit update rate to conserve power when the battery is low and not charging.
@@ -864,19 +878,35 @@
             } else {
                 renderer.interactiveDrawModeUpdateDelayMillis
             }
-        // Note beginFrameTimeMillis could be in the future if the user adjusted the time so we need
-        // to compute min(beginFrameTimeMillis, currentTimeMillis).
-        var nextFrameTimeMillis =
-            Math.min(beginFrameTimeMillis, currentTimeMillis) + updateRateMillis
-        // Drop frames if needed (happens when onDraw is slow).
-        if (nextFrameTimeMillis <= currentTimeMillis) {
-            // Compute the next runtime after currentTimeMillis with the same phase as
-            // beginFrameTimeMillis to keep the animation smooth.
-            val phaseAdjust =
-                updateRateMillis +
-                    ((nextFrameTimeMillis - currentTimeMillis) % updateRateMillis)
-            nextFrameTimeMillis = currentTimeMillis + phaseAdjust
+
+        var previousRequestedFrameTimeMillis = nextDrawTimeMillis
+
+        // Its possible for nextDrawTimeMillis to be in the past (it's initialized to 0) or the
+        // future (the user might have changed the system time) which we need to account for.
+        val earliestSensiblePreviousRequestedFrameTimeMillis = startTimeMillis - updateRateMillis
+        if (previousRequestedFrameTimeMillis < earliestSensiblePreviousRequestedFrameTimeMillis) {
+            previousRequestedFrameTimeMillis = startTimeMillis
         }
+        if (previousRequestedFrameTimeMillis > startTimeMillis) {
+            previousRequestedFrameTimeMillis = startTimeMillis
+        }
+
+        // If the delay is long then round to the beginning of the next period.
+        var nextFrameTimeMillis = if (updateRateMillis >= 500) {
+            val nextUnroundedTime = previousRequestedFrameTimeMillis + updateRateMillis
+            val delay =
+                (updateRateMillis - (nextUnroundedTime % updateRateMillis)) % updateRateMillis
+            previousRequestedFrameTimeMillis + delay
+        } else {
+            previousRequestedFrameTimeMillis + updateRateMillis
+        }
+
+        // If updateRateMillis is a multiple of 1 minute then align rendering to the beginning of
+        // the minute.
+        if ((updateRateMillis % 60000) == 60L) {
+            nextFrameTimeMillis += (60000 - (nextFrameTimeMillis % 60000)) % 60000
+        }
+
         return nextFrameTimeMillis - currentTimeMillis
     }
 
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
index 3af5a79..5be8275 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
@@ -28,6 +28,7 @@
 import android.os.HandlerThread
 import android.os.Looper
 import android.os.PowerManager
+import android.os.Process
 import android.os.RemoteException
 import android.os.Trace
 import android.service.wallpaper.WallpaperService
@@ -300,11 +301,14 @@
 
     internal var backgroundThread: HandlerThread? = null
 
-    /** This is open for testing. */
+    /** This is open for testing. The background thread is used for watch face initialization. */
     internal open fun getBackgroundThreadHandlerImpl(): Handler {
         synchronized(this) {
             if (backgroundThread == null) {
-                backgroundThread = HandlerThread("WatchFaceBackground").apply { start() }
+                backgroundThread = HandlerThread(
+                    "WatchFaceBackground",
+                    Process.THREAD_PRIORITY_FOREGROUND // The user is waiting on WF init.
+                ).apply { start() }
             }
             return Handler(backgroundThread!!.looper)
         }
@@ -322,6 +326,9 @@
      */
     internal open fun expectPreRInitFlow() = Build.VERSION.SDK_INT < Build.VERSION_CODES.R
 
+    /** This is open to allow mocking. */
+    internal open fun getChoreographer(): Choreographer = Choreographer.getInstance()
+
     /**
      * This is open for use by tests, it allows them to inject a custom [SurfaceHolder].
      * @hide
@@ -722,7 +729,16 @@
                     "Choreographer doFrame called but allowWatchfaceToAnimate is false"
                 }
                 frameCallbackPending = false
-                draw()
+
+                val watchFaceImpl: WatchFaceImpl? = getWatchFaceImplOrNull()
+
+                /**
+                 * It's possible we went ambient by the time our callback occurred in which case
+                 * there's no point drawing. Note if watchFaceImpl is null we call [drawBlack].
+                 */
+                if (watchFaceImpl?.renderer?.shouldAnimate() != false) {
+                    draw()
+                }
             }
         }
 
@@ -871,7 +887,7 @@
                 // It's unlikely an ambient tick would be sent to a watch face that hasn't loaded
                 // yet. The watch face will render at least once upon loading so we don't need to do
                 // anything special here.
-                invalidate()
+                draw()
                 ambientUpdateWakelock.acquire(SURFACE_DRAW_TIMEOUT_MS)
             }
         }
@@ -1001,7 +1017,6 @@
             holder: SurfaceHolder
         ): Unit = TraceEvent("EngineWrapper.onCreate").use {
             super.onCreate(holder)
-
             ambientUpdateWakelock =
                 (getSystemService(Context.POWER_SERVICE) as PowerManager)
                     .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "$TAG:[AmbientUpdate]")
@@ -1635,7 +1650,7 @@
                 }
                 frameCallbackPending = true
                 if (!this::choreographer.isInitialized) {
-                    choreographer = Choreographer.getInstance()
+                    choreographer = getChoreographer()
                 }
                 choreographer.postFrameCallback(frameCallback)
             } else {
@@ -1651,7 +1666,7 @@
                     Trace.beginSection("onDraw")
                 }
                 if (LOG_VERBOSE) {
-                    Log.v(WatchFaceService.TAG, "drawing frame")
+                    Log.v(TAG, "drawing frame")
                 }
 
                 val watchFaceImpl: WatchFaceImpl? = getWatchFaceImplOrNull()
diff --git a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/TestCommon.kt b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/TestCommon.kt
index 0e47167..4007607 100644
--- a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/TestCommon.kt
+++ b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/TestCommon.kt
@@ -28,6 +28,7 @@
 import android.support.wearable.watchface.IWatchFaceService
 import android.support.wearable.watchface.WatchFaceStyle
 import android.support.wearable.watchface.accessibility.ContentDescriptionLabel
+import android.view.Choreographer
 import android.view.SurfaceHolder
 import androidx.test.core.app.ApplicationProvider
 import androidx.wear.watchface.complications.data.toApiComplicationData
@@ -53,7 +54,8 @@
     private val handler: Handler,
     private val tapListener: WatchFace.TapListener?,
     private val preAndroidR: Boolean,
-    private val directBootParams: WallpaperInteractiveWatchFaceInstanceParams?
+    private val directBootParams: WallpaperInteractiveWatchFaceInstanceParams?,
+    private val choreographer: Choreographer
 ) : WatchFaceService() {
     /** The ids of the [ComplicationSlot]s that have been tapped. */
     val tappedComplicationSlotIds: List<Int>
@@ -121,6 +123,8 @@
 
     override fun getMutableWatchState() = watchState
 
+    override fun getChoreographer() = choreographer
+
     fun setIsVisible(isVisible: Boolean) {
         watchState.isVisible.value = isVisible
     }
diff --git a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
index 2c5c851..b7acfd5 100644
--- a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
+++ b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
@@ -39,6 +39,7 @@
 import android.support.wearable.watchface.IWatchFaceService
 import android.support.wearable.watchface.WatchFaceStyle
 import android.support.wearable.watchface.accessibility.ContentDescriptionLabel
+import android.view.Choreographer
 import android.view.SurfaceHolder
 import android.view.WindowInsets
 import androidx.annotation.Px
@@ -129,6 +130,7 @@
     private val iWatchFaceService = mock<IWatchFaceService>()
     private val surfaceHolder = mock<SurfaceHolder>()
     private val tapListener = mock<WatchFace.TapListener>()
+    private val choreographer = mock<Choreographer>()
     private val watchState = MutableWatchState()
 
     init {
@@ -349,12 +351,19 @@
     private val pendingTasks = PriorityQueue<Task>()
 
     private fun runPostedTasksFor(durationMillis: Long) {
-        looperTimeMillis += durationMillis
+        val stopTime = looperTimeMillis + durationMillis
+
         while (pendingTasks.isNotEmpty() &&
-            pendingTasks.peek()!!.runTimeMillis <= looperTimeMillis
+            pendingTasks.peek()!!.runTimeMillis <= stopTime
         ) {
-            pendingTasks.remove().runnable.run()
+            val task = pendingTasks.remove()
+            testWatchFaceService.mockSystemTimeMillis = task.runTimeMillis
+            looperTimeMillis = task.runTimeMillis
+            task.runnable.run()
         }
+
+        looperTimeMillis = stopTime
+        testWatchFaceService.mockSystemTimeMillis = stopTime
     }
 
     private fun initEngine(
@@ -414,7 +423,8 @@
             handler,
             tapListener,
             true,
-            null
+            null,
+            choreographer
         )
         engineWrapper = testWatchFaceService.onCreateEngine() as WatchFaceService.EngineWrapper
         engineWrapper.onCreate(surfaceHolder)
@@ -470,7 +480,8 @@
             handler,
             null,
             false,
-            null
+            null,
+            choreographer
         )
 
         InteractiveInstanceManager
@@ -578,6 +589,13 @@
         }.`when`(handler).postDelayed(any(), anyLong())
 
         doAnswer {
+            // Simulate waiting for the next frame.
+            val nextFrameTimeMillis = looperTimeMillis + (16 - looperTimeMillis % 16)
+            val callback = it.arguments[0] as Choreographer.FrameCallback
+            pendingTasks.add(Task(nextFrameTimeMillis, { callback.doFrame(0) }))
+        }.`when`(choreographer).postFrameCallback(any())
+
+        doAnswer {
             // Remove task from the priority queue.  There's no good way of doing this quickly.
             val queue = ArrayDeque<Task>()
             while (pendingTasks.isNotEmpty()) {
@@ -1061,7 +1079,7 @@
 
         assertThat(
             watchFaceImpl.computeDelayTillNextFrame(
-                beginFrameTimeMillis = 0,
+                startTimeMillis = 0,
                 currentTimeMillis = 2
             )
         )
@@ -1069,38 +1087,20 @@
     }
 
     @Test
-    public fun computeDelayTillNextFrame_dropsFramesForVerySlowDraw() {
+    public fun computeDelayTillNextFrame_verySlowDraw() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
             UserStyleSchema(emptyList())
         )
 
+        // If the frame is very slow we'll want to post a choreographer frame immediately.
         assertThat(
             watchFaceImpl.computeDelayTillNextFrame(
-                beginFrameTimeMillis = 0,
-                currentTimeMillis = INTERACTIVE_UPDATE_RATE_MS
-            )
-        ).isEqualTo(INTERACTIVE_UPDATE_RATE_MS)
-    }
-
-    @Test
-    public fun computeDelayTillNextFrame_perservesPhaseForVerySlowDraw() {
-        initEngine(
-            WatchFaceType.ANALOG,
-            listOf(leftComplication, rightComplication),
-            UserStyleSchema(emptyList())
-        )
-
-        // The phase of beginFrameTimeMillis % INTERACTIVE_UPDATE_RATE_MS is 2, but the phase of
-        // currentTimeMillis % INTERACTIVE_UPDATE_RATE_MS is 3, so we expect to delay
-        // INTERACTIVE_UPDATE_RATE_MS - 1 to preserve the phase while dropping a frame.
-        assertThat(
-            watchFaceImpl.computeDelayTillNextFrame(
-                beginFrameTimeMillis = 2,
+                startTimeMillis = 2,
                 currentTimeMillis = INTERACTIVE_UPDATE_RATE_MS + 3
             )
-        ).isEqualTo(INTERACTIVE_UPDATE_RATE_MS - 1)
+        ).isEqualTo(- 1)
     }
 
     @Test
@@ -1111,12 +1111,35 @@
             UserStyleSchema(emptyList())
         )
 
+        watchFaceImpl.nextDrawTimeMillis = 1000
+
+        // Simulate time going backwards between renders.
         assertThat(
             watchFaceImpl.computeDelayTillNextFrame(
-                beginFrameTimeMillis = 100,
-                currentTimeMillis = 10
+                startTimeMillis = 20,
+                currentTimeMillis = 24
             )
-        ).isEqualTo(INTERACTIVE_UPDATE_RATE_MS)
+        ).isEqualTo(INTERACTIVE_UPDATE_RATE_MS - 4)
+    }
+
+    @Test
+    public fun computeDelayTillNextFrame_1000ms_update_atTopOfSecond() {
+        initEngine(
+            WatchFaceType.ANALOG,
+            listOf(leftComplication, rightComplication),
+            UserStyleSchema(emptyList())
+        )
+
+        renderer.interactiveDrawModeUpdateDelayMillis = 1000
+
+        // Simulate rendering 0.74s into a second, after which we expect a short delay.
+        watchFaceImpl.nextDrawTimeMillis = 100740
+        assertThat(
+            watchFaceImpl.computeDelayTillNextFrame(
+                startTimeMillis = 100740,
+                currentTimeMillis = 100750
+            )
+        ).isEqualTo(250)
     }
 
     @Test
@@ -1203,7 +1226,8 @@
             handler,
             null,
             true,
-            null
+            null,
+            choreographer
         )
 
         // Trigger watch face creation.
@@ -1644,7 +1668,8 @@
             handler,
             null,
             true,
-            null
+            null,
+            choreographer
         )
         engineWrapper = testWatchFaceService.onCreateEngine() as WatchFaceService.EngineWrapper
         engineWrapper.onCreate(surfaceHolder)
@@ -1869,7 +1894,8 @@
             handler,
             null,
             true,
-            null
+            null,
+            choreographer
         )
 
         engineWrapper = testWatchFaceService.onCreateEngine() as WatchFaceService.EngineWrapper
@@ -2237,7 +2263,8 @@
                     )
                 ).toWireFormat(),
                 null
-            )
+            ),
+            choreographer
         )
 
         engineWrapper =
@@ -2476,7 +2503,8 @@
             handler,
             null,
             true,
-            null
+            null,
+            choreographer
         )
 
         engineWrapper = testWatchFaceService.onCreateEngine() as WatchFaceService.EngineWrapper
@@ -2490,6 +2518,87 @@
     }
 
     @Test
+    public fun ambientToInteractiveTransition() {
+        initWallpaperInteractiveWatchFaceInstance(
+            WatchFaceType.ANALOG,
+            emptyList(),
+            UserStyleSchema(emptyList()),
+            WallpaperInteractiveWatchFaceInstanceParams(
+                "interactiveInstanceId",
+                DeviceConfig(
+                    false,
+                    false,
+                    0,
+                    0
+                ),
+                WatchUiState(true, 0),
+                UserStyle(emptyMap()).toWireFormat(),
+                null
+            )
+        )
+
+        // We get an initial renderer when watch face init completes.
+        assertThat(renderer.lastOnDrawZonedDateTime!!.toInstant().toEpochMilli()).isEqualTo(0L)
+        runPostedTasksFor(1000L)
+
+        // But no subsequent renders are scheduled.
+        assertThat(renderer.lastOnDrawZonedDateTime!!.toInstant().toEpochMilli()).isEqualTo(0L)
+
+        // An ambientTickUpdate should trigger a render immediately.
+        engineWrapper.ambientTickUpdate()
+        assertThat(renderer.lastOnDrawZonedDateTime!!.toInstant().toEpochMilli()).isEqualTo(1000L)
+
+        // But not trigger any subsequent rendering.
+        runPostedTasksFor(1000L)
+        assertThat(renderer.lastOnDrawZonedDateTime!!.toInstant().toEpochMilli()).isEqualTo(1000L)
+
+        // When going interactive a frame should be rendered immediately.
+        engineWrapper.setWatchUiState(WatchUiState(false, 0))
+        assertThat(renderer.lastOnDrawZonedDateTime!!.toInstant().toEpochMilli()).isEqualTo(2000L)
+
+        // And we should be producing frames.
+        runPostedTasksFor(100L)
+        assertThat(renderer.lastOnDrawZonedDateTime!!.toInstant().toEpochMilli()).isEqualTo(2096L)
+    }
+
+    @Test
+    public fun interactiveToAmbientTransition() {
+        initWallpaperInteractiveWatchFaceInstance(
+            WatchFaceType.ANALOG,
+            emptyList(),
+            UserStyleSchema(emptyList()),
+            WallpaperInteractiveWatchFaceInstanceParams(
+                "interactiveInstanceId",
+                DeviceConfig(
+                    false,
+                    false,
+                    0,
+                    0
+                ),
+                WatchUiState(false, 0),
+                UserStyle(emptyMap()).toWireFormat(),
+                null
+            )
+        )
+
+        // We get an initial renderer when watch face init completes.
+        assertThat(renderer.lastOnDrawZonedDateTime!!.toInstant().toEpochMilli()).isEqualTo(0L)
+        runPostedTasksFor(1000L)
+
+        // There's a number of subsequent renders every 16ms.
+        assertThat(renderer.lastOnDrawZonedDateTime!!.toInstant().toEpochMilli()).isEqualTo(992L)
+
+        // After going ambient we should render immediately and then stop.
+        engineWrapper.setWatchUiState(WatchUiState(true, 0))
+        runPostedTasksFor(5000L)
+        assertThat(renderer.lastOnDrawZonedDateTime!!.toInstant().toEpochMilli()).isEqualTo(1000L)
+
+        // An ambientTickUpdate should trigger a render immediately.
+        engineWrapper.ambientTickUpdate()
+        assertThat(renderer.lastOnDrawZonedDateTime!!.toInstant().toEpochMilli()).isEqualTo(6000L)
+    }
+
+    @Test
     public fun onDestroy_clearsInstanceRecord() {
         val instanceId = "interactiveInstanceId"
         initWallpaperInteractiveWatchFaceInstance(
@@ -2622,7 +2731,8 @@
                     )
                 ).toWireFormat(),
                 null
-            )
+            ),
+            choreographer
         )
 
         engineWrapper = testWatchFaceService.onCreateEngine() as WatchFaceService.EngineWrapper
@@ -2673,7 +2783,8 @@
                     )
                 ).toWireFormat(),
                 null
-            )
+            ),
+            choreographer
         )
 
         testWatchFaceService.createHeadlessEngine()
@@ -2917,7 +3028,8 @@
             handler,
             null,
             false,
-            null
+            null,
+            choreographer
         )
 
         InteractiveInstanceManager
diff --git a/work/work-runtime/lint-baseline.xml b/work/work-runtime/lint-baseline.xml
index bb2be82..e0e0f58 100644
--- a/work/work-runtime/lint-baseline.xml
+++ b/work/work-runtime/lint-baseline.xml
@@ -388,6 +388,17 @@
 
     <issue
         id="ClassVerificationFailure"
+        message="This call references a method added in API level 31; however, the containing class androidx.work.impl.background.systemjob.SystemJobInfoConverter is reachable from earlier API levels and will fail run-time class verification."
+        errorLine1="            builder.setExpedited(true);"
+        errorLine2="                    ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java"
+            line="130"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="ClassVerificationFailure"
         message="This call references a method added in API level 24; however, the containing class androidx.work.impl.background.systemjob.SystemJobInfoConverter is reachable from earlier API levels and will fail run-time class verification."
         errorLine1="        return new JobInfo.TriggerContentUri(trigger.getUri(), flag);"
         errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">