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] [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] [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] [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] [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] = "one""
+ 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=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">