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