Merge "Resolve ignored returned Futures in media2-session tests" into androidx-master-dev
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 550b016..30cedff 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -44,6 +44,8 @@
<option name="IGNORE_POINT_TO_ITSELF" value="false" />
<option name="myAdditionalJavadocTags" value="hide" />
</inspection_tool>
+ <inspection_tool class="KDocUnresolvedReference" enabled="true" level="ERROR" enabled_by_default="true" />
+ <inspection_tool class="LongLine" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MissingDeprecatedAnnotation" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="NullableProblems" enabled="true" level="ERROR" enabled_by_default="true">
<option name="REPORT_NULLABLE_METHOD_OVERRIDES_NOTNULL" value="true" />
@@ -67,6 +69,7 @@
</inspection_tool>
<inspection_tool class="RawTypeCanBeGeneric" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="RawUseOfParameterizedType" enabled="true" level="ERROR" enabled_by_default="true" />
+ <inspection_tool class="Reformat" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="RemoveEmptyParenthesesFromAnnotationEntry" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<scope name="Compose" level="WEAK WARNING" enabled="false" />
</inspection_tool>
diff --git a/annotation/annotation/README.md b/annotation/annotation/README.md
index b7f5912..e65290d 100644
--- a/annotation/annotation/README.md
+++ b/annotation/annotation/README.md
@@ -7,7 +7,7 @@
[Release notes](https://developer.android.com/jetpack/androidx/releases/annotation)
-[Browse source](https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/annotations/)
+[Browse source](https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/annotation/annotation/)
[Reference documentation](https://developer.android.com/reference/androidx/classes.html)
diff --git a/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/ParameterizedBenchmark.kt b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/ParameterizedBenchmark.kt
index 050c32d..09a21f4 100644
--- a/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/ParameterizedBenchmark.kt
+++ b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/ParameterizedBenchmark.kt
@@ -23,14 +23,18 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
@LargeTest
@RunWith(Parameterized::class)
-class ParameterizedBenchmark(@Suppress("unused") private val input: Int) {
+class ParameterizedBenchmark(
+ @Suppress("unused") private val input: Int,
+ @Suppress("unused") private val stringInput: String
+) {
companion object {
@JvmStatic
- @Parameterized.Parameters
- fun data(): Collection<Array<Int>> = List(2) { arrayOf(it) }
+ @Parameters(name = "size={0},str:{1}")
+ fun data(): Collection<Array<Any>> = List(2) { arrayOf(it, "$it=:") }
}
@get:Rule
diff --git a/benchmark/common/build.gradle b/benchmark/common/build.gradle
index 488d327..a6d74c9 100644
--- a/benchmark/common/build.gradle
+++ b/benchmark/common/build.gradle
@@ -32,6 +32,7 @@
androidTestImplementation(ANDROIDX_TEST_RULES)
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
+ androidTestImplementation(KOTLIN_TEST_COMMON)
}
androidx {
diff --git a/benchmark/common/src/androidTest/java/androidx/benchmark/ResultWriterTest.kt b/benchmark/common/src/androidTest/java/androidx/benchmark/ResultWriterTest.kt
index d48f976..5783636 100644
--- a/benchmark/common/src/androidTest/java/androidx/benchmark/ResultWriterTest.kt
+++ b/benchmark/common/src/androidTest/java/androidx/benchmark/ResultWriterTest.kt
@@ -24,6 +24,7 @@
import org.junit.rules.TemporaryFolder
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import kotlin.test.assertTrue
@SmallTest
@RunWith(JUnit4::class)
@@ -90,6 +91,7 @@
"benchmarks": [
{
"name": "MethodA",
+ "params": {},
"className": "package.Class1",
"totalRunTimeNs": 900000000,
"metrics": {
@@ -110,6 +112,7 @@
},
{
"name": "MethodB",
+ "params": {},
"className": "package.Class2",
"totalRunTimeNs": 900000000,
"metrics": {
@@ -134,4 +137,61 @@
tempFile.readText()
)
}
+
+ @Test
+ fun validateJsonWithParams() {
+ val reportWithParams = BenchmarkState.Report(
+ testName = "MethodWithParams[number=2,primeNumber=true]",
+ className = "package.Class",
+ totalRunTimeNs = 900000000,
+ data = listOf(100, 101, 102),
+ repeatIterations = 100000,
+ thermalThrottleSleepSeconds = 90000000,
+ warmupIterations = 8000
+ )
+
+ val tempFile = tempFolder.newFile()
+ ResultWriter.writeReport(tempFile, listOf(reportWithParams))
+ val reportText = tempFile.readText()
+
+ assertTrue {
+ reportText.contains(
+ """
+ | "name": "MethodWithParams[number=2,primeNumber=true]",
+ | "params": {
+ | "number": "2",
+ | "primeNumber": "true"
+ | },
+ """.trimMargin()
+ )
+ }
+ }
+
+ @Test
+ fun validateJsonWithInvalidParams() {
+ val reportWithInvalidParams = BenchmarkState.Report(
+ testName = "MethodWithParams[number=2,=true,]",
+ className = "package.Class",
+ totalRunTimeNs = 900000000,
+ data = listOf(100, 101, 102),
+ repeatIterations = 100000,
+ thermalThrottleSleepSeconds = 90000000,
+ warmupIterations = 8000
+ )
+
+ val tempFile = tempFolder.newFile()
+ ResultWriter.writeReport(tempFile, listOf(reportWithInvalidParams))
+ val reportText = tempFile.readText()
+
+ assertTrue {
+ reportText.contains(
+ """
+ | "name": "MethodWithParams[number=2,=true,]",
+ | "params": {
+ | "number": "2"
+ | },
+ """.trimMargin()
+ )
+ }
+ }
}
diff --git a/benchmark/common/src/main/java/androidx/benchmark/ResultWriter.kt b/benchmark/common/src/main/java/androidx/benchmark/ResultWriter.kt
index 6b6ca2e..593e5dd 100644
--- a/benchmark/common/src/main/java/androidx/benchmark/ResultWriter.kt
+++ b/benchmark/common/src/main/java/androidx/benchmark/ResultWriter.kt
@@ -91,16 +91,41 @@
private fun JsonWriter.reportObject(report: BenchmarkState.Report): JsonWriter {
beginObject()
.name("name").value(report.testName)
+ .name("params").paramsObject(report)
.name("className").value(report.className)
.name("totalRunTimeNs").value(report.totalRunTimeNs)
.name("metrics").metricsObject(report)
.name("warmupIterations").value(report.warmupIterations)
.name("repeatIterations").value(report.repeatIterations)
.name("thermalThrottleSleepSeconds").value(report.thermalThrottleSleepSeconds)
-
return endObject()
}
+ private fun JsonWriter.paramsObject(report: BenchmarkState.Report): JsonWriter {
+ beginObject()
+ getParams(report.testName).forEach { name(it.key).value(it.value) }
+ return endObject()
+ }
+
+ private fun getParams(testName: String): Map<String, String> {
+ val parameterStrStart = testName.indexOf('[')
+ val parameterStrEnd = testName.lastIndexOf(']')
+
+ val params = HashMap<String, String>()
+ if (parameterStrStart >= 0 && parameterStrEnd >= 0) {
+ val paramListString = testName.substring(parameterStrStart + 1, parameterStrEnd)
+ paramListString.split(",").forEach { paramString ->
+ val separatorIndex = paramString.indexOfFirst { it == ':' || it == '=' }
+ if (separatorIndex in 1 until paramString.length - 1) {
+ val key = paramString.substring(0, separatorIndex)
+ val value = paramString.substring(separatorIndex + 1)
+ params[key] = value
+ }
+ }
+ }
+ return params
+ }
+
private fun JsonWriter.metricsObject(report: BenchmarkState.Report): JsonWriter {
beginObject()
diff --git a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkPlugin.kt b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkPlugin.kt
index 1a57e4f9..85617f0 100644
--- a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkPlugin.kt
+++ b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkPlugin.kt
@@ -17,8 +17,8 @@
package androidx.benchmark.gradle
import com.android.build.gradle.AppExtension
-import com.android.build.gradle.BaseExtension
import com.android.build.gradle.LibraryExtension
+import com.android.build.gradle.TestedExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.tasks.StopExecutionException
@@ -57,15 +57,24 @@
private fun configureWithAndroidPlugin(project: Project) {
if (!foundAndroidPlugin) {
foundAndroidPlugin = true
- val extension = project.extensions.getByType(BaseExtension::class.java)
+ val extension = project.extensions.getByType(TestedExtension::class.java)
configureWithAndroidExtension(project, extension)
}
}
- private fun configureWithAndroidExtension(project: Project, extension: BaseExtension) {
+ private fun configureWithAndroidExtension(project: Project, extension: TestedExtension) {
val defaultConfig = extension.defaultConfig
val testInstrumentationArgs = defaultConfig.testInstrumentationRunnerArguments
+ defaultConfig.testInstrumentationRunner = "androidx.benchmark.junit4.AndroidBenchmarkRunner"
+
+ // disable overhead from test coverage by default, even if we use a debug variant
+ extension.buildTypes.getByName("debug").isTestCoverageEnabled = false
+
+ // Benchmarks default to release build type to avoid pulling in debug libraries, which
+ // may have been compiled with coverage enabled.
+ extension.testBuildType = "release"
+
// Registering this block as a configureEach callback is only necessary because Studio skips
// Gradle if there are no changes, which stops this plugin from being re-applied.
var enabledOutput = false
diff --git a/benchmark/gradle-plugin/src/test/kotlin/androidx/benchmark/gradle/BenchmarkPluginTest.kt b/benchmark/gradle-plugin/src/test/kotlin/androidx/benchmark/gradle/BenchmarkPluginTest.kt
index 73a987d..61974bc 100644
--- a/benchmark/gradle-plugin/src/test/kotlin/androidx/benchmark/gradle/BenchmarkPluginTest.kt
+++ b/benchmark/gradle-plugin/src/test/kotlin/androidx/benchmark/gradle/BenchmarkPluginTest.kt
@@ -326,4 +326,128 @@
val argsOutput = gradleRunner.withArguments("printInstrumentationArgs").build()
assertTrue { argsOutput.output.contains("no-isolated-storage:1") }
}
+
+ @Test
+ fun applyPluginDefaultAgpProperties() {
+ buildFile.writeText(
+ """
+ import com.android.build.gradle.TestedExtension
+
+ plugins {
+ id('com.android.library')
+ id('androidx.benchmark')
+ }
+
+ repositories {
+ maven { url "$prebuiltsRepo/androidx/external" }
+ maven { url "$prebuiltsRepo/androidx/internal" }
+ }
+
+ android {
+ compileSdkVersion $compileSdkVersion
+ buildToolsVersion "$buildToolsVersion"
+
+ defaultConfig {
+ minSdkVersion $minSdkVersion
+ }
+ }
+
+ dependencies {
+ androidTestImplementation "androidx.benchmark:benchmark:1.0.0-alpha01"
+
+ }
+
+ tasks.register("printTestInstrumentationRunner") {
+ println android.defaultConfig.testInstrumentationRunner
+ }
+
+ tasks.register("printTestCoverageEnabled") {
+ def extension = project.extensions.getByType(TestedExtension)
+ println extension.buildTypes.getByName("debug").testCoverageEnabled
+ }
+
+ tasks.register("printTestBuildType") {
+ def extension = project.extensions.getByType(TestedExtension)
+ println extension.testBuildType = "release"
+ }
+ """.trimIndent()
+ )
+
+ val runnerOutput = gradleRunner.withArguments("printTestInstrumentationRunner").build()
+ assertTrue {
+ runnerOutput.output.contains("androidx.benchmark.junit4.AndroidBenchmarkRunner")
+ }
+
+ val codeCoverageOutput = gradleRunner.withArguments("printTestCoverageEnabled").build()
+ assertTrue { codeCoverageOutput.output.contains("false") }
+
+ val testBuildTypeOutput = gradleRunner.withArguments("printTestBuildType").build()
+ assertTrue { testBuildTypeOutput.output.contains("release") }
+ }
+
+ @Test
+ fun applyPluginOverrideAgpProperties() {
+ buildFile.writeText(
+ """
+ import com.android.build.gradle.TestedExtension
+
+ plugins {
+ id('com.android.library')
+ id('androidx.benchmark')
+ }
+
+ repositories {
+ maven { url "$prebuiltsRepo/androidx/external" }
+ maven { url "$prebuiltsRepo/androidx/internal" }
+ }
+
+ android {
+ compileSdkVersion $compileSdkVersion
+ buildToolsVersion "$buildToolsVersion"
+ testBuildType = "debug"
+
+ defaultConfig {
+ minSdkVersion $minSdkVersion
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ debug {
+ testCoverageEnabled = true
+ }
+ }
+ }
+
+ dependencies {
+ androidTestImplementation "androidx.benchmark:benchmark:1.0.0-alpha01"
+
+ }
+
+ tasks.register("printTestInstrumentationRunner") {
+ println android.defaultConfig.testInstrumentationRunner
+ }
+
+ tasks.register("printTestCoverageEnabled") {
+ def extension = project.extensions.getByType(TestedExtension)
+ println extension.buildTypes.getByName("debug").testCoverageEnabled
+ }
+
+ tasks.register("printTestBuildType") {
+ def extension = project.extensions.getByType(TestedExtension)
+ println extension.testBuildType
+ }
+ """.trimIndent()
+ )
+
+ val runnerOutput = gradleRunner.withArguments("printTestInstrumentationRunner").build()
+ assertTrue {
+ runnerOutput.output.contains("androidx.test.runner.AndroidJUnitRunner")
+ }
+
+ val codeCoverageOutput = gradleRunner.withArguments("printTestCoverageEnabled").build()
+ assertTrue { codeCoverageOutput.output.contains("true") }
+
+ val testBuildTypeOutput = gradleRunner.withArguments("printTestBuildType").build()
+ assertTrue { testBuildTypeOutput.output.contains("debug") }
+ }
}
diff --git a/benchmark/junit4/build.gradle b/benchmark/junit4/build.gradle
index 6fa16a5..d4f47cc 100644
--- a/benchmark/junit4/build.gradle
+++ b/benchmark/junit4/build.gradle
@@ -27,7 +27,7 @@
android {
defaultConfig {
- testInstrumentationRunner "androidx.benchmark.junit4.AndroidBenchmarkRunner"
+ testInstrumentationRunner = "androidx.benchmark.junit4.AndroidBenchmarkRunner"
}
}
diff --git a/buildSrc-tests/build.gradle b/buildSrc-tests/build.gradle
new file mode 100644
index 0000000..f2a49f8
--- /dev/null
+++ b/buildSrc-tests/build.gradle
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019 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.
+ */
+
+// This project contains tests for code contained in buildSrc
+// This project is stored outside of buildSrc/ so that waiting for these tests to complete doesn't delay the rest of the build
+
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.BuildServerConfigurationKt
+
+plugins {
+ id("AndroidXPlugin")
+ id("kotlin")
+}
+
+dependencies {
+ implementation gradleApi()
+ testImplementation JUNIT
+ implementation(project.files(new File(BuildServerConfigurationKt.getRootOutDirectory(project), "buildSrc/build/libs/buildSrc.jar")))
+}
+
+androidx {
+ toolingProject = true
+}
diff --git a/buildSrc-tests/lint-checks/build.gradle b/buildSrc-tests/lint-checks/build.gradle
new file mode 100644
index 0000000..9e9f531
--- /dev/null
+++ b/buildSrc-tests/lint-checks/build.gradle
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+import androidx.build.BuildServerConfigurationKt
+
+import java.io.File
+
+plugins {
+ id("AndroidXPlugin")
+ id("kotlin")
+}
+
+dependencies {
+ implementation build_libs.lint.core
+ implementation build_libs.lint.api
+ implementation build_libs.kotlin.stdlib
+ testImplementation build_libs.lint.tests
+ api("androidx.annotation:annotation:1.0.0")
+ implementation project.files(new File(BuildServerConfigurationKt.getRootOutDirectory(project), "buildSrc/lint-checks/build/libs/lint-checks.jar"))
+}
+
+androidx {
+ toolingProject = true
+}
diff --git a/buildSrc/lint-checks/src/test/java/androidx/build/lint/ObsoleteBuildCompatUsageDetectorTest.kt b/buildSrc-tests/lint-checks/src/test/java/androidx/build/lint/ObsoleteBuildCompatUsageDetectorTest.kt
similarity index 99%
rename from buildSrc/lint-checks/src/test/java/androidx/build/lint/ObsoleteBuildCompatUsageDetectorTest.kt
rename to buildSrc-tests/lint-checks/src/test/java/androidx/build/lint/ObsoleteBuildCompatUsageDetectorTest.kt
index 30bc8ee..a5f30df 100644
--- a/buildSrc/lint-checks/src/test/java/androidx/build/lint/ObsoleteBuildCompatUsageDetectorTest.kt
+++ b/buildSrc-tests/lint-checks/src/test/java/androidx/build/lint/ObsoleteBuildCompatUsageDetectorTest.kt
@@ -19,6 +19,7 @@
import com.android.tools.lint.checks.infrastructure.TestFiles.java
import com.android.tools.lint.checks.infrastructure.TestLintResult
import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
+
import org.junit.Ignore
import org.junit.Test
diff --git a/buildSrc/lint-checks/src/test/java/androidx/build/lint/SampledAnnotationEnforcerTest.kt b/buildSrc-tests/lint-checks/src/test/java/androidx/build/lint/SampledAnnotationEnforcerTest.kt
similarity index 100%
rename from buildSrc/lint-checks/src/test/java/androidx/build/lint/SampledAnnotationEnforcerTest.kt
rename to buildSrc-tests/lint-checks/src/test/java/androidx/build/lint/SampledAnnotationEnforcerTest.kt
diff --git a/buildSrc/src/test/kotlin/androidx/build/VersionTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/VersionTest.kt
similarity index 100%
rename from buildSrc/src/test/kotlin/androidx/build/VersionTest.kt
rename to buildSrc-tests/src/test/kotlin/androidx/build/VersionTest.kt
diff --git a/buildSrc/src/test/kotlin/androidx/build/dependencyTracker/AffectedModuleDetectorImplTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/AffectedModuleDetectorImplTest.kt
similarity index 100%
rename from buildSrc/src/test/kotlin/androidx/build/dependencyTracker/AffectedModuleDetectorImplTest.kt
rename to buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/AffectedModuleDetectorImplTest.kt
diff --git a/buildSrc/src/test/kotlin/androidx/build/dependencyTracker/AttachLogsTestRule.kt b/buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/AttachLogsTestRule.kt
similarity index 100%
rename from buildSrc/src/test/kotlin/androidx/build/dependencyTracker/AttachLogsTestRule.kt
rename to buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/AttachLogsTestRule.kt
diff --git a/buildSrc/src/test/kotlin/androidx/build/dependencyTracker/BuildPropParserTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/BuildPropParserTest.kt
similarity index 100%
rename from buildSrc/src/test/kotlin/androidx/build/dependencyTracker/BuildPropParserTest.kt
rename to buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/BuildPropParserTest.kt
diff --git a/buildSrc/src/test/kotlin/androidx/build/dependencyTracker/GitClientImplTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/GitClientImplTest.kt
similarity index 100%
rename from buildSrc/src/test/kotlin/androidx/build/dependencyTracker/GitClientImplTest.kt
rename to buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/GitClientImplTest.kt
diff --git a/buildSrc/src/test/kotlin/androidx/build/dependencyTracker/ProjectGraphTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/ProjectGraphTest.kt
similarity index 100%
rename from buildSrc/src/test/kotlin/androidx/build/dependencyTracker/ProjectGraphTest.kt
rename to buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/ProjectGraphTest.kt
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index c51fd30..5b08e3b 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -44,13 +44,6 @@
allprojects {
repos.addMavenRepositories(repositories)
- tasks.withType(Test) {
- testLogging {
- events = ["failed"]
- exceptionFormat "full"
- }
- }
-
tasks.withType(KotlinCompile).configureEach {
kotlinOptions {
freeCompilerArgs += ["-Werror"]
@@ -65,7 +58,6 @@
implementation build_libs.kotlin.gradle_plugin
implementation gradleApi()
implementation project("jetpad-integration")
- testImplementation "junit:junit:4.12"
}
apply plugin: "java-gradle-plugin"
diff --git a/buildSrc/lint-checks/build.gradle b/buildSrc/lint-checks/build.gradle
index 6176fc3..1e34235 100644
--- a/buildSrc/lint-checks/build.gradle
+++ b/buildSrc/lint-checks/build.gradle
@@ -20,7 +20,6 @@
implementation build_libs.lint.core
implementation build_libs.lint.api
implementation build_libs.kotlin.stdlib
- testImplementation build_libs.lint.tests
api("androidx.annotation:annotation:1.0.0")
}
jar {
diff --git a/buildSrc/out.gradle b/buildSrc/out.gradle
index 7ad4cbf..4fe58ca 100644
--- a/buildSrc/out.gradle
+++ b/buildSrc/out.gradle
@@ -24,7 +24,10 @@
def outDir = System.env.OUT_DIR
if (outDir == null) {
outDir = new File("${buildscript.getSourceFile().parent}/../../../out${subdir}")
+ } else {
+ outDir = new File(outDir)
}
+ project.ext.outDir = outDir
buildDir = new File(outDir, "$project.name/build")
.getCanonicalFile()
subprojects {
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
index 35fedcb..d77740c 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
@@ -16,7 +16,7 @@
package androidx.build
-import androidx.build.SupportConfig.BENCHMARK_INSTRUMENTATION_RUNNER
+import androidx.benchmark.gradle.BenchmarkPlugin
import androidx.build.SupportConfig.BUILD_TOOLS_VERSION
import androidx.build.SupportConfig.COMPILE_SDK_VERSION
import androidx.build.SupportConfig.DEFAULT_MIN_SDK_VERSION
@@ -359,8 +359,13 @@
compileSdkVersion(COMPILE_SDK_VERSION)
buildToolsVersion = BUILD_TOOLS_VERSION
defaultConfig.targetSdkVersion(TARGET_SDK_VERSION)
- defaultConfig.testInstrumentationRunner =
- if (project.isBenchmark()) BENCHMARK_INSTRUMENTATION_RUNNER else INSTRUMENTATION_RUNNER
+
+ defaultConfig.testInstrumentationRunner = INSTRUMENTATION_RUNNER
+
+ // Enable code coverage for debug builds only if we are not running inside the IDE, since
+ // enabling coverage reports breaks the method parameter resolution in the IDE debugger.
+ buildTypes.getByName("debug").isTestCoverageEnabled =
+ !project.hasProperty("android.injected.invoked.from.ide")
// Pass the --no-window-animation flag with a hack (b/138120842)
// NOTE - We're exploiting the fact that anything after a space in the value of a
@@ -416,12 +421,6 @@
project.configureErrorProneForAndroid(variants)
- // Enable code coverage for debug builds only if we are not running inside the IDE, since
- // enabling coverage reports breaks the method parameter resolution in the IDE debugger.
- buildTypes.getByName("debug").isTestCoverageEnabled =
- !project.hasProperty("android.injected.invoked.from.ide") &&
- !project.isBenchmark()
-
// Set the officially published version to be the debug version with minimum dependency
// versions.
defaultPublishConfig(Release.DEFAULT_PUBLISH_CONFIG)
@@ -476,10 +475,9 @@
// Exclude media-compat-test-* and media2-test-* modules from
// existing support library presubmit tests.
fileName.replace("-debug-androidTest", "")
- } else if (fileName.contains("-benchmark-debug-androidTest")) {
- // Exclude '-benchmark' modules from correctness tests, and
- // remove '-debug' from the APK name, since it's incorrect
- fileName.replace("-debug-androidTest", "-androidBenchmark")
+ } else if (project.plugins.hasPlugin(BenchmarkPlugin::class.java)) {
+ // Exclude '-benchmark' modules from correctness tests
+ fileName.replace("-androidTest", "-androidBenchmark")
} else {
// multiple modules may have the same name so prefix the name with
// the module's path to ensure it is unique.
@@ -629,11 +627,6 @@
}
}
-fun Project.isBenchmark(): Boolean {
- // benchmark convention is to end name with "-benchmark"
- return name.endsWith("-benchmark")
-}
-
fun Project.hideJavadocTask() {
// Most tasks named "javadoc" are unused
// So, few tasks named "javadoc" are interesting to developers
diff --git a/buildSrc/src/main/kotlin/androidx/build/BuildServerConfiguration.kt b/buildSrc/src/main/kotlin/androidx/build/BuildServerConfiguration.kt
index 4d065ed..aa1f726 100644
--- a/buildSrc/src/main/kotlin/androidx/build/BuildServerConfiguration.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/BuildServerConfiguration.kt
@@ -46,6 +46,13 @@
}
/**
+ * Returns the out directory (an ancestor of all files generated by the build)
+ */
+fun Project.getRootOutDirectory(): File {
+ return project.rootProject.extensions.extraProperties.get("outDir") as File
+}
+
+/**
* Directory to put build info files for release service dependency files.
*/
fun Project.getBuildInfoDirectory(): File =
diff --git a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
index 6c5bd2c..0b2fb46 100644
--- a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
@@ -51,7 +51,7 @@
.addStubs("car/stubs/android.car.jar")
prebuilts(LibraryGroups.CARDVIEW, "1.0.0")
prebuilts(LibraryGroups.COLLECTION, "1.1.0")
- prebuilts(LibraryGroups.CONCURRENT, "1.0.0-beta01")
+ prebuilts(LibraryGroups.CONCURRENT, "1.0.0-rc01")
prebuilts(LibraryGroups.CONTENTPAGER, "1.0.0")
prebuilts(LibraryGroups.COORDINATORLAYOUT, "1.1.0-beta01")
prebuilts(LibraryGroups.CORE, "core", "1.2.0-alpha02")
@@ -79,11 +79,11 @@
ignore(LibraryGroups.LOADER.group, "loader-ktx")
prebuilts(LibraryGroups.LOADER, "1.1.0-rc01")
prebuilts(LibraryGroups.LOCALBROADCASTMANAGER, "1.1.0-alpha01")
- prebuilts(LibraryGroups.MEDIA, "media", "1.1.0-rc01")
+ prebuilts(LibraryGroups.MEDIA, "media", "1.1.0")
ignore(LibraryGroups.MEDIA2.group, "media2-exoplayer")
prebuilts(LibraryGroups.MEDIA2, "media2-widget", "1.0.0-beta01")
prebuilts(LibraryGroups.MEDIA2, "1.0.0-rc01")
- prebuilts(LibraryGroups.MEDIAROUTER, "1.1.0-rc01")
+ prebuilts(LibraryGroups.MEDIAROUTER, "1.1.0")
ignore(LibraryGroups.NAVIGATION.group, "navigation-testing")
ignore(LibraryGroups.NAVIGATION.group, "navigation-safe-args-generator")
ignore(LibraryGroups.NAVIGATION.group, "navigation-safe-args-gradle-plugin")
diff --git a/buildSrc/src/main/kotlin/androidx/build/SupportConfig.kt b/buildSrc/src/main/kotlin/androidx/build/SupportConfig.kt
index f9b32b8d..4994312 100644
--- a/buildSrc/src/main/kotlin/androidx/build/SupportConfig.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/SupportConfig.kt
@@ -23,7 +23,6 @@
object SupportConfig {
const val DEFAULT_MIN_SDK_VERSION = 14
const val INSTRUMENTATION_RUNNER = "androidx.test.runner.AndroidJUnitRunner"
- const val BENCHMARK_INSTRUMENTATION_RUNNER = "androidx.benchmark.junit4.AndroidBenchmarkRunner"
const val BUILD_TOOLS_VERSION = "28.0.3"
/**
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
index c20ff94..d3df2ff 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
@@ -48,7 +48,7 @@
* ALL_AFFECTED_PROJECTS -- The union of CHANGED_PROJECTS and DEPENDENT_PROJECTS,
* which encompasses all projects that could possibly break due to the changes.
*/
-internal enum class ProjectSubset { DEPENDENT_PROJECTS, CHANGED_PROJECTS, ALL_AFFECTED_PROJECTS }
+enum class ProjectSubset { DEPENDENT_PROJECTS, CHANGED_PROJECTS, ALL_AFFECTED_PROJECTS }
/**
* A utility class that can discover which files are changed based on git history.
@@ -183,7 +183,7 @@
*
* When a file in a module is changed, all modules that depend on it are considered as changed.
*/
-internal class AffectedModuleDetectorImpl constructor(
+class AffectedModuleDetectorImpl constructor(
private val rootProject: Project,
private val logger: Logger?,
// used for debugging purposes when we want to ignore non module files
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/BuildPropParser.kt b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/BuildPropParser.kt
index c877e7a..8f48cd6 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/BuildPropParser.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/BuildPropParser.kt
@@ -24,7 +24,7 @@
*
* Currently, we don't use it since build system does not give us the right shas.
*/
-internal object BuildPropParser {
+object BuildPropParser {
/**
* Returns the sha which is the reference sha that we should use to find changed files.
*
@@ -90,4 +90,4 @@
val repoSha: String,
val buildSha: String
)
-}
\ No newline at end of file
+}
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/GitClient.kt b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/GitClient.kt
index 7e57766..486df04 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/GitClient.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/GitClient.kt
@@ -20,7 +20,7 @@
import java.io.File
import java.util.concurrent.TimeUnit
-internal interface GitClient {
+interface GitClient {
fun findChangedFilesSince(
sha: String,
top: String = "HEAD",
@@ -42,7 +42,7 @@
* A simple git client that uses system process commands to communicate with the git setup in the
* given working directory.
*/
-internal class GitClientImpl(
+class GitClientImpl(
/**
* The root location for git
*/
@@ -109,7 +109,7 @@
}
companion object {
- internal const val PREV_MERGE_CMD = "git log -1 --merges --oneline"
- internal const val CHANGED_FILES_CMD_PREFIX = "git diff --name-only"
+ const val PREV_MERGE_CMD = "git log -1 --merges --oneline"
+ const val CHANGED_FILES_CMD_PREFIX = "git diff --name-only"
}
}
diff --git a/buildSrc/src/main/kotlin/androidx/build/dokka/Dokka.kt b/buildSrc/src/main/kotlin/androidx/build/dokka/Dokka.kt
index f43a945..fd330de 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dokka/Dokka.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dokka/Dokka.kt
@@ -25,6 +25,7 @@
import com.android.build.gradle.LibraryExtension
import org.gradle.api.Project
import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.Zip
import org.gradle.kotlin.dsl.apply
import org.jetbrains.dokka.gradle.DokkaAndroidPlugin
@@ -33,9 +34,11 @@
import java.io.File
object Dokka {
- fun generatorTaskNameForType(docsType: String): String {
- return "dokka${docsType}Docs"
+ fun generatorTaskNameForType(docsType: String, language: String = ""): String {
+ val formattedLangauage = language.toLowerCase().capitalize()
+ return "dokka${formattedLangauage}${docsType}Docs"
}
+
fun archiveTaskNameForType(docsType: String): String {
return "dist${docsType}DokkaDocs"
}
@@ -44,7 +47,6 @@
project: Project,
hiddenPackages: List<String>
) {
- val taskName = generatorTaskNameForType(docsType)
val archiveTaskName = archiveTaskNameForType(docsType)
project.apply<DokkaAndroidPlugin>()
// We don't use the `dokka` task, but it normally appears in `./gradlew tasks`
@@ -53,38 +55,72 @@
if (project.name != "support" && project.name != "docs-runner") {
throw Exception("Illegal project passed to createDocsTask: " + project.name)
}
- val docsTask = project.tasks.create(taskName, DokkaAndroidTask::class.java) { docsTask ->
- docsTask.moduleName = project.name
- docsTask.outputDirectory = File(project.buildDir, taskName).absolutePath
- docsTask.description = "Generates $docsType Kotlin documentation in the style of " +
- "d.android.com. Places docs in ${docsTask.outputDirectory}"
- docsTask.outputFormat = "dac"
- docsTask.outlineRoot = "androidx/"
- docsTask.dacRoot = "/reference/kotlin"
- docsTask.moduleName = ""
+
+ val kotlinDocsTask = createDokkaTask(project,
+ docsType,
+ hiddenPackages,
+ "Kotlin",
+ "dac",
+ "/reference/kotlin")
+ val javaDocsTask = createDokkaTask(project,
+ docsType,
+ hiddenPackages,
+ "Java",
+ "dac-as-java",
+ "/reference/")
+
+ project.tasks.register(archiveTaskName, Zip::class.java) { zipTask ->
+
+ zipTask.dependsOn(javaDocsTask)
+ zipTask.from(javaDocsTask.map { it.outputDirectory }) { copySpec ->
+ copySpec.into("reference/java")
+ }
+
+ zipTask.dependsOn(kotlinDocsTask)
+ zipTask.from(kotlinDocsTask.map { it.outputDirectory }) { copySpec ->
+ copySpec.into("reference/kotlin")
+ }
+
+ val buildId = getBuildId()
+ val archiveBaseName = generatorTaskNameForType(docsType)
+ zipTask.archiveBaseName.set(archiveBaseName)
+ zipTask.archiveVersion.set(buildId)
+ zipTask.destinationDirectory.set(project.getDistributionDirectory())
+ val filePath = "${project.getDistributionDirectory().canonicalPath}/"
+ val fileName = "$archiveBaseName-$buildId.zip"
+ zipTask.description = "Zips $docsType documentation (generated via " +
+ "Dokka in the style of d.android.com) into ${filePath + fileName}"
+ zipTask.group = JavaBasePlugin.DOCUMENTATION_GROUP
+ }
+ }
+
+ private fun createDokkaTask(
+ project: Project,
+ docsType: String,
+ hiddenPackages: List<String>,
+ language: String,
+ outputFormat: String,
+ dacRoot: String
+ ): TaskProvider<DokkaAndroidTask> {
+
+ val docTaskName = generatorTaskNameForType(docsType, language)
+
+ return project.tasks.register(docTaskName, DokkaAndroidTask::class.java) { task ->
+ task.moduleName = project.name
+ task.outputDirectory = File(project.buildDir, docTaskName).absolutePath
+ task.description = "Generates $docsType $language documentation in the style of " +
+ "d.android.com. Places docs in ${task.outputDirectory}"
+ task.outputFormat = outputFormat
+ task.outlineRoot = "androidx/"
+ task.dacRoot = dacRoot
+ task.moduleName = ""
for (hiddenPackage in hiddenPackages) {
val opts = PackageOptions()
opts.prefix = hiddenPackage
opts.suppress = true
- docsTask.perPackageOptions.add(opts)
+ task.perPackageOptions.add(opts)
}
}
-
- project.tasks.create(archiveTaskName, Zip::class.java) { zipTask ->
- zipTask.dependsOn(docsTask)
- zipTask.from(docsTask.outputDirectory) { copySpec ->
- copySpec.into("reference/kotlin")
- }
- val buildId = getBuildId()
- zipTask.archiveBaseName.set(taskName)
- zipTask.archiveVersion.set(buildId)
- zipTask.destinationDirectory.set(project.getDistributionDirectory())
- val filePath = "${project.getDistributionDirectory().canonicalPath}/"
- val fileName = "$taskName-$buildId.zip"
- zipTask.description = "Zips $docsType Kotlin documentation (generated via " +
- "Dokka in the style of d.android.com) into ${filePath + fileName}"
- zipTask.group = JavaBasePlugin.DOCUMENTATION_GROUP
- }
}
fun Project.configureAndroidProjectForDokka(
diff --git a/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaPublicDocs.kt b/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaPublicDocs.kt
index c313a71..c793a8c 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaPublicDocs.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaPublicDocs.kt
@@ -20,7 +20,6 @@
import java.io.File
import androidx.build.androidJarFile
-import androidx.build.java.JavaCompileInputs
import androidx.build.AndroidXExtension
import androidx.build.RELEASE_RULE
import androidx.build.Strategy.Ignore
@@ -30,13 +29,15 @@
import org.gradle.api.Project
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.Copy
+import org.gradle.api.tasks.TaskCollection
import org.gradle.api.tasks.TaskContainer
+import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.util.PatternFilterable
import org.jetbrains.dokka.gradle.DokkaTask
object DokkaPublicDocs {
- val ARCHIVE_TASK_NAME: String = Dokka.archiveTaskNameForType("Public")
- private val RUNNER_TASK_NAME = Dokka.generatorTaskNameForType("Public")
+ private const val DOCS_TYPE = "Public"
+ val ARCHIVE_TASK_NAME: String = Dokka.archiveTaskNameForType(DOCS_TYPE)
private const val UNZIP_DEPS_TASK_NAME = "unzipDokkaPublicDocsDeps"
val hiddenPackages = listOf(
@@ -56,6 +57,9 @@
"androidx.work.impl.utils.futures",
"androidx.work.impl.utils.taskexecutor")
+ private var docsTasks: TaskCollection<DokkaTask>? = null
+ private var unzipTask: LocateJarsTask? = null
+
fun tryGetRunnerProject(project: Project): Project? {
return project.rootProject.findProject(":docs-runner")
}
@@ -64,34 +68,56 @@
return tryGetRunnerProject(project)!!
}
- fun getDocsTask(project: Project): DokkaTask {
+ fun getDocsTasks(project: Project): TaskCollection<DokkaTask>? {
+ docsTasks?.let {
+ return it
+ }
val runnerProject = getRunnerProject(project)
- return runnerProject.tasks.getOrCreateDocsTask(runnerProject)
+ docsTasks = runnerProject.tasks.getOrCreateDocsTask(runnerProject)
+ return docsTasks
}
fun getUnzipDepsTask(project: Project): LocateJarsTask {
+ unzipTask?.let {
+ return it
+ }
val runnerProject = getRunnerProject(project)
- return runnerProject.tasks.getByName(DokkaPublicDocs.UNZIP_DEPS_TASK_NAME) as LocateJarsTask
+ unzipTask = runnerProject.tasks.getByName(UNZIP_DEPS_TASK_NAME) as LocateJarsTask
+ return unzipTask as LocateJarsTask
}
- @Synchronized fun TaskContainer.getOrCreateDocsTask(runnerProject: Project): DokkaTask {
+ @Synchronized fun TaskContainer.getOrCreateDocsTask(runnerProject: Project):
+ TaskCollection<DokkaTask> {
val tasks = this
- if (tasks.findByName(RUNNER_TASK_NAME) == null) {
- Dokka.createDocsTask("Public",
+ var dokkaTasks = runnerProject.tasks.withType(DokkaTask::class.java)
+ .matching { it.name.contains(DOCS_TYPE) }
+ if (dokkaTasks.isEmpty()) {
+ Dokka.createDocsTask(
+ DOCS_TYPE,
runnerProject,
hiddenPackages)
- val docsTask = runnerProject.tasks.getByName(RUNNER_TASK_NAME) as DokkaTask
+
+ dokkaTasks = runnerProject.tasks.withType(DokkaTask::class.java)
+ .matching { it.name.contains(DOCS_TYPE) }
+
tasks.create(UNZIP_DEPS_TASK_NAME, LocateJarsTask::class.java) { unzipTask ->
unzipTask.doLast {
for (jar in unzipTask.outputJars) {
- docsTask.classpath = docsTask.classpath.plus(runnerProject.file(jar))
+ dokkaTasks.forEach {
+ it.classpath += runnerProject.file(jar)
+ }
}
- docsTask.classpath += androidJarFile(runnerProject)
+ dokkaTasks.forEach {
+ it.classpath += androidJarFile(runnerProject)
+ }
}
- docsTask.dependsOn(unzipTask)
+
+ dokkaTasks.forEach {
+ it.dependsOn(unzipTask)
+ }
}
}
- return runnerProject.tasks.getByName(DokkaPublicDocs.RUNNER_TASK_NAME) as DokkaTask
+ return dokkaTasks
}
// specifies that <project> exists and might need us to generate documentation for it
@@ -119,14 +145,21 @@
}
// specifies that <dependency> describes an artifact containing sources that we want to include in our generated documentation
- private fun registerPrebuilt(dependency: String, runnerProject: Project): Copy {
- val docsTask = getDocsTask(runnerProject)
+ private fun registerPrebuilt(dependency: String, runnerProject: Project): TaskProvider<Copy> {
+ val dokkaTasks = getDocsTasks(runnerProject)
// unzip the sources jar
val unzipTask = getPrebuiltSources(runnerProject, "$dependency:sources")
- val sourceDir = unzipTask.destinationDir
- docsTask.dependsOn(unzipTask)
- docsTask.sourceDirs += sourceDir
+ val sourceDir = unzipTask.map { it.destinationDir }
+
+ // Avoid depending on or modifying a task that has already been executed.
+ // Because registerProject is called in an afterEvaluate, we can end up in a case where the dokka tasks are
+ // cached and have been executed in a prior run
+ dokkaTasks?.filter { it.state.isConfigurable }?.forEach {
+ val sourceDirVal = sourceDir.get()
+ it.dependsOn(unzipTask)
+ it.sourceDirs += sourceDirVal
+ }
// also make a note to unzip any dependencies too
getUnzipDepsTask(runnerProject).inputDependencies.add(dependency)
@@ -138,7 +171,7 @@
private fun getPrebuiltSources(
runnerProject: Project,
mavenId: String
- ): Copy {
+ ): TaskProvider<Copy> {
val configuration = runnerProject.configurations.detachedConfiguration(
runnerProject.dependencies.create(mavenId)
)
@@ -156,7 +189,7 @@
val sanitizedMavenId = mavenId.replace(":", "-")
val buildDir = runnerProject.buildDir
val destDir = runnerProject.file("$buildDir/sources-unzipped/$sanitizedMavenId")
- return runnerProject.tasks.create("unzip$sanitizedMavenId", Copy::class.java) {
+ return runnerProject.tasks.register("unzip$sanitizedMavenId", Copy::class.java) {
it.from(runnerProject.zipTree(configuration.singleFile)
.matching {
it.exclude("**/*.MF")
@@ -172,14 +205,6 @@
}
}
}
-
- private fun registerInputs(inputs: JavaCompileInputs, project: Project) {
- val docsTask = getDocsTask(project)
- docsTask.sourceDirs += inputs.sourcePaths
- docsTask.classpath = docsTask.classpath.plus(inputs.dependencyClasspath)
- .plus(inputs.bootClasspath)
- docsTask.dependsOn(inputs.dependencyClasspath)
- }
}
open class LocateJarsTask : DefaultTask() {
diff --git a/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaSourceDocs.kt b/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaSourceDocs.kt
index 09d95e6..80826b3 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaSourceDocs.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaSourceDocs.kt
@@ -18,23 +18,25 @@
// TODO: after DiffAndDocs and Doclava are fully obsoleted and removed, rename this from DokkaSourceDocs to just SourceDocs
package androidx.build.dokka
-import androidx.build.java.JavaCompileInputs
import androidx.build.AndroidXExtension
import androidx.build.defaultPublishVariant
+import androidx.build.java.JavaCompileInputs
import com.android.build.gradle.LibraryExtension
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPluginConvention
+import org.gradle.api.tasks.TaskCollection
import org.gradle.api.tasks.TaskContainer
import org.gradle.kotlin.dsl.getPlugin
import org.jetbrains.dokka.gradle.DokkaTask
object DokkaSourceDocs {
- private val RUNNER_TASK_NAME = Dokka.generatorTaskNameForType("TipOfTree")
- public val ARCHIVE_TASK_NAME: String = Dokka.archiveTaskNameForType("TipOfTree")
+ private const val DOCS_TYPE = "TipOfTree"
+ public val ARCHIVE_TASK_NAME: String = Dokka.archiveTaskNameForType(DOCS_TYPE)
// TODO(b/72330103) make "generateDocs" be the only archive task once Doclava is fully removed
private val ALTERNATE_ARCHIVE_TASK_NAME: String = "generateDocs"
private val hiddenPackages = DokkaPublicDocs.hiddenPackages
+ private var docsTasks: TaskCollection<DokkaTask>? = null
fun tryGetRunnerProject(project: Project): Project? {
return project.rootProject.findProject(":docs-runner")
@@ -44,22 +46,33 @@
return tryGetRunnerProject(project)!!
}
- fun getDocsTask(project: Project): DokkaTask {
- var runnerProject = getRunnerProject(project)
- return runnerProject.tasks.getOrCreateDocsTask(runnerProject)
+ fun getDocsTasks(project: Project): TaskCollection<DokkaTask>? {
+ docsTasks?.let {
+ return it
+ }
+ val runnerProject = getRunnerProject(project)
+ docsTasks = runnerProject.tasks.getOrCreateDocsTask(runnerProject)
+ return docsTasks
}
- @Synchronized fun TaskContainer.getOrCreateDocsTask(runnerProject: Project): DokkaTask {
+ @Synchronized fun TaskContainer.getOrCreateDocsTask(runnerProject: Project):
+ TaskCollection<DokkaTask> {
val tasks = this
- if (tasks.findByName(DokkaSourceDocs.RUNNER_TASK_NAME) == null) {
- Dokka.createDocsTask("TipOfTree", runnerProject, hiddenPackages)
- if (tasks.findByName(DokkaSourceDocs.ALTERNATE_ARCHIVE_TASK_NAME) == null) {
- tasks.create(ALTERNATE_ARCHIVE_TASK_NAME)
+ var dokkaTasks = runnerProject.tasks.withType(DokkaTask::class.java)
+ .matching { it.name.contains(DOCS_TYPE) }
+
+ if (dokkaTasks.isEmpty()) {
+ Dokka.createDocsTask(DOCS_TYPE, runnerProject, hiddenPackages)
+ dokkaTasks = runnerProject.tasks.withType(DokkaTask::class.java)
+ .matching { it.name.contains(DOCS_TYPE) }
+
+ if (tasks.findByName(ALTERNATE_ARCHIVE_TASK_NAME) == null) {
+ tasks.register(ALTERNATE_ARCHIVE_TASK_NAME) {
+ it.dependsOn(tasks.named(ARCHIVE_TASK_NAME))
+ }
}
- tasks.getByName(ALTERNATE_ARCHIVE_TASK_NAME)
- .dependsOn(tasks.getByName(ARCHIVE_TASK_NAME))
}
- return tasks.getByName(DokkaSourceDocs.RUNNER_TASK_NAME) as DokkaTask
+ return dokkaTasks
}
fun registerAndroidProject(
@@ -95,17 +108,24 @@
}
val javaPluginConvention = project.convention.getPlugin<JavaPluginConvention>()
val mainSourceSet = javaPluginConvention.sourceSets.getByName("main")
- project.afterEvaluate({
+ project.afterEvaluate {
val inputs = JavaCompileInputs.fromSourceSet(mainSourceSet, project)
registerInputs(inputs, project)
- })
+ }
}
fun registerInputs(inputs: JavaCompileInputs, project: Project) {
- val docsTask = getDocsTask(project)
- docsTask.sourceDirs += inputs.sourcePaths
- docsTask.classpath = docsTask.classpath.plus(inputs.dependencyClasspath)
- .plus(inputs.bootClasspath)
- docsTask.dependsOn(inputs.dependencyClasspath)
+ val dokkaTasks = getDocsTasks(project)
+
+ // Avoid depending on or modifying a task that has already been executed.
+ // Because registerInputs is called in an afterEvaluate, we can end up in a case where the dokka tasks are
+ // cached and have been executed in a prior run
+ dokkaTasks?.filter { it.state.isConfigurable }?.forEach {
+ it.sourceDirs += inputs.sourcePaths
+
+ it.classpath = it.classpath.plus(inputs.dependencyClasspath)
+ .plus(inputs.bootClasspath)
+ it.dependsOn(inputs.dependencyClasspath)
+ }
}
}
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageAnalysisTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageAnalysisTest.java
index b552085..67292a2 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageAnalysisTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageAnalysisTest.java
@@ -84,8 +84,9 @@
@Rule
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(
Manifest.permission.CAMERA);
+
@Before
- public void setUp() {
+ public void setUp() {
assumeTrue(CameraUtil.deviceHasCamera());
mAnalysisResults = new HashSet<>();
mAnalysisResultsSemaphore = new Semaphore(/*permits=*/ 0);
@@ -168,11 +169,23 @@
}
@Test
- public void analyzerAnalyzesImages_whenCameraIsOpen()
+ public void analyzesImages_withAcquireLatest_whenCameraIsOpen()
+ throws InterruptedException, CameraInfoUnavailableException {
+ analyzerAnalyzesImagesWithMode(ImageReaderMode.ACQUIRE_LATEST_IMAGE);
+ }
+
+ @Test
+ public void analyzesImages_withAcquireNext_whenCameraIsOpen()
+ throws InterruptedException, CameraInfoUnavailableException {
+ analyzerAnalyzesImagesWithMode(ImageReaderMode.ACQUIRE_NEXT_IMAGE);
+ }
+
+ private void analyzerAnalyzesImagesWithMode(ImageReaderMode imageReaderMode)
throws InterruptedException, CameraInfoUnavailableException {
final int imageFormat = ImageFormat.YUV_420_888;
ImageAnalysisConfig config =
- new ImageAnalysisConfig.Builder().setCallbackHandler(mHandler).build();
+ new ImageAnalysisConfig.Builder().setImageReaderMode(
+ imageReaderMode).setCallbackHandler(mHandler).build();
ImageAnalysis useCase = new ImageAnalysis(config);
Map<String, Size> suggestedResolutionMap = new HashMap<>();
suggestedResolutionMap.put(mCameraId, DEFAULT_RESOLUTION);
@@ -180,6 +193,8 @@
CameraUtil.openCameraWithUseCase(mCameraId, mCamera, useCase);
useCase.setAnalyzer(mAnalyzer);
+ mAnalysisResultsSemaphore.tryAcquire(5, TimeUnit.SECONDS);
+
int sensorRotation = CameraX.getCameraInfo(mCameraId).getSensorRotationDegrees();
// The frames should have properties which match the configuration.
for (ImageProperties properties : mAnalysisResults) {
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageReaderProxysTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageReaderProxysTest.java
index f11a503..3f31ab0 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageReaderProxysTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageReaderProxysTest.java
@@ -40,7 +40,7 @@
import androidx.camera.testing.fakes.FakeUseCaseConfig;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.MediumTest;
+import androidx.test.filters.LargeTest;
import org.junit.After;
import org.junit.Before;
@@ -53,10 +53,14 @@
import java.util.Map;
import java.util.concurrent.Semaphore;
-@MediumTest
+/**
+ * Instrument test for {@link ImageReaderProxy}.
+ */
+@LargeTest
@RunWith(AndroidJUnit4.class)
public final class ImageReaderProxysTest {
private static final String CAMERA_ID = "0";
+ private static final int TEST_TIMEOUT_MILLIS = 3000;
private BaseCamera mCamera;
private HandlerThread mHandlerThread;
@@ -102,7 +106,6 @@
}
}
- @MediumTest
@Test
public void sharedReadersGetFramesFromCamera() throws InterruptedException {
List<Semaphore> semaphores = new ArrayList<>();
@@ -128,7 +131,6 @@
}
}
- @MediumTest
@Test
public void isolatedReadersGetFramesFromCamera() throws InterruptedException {
List<Semaphore> semaphores = new ArrayList<>();
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
index 0679266..fa8fec7 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
@@ -290,6 +290,8 @@
* @param lensFacing the lens facing
* @return true if the device has at least one camera with the specified lens facing,
* otherwise false.
+ * @throws CameraInfoUnavailableException if unable to access cameras, perhaps due to
+ * insufficient permissions
*/
public static boolean hasCameraWithLensFacing(LensFacing lensFacing)
throws CameraInfoUnavailableException {
@@ -325,10 +327,11 @@
* @return the cameraId if camera exists or {@code null} if no camera found with the config
* @throws CameraInfoUnavailableException if unable to access cameras, perhaps due to
* insufficient permissions.
+ * @throws IllegalArgumentException if there's no lens facing set in the config.
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
- @NonNull
+ @Nullable
public static String getCameraWithCameraDeviceConfig(CameraDeviceConfig config)
throws CameraInfoUnavailableException {
Set<String> availableCameraIds = getCameraFactory().getAvailableCameraIds();
@@ -338,7 +341,10 @@
availableCameraIds =
LensFacingCameraIdFilter.createLensFacingCameraIdFilter(lensFacing)
.filter(availableCameraIds);
+ } else {
+ throw new IllegalArgumentException("Lens facing isn't set in the config.");
}
+
CameraIdFilter cameraIdFilter = config.getCameraIdFilter(null);
if (cameraIdFilter != null) {
// Filters camera ids with other filters.
@@ -348,7 +354,7 @@
if (!availableCameraIds.isEmpty()) {
return availableCameraIds.iterator().next();
} else {
- throw new CameraInfoUnavailableException("Unable to find available camera id.");
+ return null;
}
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ForwardingImageReaderListener.java b/camera/camera-core/src/main/java/androidx/camera/core/ForwardingImageReaderListener.java
index e94c9d60..566db37 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ForwardingImageReaderListener.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ForwardingImageReaderListener.java
@@ -44,6 +44,9 @@
@Override
public synchronized void onImageAvailable(ImageReaderProxy imageReaderProxy) {
ImageProxy imageProxy = imageReaderProxy.acquireNextImage();
+ if (imageProxy == null) {
+ return;
+ }
ReferenceCountedImageProxy referenceCountedImageProxy =
new ReferenceCountedImageProxy(imageProxy);
for (QueuedImageReaderProxy queuedImageReaderProxy : mImageReaders) {
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
index 5733c02..597356a 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
@@ -56,6 +56,9 @@
@RestrictTo(Scope.LIBRARY_GROUP)
public static final Defaults DEFAULT_CONFIG = new Defaults();
private static final String TAG = "ImageAnalysis";
+ // ImageReader depth for non-blocking mode.
+ private static final int NON_BLOCKING_IMAGE_DEPTH = 4;
+
final AtomicReference<Analyzer> mSubscribedAnalyzer;
final AtomicInteger mRelativeRotation = new AtomicInteger();
final Handler mHandler;
@@ -260,44 +263,27 @@
Executor backgroundExecutor = config.getBackgroundExecutor(
CameraXExecutors.highPriorityExecutor());
+ int imageQueueDepth = config.getImageReaderMode() == ImageReaderMode.ACQUIRE_NEXT_IMAGE
+ ? config.getImageQueueDepth() : NON_BLOCKING_IMAGE_DEPTH;
+
mImageReader =
ImageReaderProxys.createCompatibleReader(
cameraId,
resolution.getWidth(),
resolution.getHeight(),
getImageFormat(),
- config.getImageQueueDepth(),
+ imageQueueDepth,
backgroundExecutor);
tryUpdateRelativeRotation(cameraId);
- mImageReader.setOnImageAvailableListener(
- new ImageReaderProxy.OnImageAvailableListener() {
- @Override
- public void onImageAvailable(ImageReaderProxy imageReader) {
- Analyzer analyzer = mSubscribedAnalyzer.get();
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- try (ImageProxy image =
- config
- .getImageReaderMode(
- config.getImageReaderMode())
- .equals(ImageReaderMode.ACQUIRE_NEXT_IMAGE)
- ? imageReader.acquireNextImage()
- : imageReader.acquireLatestImage()) {
- // Do not analyze if unable to acquire an ImageProxy
- if (image == null) {
- return;
- }
- if (analyzer != null) {
- analyzer.analyze(image, mRelativeRotation.get());
- }
- }
- }
- });
- }
- },
+ ImageReaderProxy.OnImageAvailableListener onImageAvailableListener =
+ config.getImageReaderMode() == ImageReaderMode.ACQUIRE_NEXT_IMAGE
+ ? new ImageAnalysisBlockingCallback(mSubscribedAnalyzer, mRelativeRotation,
+ mHandler) :
+ new ImageAnalysisNonBlockingCallback(mSubscribedAnalyzer, mRelativeRotation,
+ mHandler, backgroundExecutor);
+ mImageReader.setOnImageAvailableListener(onImageAvailableListener,
backgroundExecutor);
SessionConfig.Builder sessionConfigBuilder = SessionConfig.Builder.createFrom(config);
@@ -383,7 +369,7 @@
@RestrictTo(Scope.LIBRARY_GROUP)
public static final class Defaults implements ConfigProvider<ImageAnalysisConfig> {
private static final ImageReaderMode DEFAULT_IMAGE_READER_MODE =
- ImageReaderMode.ACQUIRE_NEXT_IMAGE;
+ ImageReaderMode.ACQUIRE_LATEST_IMAGE;
private static final Handler DEFAULT_HANDLER = new Handler(Looper.getMainLooper());
private static final int DEFAULT_IMAGE_QUEUE_DEPTH = 6;
private static final Size DEFAULT_TARGET_RESOLUTION = new Size(640, 480);
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisBlockingCallback.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisBlockingCallback.java
new file mode 100644
index 0000000..ce59f70
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisBlockingCallback.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2019 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 android.os.Handler;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * OnImageAvailableListener with blocking behavior. It never drops image without analyzing it.
+ *
+ * <p> Used with {@link ImageAnalysis}.
+ */
+final class ImageAnalysisBlockingCallback implements ImageReaderProxy.OnImageAvailableListener {
+
+ private static final String TAG = "BlockingCallback";
+
+ final AtomicReference<ImageAnalysis.Analyzer> mSubscribedAnalyzer;
+ final AtomicInteger mRelativeRotation;
+
+ private final Handler mUserHandler;
+
+ ImageAnalysisBlockingCallback(AtomicReference<ImageAnalysis.Analyzer> subscribedAnalyzer,
+ AtomicInteger relativeRotation, Handler userHandler) {
+ mSubscribedAnalyzer = subscribedAnalyzer;
+ mRelativeRotation = relativeRotation;
+ mUserHandler = userHandler;
+ }
+
+ @Override
+ public void onImageAvailable(ImageReaderProxy imageReaderProxy) {
+ ImageProxy image = imageReaderProxy.acquireNextImage();
+ if (image == null) {
+ return;
+ }
+ try {
+ mUserHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ ImageAnalysis.Analyzer analyzer = mSubscribedAnalyzer.get();
+ if (analyzer != null) {
+ analyzer.analyze(image, mRelativeRotation.get());
+ }
+ } finally {
+ image.close();
+ }
+ }
+ });
+ } catch (RuntimeException e) {
+ image.close();
+ }
+ }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisConfig.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisConfig.java
index d7c529b..b834cc7 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisConfig.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisConfig.java
@@ -573,7 +573,8 @@
}
/**
- * Sets the number of images available to the camera pipeline.
+ * Sets the number of images available to the camera pipeline for
+ * {@link ImageReaderMode#ACQUIRE_NEXT_IMAGE} mode.
*
* <p>The image queue depth is the number of images available to the camera to fill with
* data. This includes the image currently being analyzed by {@link
@@ -591,6 +592,10 @@
* single frame period for the current frame rate, on average, to avoid stalling the camera
* pipeline.
*
+ * <p> The value only applys to {@link ImageReaderMode#ACQUIRE_NEXT_IMAGE} mode.
+ * For {@link ImageReaderMode#ACQUIRE_LATEST_IMAGE} the value is overridden by default
+ * value.
+ *
* @param depth The total number of images available to the camera.
* @return The current Builder.
*/
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisNonBlockingCallback.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisNonBlockingCallback.java
new file mode 100644
index 0000000..1d226f4
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisNonBlockingCallback.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2019 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 android.os.Handler;
+import android.util.Log;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * OnImageAvailableListener with non-blocking behavior. Analyzes images in a non-blocking way by
+ * dropping images when analyzer is busy.
+ *
+ * <p> Used with {@link ImageAnalysis}.
+ */
+final class ImageAnalysisNonBlockingCallback implements ImageReaderProxy.OnImageAvailableListener {
+
+ private static final String TAG = "NonBlockingCallback";
+
+ final AtomicReference<ImageAnalysis.Analyzer> mSubscribedAnalyzer;
+ final AtomicInteger mRelativeRotation;
+
+ final Executor mBackgroundExecutor;
+ private final Handler mUserHandler;
+
+ // The cached image when analyzer is busy. Image removed from cache must be closed by 1) closing
+ // it directly or 2) re-posting it to close it eventually.
+ @GuardedBy("this")
+ private ImageProxy mCachedImage;
+
+ // Timestamp of the last image posted to user callback thread.
+ private final AtomicLong mPostedImageTimestamp;
+ // Timestamp of the last image finished being processed by user callback thread.
+ private final AtomicLong mFinishedImageTimestamp;
+
+ ImageAnalysisNonBlockingCallback(AtomicReference<ImageAnalysis.Analyzer> subscribedAnalyzer,
+ AtomicInteger relativeRotation, Handler userHandler,
+ Executor backgroundExecutor) {
+ mSubscribedAnalyzer = subscribedAnalyzer;
+ mRelativeRotation = relativeRotation;
+ mUserHandler = userHandler;
+ mBackgroundExecutor = backgroundExecutor;
+ mPostedImageTimestamp = new AtomicLong(-1);
+ mFinishedImageTimestamp = new AtomicLong(mPostedImageTimestamp.get());
+ }
+
+ @Override
+ public void onImageAvailable(ImageReaderProxy imageReaderProxy) {
+ ImageProxy imageProxy = imageReaderProxy.acquireLatestImage();
+ if (imageProxy == null) {
+ return;
+ }
+ analyze(imageProxy);
+ }
+
+ /**
+ * Removes cached image from cache and analyze it.
+ */
+ synchronized void analyzeCachedImage() {
+ if (mCachedImage != null) {
+ ImageProxy cachedImage = mCachedImage;
+ mCachedImage = null;
+ analyze(cachedImage);
+ }
+ }
+
+ /**
+ * This method guarantees closing the image by either 1) closing the image in the current
+ * thread, 2) caching it for later or 3) posting it to user Thread to close it.
+ *
+ * @param imageProxy the incoming image frame.
+ */
+ private synchronized void analyze(@NonNull ImageProxy imageProxy) {
+ long postedImageTimestamp = mPostedImageTimestamp.get();
+ long finishedImageTimestamp = mFinishedImageTimestamp.get();
+
+ if (imageProxy.getTimestamp() <= postedImageTimestamp) {
+ // Discard image that is in wrong order. Reposted cached image can be in this state.
+ imageProxy.close();
+ return;
+ }
+
+ if (postedImageTimestamp > finishedImageTimestamp) {
+ // If analyzer is busy, cache the new image.
+ if (mCachedImage != null) {
+ mCachedImage.close();
+ }
+ mCachedImage = imageProxy;
+ return;
+ }
+
+ mPostedImageTimestamp.set(imageProxy.getTimestamp());
+ try {
+ mUserHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ ImageAnalysis.Analyzer analyzer = mSubscribedAnalyzer.get();
+ if (analyzer != null) {
+ analyzer.analyze(imageProxy, mRelativeRotation.get());
+ }
+ } finally {
+ finishImage(imageProxy);
+ mBackgroundExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ analyzeCachedImage();
+ }
+ });
+ }
+ }
+ });
+ } catch (RuntimeException e) {
+ // Unblock if fails to post to user thread.
+ Log.e(TAG, "Error calling user callback", e);
+ finishImage(imageProxy);
+ }
+ }
+
+ synchronized void finishImage(ImageProxy imageProxy) {
+ mFinishedImageTimestamp.set(imageProxy.getTimestamp());
+ imageProxy.close();
+ }
+}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java b/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java
new file mode 100644
index 0000000..2e22966
--- /dev/null
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2019 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 com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.media.Image;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Size;
+
+import androidx.camera.core.impl.utils.executor.CameraXExecutors;
+import androidx.test.filters.MediumTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.internal.DoNotInstrument;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowLooper;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * Unit test for {@link ImageAnalysis}.
+ */
+@MediumTest
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP, shadows = {ShadowCameraX.class,
+ ShadowImageReader.class})
+public class ImageAnalysisTest {
+
+ private static final Size DEFAULT_RESOLUTION = new Size(640, 480);
+ private static final int QUEUE_DEPTH = 8;
+ private static final Image MOCK_IMAGE_1 = createMockImage(1);
+ private static final Image MOCK_IMAGE_2 = createMockImage(2);
+ private static final Image MOCK_IMAGE_3 = createMockImage(3);
+
+ private Handler mCallbackHandler;
+ private Handler mBackgroundHandler;
+ private Executor mBackgroundExecutor;
+ private List<Image> mImagesReceived;
+
+ @Before
+ public void setUp() {
+ HandlerThread callbackThread = new HandlerThread("Callback");
+ callbackThread.start();
+ mCallbackHandler = new Handler(callbackThread.getLooper());
+
+ HandlerThread backgroundThread = new HandlerThread("Background");
+ backgroundThread.start();
+ mBackgroundHandler = new Handler(backgroundThread.getLooper());
+ mBackgroundExecutor = CameraXExecutors.newHandlerExecutor(mBackgroundHandler);
+
+ mImagesReceived = new ArrayList<>();
+
+ ShadowImageReader.clear();
+ }
+
+ @Test
+ public void acquireLatestMode_doesNotBlock() {
+ // Arrange.
+ setUpImageAnalysisWithMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE);
+
+ // Act.
+ // Receive images from camera feed.
+ ShadowImageReader.triggerCallbackWithImage(MOCK_IMAGE_1);
+ flushHandler(mBackgroundHandler);
+ ShadowImageReader.triggerCallbackWithImage(MOCK_IMAGE_2);
+ flushHandler(mBackgroundHandler);
+ ShadowImageReader.triggerCallbackWithImage(MOCK_IMAGE_3);
+ flushHandler(mBackgroundHandler);
+
+ // Assert.
+ // No image is received because callback handler is blocked.
+ assertThat(mImagesReceived).isEmpty();
+
+ // Flush callback handler and image1 is received.
+ flushHandler(mCallbackHandler);
+ assertThat(mImagesReceived).containsExactly(MOCK_IMAGE_1);
+
+ // Flush both handlers and the previous cached image3 is received (image2 was dropped). The
+ // code alternates the 2 threads so they have to be both flushed to proceed.
+ flushHandler(mBackgroundHandler);
+ flushHandler(mCallbackHandler);
+ assertThat(mImagesReceived).containsExactly(MOCK_IMAGE_1, MOCK_IMAGE_3);
+
+ // Flush both handlers and no more frame.
+ flushHandler(mBackgroundHandler);
+ flushHandler(mCallbackHandler);
+ assertThat(mImagesReceived).containsExactly(MOCK_IMAGE_1, MOCK_IMAGE_3);
+ }
+
+ @Test
+ public void acquireNextMode_doesNotDropFrames() {
+ // Arrange.
+ setUpImageAnalysisWithMode(ImageAnalysis.ImageReaderMode.ACQUIRE_NEXT_IMAGE);
+
+ // Act.
+ // Receive images from camera feed.
+ ShadowImageReader.triggerCallbackWithImage(MOCK_IMAGE_1);
+ flushHandler(mBackgroundHandler);
+ ShadowImageReader.triggerCallbackWithImage(MOCK_IMAGE_2);
+ flushHandler(mBackgroundHandler);
+ ShadowImageReader.triggerCallbackWithImage(MOCK_IMAGE_3);
+ flushHandler(mBackgroundHandler);
+
+ // Assert.
+ // No image is received because callback handler is blocked.
+ assertThat(mImagesReceived).isEmpty();
+
+ // Flush callback handler and 3 frames received.
+ flushHandler(mCallbackHandler);
+ assertThat(mImagesReceived).containsExactly(MOCK_IMAGE_1, MOCK_IMAGE_2, MOCK_IMAGE_3);
+ }
+
+ private void setUpImageAnalysisWithMode(ImageAnalysis.ImageReaderMode imageReaderMode) {
+ ImageAnalysis imageAnalysis = new ImageAnalysis(new ImageAnalysisConfig.Builder()
+ .setCallbackHandler(mCallbackHandler)
+ .setBackgroundExecutor(mBackgroundExecutor)
+ .setImageQueueDepth(QUEUE_DEPTH)
+ .setImageReaderMode(imageReaderMode)
+ .build());
+
+ imageAnalysis.setAnalyzer(new ImageAnalysis.Analyzer() {
+ @Override
+ public void analyze(ImageProxy image, int rotationDegrees) {
+ mImagesReceived.add(image.getImage());
+ }
+ });
+
+ Map<String, Size> suggestedResolutionMap = new HashMap<>();
+ suggestedResolutionMap.put(ShadowCameraX.DEFAULT_CAMERA_ID, DEFAULT_RESOLUTION);
+ imageAnalysis.updateSuggestedResolution(suggestedResolutionMap);
+ }
+
+ /**
+ * Flushes a {@link Handler} to run all pending tasks.
+ *
+ * @param handler the {@link Handler} to flush.
+ */
+ private static void flushHandler(Handler handler) {
+ ((ShadowLooper) Shadow.extract(handler.getLooper())).idle();
+ }
+
+ private static Image createMockImage(long timestamp) {
+ Image mockImage = mock(Image.class);
+ when(mockImage.getTimestamp()).thenReturn(timestamp);
+ return mockImage;
+ }
+}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ShadowCameraX.java b/camera/camera-core/src/test/java/androidx/camera/core/ShadowCameraX.java
new file mode 100644
index 0000000..fd893b4
--- /dev/null
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ShadowCameraX.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2019 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 org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+/**
+ * A Robolectric shadow of {@link CameraX}.
+ */
+@Implements(CameraX.class)
+public class ShadowCameraX {
+ public static final String DEFAULT_CAMERA_ID = "DEFAULT_CAMERA_ID";
+
+ private static final UseCaseConfig<ImageAnalysis> DEFAULT_IMAGE_ANALYSIS_CONFIG =
+ new ImageAnalysisConfig.Builder().setSessionOptionUnpacker(
+ new SessionConfig.OptionUnpacker() {
+ @Override
+ public void unpack(UseCaseConfig<?> config, SessionConfig.Builder builder) {
+ // no op.
+ }
+ }).build();
+
+ private static final CameraInfo DEFAULT_CAMERA_INFO = new CameraInfo() {
+
+ @Override
+ public CameraX.LensFacing getLensFacing() {
+ return CameraX.LensFacing.BACK;
+ }
+
+ @Override
+ public int getSensorRotationDegrees() {
+ return 0;
+ }
+
+ @Override
+ public int getSensorRotationDegrees(int relativeRotation) {
+ return 0;
+ }
+ };
+
+ /**
+ * Shadow of {@link ShadowCameraX#getCameraWithCameraDeviceConfig(CameraDeviceConfig)}.
+ */
+ @Implementation
+ public static String getCameraWithCameraDeviceConfig(CameraDeviceConfig config) {
+ return DEFAULT_CAMERA_ID;
+ }
+
+ /**
+ * Shadow of {@link CameraX#getCameraInfo(String)}.
+ */
+ @Implementation
+ public static CameraInfo getCameraInfo(String cameraId) throws CameraInfoUnavailableException {
+ return DEFAULT_CAMERA_INFO;
+ }
+
+ /**
+ * Shadow of {@link CameraX#getDefaultUseCaseConfig(Class, CameraX.LensFacing)}.
+ */
+ @SuppressWarnings("unchecked")
+ @Implementation
+ public static <C extends UseCaseConfig<?>> C getDefaultUseCaseConfig(
+ Class<C> configType, CameraX.LensFacing lensFacing) {
+ return (C) DEFAULT_IMAGE_ANALYSIS_CONFIG;
+ }
+
+}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ShadowImageReader.java b/camera/camera-core/src/test/java/androidx/camera/core/ShadowImageReader.java
new file mode 100644
index 0000000..40b067d
--- /dev/null
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ShadowImageReader.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2019 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 android.media.Image;
+import android.media.ImageReader;
+import android.os.Build;
+import android.os.Handler;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadow.api.Shadow;
+
+/**
+ * A Robolectric shadow of {@link ImageReader}.
+ */
+@Implements(ImageReader.class)
+public class ShadowImageReader {
+
+ // Image to return when user call acquireLatestImage() or acquireNextImage().
+ private static Image sIncomingImage;
+
+ @Nullable
+ private volatile ImageReader.OnImageAvailableListener mListener;
+
+ private static ImageReader sImageReader;
+ private static ShadowImageReader sShadowImageReader;
+
+ /**
+ * Shadow of {@link ImageReader#newInstance(int, int, int, int)}.
+ */
+ @Implementation
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ public static ImageReader newInstance(int width, int height, int format, int maxImages) {
+ sImageReader = Shadow.newInstanceOf(ImageReader.class);
+ sShadowImageReader = Shadow.extract(sImageReader);
+ return sImageReader;
+ }
+
+ /**
+ * Sets the incoming image and triggers
+ * {@link ImageReader.OnImageAvailableListener#onImageAvailable(ImageReader)}.
+ */
+ public static void triggerCallbackWithImage(Image mockImage) {
+ sIncomingImage = mockImage;
+ sShadowImageReader.getListener().onImageAvailable(sImageReader);
+ }
+
+ /**
+ * Clears incoming images.
+ */
+ public static void clear() {
+ sIncomingImage = null;
+ sImageReader = null;
+ sShadowImageReader = null;
+ }
+
+
+ /**
+ * Shadow of {@link ImageReader#setOnImageAvailableListener}.
+ */
+ @Implementation
+ public void setOnImageAvailableListener(ImageReader.OnImageAvailableListener listener,
+ Handler handler) {
+ this.mListener = listener;
+ }
+
+ /**
+ * Shadow of {@link ImageReader#acquireLatestImage()}.
+ */
+ @Implementation
+ public Image acquireLatestImage() {
+ return popIncomingImage();
+ }
+
+ /**
+ * Shadow of {@link ImageReader#acquireNextImage()}.
+ */
+ @Implementation
+ public Image acquireNextImage() {
+ return popIncomingImage();
+ }
+
+ private Image popIncomingImage() {
+ try {
+ return sIncomingImage;
+ } finally {
+ sIncomingImage = null;
+ }
+ }
+
+ /**
+ * Returns the last OnImageAvailableListener that was passed in call to
+ * setOnImageAvailableListener or null if never called.
+ */
+ @Nullable
+ public ImageReader.OnImageAvailableListener getListener() {
+ return mListener;
+ }
+}
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsErrorListenerTest.java b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsErrorListenerTest.java
index e45ab82..1cc603b 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsErrorListenerTest.java
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsErrorListenerTest.java
@@ -24,6 +24,7 @@
import android.Manifest;
import android.content.Context;
+import android.os.Build;
import androidx.annotation.NonNull;
import androidx.camera.core.AppConfig;
@@ -96,6 +97,10 @@
@Before
public void setUp() {
+ // Ignore the tests if SDK is before M since extension implementation is only supported
+ // after M.
+ assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M);
+
Context context = ApplicationProvider.getApplicationContext();
CameraDeviceSurfaceManager surfaceManager = new FakeCameraDeviceSurfaceManager();
ExtendableUseCaseConfigFactory defaultConfigFactory = new ExtendableUseCaseConfigFactory();
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/CameraUtil.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/CameraUtil.java
index b85eb35..b0898c7 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/CameraUtil.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/CameraUtil.java
@@ -22,10 +22,14 @@
import android.hardware.camera2.CameraManager;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.core.CameraDeviceConfig;
import androidx.camera.core.CameraInfoUnavailableException;
import androidx.camera.core.CameraX;
+import androidx.camera.core.LensFacingCameraIdFilter;
+
+import java.util.Set;
/**
* Utility functions for accessing camera related parameters
@@ -44,6 +48,17 @@
}
}
+ @NonNull
+ static Set<String> getCameraIdSetWithLensFacing(CameraX.LensFacing lensFacing)
+ throws CameraInfoUnavailableException {
+ Set<String> availableCameraIds = CameraX.getCameraFactory().getAvailableCameraIds();
+ LensFacingCameraIdFilter lensFacingCameraIdFilter =
+ LensFacingCameraIdFilter.createLensFacingCameraIdFilter(lensFacing);
+ availableCameraIds = lensFacingCameraIdFilter.filter(availableCameraIds);
+
+ return availableCameraIds;
+ }
+
static CameraCharacteristics getCameraCharacteristics(String cameraId) {
Context context = CameraX.getContext();
CameraManager cameraManager = (CameraManager) context.getSystemService(
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionsManager.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionsManager.java
index 4e1613a..64378b6 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionsManager.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionsManager.java
@@ -105,22 +105,23 @@
LensFacing lensFacing) {
ImageCaptureConfig.Builder builder = new ImageCaptureConfig.Builder();
builder.setLensFacing(lensFacing);
+ ImageCaptureExtender extender;
switch (effectMode) {
case BOKEH:
- BokehImageCaptureExtender.create(builder);
+ extender = BokehImageCaptureExtender.create(builder);
break;
case HDR:
- HdrImageCaptureExtender.create(builder);
+ extender = HdrImageCaptureExtender.create(builder);
break;
case NIGHT:
- NightImageCaptureExtender.create(builder);
+ extender = NightImageCaptureExtender.create(builder);
break;
case BEAUTY:
- BeautyImageCaptureExtender.create(builder);
+ extender = BeautyImageCaptureExtender.create(builder);
break;
case AUTO:
- AutoImageCaptureExtender.create(builder);
+ extender = AutoImageCaptureExtender.create(builder);
break;
case NORMAL:
return true;
@@ -128,9 +129,7 @@
return false;
}
- String cameraId = CameraUtil.getCameraId(builder.build());
-
- return cameraId != null;
+ return extender.isExtensionAvailable();
}
/**
@@ -163,22 +162,23 @@
LensFacing lensFacing) {
PreviewConfig.Builder builder = new PreviewConfig.Builder();
builder.setLensFacing(lensFacing);
+ PreviewExtender extender;
switch (effectMode) {
case BOKEH:
- BokehPreviewExtender.create(builder);
+ extender = BokehPreviewExtender.create(builder);
break;
case HDR:
- HdrPreviewExtender.create(builder);
+ extender = HdrPreviewExtender.create(builder);
break;
case NIGHT:
- NightPreviewExtender.create(builder);
+ extender = NightPreviewExtender.create(builder);
break;
case BEAUTY:
- BeautyPreviewExtender.create(builder);
+ extender = BeautyPreviewExtender.create(builder);
break;
case AUTO:
- AutoPreviewExtender.create(builder);
+ extender = AutoPreviewExtender.create(builder);
break;
case NORMAL:
return true;
@@ -186,9 +186,7 @@
return false;
}
- String cameraId = CameraUtil.getCameraId(builder.build());
-
- return cameraId != null;
+ return extender.isExtensionAvailable();
}
private ExtensionsManager() {
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ImageCaptureExtender.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ImageCaptureExtender.java
index e4d59ec..2d0e31e 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ImageCaptureExtender.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ImageCaptureExtender.java
@@ -27,6 +27,7 @@
import androidx.camera.camera2.impl.CameraEventCallbacks;
import androidx.camera.core.CameraIdFilter;
import androidx.camera.core.CameraIdFilterSet;
+import androidx.camera.core.CameraInfoUnavailableException;
import androidx.camera.core.CameraX;
import androidx.camera.core.CaptureBundle;
import androidx.camera.core.CaptureConfig;
@@ -43,6 +44,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -61,17 +63,6 @@
mBuilder = builder;
mImpl = implementation;
mEffectMode = effectMode;
-
- ExtensionCameraIdFilter extensionCameraIdFilter = new ExtensionCameraIdFilter(mImpl);
- CameraIdFilter cameraIdFilter = mBuilder.build().getCameraIdFilter(null);
- if (cameraIdFilter == null) {
- mBuilder.setCameraIdFilter(extensionCameraIdFilter);
- } else {
- CameraIdFilterSet cameraIdFilterSet = new CameraIdFilterSet();
- cameraIdFilterSet.addCameraIdFilter(cameraIdFilter);
- cameraIdFilterSet.addCameraIdFilter(extensionCameraIdFilter);
- mBuilder.setCameraIdFilter(cameraIdFilterSet);
- }
}
/**
@@ -80,8 +71,18 @@
* @return True if the specific extension function is supported for the camera device.
*/
public boolean isExtensionAvailable() {
- String cameraId = CameraUtil.getCameraId(mBuilder.build());
- return cameraId != null;
+ CameraX.LensFacing lensFacing = mBuilder.build().getLensFacing();
+ Set<String> availableCameraIds = null;
+ try {
+ availableCameraIds = CameraUtil.getCameraIdSetWithLensFacing(lensFacing);
+ } catch (CameraInfoUnavailableException e) {
+ // Returns false if camera info is unavailable.
+ return false;
+ }
+ ExtensionCameraIdFilter extensionCameraIdFilter = new ExtensionCameraIdFilter(mImpl);
+ availableCameraIds = extensionCameraIdFilter.filter(availableCameraIds);
+
+ return !availableCameraIds.isEmpty();
}
/**
@@ -92,6 +93,18 @@
* enabled together.
*/
public void enableExtension() {
+ // Add extension camera id filter to config.
+ ExtensionCameraIdFilter extensionCameraIdFilter = new ExtensionCameraIdFilter(mImpl);
+ CameraIdFilter currentCameraIdFilter = mBuilder.build().getCameraIdFilter(null);
+ if (currentCameraIdFilter == null) {
+ mBuilder.setCameraIdFilter(extensionCameraIdFilter);
+ } else {
+ CameraIdFilterSet cameraIdFilterSet = new CameraIdFilterSet();
+ cameraIdFilterSet.addCameraIdFilter(currentCameraIdFilter);
+ cameraIdFilterSet.addCameraIdFilter(extensionCameraIdFilter);
+ mBuilder.setCameraIdFilter(cameraIdFilterSet);
+ }
+
String cameraId = CameraUtil.getCameraId(mBuilder.build());
if (cameraId == null) {
// If there's no available camera id for the extender to function, just return here
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/PreviewExtender.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/PreviewExtender.java
index 955cbd9..6f9beb4 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/PreviewExtender.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/PreviewExtender.java
@@ -32,6 +32,7 @@
import androidx.camera.core.CameraCaptureResults;
import androidx.camera.core.CameraIdFilter;
import androidx.camera.core.CameraIdFilterSet;
+import androidx.camera.core.CameraInfoUnavailableException;
import androidx.camera.core.CameraX;
import androidx.camera.core.CaptureConfig;
import androidx.camera.core.CaptureStage;
@@ -48,6 +49,7 @@
import androidx.camera.extensions.impl.RequestUpdateProcessorImpl;
import java.util.Collection;
+import java.util.Set;
/**
* Class for using an OEM provided extension on preview.
@@ -65,17 +67,6 @@
mBuilder = builder;
mImpl = implementation;
mEffectMode = effectMode;
-
- ExtensionCameraIdFilter extensionCameraIdFilter = new ExtensionCameraIdFilter(mImpl);
- CameraIdFilter cameraIdFilter = mBuilder.build().getCameraIdFilter(null);
- if (cameraIdFilter == null) {
- mBuilder.setCameraIdFilter(extensionCameraIdFilter);
- } else {
- CameraIdFilterSet cameraIdFilterSet = new CameraIdFilterSet();
- cameraIdFilterSet.addCameraIdFilter(cameraIdFilter);
- cameraIdFilterSet.addCameraIdFilter(extensionCameraIdFilter);
- mBuilder.setCameraIdFilter(cameraIdFilterSet);
- }
}
/**
@@ -85,8 +76,18 @@
* @return True if the specific extension function is supported for the camera device.
*/
public boolean isExtensionAvailable() {
- String cameraId = CameraUtil.getCameraId(mBuilder.build());
- return cameraId != null;
+ CameraX.LensFacing lensFacing = mBuilder.build().getLensFacing();
+ Set<String> availableCameraIds = null;
+ try {
+ availableCameraIds = CameraUtil.getCameraIdSetWithLensFacing(lensFacing);
+ } catch (CameraInfoUnavailableException e) {
+ // Returns false if camera info is unavailable.
+ return false;
+ }
+ ExtensionCameraIdFilter extensionCameraIdFilter = new ExtensionCameraIdFilter(mImpl);
+ availableCameraIds = extensionCameraIdFilter.filter(availableCameraIds);
+
+ return !availableCameraIds.isEmpty();
}
/**
@@ -97,6 +98,18 @@
* extension is not enabled together.
*/
public void enableExtension() {
+ // Add extension camera id filter to config.
+ ExtensionCameraIdFilter extensionCameraIdFilter = new ExtensionCameraIdFilter(mImpl);
+ CameraIdFilter currentCameraIdFilter = mBuilder.build().getCameraIdFilter(null);
+ if (currentCameraIdFilter == null) {
+ mBuilder.setCameraIdFilter(extensionCameraIdFilter);
+ } else {
+ CameraIdFilterSet cameraIdFilterSet = new CameraIdFilterSet();
+ cameraIdFilterSet.addCameraIdFilter(currentCameraIdFilter);
+ cameraIdFilterSet.addCameraIdFilter(extensionCameraIdFilter);
+ mBuilder.setCameraIdFilter(cameraIdFilterSet);
+ }
+
String cameraId = CameraUtil.getCameraId(mBuilder.build());
if (cameraId == null) {
// If there's no available camera id for the extender to function, just return here
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ToggleButtonTest.java b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ToggleButtonTest.java
index 1e65e192..8fe00cf 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ToggleButtonTest.java
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ToggleButtonTest.java
@@ -53,10 +53,11 @@
public final class ToggleButtonTest {
private static final int DISMISS_LOCK_SCREEN_CODE = 82;
+ private static final String BASIC_SAMPLE_PACKAGE = "androidx.camera.integration.extensions";
@Rule
public ActivityTestRule<CameraExtensionsActivity> mActivityRule =
- new ActivityTestRule<>(CameraExtensionsActivity.class);
+ new ActivityTestRule<>(CameraExtensionsActivity.class, true, false);
@Rule
public GrantPermissionRule mCameraPermissionRule =
@@ -85,6 +86,11 @@
// Close system dialogs first to avoid interrupt.
ApplicationProvider.getApplicationContext().sendBroadcast(
new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+
+ Intent intent = ApplicationProvider.getApplicationContext().getPackageManager()
+ .getLaunchIntentForPackage(BASIC_SAMPLE_PACKAGE);
+
+ mActivityRule.launchActivity(intent);
}
@After
diff --git a/camera/integration-tests/extensionstestapp/src/main/AndroidManifest.xml b/camera/integration-tests/extensionstestapp/src/main/AndroidManifest.xml
index f7ceba1..4916933 100644
--- a/camera/integration-tests/extensionstestapp/src/main/AndroidManifest.xml
+++ b/camera/integration-tests/extensionstestapp/src/main/AndroidManifest.xml
@@ -20,10 +20,6 @@
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
- <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
- <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
- <uses-permission android:name="android.permission.WRITE_MEDIA_IMAGES" />
- <uses-permission android:name="android.permission.WRITE_MEDIA_VIDEO" />
<uses-feature android:name="android.hardware.camera" />
diff --git a/car/core/api/api_lint.ignore b/car/core/api/api_lint.ignore
index ce9444c..5a9daf1 100644
--- a/car/core/api/api_lint.ignore
+++ b/car/core/api/api_lint.ignore
@@ -1,5 +1,3 @@
// Baseline format: 1.0
KotlinOperator: androidx.car.widget.ListItemProvider#get(int):
Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.car.widget.ListItemProvider.ListProvider#get(int):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
diff --git a/collection/collection/api/api_lint.ignore b/collection/collection/api/api_lint.ignore
index 589b899..c01fd32 100644
--- a/collection/collection/api/api_lint.ignore
+++ b/collection/collection/api/api_lint.ignore
@@ -1,6 +1,4 @@
// Baseline format: 1.0
-KotlinOperator: androidx.collection.ArraySet#contains(Object):
- Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
KotlinOperator: androidx.collection.CircularArray#get(int):
Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
KotlinOperator: androidx.collection.CircularIntArray#get(int):
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxTypeResolutionTests.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxTypeResolutionTests.kt
index 1974f9d..e4bb666 100644
--- a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxTypeResolutionTests.kt
+++ b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxTypeResolutionTests.kt
@@ -160,10 +160,10 @@
@Composable fun test(
- @Children children: @Composable() () -> Unit,
+ children: @Composable() () -> Unit,
value: Int,
x: Int,
- @Children children2: @Composable() () -> Unit,
+ children2: @Composable() () -> Unit,
value2: Int
) {
<LinearLayout>
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/analysis/ChildrenAnnotationTest.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/analysis/ChildrenAnnotationTest.kt
new file mode 100644
index 0000000..dedcd26
--- /dev/null
+++ b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/analysis/ChildrenAnnotationTest.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.plugins.kotlin.analysis
+
+import androidx.compose.plugins.kotlin.AbstractComposeDiagnosticsTest
+
+class ChildrenAnnotationTest : AbstractComposeDiagnosticsTest() {
+
+ fun testReportChildrenOnWrongParameter() {
+ doTest("""
+ import androidx.compose.*;
+
+ @Composable fun MyWidget(<!CHILDREN_MUST_BE_LAST!>@Children children: ()->Unit<!>, value: Int) {
+ System.out.println(""+children+value)
+ }
+ """)
+ }
+}
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ChildAnnotationChecker.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ChildAnnotationChecker.kt
new file mode 100644
index 0000000..c618a31
--- /dev/null
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ChildAnnotationChecker.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.plugins.kotlin
+
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.descriptors.FunctionDescriptor
+import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
+import org.jetbrains.kotlin.psi.KtDeclaration
+import org.jetbrains.kotlin.psi.KtElement
+import androidx.compose.plugins.kotlin.analysis.ComposeErrors
+import org.jetbrains.kotlin.container.StorageComponentContainer
+import org.jetbrains.kotlin.container.useInstance
+import org.jetbrains.kotlin.descriptors.ModuleDescriptor
+import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
+import org.jetbrains.kotlin.resolve.TargetPlatform
+import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
+import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
+import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
+
+open class ChildAnnotationChecker() : DeclarationChecker, StorageComponentContainerContributor {
+ override fun check(
+ declaration: KtDeclaration,
+ descriptor: DeclarationDescriptor,
+ context: DeclarationCheckerContext
+ ) {
+ if(descriptor is FunctionDescriptor) {
+ descriptor.valueParameters.forEachIndexed { index, param ->
+ if(param.hasChildrenAnnotation() && index != descriptor.valueParameters.lastIndex) {
+ context.trace.report(ComposeErrors.CHILDREN_MUST_BE_LAST.on(param.findPsi() as KtElement))
+ }
+ }
+ }
+ }
+
+ override fun registerModuleComponents(
+ container: StorageComponentContainer,
+ platform: TargetPlatform,
+ moduleDescriptor: ModuleDescriptor
+ ) {
+ if (platform != JvmPlatform) return
+ container.useInstance(this)
+ }
+}
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposePlugin.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposePlugin.kt
index e046b68c..bab14b9 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposePlugin.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposePlugin.kt
@@ -111,6 +111,10 @@
)
StorageComponentContainerContributor.registerExtension(
project,
+ ChildAnnotationChecker()
+ )
+ StorageComponentContainerContributor.registerExtension(
+ project,
UnionAnnotationCheckerProvider()
)
KtxParsingExtension.registerExtension(project,
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ResolvedKtxElementCall.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ResolvedKtxElementCall.kt
index d5e819f..ed88c3c 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ResolvedKtxElementCall.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ResolvedKtxElementCall.kt
@@ -238,13 +238,13 @@
collector: MutableMap<String, MutableList<AttributeMeta>>
) {
callDescriptor?.let {
- it.valueParameters.forEach { param ->
+ it.valueParameters.forEachIndexed { index, param ->
collector.multiPut(
AttributeMeta(
name = param.name.asString(),
type = param.type,
descriptor = param,
- isChildren = param.hasChildrenAnnotation()
+ isChildren = param.hasChildrenAnnotation() || it.valueParameters.size-1 == index
)
)
}
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeDefaultErrorMessages.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeDefaultErrorMessages.kt
index 68acda7..ced33ab 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeDefaultErrorMessages.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeDefaultErrorMessages.kt
@@ -157,5 +157,10 @@
"Ambiguous targets. {0}",
Renderers.AMBIGUOUS_CALLS
)
+ MAP.put(
+ ComposeErrors.CHILDREN_MUST_BE_LAST,
+ "Children annotation must only occur on last parameter. This annotation is deprecated (move children to " +
+ "be last parameter, make it @Composable, and remove the @Children annotation.)."
+ )
}
}
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeErrors.java b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeErrors.java
index 467ab57..a9b2802 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeErrors.java
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeErrors.java
@@ -94,6 +94,8 @@
MISSING_REQUIRED_CHILDREN = DiagnosticFactory1.create(ERROR);
DiagnosticFactory2<KtExpression, Collection<KotlinType>, Collection<KotlinType>>
ILLEGAL_ASSIGN_TO_UNIONTYPE = DiagnosticFactory2.create(ERROR);
+ DiagnosticFactory0<KtElement>
+ CHILDREN_MUST_BE_LAST = DiagnosticFactory0.create(ERROR);
@SuppressWarnings("UnusedDeclaration")
Object INITIALIZER = new Object() {
diff --git a/compose/compose-runtime/compose-runtime-benchmark/build.gradle b/compose/compose-runtime/compose-runtime-benchmark/build.gradle
index dcb32f6..22c1201 100644
--- a/compose/compose-runtime/compose-runtime-benchmark/build.gradle
+++ b/compose/compose-runtime/compose-runtime-benchmark/build.gradle
@@ -30,6 +30,10 @@
android {
defaultConfig {
minSdkVersion 21
+
+ // Work-around for setting the testBuildType to "release" for benchmark projects causes gradle import get
+ // confused.
+ testBuildType = "debug"
}
lintOptions {
disable("SyntheticAccessor")
diff --git a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/AndroidManifest.xml b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/AndroidManifest.xml
index 4bba238..e48051b 100644
--- a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/AndroidManifest.xml
+++ b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/AndroidManifest.xml
@@ -18,8 +18,12 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="androidx.compose.benchmark">
- <!-- Important: disable debuggable for accurate performance results -->
+ <!--
+ ~ Important: disable debuggable for accurate performance results
+ ~ requestLegacyExternalStorage to enable legacy JSON reporting when targeting Q
+ -->
<application
+ android:requestLegacyExternalStorage="true"
android:debuggable="false"
tools:replace="android:debuggable">
<activity
diff --git a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/ComposeBenchmarkBase.kt b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/ComposeBenchmarkBase.kt
index 8d72e02..29fd820 100644
--- a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/ComposeBenchmarkBase.kt
+++ b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/ComposeBenchmarkBase.kt
@@ -37,17 +37,17 @@
val activityRule = ActivityTestRule(ComposeActivity::class.java)
fun measureCompose(block: @Composable() () -> Unit) {
+ val activity = activityRule.activity
benchmarkRule.measureRepeated {
- val activity = activityRule.activity
-
activity.setContent {
block()
}
runWithTimingDisabled {
- activity.disposeComposition()
+ activity.setContent { }
}
}
+ activity.disposeComposition()
}
fun measureRecompose(block: RecomposeReceiver.() -> Unit) {
diff --git a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/dbmonster/DbMonster.kt b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/dbmonster/DbMonster.kt
index 6525704..da720b8 100644
--- a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/dbmonster/DbMonster.kt
+++ b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/dbmonster/DbMonster.kt
@@ -73,7 +73,7 @@
}
@Composable
-fun Table(@Children children: @Composable() () -> Unit) {
+fun Table(children: @Composable() () -> Unit) {
Column { children() }
}
diff --git a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/deeptree/DeepTree.kt b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/deeptree/DeepTree.kt
index 7712cee..c7667bc 100644
--- a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/deeptree/DeepTree.kt
+++ b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/deeptree/DeepTree.kt
@@ -36,7 +36,7 @@
}
@Composable
-fun Stack(vertical: Boolean, @Children children: @Composable() () -> Unit) {
+fun Stack(vertical: Boolean, children: @Composable() () -> Unit) {
if (vertical) {
Column { children() }
} else {
@@ -45,7 +45,7 @@
}
@Composable
-fun Container(@Children children: @Composable() () -> Unit) {
+fun Container(children: @Composable() () -> Unit) {
// non-layout node component. just adds depth to the composition hierarchy.
children()
}
diff --git a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/siblings/SiblingManagement.kt b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/siblings/SiblingManagement.kt
index 6b1d4b7..f91235e 100644
--- a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/siblings/SiblingManagement.kt
+++ b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/siblings/SiblingManagement.kt
@@ -31,7 +31,7 @@
import kotlin.random.Random
@Composable
-fun Stack(@Children children: @Composable() () -> Unit) {
+fun Stack(children: @Composable() () -> Unit) {
Column {
children()
}
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Ambient.kt b/compose/compose-runtime/src/main/java/androidx/compose/Ambient.kt
index 806b474f..ebb19f5 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/Ambient.kt
+++ b/compose/compose-runtime/src/main/java/androidx/compose/Ambient.kt
@@ -113,8 +113,7 @@
@Composable
fun Provider(
value: T,
- @Children
- children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
with(currentComposerNonNull) {
val holder = +memo {
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Composer.kt b/compose/compose-runtime/src/main/java/androidx/compose/Composer.kt
index e5fb5ec..f92a9e5 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/Composer.kt
+++ b/compose/compose-runtime/src/main/java/androidx/compose/Composer.kt
@@ -834,7 +834,8 @@
if (action == END_NODE) recordUp()
recordEnd(action)
- if (slots.inEmpty) {
+ val inserting = slots.inEmpty
+ if (inserting) {
slots.endEmpty()
if (!slots.inEmpty) recordOperation { _, slots, _ -> slots.endInsert() }
}
@@ -847,7 +848,8 @@
previousPending?.let<Pending, Unit> { previous ->
// Update the parent count of nodes
previous.updateNodeCount(pending?.parentKeyInfo, expectedNodeCount)
- previous.groupIndex++
+ if (!inserting)
+ previous.groupIndex++
}
this.pending = previousPending
this.parentKeyInfo = keyStack.pop()
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Key.kt b/compose/compose-runtime/src/main/java/androidx/compose/Key.kt
index 00c0414..41e9c56 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/Key.kt
+++ b/compose/compose-runtime/src/main/java/androidx/compose/Key.kt
@@ -71,6 +71,6 @@
@Composable
@Suppress("PLUGIN_ERROR")
/* inline */
-fun Key(@Suppress("UNUSED_PARAMETER") @Pivotal key: Any?, @Children children: @Composable() () -> Unit) {
+fun Key(@Suppress("UNUSED_PARAMETER") @Pivotal key: Any?, children: @Composable() () -> Unit) {
children()
}
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Observe.kt b/compose/compose-runtime/src/main/java/androidx/compose/Observe.kt
index 3a05644..2be1010 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/Observe.kt
+++ b/compose/compose-runtime/src/main/java/androidx/compose/Observe.kt
@@ -31,7 +31,7 @@
*/
@Composable
@Suppress("PLUGIN_ERROR")
-fun Observe(@Children body: @Composable() () -> Unit) =
+fun Observe(body: @Composable() () -> Unit) =
currentComposerNonNull.let { composer ->
trace("Compose:Observe") {
composer.startGroup(observer)
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Recompose.kt b/compose/compose-runtime/src/main/java/androidx/compose/Recompose.kt
index 421d431..fb57067 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/Recompose.kt
+++ b/compose/compose-runtime/src/main/java/androidx/compose/Recompose.kt
@@ -59,7 +59,7 @@
* @see invalidate
*/
@Composable
-fun Recompose(@Children body: @Composable() (recompose: () -> Unit) -> Unit) {
+fun Recompose(body: @Composable() (recompose: () -> Unit) -> Unit) {
val composer = currentComposerNonNull
val recomposer = RecomposeHelper()
val callback = composer.startJoin(false) {
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/CompositionTests.kt b/compose/compose-runtime/src/test/java/androidx/compose/CompositionTests.kt
index 6a58b69..32fdd69 100644
--- a/compose/compose-runtime/src/test/java/androidx/compose/CompositionTests.kt
+++ b/compose/compose-runtime/src/test/java/androidx/compose/CompositionTests.kt
@@ -210,6 +210,52 @@
}
}
+ fun testReplace() {
+ var includeA = true
+ fun MockViewComposition.composition() {
+ text("Before")
+ if (includeA) {
+ linear {
+ text("A")
+ }
+ } else {
+ edit("B")
+ }
+ text("After")
+ }
+ fun MockViewValidator.composition() {
+ text("Before")
+ if (includeA) {
+ linear {
+ text("A")
+ }
+ } else {
+ edit("B")
+ }
+ text("After")
+ }
+ val composer = compose {
+ composition()
+ }
+ validate(composer.root) {
+ composition()
+ }
+ includeA = false
+ compose(composer) {
+ composition()
+ }
+ validate(composer.root) {
+ composition()
+ }
+ includeA = true
+ compose(composer) {
+ composition()
+ }
+ validate(composer.root) {
+ composition()
+ }
+ }
+
fun testInsertWithMultipleRoots() {
val chars = listOf('a', 'b', 'c')
diff --git a/coordinatorlayout/src/main/java/androidx/coordinatorlayout/widget/CoordinatorLayout.java b/coordinatorlayout/src/main/java/androidx/coordinatorlayout/widget/CoordinatorLayout.java
index 2eb7405..4a30d17 100644
--- a/coordinatorlayout/src/main/java/androidx/coordinatorlayout/widget/CoordinatorLayout.java
+++ b/coordinatorlayout/src/main/java/androidx/coordinatorlayout/widget/CoordinatorLayout.java
@@ -246,6 +246,12 @@
setupForInsets();
super.setOnHierarchyChangeListener(new HierarchyChangeListener());
+
+ if (ViewCompat.getImportantForAccessibility(this)
+ == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ ViewCompat.setImportantForAccessibility(this,
+ ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
}
@Override
diff --git a/core/core/api/1.2.0-alpha03.txt b/core/core/api/1.2.0-alpha03.txt
index f03894b..fe4b834 100644
--- a/core/core/api/1.2.0-alpha03.txt
+++ b/core/core/api/1.2.0-alpha03.txt
@@ -1610,7 +1610,7 @@
}
public interface Predicate<T> {
- method public boolean test(T);
+ method public boolean test(T?);
}
public interface Supplier<T> {
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index f03894b..fe4b834 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -1610,7 +1610,7 @@
}
public interface Predicate<T> {
- method public boolean test(T);
+ method public boolean test(T?);
}
public interface Supplier<T> {
diff --git a/core/core/api/restricted_1.2.0-alpha03.txt b/core/core/api/restricted_1.2.0-alpha03.txt
index 138fb9f..fe706a7 100644
--- a/core/core/api/restricted_1.2.0-alpha03.txt
+++ b/core/core/api/restricted_1.2.0-alpha03.txt
@@ -138,9 +138,10 @@
method public static void putBinder(android.os.Bundle, String?, android.os.IBinder?);
}
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ComponentActivity extends android.app.Activity implements androidx.core.view.KeyEventDispatcher.Component {
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ComponentActivity extends android.app.Activity implements androidx.core.view.KeyEventDispatcher.Component androidx.lifecycle.LifecycleOwner {
ctor public ComponentActivity();
method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T extends androidx.core.app.ComponentActivity.ExtraData> T! getExtraData(Class<T!>!);
+ method public androidx.lifecycle.Lifecycle getLifecycle();
method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void putExtraData(androidx.core.app.ComponentActivity.ExtraData!);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean superDispatchKeyEvent(android.view.KeyEvent!);
}
@@ -1968,7 +1969,7 @@
}
public interface Predicate<T> {
- method public boolean test(T);
+ method public boolean test(T?);
}
public interface Supplier<T> {
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index 138fb9f..fe706a7 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -138,9 +138,10 @@
method public static void putBinder(android.os.Bundle, String?, android.os.IBinder?);
}
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ComponentActivity extends android.app.Activity implements androidx.core.view.KeyEventDispatcher.Component {
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ComponentActivity extends android.app.Activity implements androidx.core.view.KeyEventDispatcher.Component androidx.lifecycle.LifecycleOwner {
ctor public ComponentActivity();
method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T extends androidx.core.app.ComponentActivity.ExtraData> T! getExtraData(Class<T!>!);
+ method public androidx.lifecycle.Lifecycle getLifecycle();
method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void putExtraData(androidx.core.app.ComponentActivity.ExtraData!);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean superDispatchKeyEvent(android.view.KeyEvent!);
}
@@ -1968,7 +1969,7 @@
}
public interface Predicate<T> {
- method public boolean test(T);
+ method public boolean test(T?);
}
public interface Supplier<T> {
diff --git a/core/core/src/main/java/androidx/core/app/ComponentActivity.java b/core/core/src/main/java/androidx/core/app/ComponentActivity.java
index 90d61c4..78ebed2 100644
--- a/core/core/src/main/java/androidx/core/app/ComponentActivity.java
+++ b/core/core/src/main/java/androidx/core/app/ComponentActivity.java
@@ -18,13 +18,22 @@
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+import android.annotation.SuppressLint;
import android.app.Activity;
+import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
+import androidx.annotation.CallSuper;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.collection.SimpleArrayMap;
import androidx.core.view.KeyEventDispatcher;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.ReportFragment;
/**
* Base class for activities that enables composition of higher level components.
@@ -36,8 +45,9 @@
* @hide
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
-public class ComponentActivity extends Activity
- implements KeyEventDispatcher.Component {
+public class ComponentActivity extends Activity implements
+ LifecycleOwner,
+ KeyEventDispatcher.Component {
/**
* Storage for {@link ExtraData} instances.
*
@@ -46,6 +56,11 @@
@SuppressWarnings("deprecation")
private SimpleArrayMap<Class<? extends ExtraData>, ExtraData> mExtraDataMap =
new SimpleArrayMap<>();
+ /**
+ * This is only used for apps that have not switched to Fragments 1.1.0, where this
+ * behavior is provided by <code>androidx.activity.ComponentActivity</code>.
+ */
+ private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
/**
* Store an instance of {@link ExtraData} for later retrieval by class name
@@ -64,6 +79,20 @@
mExtraDataMap.put(extraData.getClass(), extraData);
}
+ @SuppressLint("RestrictedApi")
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ReportFragment.injectIfNeededIn(this);
+ }
+
+ @CallSuper
+ @Override
+ protected void onSaveInstanceState(@NonNull Bundle outState) {
+ mLifecycleRegistry.markState(Lifecycle.State.CREATED);
+ super.onSaveInstanceState(outState);
+ }
+
/**
* Retrieves a previously set {@link ExtraData} by class name.
*
@@ -78,6 +107,12 @@
return (T) mExtraDataMap.get(extraDataClass);
}
+ @NonNull
+ @Override
+ public Lifecycle getLifecycle() {
+ return mLifecycleRegistry;
+ }
+
/**
* @hide
*/
diff --git a/core/core/src/main/java/androidx/core/util/Predicate.java b/core/core/src/main/java/androidx/core/util/Predicate.java
index af73843..41e8582 100644
--- a/core/core/src/main/java/androidx/core/util/Predicate.java
+++ b/core/core/src/main/java/androidx/core/util/Predicate.java
@@ -16,7 +16,7 @@
package androidx.core.util;
-import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
/**
* Compat version of {@link java.util.function.Predicate}
@@ -32,5 +32,5 @@
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
- boolean test(@NonNull T t);
+ boolean test(@Nullable T t);
}
diff --git a/fragment/fragment-ktx/api/api_lint.ignore b/fragment/fragment-ktx/api/api_lint.ignore
deleted file mode 100644
index cd75382..0000000
--- a/fragment/fragment-ktx/api/api_lint.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-DocumentExceptions: androidx.fragment.app.FragmentViewModelLazyKt#createViewModelLazy(androidx.fragment.app.Fragment, kotlin.reflect.KClass<VM>, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStore>, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>):
- Method FragmentViewModelLazyKt.createViewModelLazy appears to be throwing java.lang.IllegalStateException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
diff --git a/gradlew b/gradlew
index 1cf59b6..5e8daf4 100755
--- a/gradlew
+++ b/gradlew
@@ -9,6 +9,8 @@
# --------- androidx specific code needed for build server. ------------------
if [ -n "$OUT_DIR" ] ; then
+ mkdir -p "$OUT_DIR"
+ OUT_DIR="$(cd $OUT_DIR && pwd)"
export GRADLE_USER_HOME="$OUT_DIR/.gradle"
export LINT_PRINT_STACKTRACE=true
else
diff --git a/leanback/api/api_lint.ignore b/leanback/api/api_lint.ignore
index b8d6059..47e6f64 100644
--- a/leanback/api/api_lint.ignore
+++ b/leanback/api/api_lint.ignore
@@ -49,22 +49,8 @@
Fractions must use floats, was `androidx.leanback.widget.Parallax.PropertyMarkerValue` in `atFraction`
-KotlinOperator: androidx.leanback.widget.ArrayObjectAdapter#get(int):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.leanback.widget.CursorObjectAdapter#get(int):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
KotlinOperator: androidx.leanback.widget.ObjectAdapter#get(int):
Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.leanback.widget.Parallax.FloatProperty#get(androidx.leanback.widget.Parallax):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.leanback.widget.Parallax.FloatProperty#set(androidx.leanback.widget.Parallax, Float):
- Method can be invoked with an indexing operator from Kotlin: `set` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.leanback.widget.Parallax.IntProperty#get(androidx.leanback.widget.Parallax):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.leanback.widget.Parallax.IntProperty#set(androidx.leanback.widget.Parallax, Integer):
- Method can be invoked with an indexing operator from Kotlin: `set` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.leanback.widget.SparseArrayObjectAdapter#get(int):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
KotlinOperator: androidx.leanback.widget.SparseArrayObjectAdapter#set(int, Object):
Method can be invoked with an indexing operator from Kotlin: `set` (this is usually desirable; just make sure it makes sense for this type of object)
diff --git a/localbroadcastmanager/src/main/java/androidx/localbroadcastmanager/content/LocalBroadcastManager.java b/localbroadcastmanager/src/main/java/androidx/localbroadcastmanager/content/LocalBroadcastManager.java
index 04f2f7f..8323b05 100644
--- a/localbroadcastmanager/src/main/java/androidx/localbroadcastmanager/content/LocalBroadcastManager.java
+++ b/localbroadcastmanager/src/main/java/androidx/localbroadcastmanager/content/LocalBroadcastManager.java
@@ -212,7 +212,7 @@
* @see #registerReceiver
*
* @return Returns true if the intent has been scheduled for delivery to one or more
- * broadcast receivers. (Note tha delivery may not ultimately take place if one of those
+ * broadcast receivers. (Note that delivery may not ultimately take place if one of those
* receivers is unregistered before it is dispatched.)
*/
public boolean sendBroadcast(@NonNull Intent intent) {
diff --git a/media/api/restricted_1.1.0-rc01.txt b/media/api/restricted_1.1.0-rc01.txt
new file mode 100644
index 0000000..b48a150
--- /dev/null
+++ b/media/api/restricted_1.1.0-rc01.txt
@@ -0,0 +1,702 @@
+// Signature format: 3.0
+package android.support.v4.media {
+
+ public final class MediaBrowserCompat {
+ ctor public MediaBrowserCompat(android.content.Context!, android.content.ComponentName!, android.support.v4.media.MediaBrowserCompat.ConnectionCallback!, android.os.Bundle!);
+ method public void connect();
+ method public void disconnect();
+ method public android.os.Bundle? getExtras();
+ method public void getItem(String, android.support.v4.media.MediaBrowserCompat.ItemCallback);
+ method public String getRoot();
+ method public android.content.ComponentName getServiceComponent();
+ method public android.support.v4.media.session.MediaSessionCompat.Token getSessionToken();
+ method public boolean isConnected();
+ method public void search(String, android.os.Bundle!, android.support.v4.media.MediaBrowserCompat.SearchCallback);
+ method public void sendCustomAction(String, android.os.Bundle!, android.support.v4.media.MediaBrowserCompat.CustomActionCallback?);
+ method public void subscribe(String, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+ method public void subscribe(String, android.os.Bundle, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+ method public void unsubscribe(String);
+ method public void unsubscribe(String, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+ field public static final String CUSTOM_ACTION_DOWNLOAD = "android.support.v4.media.action.DOWNLOAD";
+ field public static final String CUSTOM_ACTION_REMOVE_DOWNLOADED_FILE = "android.support.v4.media.action.REMOVE_DOWNLOADED_FILE";
+ field public static final String EXTRA_DOWNLOAD_PROGRESS = "android.media.browse.extra.DOWNLOAD_PROGRESS";
+ field public static final String EXTRA_MEDIA_ID = "android.media.browse.extra.MEDIA_ID";
+ field public static final String EXTRA_PAGE = "android.media.browse.extra.PAGE";
+ field public static final String EXTRA_PAGE_SIZE = "android.media.browse.extra.PAGE_SIZE";
+ }
+
+ public static class MediaBrowserCompat.ConnectionCallback {
+ ctor public MediaBrowserCompat.ConnectionCallback();
+ method public void onConnected();
+ method public void onConnectionFailed();
+ method public void onConnectionSuspended();
+ }
+
+ public abstract static class MediaBrowserCompat.CustomActionCallback {
+ ctor public MediaBrowserCompat.CustomActionCallback();
+ method public void onError(String!, android.os.Bundle!, android.os.Bundle!);
+ method public void onProgressUpdate(String!, android.os.Bundle!, android.os.Bundle!);
+ method public void onResult(String!, android.os.Bundle!, android.os.Bundle!);
+ }
+
+ public abstract static class MediaBrowserCompat.ItemCallback {
+ ctor public MediaBrowserCompat.ItemCallback();
+ method public void onError(String);
+ method public void onItemLoaded(android.support.v4.media.MediaBrowserCompat.MediaItem!);
+ }
+
+ public static class MediaBrowserCompat.MediaItem implements android.os.Parcelable {
+ ctor public MediaBrowserCompat.MediaItem(android.support.v4.media.MediaDescriptionCompat, int);
+ method public int describeContents();
+ method public static android.support.v4.media.MediaBrowserCompat.MediaItem! fromMediaItem(Object!);
+ method public static java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>! fromMediaItemList(java.util.List<?>!);
+ method public android.support.v4.media.MediaDescriptionCompat getDescription();
+ method public int getFlags();
+ method public String? getMediaId();
+ method public boolean isBrowsable();
+ method public boolean isPlayable();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.MediaBrowserCompat.MediaItem!>! CREATOR;
+ field public static final int FLAG_BROWSABLE = 1; // 0x1
+ field public static final int FLAG_PLAYABLE = 2; // 0x2
+ }
+
+ public abstract static class MediaBrowserCompat.SearchCallback {
+ ctor public MediaBrowserCompat.SearchCallback();
+ method public void onError(String, android.os.Bundle!);
+ method public void onSearchResult(String, android.os.Bundle!, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>);
+ }
+
+ public abstract static class MediaBrowserCompat.SubscriptionCallback {
+ ctor public MediaBrowserCompat.SubscriptionCallback();
+ method public void onChildrenLoaded(String, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>);
+ method public void onChildrenLoaded(String, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>, android.os.Bundle);
+ method public void onError(String);
+ method public void onError(String, android.os.Bundle);
+ }
+
+ public final class MediaDescriptionCompat implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.MediaDescriptionCompat! fromMediaDescription(Object!);
+ method public CharSequence? getDescription();
+ method public android.os.Bundle? getExtras();
+ method public android.graphics.Bitmap? getIconBitmap();
+ method public android.net.Uri? getIconUri();
+ method public Object! getMediaDescription();
+ method public String? getMediaId();
+ method public android.net.Uri? getMediaUri();
+ method public CharSequence? getSubtitle();
+ method public CharSequence? getTitle();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final long BT_FOLDER_TYPE_ALBUMS = 2L; // 0x2L
+ field public static final long BT_FOLDER_TYPE_ARTISTS = 3L; // 0x3L
+ field public static final long BT_FOLDER_TYPE_GENRES = 4L; // 0x4L
+ field public static final long BT_FOLDER_TYPE_MIXED = 0L; // 0x0L
+ field public static final long BT_FOLDER_TYPE_PLAYLISTS = 5L; // 0x5L
+ field public static final long BT_FOLDER_TYPE_TITLES = 1L; // 0x1L
+ field public static final long BT_FOLDER_TYPE_YEARS = 6L; // 0x6L
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.MediaDescriptionCompat!>! CREATOR;
+ field public static final String EXTRA_BT_FOLDER_TYPE = "android.media.extra.BT_FOLDER_TYPE";
+ field public static final String EXTRA_DOWNLOAD_STATUS = "android.media.extra.DOWNLOAD_STATUS";
+ field public static final long STATUS_DOWNLOADED = 2L; // 0x2L
+ field public static final long STATUS_DOWNLOADING = 1L; // 0x1L
+ field public static final long STATUS_NOT_DOWNLOADED = 0L; // 0x0L
+ }
+
+ public static final class MediaDescriptionCompat.Builder {
+ ctor public MediaDescriptionCompat.Builder();
+ method public android.support.v4.media.MediaDescriptionCompat! build();
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setDescription(CharSequence?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setExtras(android.os.Bundle?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setIconBitmap(android.graphics.Bitmap?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setIconUri(android.net.Uri?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setMediaId(String?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setMediaUri(android.net.Uri?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setSubtitle(CharSequence?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setTitle(CharSequence?);
+ }
+
+ public final class MediaMetadataCompat implements android.os.Parcelable {
+ method public boolean containsKey(String!);
+ method public int describeContents();
+ method public static android.support.v4.media.MediaMetadataCompat! fromMediaMetadata(Object!);
+ method public android.graphics.Bitmap! getBitmap(String!);
+ method public android.os.Bundle! getBundle();
+ method public android.support.v4.media.MediaDescriptionCompat! getDescription();
+ method public long getLong(String!);
+ method public Object! getMediaMetadata();
+ method public android.support.v4.media.RatingCompat! getRating(String!);
+ method public String! getString(String!);
+ method public CharSequence! getText(String!);
+ method public java.util.Set<java.lang.String!>! keySet();
+ method public int size();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.MediaMetadataCompat!>! CREATOR;
+ field public static final String METADATA_KEY_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
+ field public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
+ field public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
+ field public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+ field public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
+ field public static final String METADATA_KEY_ART = "android.media.metadata.ART";
+ field public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
+ field public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
+ field public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
+ field public static final String METADATA_KEY_BT_FOLDER_TYPE = "android.media.metadata.BT_FOLDER_TYPE";
+ field public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
+ field public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
+ field public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
+ field public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+ field public static final String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
+ field public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
+ field public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
+ field public static final String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
+ field public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
+ field public static final String METADATA_KEY_DOWNLOAD_STATUS = "android.media.metadata.DOWNLOAD_STATUS";
+ field public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
+ field public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
+ field public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
+ field public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
+ field public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
+ field public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
+ field public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
+ field public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+ field public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
+ field public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
+ field public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
+ }
+
+ public static final class MediaMetadataCompat.Builder {
+ ctor public MediaMetadataCompat.Builder();
+ ctor public MediaMetadataCompat.Builder(android.support.v4.media.MediaMetadataCompat!);
+ method public android.support.v4.media.MediaMetadataCompat! build();
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putBitmap(String!, android.graphics.Bitmap!);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putLong(String!, long);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putRating(String!, android.support.v4.media.RatingCompat!);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putString(String!, String!);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putText(String!, CharSequence!);
+ }
+
+ public final class RatingCompat implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.RatingCompat! fromRating(Object!);
+ method public float getPercentRating();
+ method public Object! getRating();
+ method public int getRatingStyle();
+ method public float getStarRating();
+ method public boolean hasHeart();
+ method public boolean isRated();
+ method public boolean isThumbUp();
+ method public static android.support.v4.media.RatingCompat! newHeartRating(boolean);
+ method public static android.support.v4.media.RatingCompat! newPercentageRating(float);
+ method public static android.support.v4.media.RatingCompat! newStarRating(int, float);
+ method public static android.support.v4.media.RatingCompat! newThumbRating(boolean);
+ method public static android.support.v4.media.RatingCompat! newUnratedRating(int);
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.RatingCompat!>! CREATOR;
+ field public static final int RATING_3_STARS = 3; // 0x3
+ field public static final int RATING_4_STARS = 4; // 0x4
+ field public static final int RATING_5_STARS = 5; // 0x5
+ field public static final int RATING_HEART = 1; // 0x1
+ field public static final int RATING_NONE = 0; // 0x0
+ field public static final int RATING_PERCENTAGE = 6; // 0x6
+ field public static final int RATING_THUMB_UP_DOWN = 2; // 0x2
+ }
+
+}
+
+package android.support.v4.media.session {
+
+ public final class MediaControllerCompat {
+ ctor public MediaControllerCompat(android.content.Context!, android.support.v4.media.session.MediaSessionCompat);
+ ctor public MediaControllerCompat(android.content.Context!, android.support.v4.media.session.MediaSessionCompat.Token) throws android.os.RemoteException;
+ method public void addQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method public void addQueueItem(android.support.v4.media.MediaDescriptionCompat!, int);
+ method public void adjustVolume(int, int);
+ method public boolean dispatchMediaButtonEvent(android.view.KeyEvent!);
+ method public android.os.Bundle! getExtras();
+ method public long getFlags();
+ method public static android.support.v4.media.session.MediaControllerCompat! getMediaController(android.app.Activity);
+ method public Object! getMediaController();
+ method public android.support.v4.media.MediaMetadataCompat! getMetadata();
+ method public String! getPackageName();
+ method public android.support.v4.media.session.MediaControllerCompat.PlaybackInfo! getPlaybackInfo();
+ method public android.support.v4.media.session.PlaybackStateCompat! getPlaybackState();
+ method public java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem!>! getQueue();
+ method public CharSequence! getQueueTitle();
+ method public int getRatingType();
+ method public int getRepeatMode();
+ method public android.app.PendingIntent! getSessionActivity();
+ method public android.support.v4.media.session.MediaSessionCompat.Token! getSessionToken();
+ method public int getShuffleMode();
+ method public android.support.v4.media.session.MediaControllerCompat.TransportControls! getTransportControls();
+ method public boolean isCaptioningEnabled();
+ method public boolean isSessionReady();
+ method public void registerCallback(android.support.v4.media.session.MediaControllerCompat.Callback);
+ method public void registerCallback(android.support.v4.media.session.MediaControllerCompat.Callback, android.os.Handler!);
+ method public void removeQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method @Deprecated public void removeQueueItemAt(int);
+ method public void sendCommand(String, android.os.Bundle?, android.os.ResultReceiver?);
+ method public static void setMediaController(android.app.Activity, android.support.v4.media.session.MediaControllerCompat!);
+ method public void setVolumeTo(int, int);
+ method public void unregisterCallback(android.support.v4.media.session.MediaControllerCompat.Callback);
+ }
+
+ public abstract static class MediaControllerCompat.Callback implements android.os.IBinder.DeathRecipient {
+ ctor public MediaControllerCompat.Callback();
+ method public void binderDied();
+ method public void onAudioInfoChanged(android.support.v4.media.session.MediaControllerCompat.PlaybackInfo!);
+ method public void onCaptioningEnabledChanged(boolean);
+ method public void onExtrasChanged(android.os.Bundle!);
+ method public void onMetadataChanged(android.support.v4.media.MediaMetadataCompat!);
+ method public void onPlaybackStateChanged(android.support.v4.media.session.PlaybackStateCompat!);
+ method public void onQueueChanged(java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem!>!);
+ method public void onQueueTitleChanged(CharSequence!);
+ method public void onRepeatModeChanged(int);
+ method public void onSessionDestroyed();
+ method public void onSessionEvent(String!, android.os.Bundle!);
+ method public void onSessionReady();
+ method public void onShuffleModeChanged(int);
+ }
+
+ public static final class MediaControllerCompat.PlaybackInfo {
+ method public androidx.media.AudioAttributesCompat getAudioAttributes();
+ method @Deprecated public int getAudioStream();
+ method public int getCurrentVolume();
+ method public int getMaxVolume();
+ method public int getPlaybackType();
+ method public int getVolumeControl();
+ field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
+ field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
+ }
+
+ public abstract static class MediaControllerCompat.TransportControls {
+ method public abstract void fastForward();
+ method public abstract void pause();
+ method public abstract void play();
+ method public abstract void playFromMediaId(String!, android.os.Bundle!);
+ method public abstract void playFromSearch(String!, android.os.Bundle!);
+ method public abstract void playFromUri(android.net.Uri!, android.os.Bundle!);
+ method public abstract void prepare();
+ method public abstract void prepareFromMediaId(String!, android.os.Bundle!);
+ method public abstract void prepareFromSearch(String!, android.os.Bundle!);
+ method public abstract void prepareFromUri(android.net.Uri!, android.os.Bundle!);
+ method public abstract void rewind();
+ method public abstract void seekTo(long);
+ method public abstract void sendCustomAction(android.support.v4.media.session.PlaybackStateCompat.CustomAction!, android.os.Bundle!);
+ method public abstract void sendCustomAction(String!, android.os.Bundle!);
+ method public abstract void setCaptioningEnabled(boolean);
+ method public abstract void setRating(android.support.v4.media.RatingCompat!);
+ method public abstract void setRating(android.support.v4.media.RatingCompat!, android.os.Bundle!);
+ method public abstract void setRepeatMode(int);
+ method public abstract void setShuffleMode(int);
+ method public abstract void skipToNext();
+ method public abstract void skipToPrevious();
+ method public abstract void skipToQueueItem(long);
+ method public abstract void stop();
+ field public static final String EXTRA_LEGACY_STREAM_TYPE = "android.media.session.extra.LEGACY_STREAM_TYPE";
+ }
+
+ public class MediaSessionCompat {
+ ctor public MediaSessionCompat(android.content.Context, String);
+ ctor public MediaSessionCompat(android.content.Context, String, android.content.ComponentName?, android.app.PendingIntent?);
+ method public void addOnActiveChangeListener(android.support.v4.media.session.MediaSessionCompat.OnActiveChangeListener!);
+ method public static android.support.v4.media.session.MediaSessionCompat! fromMediaSession(android.content.Context!, Object!);
+ method public android.support.v4.media.session.MediaControllerCompat! getController();
+ method public final androidx.media.MediaSessionManager.RemoteUserInfo getCurrentControllerInfo();
+ method public Object! getMediaSession();
+ method public Object! getRemoteControlClient();
+ method public android.support.v4.media.session.MediaSessionCompat.Token! getSessionToken();
+ method public boolean isActive();
+ method public void release();
+ method public void removeOnActiveChangeListener(android.support.v4.media.session.MediaSessionCompat.OnActiveChangeListener!);
+ method public void sendSessionEvent(String!, android.os.Bundle!);
+ method public void setActive(boolean);
+ method public void setCallback(android.support.v4.media.session.MediaSessionCompat.Callback!);
+ method public void setCallback(android.support.v4.media.session.MediaSessionCompat.Callback!, android.os.Handler!);
+ method public void setCaptioningEnabled(boolean);
+ method public void setExtras(android.os.Bundle!);
+ method public void setFlags(int);
+ method public void setMediaButtonReceiver(android.app.PendingIntent!);
+ method public void setMetadata(android.support.v4.media.MediaMetadataCompat!);
+ method public void setPlaybackState(android.support.v4.media.session.PlaybackStateCompat!);
+ method public void setPlaybackToLocal(int);
+ method public void setPlaybackToRemote(androidx.media.VolumeProviderCompat!);
+ method public void setQueue(java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem!>!);
+ method public void setQueueTitle(CharSequence!);
+ method public void setRatingType(int);
+ method public void setRepeatMode(int);
+ method public void setSessionActivity(android.app.PendingIntent!);
+ method public void setShuffleMode(int);
+ field public static final String ACTION_FLAG_AS_INAPPROPRIATE = "android.support.v4.media.session.action.FLAG_AS_INAPPROPRIATE";
+ field public static final String ACTION_FOLLOW = "android.support.v4.media.session.action.FOLLOW";
+ field public static final String ACTION_SKIP_AD = "android.support.v4.media.session.action.SKIP_AD";
+ field public static final String ACTION_UNFOLLOW = "android.support.v4.media.session.action.UNFOLLOW";
+ field public static final String ARGUMENT_MEDIA_ATTRIBUTE = "android.support.v4.media.session.ARGUMENT_MEDIA_ATTRIBUTE";
+ field public static final String ARGUMENT_MEDIA_ATTRIBUTE_VALUE = "android.support.v4.media.session.ARGUMENT_MEDIA_ATTRIBUTE_VALUE";
+ field @Deprecated public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
+ field public static final int FLAG_HANDLES_QUEUE_COMMANDS = 4; // 0x4
+ field @Deprecated public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
+ field public static final int MEDIA_ATTRIBUTE_ALBUM = 1; // 0x1
+ field public static final int MEDIA_ATTRIBUTE_ARTIST = 0; // 0x0
+ field public static final int MEDIA_ATTRIBUTE_PLAYLIST = 2; // 0x2
+ }
+
+ public abstract static class MediaSessionCompat.Callback {
+ ctor public MediaSessionCompat.Callback();
+ method public void onAddQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method public void onAddQueueItem(android.support.v4.media.MediaDescriptionCompat!, int);
+ method public void onCommand(String!, android.os.Bundle!, android.os.ResultReceiver!);
+ method public void onCustomAction(String!, android.os.Bundle!);
+ method public void onFastForward();
+ method public boolean onMediaButtonEvent(android.content.Intent!);
+ method public void onPause();
+ method public void onPlay();
+ method public void onPlayFromMediaId(String!, android.os.Bundle!);
+ method public void onPlayFromSearch(String!, android.os.Bundle!);
+ method public void onPlayFromUri(android.net.Uri!, android.os.Bundle!);
+ method public void onPrepare();
+ method public void onPrepareFromMediaId(String!, android.os.Bundle!);
+ method public void onPrepareFromSearch(String!, android.os.Bundle!);
+ method public void onPrepareFromUri(android.net.Uri!, android.os.Bundle!);
+ method public void onRemoveQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method @Deprecated public void onRemoveQueueItemAt(int);
+ method public void onRewind();
+ method public void onSeekTo(long);
+ method public void onSetCaptioningEnabled(boolean);
+ method public void onSetRating(android.support.v4.media.RatingCompat!);
+ method public void onSetRating(android.support.v4.media.RatingCompat!, android.os.Bundle!);
+ method public void onSetRepeatMode(int);
+ method public void onSetShuffleMode(int);
+ method public void onSkipToNext();
+ method public void onSkipToPrevious();
+ method public void onSkipToQueueItem(long);
+ method public void onStop();
+ }
+
+ public static interface MediaSessionCompat.OnActiveChangeListener {
+ method public void onActiveChanged();
+ }
+
+ public static final class MediaSessionCompat.QueueItem implements android.os.Parcelable {
+ ctor public MediaSessionCompat.QueueItem(android.support.v4.media.MediaDescriptionCompat!, long);
+ method public int describeContents();
+ method public static android.support.v4.media.session.MediaSessionCompat.QueueItem! fromQueueItem(Object!);
+ method public static java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem!>! fromQueueItemList(java.util.List<?>!);
+ method public android.support.v4.media.MediaDescriptionCompat! getDescription();
+ method public long getQueueId();
+ method public Object! getQueueItem();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.MediaSessionCompat.QueueItem!>! CREATOR;
+ field public static final int UNKNOWN_ID = -1; // 0xffffffff
+ }
+
+ public static final class MediaSessionCompat.Token implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.session.MediaSessionCompat.Token! fromToken(Object!);
+ method public Object! getToken();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.MediaSessionCompat.Token!>! CREATOR;
+ }
+
+ public class ParcelableVolumeInfo implements android.os.Parcelable {
+ ctor public ParcelableVolumeInfo(int, int, int, int, int);
+ ctor public ParcelableVolumeInfo(android.os.Parcel!);
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.ParcelableVolumeInfo!>! CREATOR;
+ field public int audioStream;
+ field public int controlType;
+ field public int currentVolume;
+ field public int maxVolume;
+ field public int volumeType;
+ }
+
+ public final class PlaybackStateCompat implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.session.PlaybackStateCompat! fromPlaybackState(Object!);
+ method public long getActions();
+ method public long getActiveQueueItemId();
+ method public long getBufferedPosition();
+ method public java.util.List<android.support.v4.media.session.PlaybackStateCompat.CustomAction!>! getCustomActions();
+ method public int getErrorCode();
+ method public CharSequence! getErrorMessage();
+ method public android.os.Bundle? getExtras();
+ method public long getLastPositionUpdateTime();
+ method public float getPlaybackSpeed();
+ method public Object! getPlaybackState();
+ method public long getPosition();
+ method public int getState();
+ method public static int toKeyCode(long);
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final long ACTION_FAST_FORWARD = 64L; // 0x40L
+ field public static final long ACTION_PAUSE = 2L; // 0x2L
+ field public static final long ACTION_PLAY = 4L; // 0x4L
+ field public static final long ACTION_PLAY_FROM_MEDIA_ID = 1024L; // 0x400L
+ field public static final long ACTION_PLAY_FROM_SEARCH = 2048L; // 0x800L
+ field public static final long ACTION_PLAY_FROM_URI = 8192L; // 0x2000L
+ field public static final long ACTION_PLAY_PAUSE = 512L; // 0x200L
+ field public static final long ACTION_PREPARE = 16384L; // 0x4000L
+ field public static final long ACTION_PREPARE_FROM_MEDIA_ID = 32768L; // 0x8000L
+ field public static final long ACTION_PREPARE_FROM_SEARCH = 65536L; // 0x10000L
+ field public static final long ACTION_PREPARE_FROM_URI = 131072L; // 0x20000L
+ field public static final long ACTION_REWIND = 8L; // 0x8L
+ field public static final long ACTION_SEEK_TO = 256L; // 0x100L
+ field public static final long ACTION_SET_CAPTIONING_ENABLED = 1048576L; // 0x100000L
+ field public static final long ACTION_SET_RATING = 128L; // 0x80L
+ field public static final long ACTION_SET_REPEAT_MODE = 262144L; // 0x40000L
+ field public static final long ACTION_SET_SHUFFLE_MODE = 2097152L; // 0x200000L
+ field @Deprecated public static final long ACTION_SET_SHUFFLE_MODE_ENABLED = 524288L; // 0x80000L
+ field public static final long ACTION_SKIP_TO_NEXT = 32L; // 0x20L
+ field public static final long ACTION_SKIP_TO_PREVIOUS = 16L; // 0x10L
+ field public static final long ACTION_SKIP_TO_QUEUE_ITEM = 4096L; // 0x1000L
+ field public static final long ACTION_STOP = 1L; // 0x1L
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.PlaybackStateCompat!>! CREATOR;
+ field public static final int ERROR_CODE_ACTION_ABORTED = 10; // 0xa
+ field public static final int ERROR_CODE_APP_ERROR = 1; // 0x1
+ field public static final int ERROR_CODE_AUTHENTICATION_EXPIRED = 3; // 0x3
+ field public static final int ERROR_CODE_CONCURRENT_STREAM_LIMIT = 5; // 0x5
+ field public static final int ERROR_CODE_CONTENT_ALREADY_PLAYING = 8; // 0x8
+ field public static final int ERROR_CODE_END_OF_QUEUE = 11; // 0xb
+ field public static final int ERROR_CODE_NOT_AVAILABLE_IN_REGION = 7; // 0x7
+ field public static final int ERROR_CODE_NOT_SUPPORTED = 2; // 0x2
+ field public static final int ERROR_CODE_PARENTAL_CONTROL_RESTRICTED = 6; // 0x6
+ field public static final int ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED = 4; // 0x4
+ field public static final int ERROR_CODE_SKIP_LIMIT_REACHED = 9; // 0x9
+ field public static final int ERROR_CODE_UNKNOWN_ERROR = 0; // 0x0
+ field public static final long PLAYBACK_POSITION_UNKNOWN = -1L; // 0xffffffffffffffffL
+ field public static final int REPEAT_MODE_ALL = 2; // 0x2
+ field public static final int REPEAT_MODE_GROUP = 3; // 0x3
+ field public static final int REPEAT_MODE_INVALID = -1; // 0xffffffff
+ field public static final int REPEAT_MODE_NONE = 0; // 0x0
+ field public static final int REPEAT_MODE_ONE = 1; // 0x1
+ field public static final int SHUFFLE_MODE_ALL = 1; // 0x1
+ field public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
+ field public static final int SHUFFLE_MODE_INVALID = -1; // 0xffffffff
+ field public static final int SHUFFLE_MODE_NONE = 0; // 0x0
+ field public static final int STATE_BUFFERING = 6; // 0x6
+ field public static final int STATE_CONNECTING = 8; // 0x8
+ field public static final int STATE_ERROR = 7; // 0x7
+ field public static final int STATE_FAST_FORWARDING = 4; // 0x4
+ field public static final int STATE_NONE = 0; // 0x0
+ field public static final int STATE_PAUSED = 2; // 0x2
+ field public static final int STATE_PLAYING = 3; // 0x3
+ field public static final int STATE_REWINDING = 5; // 0x5
+ field public static final int STATE_SKIPPING_TO_NEXT = 10; // 0xa
+ field public static final int STATE_SKIPPING_TO_PREVIOUS = 9; // 0x9
+ field public static final int STATE_SKIPPING_TO_QUEUE_ITEM = 11; // 0xb
+ field public static final int STATE_STOPPED = 1; // 0x1
+ }
+
+ public static final class PlaybackStateCompat.Builder {
+ ctor public PlaybackStateCompat.Builder();
+ ctor public PlaybackStateCompat.Builder(android.support.v4.media.session.PlaybackStateCompat!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! addCustomAction(String!, String!, int);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! addCustomAction(android.support.v4.media.session.PlaybackStateCompat.CustomAction!);
+ method public android.support.v4.media.session.PlaybackStateCompat! build();
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setActions(long);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setActiveQueueItemId(long);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setBufferedPosition(long);
+ method @Deprecated public android.support.v4.media.session.PlaybackStateCompat.Builder! setErrorMessage(CharSequence!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setErrorMessage(int, CharSequence!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setExtras(android.os.Bundle!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setState(int, long, float);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setState(int, long, float, long);
+ }
+
+ public static final class PlaybackStateCompat.CustomAction implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.session.PlaybackStateCompat.CustomAction! fromCustomAction(Object!);
+ method public String! getAction();
+ method public Object! getCustomAction();
+ method public android.os.Bundle! getExtras();
+ method public int getIcon();
+ method public CharSequence! getName();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.PlaybackStateCompat.CustomAction!>! CREATOR;
+ }
+
+ public static final class PlaybackStateCompat.CustomAction.Builder {
+ ctor public PlaybackStateCompat.CustomAction.Builder(String!, CharSequence!, int);
+ method public android.support.v4.media.session.PlaybackStateCompat.CustomAction! build();
+ method public android.support.v4.media.session.PlaybackStateCompat.CustomAction.Builder! setExtras(android.os.Bundle!);
+ }
+
+}
+
+package androidx.media {
+
+ public class AudioAttributesCompat implements androidx.versionedparcelable.VersionedParcelable {
+ method public int getContentType();
+ method public int getFlags();
+ method public int getLegacyStreamType();
+ method public int getUsage();
+ method public int getVolumeControlStream();
+ method public Object? unwrap();
+ method public static androidx.media.AudioAttributesCompat? wrap(Object);
+ field public static final int CONTENT_TYPE_MOVIE = 3; // 0x3
+ field public static final int CONTENT_TYPE_MUSIC = 2; // 0x2
+ field public static final int CONTENT_TYPE_SONIFICATION = 4; // 0x4
+ field public static final int CONTENT_TYPE_SPEECH = 1; // 0x1
+ field public static final int CONTENT_TYPE_UNKNOWN = 0; // 0x0
+ field public static final int FLAG_AUDIBILITY_ENFORCED = 1; // 0x1
+ field public static final int FLAG_HW_AV_SYNC = 16; // 0x10
+ field public static final int USAGE_ALARM = 4; // 0x4
+ field public static final int USAGE_ASSISTANCE_ACCESSIBILITY = 11; // 0xb
+ field public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12; // 0xc
+ field public static final int USAGE_ASSISTANCE_SONIFICATION = 13; // 0xd
+ field public static final int USAGE_ASSISTANT = 16; // 0x10
+ field public static final int USAGE_GAME = 14; // 0xe
+ field public static final int USAGE_MEDIA = 1; // 0x1
+ field public static final int USAGE_NOTIFICATION = 5; // 0x5
+ field public static final int USAGE_NOTIFICATION_COMMUNICATION_DELAYED = 9; // 0x9
+ field public static final int USAGE_NOTIFICATION_COMMUNICATION_INSTANT = 8; // 0x8
+ field public static final int USAGE_NOTIFICATION_COMMUNICATION_REQUEST = 7; // 0x7
+ field public static final int USAGE_NOTIFICATION_EVENT = 10; // 0xa
+ field public static final int USAGE_NOTIFICATION_RINGTONE = 6; // 0x6
+ field public static final int USAGE_UNKNOWN = 0; // 0x0
+ field public static final int USAGE_VOICE_COMMUNICATION = 2; // 0x2
+ field public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING = 3; // 0x3
+ }
+
+ public static class AudioAttributesCompat.Builder {
+ ctor public AudioAttributesCompat.Builder();
+ ctor public AudioAttributesCompat.Builder(androidx.media.AudioAttributesCompat!);
+ method public androidx.media.AudioAttributesCompat! build();
+ method public androidx.media.AudioAttributesCompat.Builder! setContentType(int);
+ method public androidx.media.AudioAttributesCompat.Builder! setFlags(int);
+ method public androidx.media.AudioAttributesCompat.Builder! setLegacyStreamType(int);
+ method public androidx.media.AudioAttributesCompat.Builder! setUsage(int);
+ }
+
+ public class AudioFocusRequestCompat {
+ method public androidx.media.AudioAttributesCompat getAudioAttributesCompat();
+ method public android.os.Handler getFocusChangeHandler();
+ method public int getFocusGain();
+ method public android.media.AudioManager.OnAudioFocusChangeListener getOnAudioFocusChangeListener();
+ method public boolean willPauseWhenDucked();
+ }
+
+ public static final class AudioFocusRequestCompat.Builder {
+ ctor public AudioFocusRequestCompat.Builder(int);
+ ctor public AudioFocusRequestCompat.Builder(androidx.media.AudioFocusRequestCompat);
+ method public androidx.media.AudioFocusRequestCompat! build();
+ method public androidx.media.AudioFocusRequestCompat.Builder setAudioAttributes(androidx.media.AudioAttributesCompat);
+ method public androidx.media.AudioFocusRequestCompat.Builder setFocusGain(int);
+ method public androidx.media.AudioFocusRequestCompat.Builder setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener);
+ method public androidx.media.AudioFocusRequestCompat.Builder setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener, android.os.Handler);
+ method public androidx.media.AudioFocusRequestCompat.Builder setWillPauseWhenDucked(boolean);
+ }
+
+ public final class AudioManagerCompat {
+ method public static int abandonAudioFocusRequest(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
+ method public static int requestAudioFocus(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
+ field public static final int AUDIOFOCUS_GAIN = 1; // 0x1
+ field public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; // 0x2
+ field public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE = 4; // 0x4
+ field public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK = 3; // 0x3
+ }
+
+ public abstract class MediaBrowserServiceCompat extends android.app.Service {
+ ctor public MediaBrowserServiceCompat();
+ method public void dump(java.io.FileDescriptor!, java.io.PrintWriter!, String![]!);
+ method public final android.os.Bundle! getBrowserRootHints();
+ method public final androidx.media.MediaSessionManager.RemoteUserInfo getCurrentBrowserInfo();
+ method public android.support.v4.media.session.MediaSessionCompat.Token? getSessionToken();
+ method public void notifyChildrenChanged(String);
+ method public void notifyChildrenChanged(String, android.os.Bundle);
+ method public android.os.IBinder! onBind(android.content.Intent!);
+ method public void onCustomAction(String, android.os.Bundle!, androidx.media.MediaBrowserServiceCompat.Result<android.os.Bundle!>);
+ method public abstract androidx.media.MediaBrowserServiceCompat.BrowserRoot? onGetRoot(String, int, android.os.Bundle?);
+ method public abstract void onLoadChildren(String, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>!>);
+ method public void onLoadChildren(String, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>!>, android.os.Bundle);
+ method public void onLoadItem(String!, androidx.media.MediaBrowserServiceCompat.Result<android.support.v4.media.MediaBrowserCompat.MediaItem!>);
+ method public void onSearch(String, android.os.Bundle!, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>!>);
+ method public void setSessionToken(android.support.v4.media.session.MediaSessionCompat.Token!);
+ field public static final String SERVICE_INTERFACE = "android.media.browse.MediaBrowserService";
+ }
+
+ public static final class MediaBrowserServiceCompat.BrowserRoot {
+ ctor public MediaBrowserServiceCompat.BrowserRoot(String, android.os.Bundle?);
+ method public android.os.Bundle! getExtras();
+ method public String! getRootId();
+ field public static final String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
+ field public static final String EXTRA_RECENT = "android.service.media.extra.RECENT";
+ field public static final String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
+ field @Deprecated public static final String EXTRA_SUGGESTION_KEYWORDS = "android.service.media.extra.SUGGESTION_KEYWORDS";
+ }
+
+ public static class MediaBrowserServiceCompat.Result<T> {
+ method public void detach();
+ method public void sendError(android.os.Bundle!);
+ method public void sendProgressUpdate(android.os.Bundle!);
+ method public void sendResult(T!);
+ }
+
+ public final class MediaSessionManager {
+ method public static androidx.media.MediaSessionManager getSessionManager(android.content.Context);
+ method public boolean isTrustedForMediaControl(androidx.media.MediaSessionManager.RemoteUserInfo);
+ }
+
+ public static final class MediaSessionManager.RemoteUserInfo {
+ ctor public MediaSessionManager.RemoteUserInfo(String, int, int);
+ method public String getPackageName();
+ method public int getPid();
+ method public int getUid();
+ field public static final String LEGACY_CONTROLLER = "android.media.session.MediaController";
+ }
+
+ public abstract class VolumeProviderCompat {
+ ctor public VolumeProviderCompat(int, int, int);
+ method public final int getCurrentVolume();
+ method public final int getMaxVolume();
+ method public final int getVolumeControl();
+ method public Object! getVolumeProvider();
+ method public void onAdjustVolume(int);
+ method public void onSetVolumeTo(int);
+ method public void setCallback(androidx.media.VolumeProviderCompat.Callback!);
+ method public final void setCurrentVolume(int);
+ field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
+ field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
+ field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
+ }
+
+ public abstract static class VolumeProviderCompat.Callback {
+ ctor public VolumeProviderCompat.Callback();
+ method public abstract void onVolumeChanged(androidx.media.VolumeProviderCompat!);
+ }
+
+}
+
+package androidx.media.app {
+
+ public class NotificationCompat {
+ }
+
+ public static class NotificationCompat.DecoratedMediaCustomViewStyle extends androidx.media.app.NotificationCompat.MediaStyle {
+ ctor public NotificationCompat.DecoratedMediaCustomViewStyle();
+ }
+
+ public static class NotificationCompat.MediaStyle extends androidx.core.app.NotificationCompat.Style {
+ ctor public NotificationCompat.MediaStyle();
+ ctor public NotificationCompat.MediaStyle(androidx.core.app.NotificationCompat.Builder!);
+ method public static android.support.v4.media.session.MediaSessionCompat.Token! getMediaSession(android.app.Notification!);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setCancelButtonIntent(android.app.PendingIntent!);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setMediaSession(android.support.v4.media.session.MediaSessionCompat.Token!);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setShowActionsInCompactView(int...);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setShowCancelButton(boolean);
+ }
+
+}
+
+package androidx.media.session {
+
+ public class MediaButtonReceiver extends android.content.BroadcastReceiver {
+ ctor public MediaButtonReceiver();
+ method public static android.app.PendingIntent! buildMediaButtonPendingIntent(android.content.Context!, long);
+ method public static android.app.PendingIntent! buildMediaButtonPendingIntent(android.content.Context!, android.content.ComponentName!, long);
+ method public static android.view.KeyEvent! handleIntent(android.support.v4.media.session.MediaSessionCompat!, android.content.Intent!);
+ method public void onReceive(android.content.Context!, android.content.Intent!);
+ }
+
+}
+
diff --git a/media/api/restricted_1.2.0-alpha01.txt b/media/api/restricted_1.2.0-alpha01.txt
new file mode 100644
index 0000000..9c1f0db
--- /dev/null
+++ b/media/api/restricted_1.2.0-alpha01.txt
@@ -0,0 +1,743 @@
+// Signature format: 3.0
+package android.support.v4.media {
+
+ public final class MediaBrowserCompat {
+ ctor public MediaBrowserCompat(android.content.Context!, android.content.ComponentName!, android.support.v4.media.MediaBrowserCompat.ConnectionCallback!, android.os.Bundle!);
+ method public void connect();
+ method public void disconnect();
+ method public android.os.Bundle? getExtras();
+ method public void getItem(String, android.support.v4.media.MediaBrowserCompat.ItemCallback);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.os.Bundle? getNotifyChildrenChangedOptions();
+ method public String getRoot();
+ method public android.content.ComponentName getServiceComponent();
+ method public android.support.v4.media.session.MediaSessionCompat.Token getSessionToken();
+ method public boolean isConnected();
+ method public void search(String, android.os.Bundle!, android.support.v4.media.MediaBrowserCompat.SearchCallback);
+ method public void sendCustomAction(String, android.os.Bundle!, android.support.v4.media.MediaBrowserCompat.CustomActionCallback?);
+ method public void subscribe(String, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+ method public void subscribe(String, android.os.Bundle, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+ method public void unsubscribe(String);
+ method public void unsubscribe(String, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+ field public static final String CUSTOM_ACTION_DOWNLOAD = "android.support.v4.media.action.DOWNLOAD";
+ field public static final String CUSTOM_ACTION_REMOVE_DOWNLOADED_FILE = "android.support.v4.media.action.REMOVE_DOWNLOADED_FILE";
+ field public static final String EXTRA_DOWNLOAD_PROGRESS = "android.media.browse.extra.DOWNLOAD_PROGRESS";
+ field public static final String EXTRA_MEDIA_ID = "android.media.browse.extra.MEDIA_ID";
+ field public static final String EXTRA_PAGE = "android.media.browse.extra.PAGE";
+ field public static final String EXTRA_PAGE_SIZE = "android.media.browse.extra.PAGE_SIZE";
+ }
+
+ public static class MediaBrowserCompat.ConnectionCallback {
+ ctor public MediaBrowserCompat.ConnectionCallback();
+ method public void onConnected();
+ method public void onConnectionFailed();
+ method public void onConnectionSuspended();
+ }
+
+ public abstract static class MediaBrowserCompat.CustomActionCallback {
+ ctor public MediaBrowserCompat.CustomActionCallback();
+ method public void onError(String!, android.os.Bundle!, android.os.Bundle!);
+ method public void onProgressUpdate(String!, android.os.Bundle!, android.os.Bundle!);
+ method public void onResult(String!, android.os.Bundle!, android.os.Bundle!);
+ }
+
+ public abstract static class MediaBrowserCompat.ItemCallback {
+ ctor public MediaBrowserCompat.ItemCallback();
+ method public void onError(String);
+ method public void onItemLoaded(android.support.v4.media.MediaBrowserCompat.MediaItem!);
+ }
+
+ public static class MediaBrowserCompat.MediaItem implements android.os.Parcelable {
+ ctor public MediaBrowserCompat.MediaItem(android.support.v4.media.MediaDescriptionCompat, int);
+ method public int describeContents();
+ method public static android.support.v4.media.MediaBrowserCompat.MediaItem! fromMediaItem(Object!);
+ method public static java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>! fromMediaItemList(java.util.List<?>!);
+ method public android.support.v4.media.MediaDescriptionCompat getDescription();
+ method public int getFlags();
+ method public String? getMediaId();
+ method public boolean isBrowsable();
+ method public boolean isPlayable();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.MediaBrowserCompat.MediaItem!>! CREATOR;
+ field public static final int FLAG_BROWSABLE = 1; // 0x1
+ field public static final int FLAG_PLAYABLE = 2; // 0x2
+ }
+
+ public abstract static class MediaBrowserCompat.SearchCallback {
+ ctor public MediaBrowserCompat.SearchCallback();
+ method public void onError(String, android.os.Bundle!);
+ method public void onSearchResult(String, android.os.Bundle!, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>);
+ }
+
+ public abstract static class MediaBrowserCompat.SubscriptionCallback {
+ ctor public MediaBrowserCompat.SubscriptionCallback();
+ method public void onChildrenLoaded(String, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>);
+ method public void onChildrenLoaded(String, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>, android.os.Bundle);
+ method public void onError(String);
+ method public void onError(String, android.os.Bundle);
+ }
+
+ public final class MediaDescriptionCompat implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.MediaDescriptionCompat! fromMediaDescription(Object!);
+ method public CharSequence? getDescription();
+ method public android.os.Bundle? getExtras();
+ method public android.graphics.Bitmap? getIconBitmap();
+ method public android.net.Uri? getIconUri();
+ method public Object! getMediaDescription();
+ method public String? getMediaId();
+ method public android.net.Uri? getMediaUri();
+ method public CharSequence? getSubtitle();
+ method public CharSequence? getTitle();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final long BT_FOLDER_TYPE_ALBUMS = 2L; // 0x2L
+ field public static final long BT_FOLDER_TYPE_ARTISTS = 3L; // 0x3L
+ field public static final long BT_FOLDER_TYPE_GENRES = 4L; // 0x4L
+ field public static final long BT_FOLDER_TYPE_MIXED = 0L; // 0x0L
+ field public static final long BT_FOLDER_TYPE_PLAYLISTS = 5L; // 0x5L
+ field public static final long BT_FOLDER_TYPE_TITLES = 1L; // 0x1L
+ field public static final long BT_FOLDER_TYPE_YEARS = 6L; // 0x6L
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.MediaDescriptionCompat!>! CREATOR;
+ field public static final String EXTRA_BT_FOLDER_TYPE = "android.media.extra.BT_FOLDER_TYPE";
+ field public static final String EXTRA_DOWNLOAD_STATUS = "android.media.extra.DOWNLOAD_STATUS";
+ field public static final long STATUS_DOWNLOADED = 2L; // 0x2L
+ field public static final long STATUS_DOWNLOADING = 1L; // 0x1L
+ field public static final long STATUS_NOT_DOWNLOADED = 0L; // 0x0L
+ }
+
+ public static final class MediaDescriptionCompat.Builder {
+ ctor public MediaDescriptionCompat.Builder();
+ method public android.support.v4.media.MediaDescriptionCompat! build();
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setDescription(CharSequence?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setExtras(android.os.Bundle?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setIconBitmap(android.graphics.Bitmap?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setIconUri(android.net.Uri?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setMediaId(String?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setMediaUri(android.net.Uri?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setSubtitle(CharSequence?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setTitle(CharSequence?);
+ }
+
+ public final class MediaMetadataCompat implements android.os.Parcelable {
+ method public boolean containsKey(String!);
+ method public int describeContents();
+ method public static android.support.v4.media.MediaMetadataCompat! fromMediaMetadata(Object!);
+ method public android.graphics.Bitmap! getBitmap(String!);
+ method public android.os.Bundle! getBundle();
+ method public android.support.v4.media.MediaDescriptionCompat! getDescription();
+ method public long getLong(String!);
+ method public Object! getMediaMetadata();
+ method public android.support.v4.media.RatingCompat! getRating(String!);
+ method public String! getString(String!);
+ method public CharSequence! getText(String!);
+ method public java.util.Set<java.lang.String!>! keySet();
+ method public int size();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.MediaMetadataCompat!>! CREATOR;
+ field public static final String METADATA_KEY_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
+ field public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
+ field public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
+ field public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+ field public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
+ field public static final String METADATA_KEY_ART = "android.media.metadata.ART";
+ field public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
+ field public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
+ field public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
+ field public static final String METADATA_KEY_BT_FOLDER_TYPE = "android.media.metadata.BT_FOLDER_TYPE";
+ field public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
+ field public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
+ field public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
+ field public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+ field public static final String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
+ field public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
+ field public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
+ field public static final String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
+ field public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
+ field public static final String METADATA_KEY_DOWNLOAD_STATUS = "android.media.metadata.DOWNLOAD_STATUS";
+ field public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
+ field public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
+ field public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
+ field public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
+ field public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
+ field public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
+ field public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
+ field public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+ field public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
+ field public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
+ field public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
+ }
+
+ public static final class MediaMetadataCompat.Builder {
+ ctor public MediaMetadataCompat.Builder();
+ ctor public MediaMetadataCompat.Builder(android.support.v4.media.MediaMetadataCompat!);
+ method public android.support.v4.media.MediaMetadataCompat! build();
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putBitmap(String!, android.graphics.Bitmap!);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putLong(String!, long);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putRating(String!, android.support.v4.media.RatingCompat!);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putString(String!, String!);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putText(String!, CharSequence!);
+ }
+
+ public final class RatingCompat implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.RatingCompat! fromRating(Object!);
+ method public float getPercentRating();
+ method public Object! getRating();
+ method @android.support.v4.media.RatingCompat.Style public int getRatingStyle();
+ method public float getStarRating();
+ method public boolean hasHeart();
+ method public boolean isRated();
+ method public boolean isThumbUp();
+ method public static android.support.v4.media.RatingCompat! newHeartRating(boolean);
+ method public static android.support.v4.media.RatingCompat! newPercentageRating(float);
+ method public static android.support.v4.media.RatingCompat! newStarRating(@android.support.v4.media.RatingCompat.StarStyle int, float);
+ method public static android.support.v4.media.RatingCompat! newThumbRating(boolean);
+ method public static android.support.v4.media.RatingCompat! newUnratedRating(@android.support.v4.media.RatingCompat.Style int);
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.RatingCompat!>! CREATOR;
+ field public static final int RATING_3_STARS = 3; // 0x3
+ field public static final int RATING_4_STARS = 4; // 0x4
+ field public static final int RATING_5_STARS = 5; // 0x5
+ field public static final int RATING_HEART = 1; // 0x1
+ field public static final int RATING_NONE = 0; // 0x0
+ field public static final int RATING_PERCENTAGE = 6; // 0x6
+ field public static final int RATING_THUMB_UP_DOWN = 2; // 0x2
+ }
+
+
+ @IntDef({android.support.v4.media.RatingCompat.RATING_NONE, android.support.v4.media.RatingCompat.RATING_HEART, android.support.v4.media.RatingCompat.RATING_THUMB_UP_DOWN, android.support.v4.media.RatingCompat.RATING_3_STARS, android.support.v4.media.RatingCompat.RATING_4_STARS, android.support.v4.media.RatingCompat.RATING_5_STARS, android.support.v4.media.RatingCompat.RATING_PERCENTAGE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface RatingCompat.Style {
+ }
+
+}
+
+package android.support.v4.media.session {
+
+ public final class MediaControllerCompat {
+ ctor public MediaControllerCompat(android.content.Context!, android.support.v4.media.session.MediaSessionCompat);
+ ctor public MediaControllerCompat(android.content.Context!, android.support.v4.media.session.MediaSessionCompat.Token);
+ method public void addQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method public void addQueueItem(android.support.v4.media.MediaDescriptionCompat!, int);
+ method public void adjustVolume(int, int);
+ method public boolean dispatchMediaButtonEvent(android.view.KeyEvent!);
+ method public android.os.Bundle! getExtras();
+ method public long getFlags();
+ method public static android.support.v4.media.session.MediaControllerCompat! getMediaController(android.app.Activity);
+ method public Object! getMediaController();
+ method public android.support.v4.media.MediaMetadataCompat! getMetadata();
+ method public String! getPackageName();
+ method public android.support.v4.media.session.MediaControllerCompat.PlaybackInfo! getPlaybackInfo();
+ method public android.support.v4.media.session.PlaybackStateCompat! getPlaybackState();
+ method public java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem!>! getQueue();
+ method public CharSequence! getQueueTitle();
+ method public int getRatingType();
+ method public int getRepeatMode();
+ method public android.app.PendingIntent! getSessionActivity();
+ method public android.os.Bundle getSessionInfo();
+ method public android.support.v4.media.session.MediaSessionCompat.Token! getSessionToken();
+ method public int getShuffleMode();
+ method public android.support.v4.media.session.MediaControllerCompat.TransportControls! getTransportControls();
+ method public boolean isCaptioningEnabled();
+ method public boolean isSessionReady();
+ method public void registerCallback(android.support.v4.media.session.MediaControllerCompat.Callback);
+ method public void registerCallback(android.support.v4.media.session.MediaControllerCompat.Callback, android.os.Handler!);
+ method public void removeQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method @Deprecated public void removeQueueItemAt(int);
+ method public void sendCommand(String, android.os.Bundle?, android.os.ResultReceiver?);
+ method public static void setMediaController(android.app.Activity, android.support.v4.media.session.MediaControllerCompat!);
+ method public void setVolumeTo(int, int);
+ method public void unregisterCallback(android.support.v4.media.session.MediaControllerCompat.Callback);
+ }
+
+ public abstract static class MediaControllerCompat.Callback implements android.os.IBinder.DeathRecipient {
+ ctor public MediaControllerCompat.Callback();
+ method public void binderDied();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.support.v4.media.session.IMediaControllerCallback! getIControllerCallback();
+ method public void onAudioInfoChanged(android.support.v4.media.session.MediaControllerCompat.PlaybackInfo!);
+ method public void onCaptioningEnabledChanged(boolean);
+ method public void onExtrasChanged(android.os.Bundle!);
+ method public void onMetadataChanged(android.support.v4.media.MediaMetadataCompat!);
+ method public void onPlaybackStateChanged(android.support.v4.media.session.PlaybackStateCompat!);
+ method public void onQueueChanged(java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem!>!);
+ method public void onQueueTitleChanged(CharSequence!);
+ method public void onRepeatModeChanged(@android.support.v4.media.session.PlaybackStateCompat.RepeatMode int);
+ method public void onSessionDestroyed();
+ method public void onSessionEvent(String!, android.os.Bundle!);
+ method public void onSessionReady();
+ method public void onShuffleModeChanged(@android.support.v4.media.session.PlaybackStateCompat.ShuffleMode int);
+ }
+
+ public static final class MediaControllerCompat.PlaybackInfo {
+ method public androidx.media.AudioAttributesCompat getAudioAttributes();
+ method @Deprecated public int getAudioStream();
+ method public int getCurrentVolume();
+ method public int getMaxVolume();
+ method public int getPlaybackType();
+ method public int getVolumeControl();
+ field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
+ field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
+ }
+
+ public abstract static class MediaControllerCompat.TransportControls {
+ method public abstract void fastForward();
+ method public abstract void pause();
+ method public abstract void play();
+ method public abstract void playFromMediaId(String!, android.os.Bundle!);
+ method public abstract void playFromSearch(String!, android.os.Bundle!);
+ method public abstract void playFromUri(android.net.Uri!, android.os.Bundle!);
+ method public abstract void prepare();
+ method public abstract void prepareFromMediaId(String!, android.os.Bundle!);
+ method public abstract void prepareFromSearch(String!, android.os.Bundle!);
+ method public abstract void prepareFromUri(android.net.Uri!, android.os.Bundle!);
+ method public abstract void rewind();
+ method public abstract void seekTo(long);
+ method public abstract void sendCustomAction(android.support.v4.media.session.PlaybackStateCompat.CustomAction!, android.os.Bundle!);
+ method public abstract void sendCustomAction(String!, android.os.Bundle!);
+ method public abstract void setCaptioningEnabled(boolean);
+ method public void setPlaybackSpeed(float);
+ method public abstract void setRating(android.support.v4.media.RatingCompat!);
+ method public abstract void setRating(android.support.v4.media.RatingCompat!, android.os.Bundle!);
+ method public abstract void setRepeatMode(@android.support.v4.media.session.PlaybackStateCompat.RepeatMode int);
+ method public abstract void setShuffleMode(@android.support.v4.media.session.PlaybackStateCompat.ShuffleMode int);
+ method public abstract void skipToNext();
+ method public abstract void skipToPrevious();
+ method public abstract void skipToQueueItem(long);
+ method public abstract void stop();
+ field public static final String EXTRA_LEGACY_STREAM_TYPE = "android.media.session.extra.LEGACY_STREAM_TYPE";
+ }
+
+ public class MediaSessionCompat {
+ ctor public MediaSessionCompat(android.content.Context, String);
+ ctor public MediaSessionCompat(android.content.Context, String, android.content.ComponentName?, android.app.PendingIntent?);
+ ctor public MediaSessionCompat(android.content.Context, String, android.content.ComponentName?, android.app.PendingIntent?, android.os.Bundle?);
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public MediaSessionCompat(android.content.Context, String, android.content.ComponentName?, android.app.PendingIntent?, android.os.Bundle?, androidx.versionedparcelable.VersionedParcelable?);
+ method public void addOnActiveChangeListener(android.support.v4.media.session.MediaSessionCompat.OnActiveChangeListener!);
+ method public static android.support.v4.media.session.MediaSessionCompat! fromMediaSession(android.content.Context!, Object!);
+ method public android.support.v4.media.session.MediaControllerCompat! getController();
+ method public final androidx.media.MediaSessionManager.RemoteUserInfo getCurrentControllerInfo();
+ method public Object! getMediaSession();
+ method public Object! getRemoteControlClient();
+ method public android.support.v4.media.session.MediaSessionCompat.Token! getSessionToken();
+ method public boolean isActive();
+ method public void release();
+ method public void removeOnActiveChangeListener(android.support.v4.media.session.MediaSessionCompat.OnActiveChangeListener!);
+ method public void sendSessionEvent(String!, android.os.Bundle!);
+ method public void setActive(boolean);
+ method public void setCallback(android.support.v4.media.session.MediaSessionCompat.Callback!);
+ method public void setCallback(android.support.v4.media.session.MediaSessionCompat.Callback!, android.os.Handler!);
+ method public void setCaptioningEnabled(boolean);
+ method public void setExtras(android.os.Bundle!);
+ method public void setFlags(int);
+ method public void setMediaButtonReceiver(android.app.PendingIntent!);
+ method public void setMetadata(android.support.v4.media.MediaMetadataCompat!);
+ method public void setPlaybackState(android.support.v4.media.session.PlaybackStateCompat!);
+ method public void setPlaybackToLocal(int);
+ method public void setPlaybackToRemote(androidx.media.VolumeProviderCompat!);
+ method public void setQueue(java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem!>!);
+ method public void setQueueTitle(CharSequence!);
+ method public void setRatingType(@android.support.v4.media.RatingCompat.Style int);
+ method public void setRepeatMode(@android.support.v4.media.session.PlaybackStateCompat.RepeatMode int);
+ method public void setSessionActivity(android.app.PendingIntent!);
+ method public void setShuffleMode(@android.support.v4.media.session.PlaybackStateCompat.ShuffleMode int);
+ field public static final String ACTION_FLAG_AS_INAPPROPRIATE = "android.support.v4.media.session.action.FLAG_AS_INAPPROPRIATE";
+ field public static final String ACTION_FOLLOW = "android.support.v4.media.session.action.FOLLOW";
+ field public static final String ACTION_SKIP_AD = "android.support.v4.media.session.action.SKIP_AD";
+ field public static final String ACTION_UNFOLLOW = "android.support.v4.media.session.action.UNFOLLOW";
+ field public static final String ARGUMENT_MEDIA_ATTRIBUTE = "android.support.v4.media.session.ARGUMENT_MEDIA_ATTRIBUTE";
+ field public static final String ARGUMENT_MEDIA_ATTRIBUTE_VALUE = "android.support.v4.media.session.ARGUMENT_MEDIA_ATTRIBUTE_VALUE";
+ field @Deprecated public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
+ field public static final int FLAG_HANDLES_QUEUE_COMMANDS = 4; // 0x4
+ field @Deprecated public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
+ field public static final int MEDIA_ATTRIBUTE_ALBUM = 1; // 0x1
+ field public static final int MEDIA_ATTRIBUTE_ARTIST = 0; // 0x0
+ field public static final int MEDIA_ATTRIBUTE_PLAYLIST = 2; // 0x2
+ }
+
+ public abstract static class MediaSessionCompat.Callback {
+ ctor public MediaSessionCompat.Callback();
+ method public void onAddQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method public void onAddQueueItem(android.support.v4.media.MediaDescriptionCompat!, int);
+ method public void onCommand(String!, android.os.Bundle!, android.os.ResultReceiver!);
+ method public void onCustomAction(String!, android.os.Bundle!);
+ method public void onFastForward();
+ method public boolean onMediaButtonEvent(android.content.Intent!);
+ method public void onPause();
+ method public void onPlay();
+ method public void onPlayFromMediaId(String!, android.os.Bundle!);
+ method public void onPlayFromSearch(String!, android.os.Bundle!);
+ method public void onPlayFromUri(android.net.Uri!, android.os.Bundle!);
+ method public void onPrepare();
+ method public void onPrepareFromMediaId(String!, android.os.Bundle!);
+ method public void onPrepareFromSearch(String!, android.os.Bundle!);
+ method public void onPrepareFromUri(android.net.Uri!, android.os.Bundle!);
+ method public void onRemoveQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method @Deprecated public void onRemoveQueueItemAt(int);
+ method public void onRewind();
+ method public void onSeekTo(long);
+ method public void onSetCaptioningEnabled(boolean);
+ method public void onSetPlaybackSpeed(float);
+ method public void onSetRating(android.support.v4.media.RatingCompat!);
+ method public void onSetRating(android.support.v4.media.RatingCompat!, android.os.Bundle!);
+ method public void onSetRepeatMode(@android.support.v4.media.session.PlaybackStateCompat.RepeatMode int);
+ method public void onSetShuffleMode(@android.support.v4.media.session.PlaybackStateCompat.ShuffleMode int);
+ method public void onSkipToNext();
+ method public void onSkipToPrevious();
+ method public void onSkipToQueueItem(long);
+ method public void onStop();
+ }
+
+ public static interface MediaSessionCompat.OnActiveChangeListener {
+ method public void onActiveChanged();
+ }
+
+ public static final class MediaSessionCompat.QueueItem implements android.os.Parcelable {
+ ctor public MediaSessionCompat.QueueItem(android.support.v4.media.MediaDescriptionCompat!, long);
+ method public int describeContents();
+ method public static android.support.v4.media.session.MediaSessionCompat.QueueItem! fromQueueItem(Object!);
+ method public static java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem!>! fromQueueItemList(java.util.List<?>!);
+ method public android.support.v4.media.MediaDescriptionCompat! getDescription();
+ method public long getQueueId();
+ method public Object! getQueueItem();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.MediaSessionCompat.QueueItem!>! CREATOR;
+ field public static final int UNKNOWN_ID = -1; // 0xffffffff
+ }
+
+ public static final class MediaSessionCompat.Token implements android.os.Parcelable {
+ method public int describeContents();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.support.v4.media.session.MediaSessionCompat.Token! fromBundle(android.os.Bundle!);
+ method public static android.support.v4.media.session.MediaSessionCompat.Token! fromToken(Object!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.versionedparcelable.VersionedParcelable! getSession2Token();
+ method public Object! getToken();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSession2Token(androidx.versionedparcelable.VersionedParcelable!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.os.Bundle! toBundle();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.MediaSessionCompat.Token!>! CREATOR;
+ }
+
+ public class ParcelableVolumeInfo implements android.os.Parcelable {
+ ctor public ParcelableVolumeInfo(int, int, int, int, int);
+ ctor public ParcelableVolumeInfo(android.os.Parcel!);
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.ParcelableVolumeInfo!>! CREATOR;
+ field public int audioStream;
+ field public int controlType;
+ field public int currentVolume;
+ field public int maxVolume;
+ field public int volumeType;
+ }
+
+ public final class PlaybackStateCompat implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.session.PlaybackStateCompat! fromPlaybackState(Object!);
+ method @android.support.v4.media.session.PlaybackStateCompat.Actions public long getActions();
+ method public long getActiveQueueItemId();
+ method public long getBufferedPosition();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public long getCurrentPosition(Long!);
+ method public java.util.List<android.support.v4.media.session.PlaybackStateCompat.CustomAction!>! getCustomActions();
+ method public int getErrorCode();
+ method public CharSequence! getErrorMessage();
+ method public android.os.Bundle? getExtras();
+ method public long getLastPositionUpdateTime();
+ method public float getPlaybackSpeed();
+ method public Object! getPlaybackState();
+ method public long getPosition();
+ method @android.support.v4.media.session.PlaybackStateCompat.State public int getState();
+ method public static int toKeyCode(@android.support.v4.media.session.PlaybackStateCompat.MediaKeyAction long);
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final long ACTION_FAST_FORWARD = 64L; // 0x40L
+ field public static final long ACTION_PAUSE = 2L; // 0x2L
+ field public static final long ACTION_PLAY = 4L; // 0x4L
+ field public static final long ACTION_PLAY_FROM_MEDIA_ID = 1024L; // 0x400L
+ field public static final long ACTION_PLAY_FROM_SEARCH = 2048L; // 0x800L
+ field public static final long ACTION_PLAY_FROM_URI = 8192L; // 0x2000L
+ field public static final long ACTION_PLAY_PAUSE = 512L; // 0x200L
+ field public static final long ACTION_PREPARE = 16384L; // 0x4000L
+ field public static final long ACTION_PREPARE_FROM_MEDIA_ID = 32768L; // 0x8000L
+ field public static final long ACTION_PREPARE_FROM_SEARCH = 65536L; // 0x10000L
+ field public static final long ACTION_PREPARE_FROM_URI = 131072L; // 0x20000L
+ field public static final long ACTION_REWIND = 8L; // 0x8L
+ field public static final long ACTION_SEEK_TO = 256L; // 0x100L
+ field public static final long ACTION_SET_CAPTIONING_ENABLED = 1048576L; // 0x100000L
+ field public static final long ACTION_SET_RATING = 128L; // 0x80L
+ field public static final long ACTION_SET_REPEAT_MODE = 262144L; // 0x40000L
+ field public static final long ACTION_SET_SHUFFLE_MODE = 2097152L; // 0x200000L
+ field @Deprecated public static final long ACTION_SET_SHUFFLE_MODE_ENABLED = 524288L; // 0x80000L
+ field public static final long ACTION_SKIP_TO_NEXT = 32L; // 0x20L
+ field public static final long ACTION_SKIP_TO_PREVIOUS = 16L; // 0x10L
+ field public static final long ACTION_SKIP_TO_QUEUE_ITEM = 4096L; // 0x1000L
+ field public static final long ACTION_STOP = 1L; // 0x1L
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.PlaybackStateCompat!>! CREATOR;
+ field public static final int ERROR_CODE_ACTION_ABORTED = 10; // 0xa
+ field public static final int ERROR_CODE_APP_ERROR = 1; // 0x1
+ field public static final int ERROR_CODE_AUTHENTICATION_EXPIRED = 3; // 0x3
+ field public static final int ERROR_CODE_CONCURRENT_STREAM_LIMIT = 5; // 0x5
+ field public static final int ERROR_CODE_CONTENT_ALREADY_PLAYING = 8; // 0x8
+ field public static final int ERROR_CODE_END_OF_QUEUE = 11; // 0xb
+ field public static final int ERROR_CODE_NOT_AVAILABLE_IN_REGION = 7; // 0x7
+ field public static final int ERROR_CODE_NOT_SUPPORTED = 2; // 0x2
+ field public static final int ERROR_CODE_PARENTAL_CONTROL_RESTRICTED = 6; // 0x6
+ field public static final int ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED = 4; // 0x4
+ field public static final int ERROR_CODE_SKIP_LIMIT_REACHED = 9; // 0x9
+ field public static final int ERROR_CODE_UNKNOWN_ERROR = 0; // 0x0
+ field public static final long PLAYBACK_POSITION_UNKNOWN = -1L; // 0xffffffffffffffffL
+ field public static final int REPEAT_MODE_ALL = 2; // 0x2
+ field public static final int REPEAT_MODE_GROUP = 3; // 0x3
+ field public static final int REPEAT_MODE_INVALID = -1; // 0xffffffff
+ field public static final int REPEAT_MODE_NONE = 0; // 0x0
+ field public static final int REPEAT_MODE_ONE = 1; // 0x1
+ field public static final int SHUFFLE_MODE_ALL = 1; // 0x1
+ field public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
+ field public static final int SHUFFLE_MODE_INVALID = -1; // 0xffffffff
+ field public static final int SHUFFLE_MODE_NONE = 0; // 0x0
+ field public static final int STATE_BUFFERING = 6; // 0x6
+ field public static final int STATE_CONNECTING = 8; // 0x8
+ field public static final int STATE_ERROR = 7; // 0x7
+ field public static final int STATE_FAST_FORWARDING = 4; // 0x4
+ field public static final int STATE_NONE = 0; // 0x0
+ field public static final int STATE_PAUSED = 2; // 0x2
+ field public static final int STATE_PLAYING = 3; // 0x3
+ field public static final int STATE_REWINDING = 5; // 0x5
+ field public static final int STATE_SKIPPING_TO_NEXT = 10; // 0xa
+ field public static final int STATE_SKIPPING_TO_PREVIOUS = 9; // 0x9
+ field public static final int STATE_SKIPPING_TO_QUEUE_ITEM = 11; // 0xb
+ field public static final int STATE_STOPPED = 1; // 0x1
+ }
+
+ @LongDef(flag=true, value={android.support.v4.media.session.PlaybackStateCompat.ACTION_STOP, android.support.v4.media.session.PlaybackStateCompat.ACTION_PAUSE, android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY, android.support.v4.media.session.PlaybackStateCompat.ACTION_REWIND, android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS, android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_NEXT, android.support.v4.media.session.PlaybackStateCompat.ACTION_FAST_FORWARD, android.support.v4.media.session.PlaybackStateCompat.ACTION_SET_RATING, android.support.v4.media.session.PlaybackStateCompat.ACTION_SEEK_TO, android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_PAUSE, android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID, android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH, android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM, android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_FROM_URI, android.support.v4.media.session.PlaybackStateCompat.ACTION_PREPARE, android.support.v4.media.session.PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID, android.support.v4.media.session.PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH, android.support.v4.media.session.PlaybackStateCompat.ACTION_PREPARE_FROM_URI, android.support.v4.media.session.PlaybackStateCompat.ACTION_SET_REPEAT_MODE, android.support.v4.media.session.PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE, android.support.v4.media.session.PlaybackStateCompat.ACTION_SET_CAPTIONING_ENABLED}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PlaybackStateCompat.Actions {
+ }
+
+ public static final class PlaybackStateCompat.Builder {
+ ctor public PlaybackStateCompat.Builder();
+ ctor public PlaybackStateCompat.Builder(android.support.v4.media.session.PlaybackStateCompat!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! addCustomAction(String!, String!, int);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! addCustomAction(android.support.v4.media.session.PlaybackStateCompat.CustomAction!);
+ method public android.support.v4.media.session.PlaybackStateCompat! build();
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setActions(@android.support.v4.media.session.PlaybackStateCompat.Actions long);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setActiveQueueItemId(long);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setBufferedPosition(long);
+ method @Deprecated public android.support.v4.media.session.PlaybackStateCompat.Builder! setErrorMessage(CharSequence!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setErrorMessage(int, CharSequence!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setExtras(android.os.Bundle!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setState(@android.support.v4.media.session.PlaybackStateCompat.State int, long, float);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setState(@android.support.v4.media.session.PlaybackStateCompat.State int, long, float, long);
+ }
+
+ public static final class PlaybackStateCompat.CustomAction implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.session.PlaybackStateCompat.CustomAction! fromCustomAction(Object!);
+ method public String! getAction();
+ method public Object! getCustomAction();
+ method public android.os.Bundle! getExtras();
+ method public int getIcon();
+ method public CharSequence! getName();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.PlaybackStateCompat.CustomAction!>! CREATOR;
+ }
+
+ public static final class PlaybackStateCompat.CustomAction.Builder {
+ ctor public PlaybackStateCompat.CustomAction.Builder(String!, CharSequence!, int);
+ method public android.support.v4.media.session.PlaybackStateCompat.CustomAction! build();
+ method public android.support.v4.media.session.PlaybackStateCompat.CustomAction.Builder! setExtras(android.os.Bundle!);
+ }
+
+
+ @IntDef({android.support.v4.media.session.PlaybackStateCompat.REPEAT_MODE_INVALID, android.support.v4.media.session.PlaybackStateCompat.REPEAT_MODE_NONE, android.support.v4.media.session.PlaybackStateCompat.REPEAT_MODE_ONE, android.support.v4.media.session.PlaybackStateCompat.REPEAT_MODE_ALL, android.support.v4.media.session.PlaybackStateCompat.REPEAT_MODE_GROUP}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PlaybackStateCompat.RepeatMode {
+ }
+
+ @IntDef({android.support.v4.media.session.PlaybackStateCompat.SHUFFLE_MODE_INVALID, android.support.v4.media.session.PlaybackStateCompat.SHUFFLE_MODE_NONE, android.support.v4.media.session.PlaybackStateCompat.SHUFFLE_MODE_ALL, android.support.v4.media.session.PlaybackStateCompat.SHUFFLE_MODE_GROUP}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PlaybackStateCompat.ShuffleMode {
+ }
+
+ @IntDef({android.support.v4.media.session.PlaybackStateCompat.STATE_NONE, android.support.v4.media.session.PlaybackStateCompat.STATE_STOPPED, android.support.v4.media.session.PlaybackStateCompat.STATE_PAUSED, android.support.v4.media.session.PlaybackStateCompat.STATE_PLAYING, android.support.v4.media.session.PlaybackStateCompat.STATE_FAST_FORWARDING, android.support.v4.media.session.PlaybackStateCompat.STATE_REWINDING, android.support.v4.media.session.PlaybackStateCompat.STATE_BUFFERING, android.support.v4.media.session.PlaybackStateCompat.STATE_ERROR, android.support.v4.media.session.PlaybackStateCompat.STATE_CONNECTING, android.support.v4.media.session.PlaybackStateCompat.STATE_SKIPPING_TO_PREVIOUS, android.support.v4.media.session.PlaybackStateCompat.STATE_SKIPPING_TO_NEXT, android.support.v4.media.session.PlaybackStateCompat.STATE_SKIPPING_TO_QUEUE_ITEM}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PlaybackStateCompat.State {
+ }
+
+}
+
+package androidx.media {
+
+ public class AudioAttributesCompat implements androidx.versionedparcelable.VersionedParcelable {
+ method public int getContentType();
+ method public int getFlags();
+ method public int getLegacyStreamType();
+ method @androidx.media.AudioAttributesCompat.AttributeUsage public int getUsage();
+ method public int getVolumeControlStream();
+ method public Object? unwrap();
+ method public static androidx.media.AudioAttributesCompat? wrap(Object);
+ field public static final int CONTENT_TYPE_MOVIE = 3; // 0x3
+ field public static final int CONTENT_TYPE_MUSIC = 2; // 0x2
+ field public static final int CONTENT_TYPE_SONIFICATION = 4; // 0x4
+ field public static final int CONTENT_TYPE_SPEECH = 1; // 0x1
+ field public static final int CONTENT_TYPE_UNKNOWN = 0; // 0x0
+ field public static final int FLAG_AUDIBILITY_ENFORCED = 1; // 0x1
+ field public static final int FLAG_HW_AV_SYNC = 16; // 0x10
+ field public static final int USAGE_ALARM = 4; // 0x4
+ field public static final int USAGE_ASSISTANCE_ACCESSIBILITY = 11; // 0xb
+ field public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12; // 0xc
+ field public static final int USAGE_ASSISTANCE_SONIFICATION = 13; // 0xd
+ field public static final int USAGE_ASSISTANT = 16; // 0x10
+ field public static final int USAGE_GAME = 14; // 0xe
+ field public static final int USAGE_MEDIA = 1; // 0x1
+ field public static final int USAGE_NOTIFICATION = 5; // 0x5
+ field public static final int USAGE_NOTIFICATION_COMMUNICATION_DELAYED = 9; // 0x9
+ field public static final int USAGE_NOTIFICATION_COMMUNICATION_INSTANT = 8; // 0x8
+ field public static final int USAGE_NOTIFICATION_COMMUNICATION_REQUEST = 7; // 0x7
+ field public static final int USAGE_NOTIFICATION_EVENT = 10; // 0xa
+ field public static final int USAGE_NOTIFICATION_RINGTONE = 6; // 0x6
+ field public static final int USAGE_UNKNOWN = 0; // 0x0
+ field public static final int USAGE_VOICE_COMMUNICATION = 2; // 0x2
+ field public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING = 3; // 0x3
+ }
+
+
+
+ public static class AudioAttributesCompat.Builder {
+ ctor public AudioAttributesCompat.Builder();
+ ctor public AudioAttributesCompat.Builder(androidx.media.AudioAttributesCompat!);
+ method public androidx.media.AudioAttributesCompat! build();
+ method public androidx.media.AudioAttributesCompat.Builder! setContentType(@androidx.media.AudioAttributesCompat.AttributeContentType int);
+ method public androidx.media.AudioAttributesCompat.Builder! setFlags(int);
+ method public androidx.media.AudioAttributesCompat.Builder! setLegacyStreamType(int);
+ method public androidx.media.AudioAttributesCompat.Builder! setUsage(@androidx.media.AudioAttributesCompat.AttributeUsage int);
+ }
+
+
+
+
+ public class AudioFocusRequestCompat {
+ method public androidx.media.AudioAttributesCompat getAudioAttributesCompat();
+ method public android.os.Handler getFocusChangeHandler();
+ method public int getFocusGain();
+ method public android.media.AudioManager.OnAudioFocusChangeListener getOnAudioFocusChangeListener();
+ method public boolean willPauseWhenDucked();
+ }
+
+ public static final class AudioFocusRequestCompat.Builder {
+ ctor public AudioFocusRequestCompat.Builder(int);
+ ctor public AudioFocusRequestCompat.Builder(androidx.media.AudioFocusRequestCompat);
+ method public androidx.media.AudioFocusRequestCompat! build();
+ method public androidx.media.AudioFocusRequestCompat.Builder setAudioAttributes(androidx.media.AudioAttributesCompat);
+ method public androidx.media.AudioFocusRequestCompat.Builder setFocusGain(int);
+ method public androidx.media.AudioFocusRequestCompat.Builder setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener);
+ method public androidx.media.AudioFocusRequestCompat.Builder setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener, android.os.Handler);
+ method public androidx.media.AudioFocusRequestCompat.Builder setWillPauseWhenDucked(boolean);
+ }
+
+ public final class AudioManagerCompat {
+ method public static int abandonAudioFocusRequest(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
+ method public static int requestAudioFocus(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
+ field public static final int AUDIOFOCUS_GAIN = 1; // 0x1
+ field public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; // 0x2
+ field public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE = 4; // 0x4
+ field public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK = 3; // 0x3
+ }
+
+
+
+ public abstract class MediaBrowserServiceCompat extends android.app.Service {
+ ctor public MediaBrowserServiceCompat();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void attachToBaseContext(android.content.Context!);
+ method public void dump(java.io.FileDescriptor!, java.io.PrintWriter!, String![]!);
+ method public final android.os.Bundle! getBrowserRootHints();
+ method public final androidx.media.MediaSessionManager.RemoteUserInfo getCurrentBrowserInfo();
+ method public android.support.v4.media.session.MediaSessionCompat.Token? getSessionToken();
+ method public void notifyChildrenChanged(String);
+ method public void notifyChildrenChanged(String, android.os.Bundle);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void notifyChildrenChanged(androidx.media.MediaSessionManager.RemoteUserInfo, String, android.os.Bundle);
+ method public android.os.IBinder! onBind(android.content.Intent!);
+ method public void onCustomAction(String, android.os.Bundle!, androidx.media.MediaBrowserServiceCompat.Result<android.os.Bundle!>);
+ method public abstract androidx.media.MediaBrowserServiceCompat.BrowserRoot? onGetRoot(String, int, android.os.Bundle?);
+ method public abstract void onLoadChildren(String, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>!>);
+ method public void onLoadChildren(String, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>!>, android.os.Bundle);
+ method public void onLoadItem(String!, androidx.media.MediaBrowserServiceCompat.Result<android.support.v4.media.MediaBrowserCompat.MediaItem!>);
+ method public void onSearch(String, android.os.Bundle!, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>!>);
+ method public void setSessionToken(android.support.v4.media.session.MediaSessionCompat.Token!);
+ field public static final String SERVICE_INTERFACE = "android.media.browse.MediaBrowserService";
+ }
+
+ public static final class MediaBrowserServiceCompat.BrowserRoot {
+ ctor public MediaBrowserServiceCompat.BrowserRoot(String, android.os.Bundle?);
+ method public android.os.Bundle! getExtras();
+ method public String! getRootId();
+ field public static final String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
+ field public static final String EXTRA_RECENT = "android.service.media.extra.RECENT";
+ field public static final String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
+ field @Deprecated public static final String EXTRA_SUGGESTION_KEYWORDS = "android.service.media.extra.SUGGESTION_KEYWORDS";
+ }
+
+ public static class MediaBrowserServiceCompat.Result<T> {
+ method public void detach();
+ method public void sendError(android.os.Bundle!);
+ method public void sendProgressUpdate(android.os.Bundle!);
+ method public void sendResult(T!);
+ }
+
+ public final class MediaSessionManager {
+ method public static androidx.media.MediaSessionManager getSessionManager(android.content.Context);
+ method public boolean isTrustedForMediaControl(androidx.media.MediaSessionManager.RemoteUserInfo);
+ }
+
+ public static final class MediaSessionManager.RemoteUserInfo {
+ ctor public MediaSessionManager.RemoteUserInfo(String, int, int);
+ method public String getPackageName();
+ method public int getPid();
+ method public int getUid();
+ field public static final String LEGACY_CONTROLLER = "android.media.session.MediaController";
+ }
+
+ public abstract class VolumeProviderCompat {
+ ctor public VolumeProviderCompat(@androidx.media.VolumeProviderCompat.ControlType int, int, int);
+ method public final int getCurrentVolume();
+ method public final int getMaxVolume();
+ method @androidx.media.VolumeProviderCompat.ControlType public final int getVolumeControl();
+ method public Object! getVolumeProvider();
+ method public void onAdjustVolume(int);
+ method public void onSetVolumeTo(int);
+ method public void setCallback(androidx.media.VolumeProviderCompat.Callback!);
+ method public final void setCurrentVolume(int);
+ field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
+ field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
+ field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
+ }
+
+ public abstract static class VolumeProviderCompat.Callback {
+ ctor public VolumeProviderCompat.Callback();
+ method public abstract void onVolumeChanged(androidx.media.VolumeProviderCompat!);
+ }
+
+ @IntDef({androidx.media.VolumeProviderCompat.VOLUME_CONTROL_FIXED, androidx.media.VolumeProviderCompat.VOLUME_CONTROL_RELATIVE, androidx.media.VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface VolumeProviderCompat.ControlType {
+ }
+
+}
+
+package androidx.media.app {
+
+ public class NotificationCompat {
+ }
+
+ public static class NotificationCompat.DecoratedMediaCustomViewStyle extends androidx.media.app.NotificationCompat.MediaStyle {
+ ctor public NotificationCompat.DecoratedMediaCustomViewStyle();
+ }
+
+ public static class NotificationCompat.MediaStyle extends androidx.core.app.NotificationCompat.Style {
+ ctor public NotificationCompat.MediaStyle();
+ ctor public NotificationCompat.MediaStyle(androidx.core.app.NotificationCompat.Builder!);
+ method public static android.support.v4.media.session.MediaSessionCompat.Token! getMediaSession(android.app.Notification!);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setCancelButtonIntent(android.app.PendingIntent!);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setMediaSession(android.support.v4.media.session.MediaSessionCompat.Token!);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setShowActionsInCompactView(int...);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setShowCancelButton(boolean);
+ }
+
+}
+
+package androidx.media.session {
+
+ public class MediaButtonReceiver extends android.content.BroadcastReceiver {
+ ctor public MediaButtonReceiver();
+ method public static android.app.PendingIntent! buildMediaButtonPendingIntent(android.content.Context!, @android.support.v4.media.session.PlaybackStateCompat.MediaKeyAction long);
+ method public static android.app.PendingIntent! buildMediaButtonPendingIntent(android.content.Context!, android.content.ComponentName!, @android.support.v4.media.session.PlaybackStateCompat.MediaKeyAction long);
+ method public static android.view.KeyEvent! handleIntent(android.support.v4.media.session.MediaSessionCompat!, android.content.Intent!);
+ method public void onReceive(android.content.Context!, android.content.Intent!);
+ }
+
+}
+
diff --git a/media/api/restricted_current.txt b/media/api/restricted_current.txt
new file mode 100644
index 0000000..9c1f0db
--- /dev/null
+++ b/media/api/restricted_current.txt
@@ -0,0 +1,743 @@
+// Signature format: 3.0
+package android.support.v4.media {
+
+ public final class MediaBrowserCompat {
+ ctor public MediaBrowserCompat(android.content.Context!, android.content.ComponentName!, android.support.v4.media.MediaBrowserCompat.ConnectionCallback!, android.os.Bundle!);
+ method public void connect();
+ method public void disconnect();
+ method public android.os.Bundle? getExtras();
+ method public void getItem(String, android.support.v4.media.MediaBrowserCompat.ItemCallback);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.os.Bundle? getNotifyChildrenChangedOptions();
+ method public String getRoot();
+ method public android.content.ComponentName getServiceComponent();
+ method public android.support.v4.media.session.MediaSessionCompat.Token getSessionToken();
+ method public boolean isConnected();
+ method public void search(String, android.os.Bundle!, android.support.v4.media.MediaBrowserCompat.SearchCallback);
+ method public void sendCustomAction(String, android.os.Bundle!, android.support.v4.media.MediaBrowserCompat.CustomActionCallback?);
+ method public void subscribe(String, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+ method public void subscribe(String, android.os.Bundle, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+ method public void unsubscribe(String);
+ method public void unsubscribe(String, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+ field public static final String CUSTOM_ACTION_DOWNLOAD = "android.support.v4.media.action.DOWNLOAD";
+ field public static final String CUSTOM_ACTION_REMOVE_DOWNLOADED_FILE = "android.support.v4.media.action.REMOVE_DOWNLOADED_FILE";
+ field public static final String EXTRA_DOWNLOAD_PROGRESS = "android.media.browse.extra.DOWNLOAD_PROGRESS";
+ field public static final String EXTRA_MEDIA_ID = "android.media.browse.extra.MEDIA_ID";
+ field public static final String EXTRA_PAGE = "android.media.browse.extra.PAGE";
+ field public static final String EXTRA_PAGE_SIZE = "android.media.browse.extra.PAGE_SIZE";
+ }
+
+ public static class MediaBrowserCompat.ConnectionCallback {
+ ctor public MediaBrowserCompat.ConnectionCallback();
+ method public void onConnected();
+ method public void onConnectionFailed();
+ method public void onConnectionSuspended();
+ }
+
+ public abstract static class MediaBrowserCompat.CustomActionCallback {
+ ctor public MediaBrowserCompat.CustomActionCallback();
+ method public void onError(String!, android.os.Bundle!, android.os.Bundle!);
+ method public void onProgressUpdate(String!, android.os.Bundle!, android.os.Bundle!);
+ method public void onResult(String!, android.os.Bundle!, android.os.Bundle!);
+ }
+
+ public abstract static class MediaBrowserCompat.ItemCallback {
+ ctor public MediaBrowserCompat.ItemCallback();
+ method public void onError(String);
+ method public void onItemLoaded(android.support.v4.media.MediaBrowserCompat.MediaItem!);
+ }
+
+ public static class MediaBrowserCompat.MediaItem implements android.os.Parcelable {
+ ctor public MediaBrowserCompat.MediaItem(android.support.v4.media.MediaDescriptionCompat, int);
+ method public int describeContents();
+ method public static android.support.v4.media.MediaBrowserCompat.MediaItem! fromMediaItem(Object!);
+ method public static java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>! fromMediaItemList(java.util.List<?>!);
+ method public android.support.v4.media.MediaDescriptionCompat getDescription();
+ method public int getFlags();
+ method public String? getMediaId();
+ method public boolean isBrowsable();
+ method public boolean isPlayable();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.MediaBrowserCompat.MediaItem!>! CREATOR;
+ field public static final int FLAG_BROWSABLE = 1; // 0x1
+ field public static final int FLAG_PLAYABLE = 2; // 0x2
+ }
+
+ public abstract static class MediaBrowserCompat.SearchCallback {
+ ctor public MediaBrowserCompat.SearchCallback();
+ method public void onError(String, android.os.Bundle!);
+ method public void onSearchResult(String, android.os.Bundle!, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>);
+ }
+
+ public abstract static class MediaBrowserCompat.SubscriptionCallback {
+ ctor public MediaBrowserCompat.SubscriptionCallback();
+ method public void onChildrenLoaded(String, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>);
+ method public void onChildrenLoaded(String, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>, android.os.Bundle);
+ method public void onError(String);
+ method public void onError(String, android.os.Bundle);
+ }
+
+ public final class MediaDescriptionCompat implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.MediaDescriptionCompat! fromMediaDescription(Object!);
+ method public CharSequence? getDescription();
+ method public android.os.Bundle? getExtras();
+ method public android.graphics.Bitmap? getIconBitmap();
+ method public android.net.Uri? getIconUri();
+ method public Object! getMediaDescription();
+ method public String? getMediaId();
+ method public android.net.Uri? getMediaUri();
+ method public CharSequence? getSubtitle();
+ method public CharSequence? getTitle();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final long BT_FOLDER_TYPE_ALBUMS = 2L; // 0x2L
+ field public static final long BT_FOLDER_TYPE_ARTISTS = 3L; // 0x3L
+ field public static final long BT_FOLDER_TYPE_GENRES = 4L; // 0x4L
+ field public static final long BT_FOLDER_TYPE_MIXED = 0L; // 0x0L
+ field public static final long BT_FOLDER_TYPE_PLAYLISTS = 5L; // 0x5L
+ field public static final long BT_FOLDER_TYPE_TITLES = 1L; // 0x1L
+ field public static final long BT_FOLDER_TYPE_YEARS = 6L; // 0x6L
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.MediaDescriptionCompat!>! CREATOR;
+ field public static final String EXTRA_BT_FOLDER_TYPE = "android.media.extra.BT_FOLDER_TYPE";
+ field public static final String EXTRA_DOWNLOAD_STATUS = "android.media.extra.DOWNLOAD_STATUS";
+ field public static final long STATUS_DOWNLOADED = 2L; // 0x2L
+ field public static final long STATUS_DOWNLOADING = 1L; // 0x1L
+ field public static final long STATUS_NOT_DOWNLOADED = 0L; // 0x0L
+ }
+
+ public static final class MediaDescriptionCompat.Builder {
+ ctor public MediaDescriptionCompat.Builder();
+ method public android.support.v4.media.MediaDescriptionCompat! build();
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setDescription(CharSequence?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setExtras(android.os.Bundle?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setIconBitmap(android.graphics.Bitmap?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setIconUri(android.net.Uri?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setMediaId(String?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setMediaUri(android.net.Uri?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setSubtitle(CharSequence?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setTitle(CharSequence?);
+ }
+
+ public final class MediaMetadataCompat implements android.os.Parcelable {
+ method public boolean containsKey(String!);
+ method public int describeContents();
+ method public static android.support.v4.media.MediaMetadataCompat! fromMediaMetadata(Object!);
+ method public android.graphics.Bitmap! getBitmap(String!);
+ method public android.os.Bundle! getBundle();
+ method public android.support.v4.media.MediaDescriptionCompat! getDescription();
+ method public long getLong(String!);
+ method public Object! getMediaMetadata();
+ method public android.support.v4.media.RatingCompat! getRating(String!);
+ method public String! getString(String!);
+ method public CharSequence! getText(String!);
+ method public java.util.Set<java.lang.String!>! keySet();
+ method public int size();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.MediaMetadataCompat!>! CREATOR;
+ field public static final String METADATA_KEY_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
+ field public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
+ field public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
+ field public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+ field public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
+ field public static final String METADATA_KEY_ART = "android.media.metadata.ART";
+ field public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
+ field public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
+ field public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
+ field public static final String METADATA_KEY_BT_FOLDER_TYPE = "android.media.metadata.BT_FOLDER_TYPE";
+ field public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
+ field public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
+ field public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
+ field public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+ field public static final String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
+ field public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
+ field public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
+ field public static final String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
+ field public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
+ field public static final String METADATA_KEY_DOWNLOAD_STATUS = "android.media.metadata.DOWNLOAD_STATUS";
+ field public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
+ field public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
+ field public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
+ field public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
+ field public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
+ field public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
+ field public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
+ field public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+ field public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
+ field public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
+ field public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
+ }
+
+ public static final class MediaMetadataCompat.Builder {
+ ctor public MediaMetadataCompat.Builder();
+ ctor public MediaMetadataCompat.Builder(android.support.v4.media.MediaMetadataCompat!);
+ method public android.support.v4.media.MediaMetadataCompat! build();
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putBitmap(String!, android.graphics.Bitmap!);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putLong(String!, long);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putRating(String!, android.support.v4.media.RatingCompat!);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putString(String!, String!);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putText(String!, CharSequence!);
+ }
+
+ public final class RatingCompat implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.RatingCompat! fromRating(Object!);
+ method public float getPercentRating();
+ method public Object! getRating();
+ method @android.support.v4.media.RatingCompat.Style public int getRatingStyle();
+ method public float getStarRating();
+ method public boolean hasHeart();
+ method public boolean isRated();
+ method public boolean isThumbUp();
+ method public static android.support.v4.media.RatingCompat! newHeartRating(boolean);
+ method public static android.support.v4.media.RatingCompat! newPercentageRating(float);
+ method public static android.support.v4.media.RatingCompat! newStarRating(@android.support.v4.media.RatingCompat.StarStyle int, float);
+ method public static android.support.v4.media.RatingCompat! newThumbRating(boolean);
+ method public static android.support.v4.media.RatingCompat! newUnratedRating(@android.support.v4.media.RatingCompat.Style int);
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.RatingCompat!>! CREATOR;
+ field public static final int RATING_3_STARS = 3; // 0x3
+ field public static final int RATING_4_STARS = 4; // 0x4
+ field public static final int RATING_5_STARS = 5; // 0x5
+ field public static final int RATING_HEART = 1; // 0x1
+ field public static final int RATING_NONE = 0; // 0x0
+ field public static final int RATING_PERCENTAGE = 6; // 0x6
+ field public static final int RATING_THUMB_UP_DOWN = 2; // 0x2
+ }
+
+
+ @IntDef({android.support.v4.media.RatingCompat.RATING_NONE, android.support.v4.media.RatingCompat.RATING_HEART, android.support.v4.media.RatingCompat.RATING_THUMB_UP_DOWN, android.support.v4.media.RatingCompat.RATING_3_STARS, android.support.v4.media.RatingCompat.RATING_4_STARS, android.support.v4.media.RatingCompat.RATING_5_STARS, android.support.v4.media.RatingCompat.RATING_PERCENTAGE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface RatingCompat.Style {
+ }
+
+}
+
+package android.support.v4.media.session {
+
+ public final class MediaControllerCompat {
+ ctor public MediaControllerCompat(android.content.Context!, android.support.v4.media.session.MediaSessionCompat);
+ ctor public MediaControllerCompat(android.content.Context!, android.support.v4.media.session.MediaSessionCompat.Token);
+ method public void addQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method public void addQueueItem(android.support.v4.media.MediaDescriptionCompat!, int);
+ method public void adjustVolume(int, int);
+ method public boolean dispatchMediaButtonEvent(android.view.KeyEvent!);
+ method public android.os.Bundle! getExtras();
+ method public long getFlags();
+ method public static android.support.v4.media.session.MediaControllerCompat! getMediaController(android.app.Activity);
+ method public Object! getMediaController();
+ method public android.support.v4.media.MediaMetadataCompat! getMetadata();
+ method public String! getPackageName();
+ method public android.support.v4.media.session.MediaControllerCompat.PlaybackInfo! getPlaybackInfo();
+ method public android.support.v4.media.session.PlaybackStateCompat! getPlaybackState();
+ method public java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem!>! getQueue();
+ method public CharSequence! getQueueTitle();
+ method public int getRatingType();
+ method public int getRepeatMode();
+ method public android.app.PendingIntent! getSessionActivity();
+ method public android.os.Bundle getSessionInfo();
+ method public android.support.v4.media.session.MediaSessionCompat.Token! getSessionToken();
+ method public int getShuffleMode();
+ method public android.support.v4.media.session.MediaControllerCompat.TransportControls! getTransportControls();
+ method public boolean isCaptioningEnabled();
+ method public boolean isSessionReady();
+ method public void registerCallback(android.support.v4.media.session.MediaControllerCompat.Callback);
+ method public void registerCallback(android.support.v4.media.session.MediaControllerCompat.Callback, android.os.Handler!);
+ method public void removeQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method @Deprecated public void removeQueueItemAt(int);
+ method public void sendCommand(String, android.os.Bundle?, android.os.ResultReceiver?);
+ method public static void setMediaController(android.app.Activity, android.support.v4.media.session.MediaControllerCompat!);
+ method public void setVolumeTo(int, int);
+ method public void unregisterCallback(android.support.v4.media.session.MediaControllerCompat.Callback);
+ }
+
+ public abstract static class MediaControllerCompat.Callback implements android.os.IBinder.DeathRecipient {
+ ctor public MediaControllerCompat.Callback();
+ method public void binderDied();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.support.v4.media.session.IMediaControllerCallback! getIControllerCallback();
+ method public void onAudioInfoChanged(android.support.v4.media.session.MediaControllerCompat.PlaybackInfo!);
+ method public void onCaptioningEnabledChanged(boolean);
+ method public void onExtrasChanged(android.os.Bundle!);
+ method public void onMetadataChanged(android.support.v4.media.MediaMetadataCompat!);
+ method public void onPlaybackStateChanged(android.support.v4.media.session.PlaybackStateCompat!);
+ method public void onQueueChanged(java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem!>!);
+ method public void onQueueTitleChanged(CharSequence!);
+ method public void onRepeatModeChanged(@android.support.v4.media.session.PlaybackStateCompat.RepeatMode int);
+ method public void onSessionDestroyed();
+ method public void onSessionEvent(String!, android.os.Bundle!);
+ method public void onSessionReady();
+ method public void onShuffleModeChanged(@android.support.v4.media.session.PlaybackStateCompat.ShuffleMode int);
+ }
+
+ public static final class MediaControllerCompat.PlaybackInfo {
+ method public androidx.media.AudioAttributesCompat getAudioAttributes();
+ method @Deprecated public int getAudioStream();
+ method public int getCurrentVolume();
+ method public int getMaxVolume();
+ method public int getPlaybackType();
+ method public int getVolumeControl();
+ field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
+ field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
+ }
+
+ public abstract static class MediaControllerCompat.TransportControls {
+ method public abstract void fastForward();
+ method public abstract void pause();
+ method public abstract void play();
+ method public abstract void playFromMediaId(String!, android.os.Bundle!);
+ method public abstract void playFromSearch(String!, android.os.Bundle!);
+ method public abstract void playFromUri(android.net.Uri!, android.os.Bundle!);
+ method public abstract void prepare();
+ method public abstract void prepareFromMediaId(String!, android.os.Bundle!);
+ method public abstract void prepareFromSearch(String!, android.os.Bundle!);
+ method public abstract void prepareFromUri(android.net.Uri!, android.os.Bundle!);
+ method public abstract void rewind();
+ method public abstract void seekTo(long);
+ method public abstract void sendCustomAction(android.support.v4.media.session.PlaybackStateCompat.CustomAction!, android.os.Bundle!);
+ method public abstract void sendCustomAction(String!, android.os.Bundle!);
+ method public abstract void setCaptioningEnabled(boolean);
+ method public void setPlaybackSpeed(float);
+ method public abstract void setRating(android.support.v4.media.RatingCompat!);
+ method public abstract void setRating(android.support.v4.media.RatingCompat!, android.os.Bundle!);
+ method public abstract void setRepeatMode(@android.support.v4.media.session.PlaybackStateCompat.RepeatMode int);
+ method public abstract void setShuffleMode(@android.support.v4.media.session.PlaybackStateCompat.ShuffleMode int);
+ method public abstract void skipToNext();
+ method public abstract void skipToPrevious();
+ method public abstract void skipToQueueItem(long);
+ method public abstract void stop();
+ field public static final String EXTRA_LEGACY_STREAM_TYPE = "android.media.session.extra.LEGACY_STREAM_TYPE";
+ }
+
+ public class MediaSessionCompat {
+ ctor public MediaSessionCompat(android.content.Context, String);
+ ctor public MediaSessionCompat(android.content.Context, String, android.content.ComponentName?, android.app.PendingIntent?);
+ ctor public MediaSessionCompat(android.content.Context, String, android.content.ComponentName?, android.app.PendingIntent?, android.os.Bundle?);
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public MediaSessionCompat(android.content.Context, String, android.content.ComponentName?, android.app.PendingIntent?, android.os.Bundle?, androidx.versionedparcelable.VersionedParcelable?);
+ method public void addOnActiveChangeListener(android.support.v4.media.session.MediaSessionCompat.OnActiveChangeListener!);
+ method public static android.support.v4.media.session.MediaSessionCompat! fromMediaSession(android.content.Context!, Object!);
+ method public android.support.v4.media.session.MediaControllerCompat! getController();
+ method public final androidx.media.MediaSessionManager.RemoteUserInfo getCurrentControllerInfo();
+ method public Object! getMediaSession();
+ method public Object! getRemoteControlClient();
+ method public android.support.v4.media.session.MediaSessionCompat.Token! getSessionToken();
+ method public boolean isActive();
+ method public void release();
+ method public void removeOnActiveChangeListener(android.support.v4.media.session.MediaSessionCompat.OnActiveChangeListener!);
+ method public void sendSessionEvent(String!, android.os.Bundle!);
+ method public void setActive(boolean);
+ method public void setCallback(android.support.v4.media.session.MediaSessionCompat.Callback!);
+ method public void setCallback(android.support.v4.media.session.MediaSessionCompat.Callback!, android.os.Handler!);
+ method public void setCaptioningEnabled(boolean);
+ method public void setExtras(android.os.Bundle!);
+ method public void setFlags(int);
+ method public void setMediaButtonReceiver(android.app.PendingIntent!);
+ method public void setMetadata(android.support.v4.media.MediaMetadataCompat!);
+ method public void setPlaybackState(android.support.v4.media.session.PlaybackStateCompat!);
+ method public void setPlaybackToLocal(int);
+ method public void setPlaybackToRemote(androidx.media.VolumeProviderCompat!);
+ method public void setQueue(java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem!>!);
+ method public void setQueueTitle(CharSequence!);
+ method public void setRatingType(@android.support.v4.media.RatingCompat.Style int);
+ method public void setRepeatMode(@android.support.v4.media.session.PlaybackStateCompat.RepeatMode int);
+ method public void setSessionActivity(android.app.PendingIntent!);
+ method public void setShuffleMode(@android.support.v4.media.session.PlaybackStateCompat.ShuffleMode int);
+ field public static final String ACTION_FLAG_AS_INAPPROPRIATE = "android.support.v4.media.session.action.FLAG_AS_INAPPROPRIATE";
+ field public static final String ACTION_FOLLOW = "android.support.v4.media.session.action.FOLLOW";
+ field public static final String ACTION_SKIP_AD = "android.support.v4.media.session.action.SKIP_AD";
+ field public static final String ACTION_UNFOLLOW = "android.support.v4.media.session.action.UNFOLLOW";
+ field public static final String ARGUMENT_MEDIA_ATTRIBUTE = "android.support.v4.media.session.ARGUMENT_MEDIA_ATTRIBUTE";
+ field public static final String ARGUMENT_MEDIA_ATTRIBUTE_VALUE = "android.support.v4.media.session.ARGUMENT_MEDIA_ATTRIBUTE_VALUE";
+ field @Deprecated public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
+ field public static final int FLAG_HANDLES_QUEUE_COMMANDS = 4; // 0x4
+ field @Deprecated public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
+ field public static final int MEDIA_ATTRIBUTE_ALBUM = 1; // 0x1
+ field public static final int MEDIA_ATTRIBUTE_ARTIST = 0; // 0x0
+ field public static final int MEDIA_ATTRIBUTE_PLAYLIST = 2; // 0x2
+ }
+
+ public abstract static class MediaSessionCompat.Callback {
+ ctor public MediaSessionCompat.Callback();
+ method public void onAddQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method public void onAddQueueItem(android.support.v4.media.MediaDescriptionCompat!, int);
+ method public void onCommand(String!, android.os.Bundle!, android.os.ResultReceiver!);
+ method public void onCustomAction(String!, android.os.Bundle!);
+ method public void onFastForward();
+ method public boolean onMediaButtonEvent(android.content.Intent!);
+ method public void onPause();
+ method public void onPlay();
+ method public void onPlayFromMediaId(String!, android.os.Bundle!);
+ method public void onPlayFromSearch(String!, android.os.Bundle!);
+ method public void onPlayFromUri(android.net.Uri!, android.os.Bundle!);
+ method public void onPrepare();
+ method public void onPrepareFromMediaId(String!, android.os.Bundle!);
+ method public void onPrepareFromSearch(String!, android.os.Bundle!);
+ method public void onPrepareFromUri(android.net.Uri!, android.os.Bundle!);
+ method public void onRemoveQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method @Deprecated public void onRemoveQueueItemAt(int);
+ method public void onRewind();
+ method public void onSeekTo(long);
+ method public void onSetCaptioningEnabled(boolean);
+ method public void onSetPlaybackSpeed(float);
+ method public void onSetRating(android.support.v4.media.RatingCompat!);
+ method public void onSetRating(android.support.v4.media.RatingCompat!, android.os.Bundle!);
+ method public void onSetRepeatMode(@android.support.v4.media.session.PlaybackStateCompat.RepeatMode int);
+ method public void onSetShuffleMode(@android.support.v4.media.session.PlaybackStateCompat.ShuffleMode int);
+ method public void onSkipToNext();
+ method public void onSkipToPrevious();
+ method public void onSkipToQueueItem(long);
+ method public void onStop();
+ }
+
+ public static interface MediaSessionCompat.OnActiveChangeListener {
+ method public void onActiveChanged();
+ }
+
+ public static final class MediaSessionCompat.QueueItem implements android.os.Parcelable {
+ ctor public MediaSessionCompat.QueueItem(android.support.v4.media.MediaDescriptionCompat!, long);
+ method public int describeContents();
+ method public static android.support.v4.media.session.MediaSessionCompat.QueueItem! fromQueueItem(Object!);
+ method public static java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem!>! fromQueueItemList(java.util.List<?>!);
+ method public android.support.v4.media.MediaDescriptionCompat! getDescription();
+ method public long getQueueId();
+ method public Object! getQueueItem();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.MediaSessionCompat.QueueItem!>! CREATOR;
+ field public static final int UNKNOWN_ID = -1; // 0xffffffff
+ }
+
+ public static final class MediaSessionCompat.Token implements android.os.Parcelable {
+ method public int describeContents();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.support.v4.media.session.MediaSessionCompat.Token! fromBundle(android.os.Bundle!);
+ method public static android.support.v4.media.session.MediaSessionCompat.Token! fromToken(Object!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.versionedparcelable.VersionedParcelable! getSession2Token();
+ method public Object! getToken();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSession2Token(androidx.versionedparcelable.VersionedParcelable!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.os.Bundle! toBundle();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.MediaSessionCompat.Token!>! CREATOR;
+ }
+
+ public class ParcelableVolumeInfo implements android.os.Parcelable {
+ ctor public ParcelableVolumeInfo(int, int, int, int, int);
+ ctor public ParcelableVolumeInfo(android.os.Parcel!);
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.ParcelableVolumeInfo!>! CREATOR;
+ field public int audioStream;
+ field public int controlType;
+ field public int currentVolume;
+ field public int maxVolume;
+ field public int volumeType;
+ }
+
+ public final class PlaybackStateCompat implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.session.PlaybackStateCompat! fromPlaybackState(Object!);
+ method @android.support.v4.media.session.PlaybackStateCompat.Actions public long getActions();
+ method public long getActiveQueueItemId();
+ method public long getBufferedPosition();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public long getCurrentPosition(Long!);
+ method public java.util.List<android.support.v4.media.session.PlaybackStateCompat.CustomAction!>! getCustomActions();
+ method public int getErrorCode();
+ method public CharSequence! getErrorMessage();
+ method public android.os.Bundle? getExtras();
+ method public long getLastPositionUpdateTime();
+ method public float getPlaybackSpeed();
+ method public Object! getPlaybackState();
+ method public long getPosition();
+ method @android.support.v4.media.session.PlaybackStateCompat.State public int getState();
+ method public static int toKeyCode(@android.support.v4.media.session.PlaybackStateCompat.MediaKeyAction long);
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final long ACTION_FAST_FORWARD = 64L; // 0x40L
+ field public static final long ACTION_PAUSE = 2L; // 0x2L
+ field public static final long ACTION_PLAY = 4L; // 0x4L
+ field public static final long ACTION_PLAY_FROM_MEDIA_ID = 1024L; // 0x400L
+ field public static final long ACTION_PLAY_FROM_SEARCH = 2048L; // 0x800L
+ field public static final long ACTION_PLAY_FROM_URI = 8192L; // 0x2000L
+ field public static final long ACTION_PLAY_PAUSE = 512L; // 0x200L
+ field public static final long ACTION_PREPARE = 16384L; // 0x4000L
+ field public static final long ACTION_PREPARE_FROM_MEDIA_ID = 32768L; // 0x8000L
+ field public static final long ACTION_PREPARE_FROM_SEARCH = 65536L; // 0x10000L
+ field public static final long ACTION_PREPARE_FROM_URI = 131072L; // 0x20000L
+ field public static final long ACTION_REWIND = 8L; // 0x8L
+ field public static final long ACTION_SEEK_TO = 256L; // 0x100L
+ field public static final long ACTION_SET_CAPTIONING_ENABLED = 1048576L; // 0x100000L
+ field public static final long ACTION_SET_RATING = 128L; // 0x80L
+ field public static final long ACTION_SET_REPEAT_MODE = 262144L; // 0x40000L
+ field public static final long ACTION_SET_SHUFFLE_MODE = 2097152L; // 0x200000L
+ field @Deprecated public static final long ACTION_SET_SHUFFLE_MODE_ENABLED = 524288L; // 0x80000L
+ field public static final long ACTION_SKIP_TO_NEXT = 32L; // 0x20L
+ field public static final long ACTION_SKIP_TO_PREVIOUS = 16L; // 0x10L
+ field public static final long ACTION_SKIP_TO_QUEUE_ITEM = 4096L; // 0x1000L
+ field public static final long ACTION_STOP = 1L; // 0x1L
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.PlaybackStateCompat!>! CREATOR;
+ field public static final int ERROR_CODE_ACTION_ABORTED = 10; // 0xa
+ field public static final int ERROR_CODE_APP_ERROR = 1; // 0x1
+ field public static final int ERROR_CODE_AUTHENTICATION_EXPIRED = 3; // 0x3
+ field public static final int ERROR_CODE_CONCURRENT_STREAM_LIMIT = 5; // 0x5
+ field public static final int ERROR_CODE_CONTENT_ALREADY_PLAYING = 8; // 0x8
+ field public static final int ERROR_CODE_END_OF_QUEUE = 11; // 0xb
+ field public static final int ERROR_CODE_NOT_AVAILABLE_IN_REGION = 7; // 0x7
+ field public static final int ERROR_CODE_NOT_SUPPORTED = 2; // 0x2
+ field public static final int ERROR_CODE_PARENTAL_CONTROL_RESTRICTED = 6; // 0x6
+ field public static final int ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED = 4; // 0x4
+ field public static final int ERROR_CODE_SKIP_LIMIT_REACHED = 9; // 0x9
+ field public static final int ERROR_CODE_UNKNOWN_ERROR = 0; // 0x0
+ field public static final long PLAYBACK_POSITION_UNKNOWN = -1L; // 0xffffffffffffffffL
+ field public static final int REPEAT_MODE_ALL = 2; // 0x2
+ field public static final int REPEAT_MODE_GROUP = 3; // 0x3
+ field public static final int REPEAT_MODE_INVALID = -1; // 0xffffffff
+ field public static final int REPEAT_MODE_NONE = 0; // 0x0
+ field public static final int REPEAT_MODE_ONE = 1; // 0x1
+ field public static final int SHUFFLE_MODE_ALL = 1; // 0x1
+ field public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
+ field public static final int SHUFFLE_MODE_INVALID = -1; // 0xffffffff
+ field public static final int SHUFFLE_MODE_NONE = 0; // 0x0
+ field public static final int STATE_BUFFERING = 6; // 0x6
+ field public static final int STATE_CONNECTING = 8; // 0x8
+ field public static final int STATE_ERROR = 7; // 0x7
+ field public static final int STATE_FAST_FORWARDING = 4; // 0x4
+ field public static final int STATE_NONE = 0; // 0x0
+ field public static final int STATE_PAUSED = 2; // 0x2
+ field public static final int STATE_PLAYING = 3; // 0x3
+ field public static final int STATE_REWINDING = 5; // 0x5
+ field public static final int STATE_SKIPPING_TO_NEXT = 10; // 0xa
+ field public static final int STATE_SKIPPING_TO_PREVIOUS = 9; // 0x9
+ field public static final int STATE_SKIPPING_TO_QUEUE_ITEM = 11; // 0xb
+ field public static final int STATE_STOPPED = 1; // 0x1
+ }
+
+ @LongDef(flag=true, value={android.support.v4.media.session.PlaybackStateCompat.ACTION_STOP, android.support.v4.media.session.PlaybackStateCompat.ACTION_PAUSE, android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY, android.support.v4.media.session.PlaybackStateCompat.ACTION_REWIND, android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS, android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_NEXT, android.support.v4.media.session.PlaybackStateCompat.ACTION_FAST_FORWARD, android.support.v4.media.session.PlaybackStateCompat.ACTION_SET_RATING, android.support.v4.media.session.PlaybackStateCompat.ACTION_SEEK_TO, android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_PAUSE, android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID, android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH, android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM, android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_FROM_URI, android.support.v4.media.session.PlaybackStateCompat.ACTION_PREPARE, android.support.v4.media.session.PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID, android.support.v4.media.session.PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH, android.support.v4.media.session.PlaybackStateCompat.ACTION_PREPARE_FROM_URI, android.support.v4.media.session.PlaybackStateCompat.ACTION_SET_REPEAT_MODE, android.support.v4.media.session.PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE, android.support.v4.media.session.PlaybackStateCompat.ACTION_SET_CAPTIONING_ENABLED}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PlaybackStateCompat.Actions {
+ }
+
+ public static final class PlaybackStateCompat.Builder {
+ ctor public PlaybackStateCompat.Builder();
+ ctor public PlaybackStateCompat.Builder(android.support.v4.media.session.PlaybackStateCompat!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! addCustomAction(String!, String!, int);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! addCustomAction(android.support.v4.media.session.PlaybackStateCompat.CustomAction!);
+ method public android.support.v4.media.session.PlaybackStateCompat! build();
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setActions(@android.support.v4.media.session.PlaybackStateCompat.Actions long);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setActiveQueueItemId(long);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setBufferedPosition(long);
+ method @Deprecated public android.support.v4.media.session.PlaybackStateCompat.Builder! setErrorMessage(CharSequence!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setErrorMessage(int, CharSequence!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setExtras(android.os.Bundle!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setState(@android.support.v4.media.session.PlaybackStateCompat.State int, long, float);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setState(@android.support.v4.media.session.PlaybackStateCompat.State int, long, float, long);
+ }
+
+ public static final class PlaybackStateCompat.CustomAction implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.session.PlaybackStateCompat.CustomAction! fromCustomAction(Object!);
+ method public String! getAction();
+ method public Object! getCustomAction();
+ method public android.os.Bundle! getExtras();
+ method public int getIcon();
+ method public CharSequence! getName();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.PlaybackStateCompat.CustomAction!>! CREATOR;
+ }
+
+ public static final class PlaybackStateCompat.CustomAction.Builder {
+ ctor public PlaybackStateCompat.CustomAction.Builder(String!, CharSequence!, int);
+ method public android.support.v4.media.session.PlaybackStateCompat.CustomAction! build();
+ method public android.support.v4.media.session.PlaybackStateCompat.CustomAction.Builder! setExtras(android.os.Bundle!);
+ }
+
+
+ @IntDef({android.support.v4.media.session.PlaybackStateCompat.REPEAT_MODE_INVALID, android.support.v4.media.session.PlaybackStateCompat.REPEAT_MODE_NONE, android.support.v4.media.session.PlaybackStateCompat.REPEAT_MODE_ONE, android.support.v4.media.session.PlaybackStateCompat.REPEAT_MODE_ALL, android.support.v4.media.session.PlaybackStateCompat.REPEAT_MODE_GROUP}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PlaybackStateCompat.RepeatMode {
+ }
+
+ @IntDef({android.support.v4.media.session.PlaybackStateCompat.SHUFFLE_MODE_INVALID, android.support.v4.media.session.PlaybackStateCompat.SHUFFLE_MODE_NONE, android.support.v4.media.session.PlaybackStateCompat.SHUFFLE_MODE_ALL, android.support.v4.media.session.PlaybackStateCompat.SHUFFLE_MODE_GROUP}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PlaybackStateCompat.ShuffleMode {
+ }
+
+ @IntDef({android.support.v4.media.session.PlaybackStateCompat.STATE_NONE, android.support.v4.media.session.PlaybackStateCompat.STATE_STOPPED, android.support.v4.media.session.PlaybackStateCompat.STATE_PAUSED, android.support.v4.media.session.PlaybackStateCompat.STATE_PLAYING, android.support.v4.media.session.PlaybackStateCompat.STATE_FAST_FORWARDING, android.support.v4.media.session.PlaybackStateCompat.STATE_REWINDING, android.support.v4.media.session.PlaybackStateCompat.STATE_BUFFERING, android.support.v4.media.session.PlaybackStateCompat.STATE_ERROR, android.support.v4.media.session.PlaybackStateCompat.STATE_CONNECTING, android.support.v4.media.session.PlaybackStateCompat.STATE_SKIPPING_TO_PREVIOUS, android.support.v4.media.session.PlaybackStateCompat.STATE_SKIPPING_TO_NEXT, android.support.v4.media.session.PlaybackStateCompat.STATE_SKIPPING_TO_QUEUE_ITEM}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PlaybackStateCompat.State {
+ }
+
+}
+
+package androidx.media {
+
+ public class AudioAttributesCompat implements androidx.versionedparcelable.VersionedParcelable {
+ method public int getContentType();
+ method public int getFlags();
+ method public int getLegacyStreamType();
+ method @androidx.media.AudioAttributesCompat.AttributeUsage public int getUsage();
+ method public int getVolumeControlStream();
+ method public Object? unwrap();
+ method public static androidx.media.AudioAttributesCompat? wrap(Object);
+ field public static final int CONTENT_TYPE_MOVIE = 3; // 0x3
+ field public static final int CONTENT_TYPE_MUSIC = 2; // 0x2
+ field public static final int CONTENT_TYPE_SONIFICATION = 4; // 0x4
+ field public static final int CONTENT_TYPE_SPEECH = 1; // 0x1
+ field public static final int CONTENT_TYPE_UNKNOWN = 0; // 0x0
+ field public static final int FLAG_AUDIBILITY_ENFORCED = 1; // 0x1
+ field public static final int FLAG_HW_AV_SYNC = 16; // 0x10
+ field public static final int USAGE_ALARM = 4; // 0x4
+ field public static final int USAGE_ASSISTANCE_ACCESSIBILITY = 11; // 0xb
+ field public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12; // 0xc
+ field public static final int USAGE_ASSISTANCE_SONIFICATION = 13; // 0xd
+ field public static final int USAGE_ASSISTANT = 16; // 0x10
+ field public static final int USAGE_GAME = 14; // 0xe
+ field public static final int USAGE_MEDIA = 1; // 0x1
+ field public static final int USAGE_NOTIFICATION = 5; // 0x5
+ field public static final int USAGE_NOTIFICATION_COMMUNICATION_DELAYED = 9; // 0x9
+ field public static final int USAGE_NOTIFICATION_COMMUNICATION_INSTANT = 8; // 0x8
+ field public static final int USAGE_NOTIFICATION_COMMUNICATION_REQUEST = 7; // 0x7
+ field public static final int USAGE_NOTIFICATION_EVENT = 10; // 0xa
+ field public static final int USAGE_NOTIFICATION_RINGTONE = 6; // 0x6
+ field public static final int USAGE_UNKNOWN = 0; // 0x0
+ field public static final int USAGE_VOICE_COMMUNICATION = 2; // 0x2
+ field public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING = 3; // 0x3
+ }
+
+
+
+ public static class AudioAttributesCompat.Builder {
+ ctor public AudioAttributesCompat.Builder();
+ ctor public AudioAttributesCompat.Builder(androidx.media.AudioAttributesCompat!);
+ method public androidx.media.AudioAttributesCompat! build();
+ method public androidx.media.AudioAttributesCompat.Builder! setContentType(@androidx.media.AudioAttributesCompat.AttributeContentType int);
+ method public androidx.media.AudioAttributesCompat.Builder! setFlags(int);
+ method public androidx.media.AudioAttributesCompat.Builder! setLegacyStreamType(int);
+ method public androidx.media.AudioAttributesCompat.Builder! setUsage(@androidx.media.AudioAttributesCompat.AttributeUsage int);
+ }
+
+
+
+
+ public class AudioFocusRequestCompat {
+ method public androidx.media.AudioAttributesCompat getAudioAttributesCompat();
+ method public android.os.Handler getFocusChangeHandler();
+ method public int getFocusGain();
+ method public android.media.AudioManager.OnAudioFocusChangeListener getOnAudioFocusChangeListener();
+ method public boolean willPauseWhenDucked();
+ }
+
+ public static final class AudioFocusRequestCompat.Builder {
+ ctor public AudioFocusRequestCompat.Builder(int);
+ ctor public AudioFocusRequestCompat.Builder(androidx.media.AudioFocusRequestCompat);
+ method public androidx.media.AudioFocusRequestCompat! build();
+ method public androidx.media.AudioFocusRequestCompat.Builder setAudioAttributes(androidx.media.AudioAttributesCompat);
+ method public androidx.media.AudioFocusRequestCompat.Builder setFocusGain(int);
+ method public androidx.media.AudioFocusRequestCompat.Builder setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener);
+ method public androidx.media.AudioFocusRequestCompat.Builder setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener, android.os.Handler);
+ method public androidx.media.AudioFocusRequestCompat.Builder setWillPauseWhenDucked(boolean);
+ }
+
+ public final class AudioManagerCompat {
+ method public static int abandonAudioFocusRequest(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
+ method public static int requestAudioFocus(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
+ field public static final int AUDIOFOCUS_GAIN = 1; // 0x1
+ field public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; // 0x2
+ field public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE = 4; // 0x4
+ field public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK = 3; // 0x3
+ }
+
+
+
+ public abstract class MediaBrowserServiceCompat extends android.app.Service {
+ ctor public MediaBrowserServiceCompat();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void attachToBaseContext(android.content.Context!);
+ method public void dump(java.io.FileDescriptor!, java.io.PrintWriter!, String![]!);
+ method public final android.os.Bundle! getBrowserRootHints();
+ method public final androidx.media.MediaSessionManager.RemoteUserInfo getCurrentBrowserInfo();
+ method public android.support.v4.media.session.MediaSessionCompat.Token? getSessionToken();
+ method public void notifyChildrenChanged(String);
+ method public void notifyChildrenChanged(String, android.os.Bundle);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void notifyChildrenChanged(androidx.media.MediaSessionManager.RemoteUserInfo, String, android.os.Bundle);
+ method public android.os.IBinder! onBind(android.content.Intent!);
+ method public void onCustomAction(String, android.os.Bundle!, androidx.media.MediaBrowserServiceCompat.Result<android.os.Bundle!>);
+ method public abstract androidx.media.MediaBrowserServiceCompat.BrowserRoot? onGetRoot(String, int, android.os.Bundle?);
+ method public abstract void onLoadChildren(String, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>!>);
+ method public void onLoadChildren(String, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>!>, android.os.Bundle);
+ method public void onLoadItem(String!, androidx.media.MediaBrowserServiceCompat.Result<android.support.v4.media.MediaBrowserCompat.MediaItem!>);
+ method public void onSearch(String, android.os.Bundle!, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>!>);
+ method public void setSessionToken(android.support.v4.media.session.MediaSessionCompat.Token!);
+ field public static final String SERVICE_INTERFACE = "android.media.browse.MediaBrowserService";
+ }
+
+ public static final class MediaBrowserServiceCompat.BrowserRoot {
+ ctor public MediaBrowserServiceCompat.BrowserRoot(String, android.os.Bundle?);
+ method public android.os.Bundle! getExtras();
+ method public String! getRootId();
+ field public static final String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
+ field public static final String EXTRA_RECENT = "android.service.media.extra.RECENT";
+ field public static final String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
+ field @Deprecated public static final String EXTRA_SUGGESTION_KEYWORDS = "android.service.media.extra.SUGGESTION_KEYWORDS";
+ }
+
+ public static class MediaBrowserServiceCompat.Result<T> {
+ method public void detach();
+ method public void sendError(android.os.Bundle!);
+ method public void sendProgressUpdate(android.os.Bundle!);
+ method public void sendResult(T!);
+ }
+
+ public final class MediaSessionManager {
+ method public static androidx.media.MediaSessionManager getSessionManager(android.content.Context);
+ method public boolean isTrustedForMediaControl(androidx.media.MediaSessionManager.RemoteUserInfo);
+ }
+
+ public static final class MediaSessionManager.RemoteUserInfo {
+ ctor public MediaSessionManager.RemoteUserInfo(String, int, int);
+ method public String getPackageName();
+ method public int getPid();
+ method public int getUid();
+ field public static final String LEGACY_CONTROLLER = "android.media.session.MediaController";
+ }
+
+ public abstract class VolumeProviderCompat {
+ ctor public VolumeProviderCompat(@androidx.media.VolumeProviderCompat.ControlType int, int, int);
+ method public final int getCurrentVolume();
+ method public final int getMaxVolume();
+ method @androidx.media.VolumeProviderCompat.ControlType public final int getVolumeControl();
+ method public Object! getVolumeProvider();
+ method public void onAdjustVolume(int);
+ method public void onSetVolumeTo(int);
+ method public void setCallback(androidx.media.VolumeProviderCompat.Callback!);
+ method public final void setCurrentVolume(int);
+ field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
+ field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
+ field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
+ }
+
+ public abstract static class VolumeProviderCompat.Callback {
+ ctor public VolumeProviderCompat.Callback();
+ method public abstract void onVolumeChanged(androidx.media.VolumeProviderCompat!);
+ }
+
+ @IntDef({androidx.media.VolumeProviderCompat.VOLUME_CONTROL_FIXED, androidx.media.VolumeProviderCompat.VOLUME_CONTROL_RELATIVE, androidx.media.VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface VolumeProviderCompat.ControlType {
+ }
+
+}
+
+package androidx.media.app {
+
+ public class NotificationCompat {
+ }
+
+ public static class NotificationCompat.DecoratedMediaCustomViewStyle extends androidx.media.app.NotificationCompat.MediaStyle {
+ ctor public NotificationCompat.DecoratedMediaCustomViewStyle();
+ }
+
+ public static class NotificationCompat.MediaStyle extends androidx.core.app.NotificationCompat.Style {
+ ctor public NotificationCompat.MediaStyle();
+ ctor public NotificationCompat.MediaStyle(androidx.core.app.NotificationCompat.Builder!);
+ method public static android.support.v4.media.session.MediaSessionCompat.Token! getMediaSession(android.app.Notification!);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setCancelButtonIntent(android.app.PendingIntent!);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setMediaSession(android.support.v4.media.session.MediaSessionCompat.Token!);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setShowActionsInCompactView(int...);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setShowCancelButton(boolean);
+ }
+
+}
+
+package androidx.media.session {
+
+ public class MediaButtonReceiver extends android.content.BroadcastReceiver {
+ ctor public MediaButtonReceiver();
+ method public static android.app.PendingIntent! buildMediaButtonPendingIntent(android.content.Context!, @android.support.v4.media.session.PlaybackStateCompat.MediaKeyAction long);
+ method public static android.app.PendingIntent! buildMediaButtonPendingIntent(android.content.Context!, android.content.ComponentName!, @android.support.v4.media.session.PlaybackStateCompat.MediaKeyAction long);
+ method public static android.view.KeyEvent! handleIntent(android.support.v4.media.session.MediaSessionCompat!, android.content.Intent!);
+ method public void onReceive(android.content.Context!, android.content.Intent!);
+ }
+
+}
+
diff --git a/media/build.gradle b/media/build.gradle
index 863a93c..facba79 100644
--- a/media/build.gradle
+++ b/media/build.gradle
@@ -40,5 +40,4 @@
inceptionYear = "2011"
description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later."
failOnDeprecationWarnings = false
- trackRestrictedAPIs = false // TODO: Remove it (b/131031933)
}
diff --git a/media/src/main/java/android/support/v4/media/MediaBrowserCompat.java b/media/src/main/java/android/support/v4/media/MediaBrowserCompat.java
index eddc7a8..4ec5200 100644
--- a/media/src/main/java/android/support/v4/media/MediaBrowserCompat.java
+++ b/media/src/main/java/android/support/v4/media/MediaBrowserCompat.java
@@ -446,7 +446,7 @@
* String, Bundle)}
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // accessed by media2-session
@Nullable
public Bundle getNotifyChildrenChangedOptions() {
return mImpl.getNotifyChildrenChangedOptions();
@@ -462,11 +462,9 @@
private final int mFlags;
private final MediaDescriptionCompat mDescription;
- /** @hide */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag=true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE })
- public @interface Flags { }
+ private @interface Flags { }
/**
* Flag: Indicates that the item has children of its own.
diff --git a/media/src/main/java/android/support/v4/media/MediaDescriptionCompat.java b/media/src/main/java/android/support/v4/media/MediaDescriptionCompat.java
index e34708f..a4734db 100644
--- a/media/src/main/java/android/support/v4/media/MediaDescriptionCompat.java
+++ b/media/src/main/java/android/support/v4/media/MediaDescriptionCompat.java
@@ -15,7 +15,7 @@
*/
package android.support.v4.media;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
@@ -140,7 +140,7 @@
*
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public static final String DESCRIPTION_KEY_MEDIA_URI =
"android.support.v4.media.description.MEDIA_URI";
/**
@@ -148,7 +148,7 @@
*
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public static final String DESCRIPTION_KEY_NULL_BUNDLE_FLAG =
"android.support.v4.media.description.NULL_BUNDLE_FLAG";
/**
diff --git a/media/src/main/java/android/support/v4/media/MediaMetadataCompat.java b/media/src/main/java/android/support/v4/media/MediaMetadataCompat.java
index bf8b780..96c192c 100644
--- a/media/src/main/java/android/support/v4/media/MediaMetadataCompat.java
+++ b/media/src/main/java/android/support/v4/media/MediaMetadataCompat.java
@@ -15,7 +15,7 @@
*/
package android.support.v4.media;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
@@ -262,10 +262,6 @@
public static final String METADATA_KEY_DOWNLOAD_STATUS =
"android.media.metadata.DOWNLOAD_STATUS";
- /**
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
@StringDef(value =
{METADATA_KEY_TITLE, METADATA_KEY_ARTIST, METADATA_KEY_ALBUM, METADATA_KEY_AUTHOR,
METADATA_KEY_WRITER, METADATA_KEY_COMPOSER, METADATA_KEY_COMPILATION,
@@ -275,35 +271,23 @@
METADATA_KEY_MEDIA_ID, METADATA_KEY_MEDIA_URI},
open = true)
@Retention(RetentionPolicy.SOURCE)
- public @interface TextKey {}
+ private @interface TextKey {}
- /**
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
@StringDef(value =
{METADATA_KEY_DURATION, METADATA_KEY_YEAR, METADATA_KEY_TRACK_NUMBER,
METADATA_KEY_NUM_TRACKS, METADATA_KEY_DISC_NUMBER, METADATA_KEY_BT_FOLDER_TYPE,
METADATA_KEY_ADVERTISEMENT, METADATA_KEY_DOWNLOAD_STATUS},
open = true)
@Retention(RetentionPolicy.SOURCE)
- public @interface LongKey {}
+ private @interface LongKey {}
- /**
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
@StringDef({METADATA_KEY_ART, METADATA_KEY_ALBUM_ART, METADATA_KEY_DISPLAY_ICON})
@Retention(RetentionPolicy.SOURCE)
- public @interface BitmapKey {}
+ private @interface BitmapKey {}
- /**
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
@StringDef({METADATA_KEY_USER_RATING, METADATA_KEY_RATING})
@Retention(RetentionPolicy.SOURCE)
- public @interface RatingKey {}
+ private @interface RatingKey {}
static final int METADATA_TYPE_LONG = 0;
static final int METADATA_TYPE_TEXT = 1;
@@ -695,7 +679,7 @@
* in the metadata.
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public Builder(MediaMetadataCompat source, int maxBitmapSize) {
this(source);
for (String key : mBundle.keySet()) {
diff --git a/media/src/main/java/android/support/v4/media/RatingCompat.java b/media/src/main/java/android/support/v4/media/RatingCompat.java
index d109647..42b6da0 100644
--- a/media/src/main/java/android/support/v4/media/RatingCompat.java
+++ b/media/src/main/java/android/support/v4/media/RatingCompat.java
@@ -16,6 +16,7 @@
package android.support.v4.media;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
import android.annotation.SuppressLint;
@@ -46,7 +47,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // used by media2-session
@IntDef({RATING_NONE, RATING_HEART, RATING_THUMB_UP_DOWN, RATING_3_STARS, RATING_4_STARS,
RATING_5_STARS, RATING_PERCENTAGE})
@Retention(RetentionPolicy.SOURCE)
@@ -55,7 +56,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
@IntDef({RATING_3_STARS, RATING_4_STARS, RATING_5_STARS})
@Retention(RetentionPolicy.SOURCE)
public @interface StarStyle {}
diff --git a/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java b/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java
index a6bf8dd..7ed8a4a 100644
--- a/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java
+++ b/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java
@@ -514,7 +514,7 @@
* @return The session's token as VersionedParcelable.
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
@Nullable
public VersionedParcelable getSession2Token() {
return mToken.getSession2Token();
@@ -815,7 +815,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // accessed by media2-session
public IMediaControllerCallback getIControllerCallback() {
return mIControllerCallback;
}
diff --git a/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java b/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
index e54467b9..bbf6980 100644
--- a/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
+++ b/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
@@ -125,16 +125,12 @@
private final MediaControllerCompat mController;
private final ArrayList<OnActiveChangeListener> mActiveListeners = new ArrayList<>();
- /**
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
@IntDef(flag=true, value={
FLAG_HANDLES_MEDIA_BUTTONS,
FLAG_HANDLES_TRANSPORT_CONTROLS,
FLAG_HANDLES_QUEUE_COMMANDS })
@Retention(RetentionPolicy.SOURCE)
- public @interface SessionFlags {}
+ private @interface SessionFlags {}
/**
* Sets this flag on the session to indicate that it can handle media button
@@ -422,7 +418,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public static final String KEY_TOKEN = "android.support.v4.media.session.TOKEN";
/**
@@ -435,7 +431,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public static final String KEY_SESSION2_TOKEN =
"android.support.v4.media.session.SESSION_TOKEN2";
@@ -533,7 +529,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // accessed by media2-session
public MediaSessionCompat(@NonNull Context context, @NonNull String tag,
@Nullable ComponentName mbrComponent, @Nullable PendingIntent mbrIntent,
@Nullable Bundle sessionInfo, @Nullable VersionedParcelable session2Token) {
@@ -964,7 +960,7 @@
*
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public String getCallingPackage() {
return mImpl.getCallingPackage();
}
@@ -1031,11 +1027,8 @@
/**
* Returns whether the given bundle includes non-framework Parcelables.
- *
- * @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
- public static boolean doesBundleHaveCustomParcelable(@Nullable Bundle bundle) {
+ static boolean doesBundleHaveCustomParcelable(@Nullable Bundle bundle) {
if (bundle == null) {
return false;
}
@@ -1066,7 +1059,7 @@
*
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public static void ensureClassLoader(@Nullable Bundle bundle) {
if (bundle != null) {
bundle.setClassLoader(MediaSessionCompat.class.getClassLoader());
@@ -1832,7 +1825,7 @@
* @return A compat Token for use with {@link MediaControllerCompat}.
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public static Token fromToken(Object token, IMediaSession extraBinder) {
if (token != null && android.os.Build.VERSION.SDK_INT >= 21) {
if (!(token instanceof MediaSession.Token)) {
@@ -1901,7 +1894,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public IMediaSession getExtraBinder() {
return mExtraBinder;
}
@@ -1909,7 +1902,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public void setExtraBinder(IMediaSession extraBinder) {
mExtraBinder = extraBinder;
}
@@ -1917,7 +1910,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // accessed by media2-session
public VersionedParcelable getSession2Token() {
return mSession2Token;
}
@@ -1925,7 +1918,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // accessed by media2-session
public void setSession2Token(VersionedParcelable session2Token) {
mSession2Token = session2Token;
}
@@ -1933,7 +1926,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // accessed by media2-session
public Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putParcelable(KEY_TOKEN, this);
@@ -1953,7 +1946,7 @@
* @return A compat Token for use with {@link MediaControllerCompat}.
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // accessed by media2-session
public static Token fromBundle(Bundle tokenBundle) {
if (tokenBundle == null) {
return null;
@@ -2147,12 +2140,9 @@
* This is a wrapper for {@link ResultReceiver} for sending over aidl
* interfaces. The framework version was not exposed to aidls until
* {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
- *
- * @hide
*/
- @RestrictTo(LIBRARY)
@SuppressLint("BanParcelableUsage")
- public static final class ResultReceiverWrapper implements Parcelable {
+ /* package */ static final class ResultReceiverWrapper implements Parcelable {
ResultReceiver mResultReceiver;
public ResultReceiverWrapper(@NonNull ResultReceiver resultReceiver) {
diff --git a/media/src/main/java/android/support/v4/media/session/PlaybackStateCompat.java b/media/src/main/java/android/support/v4/media/session/PlaybackStateCompat.java
index d1399d6..164a459 100644
--- a/media/src/main/java/android/support/v4/media/session/PlaybackStateCompat.java
+++ b/media/src/main/java/android/support/v4/media/session/PlaybackStateCompat.java
@@ -15,6 +15,7 @@
*/
package android.support.v4.media.session;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
import android.annotation.SuppressLint;
@@ -48,7 +49,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // used by media2-session
@LongDef(flag=true, value={ACTION_STOP, ACTION_PAUSE, ACTION_PLAY, ACTION_REWIND,
ACTION_SKIP_TO_PREVIOUS, ACTION_SKIP_TO_NEXT, ACTION_FAST_FORWARD, ACTION_SET_RATING,
ACTION_SEEK_TO, ACTION_PLAY_PAUSE, ACTION_PLAY_FROM_MEDIA_ID, ACTION_PLAY_FROM_SEARCH,
@@ -61,7 +62,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
@LongDef({ACTION_STOP, ACTION_PAUSE, ACTION_PLAY, ACTION_REWIND, ACTION_SKIP_TO_PREVIOUS,
ACTION_SKIP_TO_NEXT, ACTION_FAST_FORWARD, ACTION_PLAY_PAUSE})
@Retention(RetentionPolicy.SOURCE)
@@ -226,7 +227,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // used by media2-session
@IntDef({STATE_NONE, STATE_STOPPED, STATE_PAUSED, STATE_PLAYING, STATE_FAST_FORWARDING,
STATE_REWINDING, STATE_BUFFERING, STATE_ERROR, STATE_CONNECTING,
STATE_SKIPPING_TO_PREVIOUS, STATE_SKIPPING_TO_NEXT, STATE_SKIPPING_TO_QUEUE_ITEM})
@@ -339,7 +340,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // used by media2-session
@IntDef({REPEAT_MODE_INVALID, REPEAT_MODE_NONE, REPEAT_MODE_ONE, REPEAT_MODE_ALL,
REPEAT_MODE_GROUP})
@Retention(RetentionPolicy.SOURCE)
@@ -380,7 +381,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // used by media2-session
@IntDef({SHUFFLE_MODE_INVALID, SHUFFLE_MODE_NONE, SHUFFLE_MODE_ALL, SHUFFLE_MODE_GROUP})
@Retention(RetentionPolicy.SOURCE)
public @interface ShuffleMode {}
@@ -411,17 +412,13 @@
*/
public static final int SHUFFLE_MODE_GROUP = 2;
- /**
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
@IntDef({ERROR_CODE_UNKNOWN_ERROR, ERROR_CODE_APP_ERROR, ERROR_CODE_NOT_SUPPORTED,
ERROR_CODE_AUTHENTICATION_EXPIRED, ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED,
ERROR_CODE_CONCURRENT_STREAM_LIMIT, ERROR_CODE_PARENTAL_CONTROL_RESTRICTED,
ERROR_CODE_NOT_AVAILABLE_IN_REGION, ERROR_CODE_CONTENT_ALREADY_PLAYING,
ERROR_CODE_SKIP_LIMIT_REACHED, ERROR_CODE_ACTION_ABORTED, ERROR_CODE_END_OF_QUEUE})
@Retention(RetentionPolicy.SOURCE)
- public @interface ErrorCode {}
+ private @interface ErrorCode {}
/**
* This is the default error code and indicates that none of the other error codes applies.
@@ -668,7 +665,7 @@
* @return The current playback position in ms
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // accessed by media2-session
public long getCurrentPosition(Long timeDiff) {
long expectedPosition = mPosition + (long) (mSpeed * (
(timeDiff != null) ? timeDiff : SystemClock.elapsedRealtime() - mUpdateTime));
diff --git a/media/src/main/java/androidx/media/AudioAttributesCompat.java b/media/src/main/java/androidx/media/AudioAttributesCompat.java
index 730daff..7337f93 100644
--- a/media/src/main/java/androidx/media/AudioAttributesCompat.java
+++ b/media/src/main/java/androidx/media/AudioAttributesCompat.java
@@ -16,7 +16,7 @@
package androidx.media;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import android.media.AudioAttributes;
import android.media.AudioManager;
@@ -234,12 +234,8 @@
static final int INVALID_STREAM_TYPE = -1; // AudioSystem.STREAM_DEFAULT
- /**
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
@ParcelField(1)
- public AudioAttributesImpl mImpl;
+ AudioAttributesImpl mImpl;
AudioAttributesCompat() {
}
@@ -538,7 +534,7 @@
*
* @hide For testing only.
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public static void setForceLegacyBehavior(boolean force) {
sForceLegacyBehavior = force;
}
@@ -631,7 +627,7 @@
USAGE_GAME,
USAGE_ASSISTANT,
})
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
@Retention(RetentionPolicy.SOURCE)
public @interface AttributeUsage {
}
@@ -645,7 +641,7 @@
CONTENT_TYPE_SONIFICATION
})
@Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public @interface AttributeContentType {
}
}
diff --git a/media/src/main/java/androidx/media/AudioAttributesImplApi21.java b/media/src/main/java/androidx/media/AudioAttributesImplApi21.java
index 81b9c2c..bddc8a6 100644
--- a/media/src/main/java/androidx/media/AudioAttributesImplApi21.java
+++ b/media/src/main/java/androidx/media/AudioAttributesImplApi21.java
@@ -16,7 +16,7 @@
package androidx.media;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.media.AudioAttributesCompat.INVALID_STREAM_TYPE;
import android.annotation.SuppressLint;
@@ -29,20 +29,16 @@
/** @hide */
@VersionedParcelize(jetifyAs = "android.support.v4.media.AudioAttributesImplApi21")
-@RestrictTo(LIBRARY_GROUP_PREFIX)
+@RestrictTo(LIBRARY)
@RequiresApi(21)
public class AudioAttributesImplApi21 implements AudioAttributesImpl {
private static final String TAG = "AudioAttributesCompat21";
- /** @hide */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
@ParcelField(1)
- public AudioAttributes mAudioAttributes;
+ AudioAttributes mAudioAttributes;
- /** @hide */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
@ParcelField(2)
- public int mLegacyStreamType = INVALID_STREAM_TYPE;
+ int mLegacyStreamType = INVALID_STREAM_TYPE;
/**
* Used for VersionedParcelable
diff --git a/media/src/main/java/androidx/media/AudioAttributesImplApi26.java b/media/src/main/java/androidx/media/AudioAttributesImplApi26.java
index 12e14cb..21411ff 100644
--- a/media/src/main/java/androidx/media/AudioAttributesImplApi26.java
+++ b/media/src/main/java/androidx/media/AudioAttributesImplApi26.java
@@ -16,7 +16,7 @@
package androidx.media;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.media.AudioAttributesCompat.INVALID_STREAM_TYPE;
import android.media.AudioAttributes;
@@ -27,7 +27,7 @@
/** @hide */
@VersionedParcelize(jetifyAs = "android.support.v4.media.AudioAttributesImplApi26")
-@RestrictTo(LIBRARY_GROUP_PREFIX)
+@RestrictTo(LIBRARY)
@RequiresApi(26)
public class AudioAttributesImplApi26 extends AudioAttributesImplApi21 {
private static final String TAG = "AudioAttributesCompat26";
diff --git a/media/src/main/java/androidx/media/AudioAttributesImplBase.java b/media/src/main/java/androidx/media/AudioAttributesImplBase.java
index 1750a12..6525ab6 100644
--- a/media/src/main/java/androidx/media/AudioAttributesImplBase.java
+++ b/media/src/main/java/androidx/media/AudioAttributesImplBase.java
@@ -16,7 +16,7 @@
package androidx.media;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.media.AudioAttributesCompat.CONTENT_TYPE_MOVIE;
import static androidx.media.AudioAttributesCompat.CONTENT_TYPE_MUSIC;
import static androidx.media.AudioAttributesCompat.CONTENT_TYPE_SONIFICATION;
@@ -50,24 +50,16 @@
/** @hide */
@VersionedParcelize(jetifyAs = "android.support.v4.media.AudioAttributesImplBase")
-@RestrictTo(LIBRARY_GROUP_PREFIX)
+@RestrictTo(LIBRARY)
public class AudioAttributesImplBase implements AudioAttributesImpl {
- /** @hide */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
@ParcelField(1)
- public int mUsage = USAGE_UNKNOWN;
- /** @hide */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ int mUsage = USAGE_UNKNOWN;
@ParcelField(2)
- public int mContentType = CONTENT_TYPE_UNKNOWN;
- /** @hide */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ int mContentType = CONTENT_TYPE_UNKNOWN;
@ParcelField(3)
- public int mFlags = 0x0;
- /** @hide */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ int mFlags = 0x0;
@ParcelField(4)
- public int mLegacyStream = INVALID_STREAM_TYPE;
+ int mLegacyStream = INVALID_STREAM_TYPE;
/**
* Used for VersionedParcelable
diff --git a/media/src/main/java/androidx/media/AudioFocusRequestCompat.java b/media/src/main/java/androidx/media/AudioFocusRequestCompat.java
index e932d63..8935faa 100644
--- a/media/src/main/java/androidx/media/AudioFocusRequestCompat.java
+++ b/media/src/main/java/androidx/media/AudioFocusRequestCompat.java
@@ -16,8 +16,6 @@
package androidx.media;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
-
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.media.AudioAttributes;
@@ -33,7 +31,6 @@
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
-import androidx.annotation.RestrictTo;
import androidx.core.util.ObjectsCompat;
import java.lang.annotation.Retention;
@@ -45,8 +42,6 @@
/* package */ static final AudioAttributesCompat FOCUS_DEFAULT_ATTR =
new AudioAttributesCompat.Builder().setUsage(AudioAttributesCompat.USAGE_MEDIA).build();
- /** @hide */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
@Retention(SOURCE)
@IntDef({
AudioManagerCompat.AUDIOFOCUS_GAIN,
@@ -54,7 +49,7 @@
AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
})
- public @interface FocusGainType {}
+ private @interface FocusGainType {}
private final int mFocusGain;
private final OnAudioFocusChangeListener mOnAudioFocusChangeListener;
@@ -392,10 +387,8 @@
*
* @param focusGain value to check
* @return true if focusGain is a valid value for an audio focus request.
- * @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
- static boolean isValidFocusGain(@FocusGainType int focusGain) {
+ private static boolean isValidFocusGain(@FocusGainType int focusGain) {
switch (focusGain) {
case AudioManagerCompat.AUDIOFOCUS_GAIN:
case AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT:
diff --git a/media/src/main/java/androidx/media/MediaBrowserCompatUtils.java b/media/src/main/java/androidx/media/MediaBrowserCompatUtils.java
index 0ca95e8..2c48e8c 100644
--- a/media/src/main/java/androidx/media/MediaBrowserCompatUtils.java
+++ b/media/src/main/java/androidx/media/MediaBrowserCompatUtils.java
@@ -16,7 +16,7 @@
package androidx.media;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import android.os.Bundle;
import android.support.v4.media.MediaBrowserCompat;
@@ -26,7 +26,7 @@
/**
* @hide
*/
-@RestrictTo(LIBRARY_GROUP_PREFIX)
+@RestrictTo(LIBRARY)
public class MediaBrowserCompatUtils {
public static boolean areSameOptions(Bundle options1, Bundle options2) {
if (options1 == options2) {
diff --git a/media/src/main/java/androidx/media/MediaBrowserProtocol.java b/media/src/main/java/androidx/media/MediaBrowserProtocol.java
index ddd3283..5326e1f 100644
--- a/media/src/main/java/androidx/media/MediaBrowserProtocol.java
+++ b/media/src/main/java/androidx/media/MediaBrowserProtocol.java
@@ -15,6 +15,8 @@
*/
package androidx.media;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
import android.os.Bundle;
import android.support.v4.media.MediaBrowserCompat;
@@ -25,7 +27,7 @@
*
* @hide
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY)
+@RestrictTo(LIBRARY)
public class MediaBrowserProtocol {
public static final String DATA_CALLBACK_TOKEN = "data_callback_token";
diff --git a/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java b/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java
index 35d46b2..82ff4b9 100644
--- a/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java
+++ b/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java
@@ -176,8 +176,6 @@
@RestrictTo(LIBRARY)
public static final int RESULT_PROGRESS_UPDATE = 1;
- /** @hide */
- @RestrictTo(LIBRARY)
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {RESULT_FLAG_OPTION_NOT_HANDLED,
RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED, RESULT_FLAG_ON_SEARCH_NOT_IMPLEMENTED})
@@ -1304,7 +1302,7 @@
*
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // accessed by media2-session
public void attachToBaseContext(Context base) {
attachBaseContext(base);
}
@@ -1422,7 +1420,7 @@
* @param option option
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public void onSubscribe(String id, Bundle option) {
}
@@ -1432,7 +1430,7 @@
* @param id
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public void onUnsubscribe(String id) {
}
@@ -1618,7 +1616,7 @@
* contain the information about the change.
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // accessed by media2-session
public void notifyChildrenChanged(@NonNull RemoteUserInfo remoteUserInfo,
@NonNull String parentId, @NonNull Bundle options) {
if (remoteUserInfo == null) {
diff --git a/media/src/main/java/androidx/media/MediaSessionManager.java b/media/src/main/java/androidx/media/MediaSessionManager.java
index 7183e02..b11f186 100644
--- a/media/src/main/java/androidx/media/MediaSessionManager.java
+++ b/media/src/main/java/androidx/media/MediaSessionManager.java
@@ -16,7 +16,7 @@
package androidx.media;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import android.content.Context;
import android.os.Build;
@@ -128,14 +128,14 @@
* Represents an unknown pid of an application.
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public static final int UNKNOWN_PID = -1;
/**
* Represents an unknown uid of an application.
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public static final int UNKNOWN_UID = -1;
RemoteUserInfoImpl mImpl;
@@ -169,7 +169,7 @@
* @param remoteUserInfo Framework RemoteUserInfo
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
@RequiresApi(28)
public RemoteUserInfo(
android.media.session.MediaSessionManager.RemoteUserInfo remoteUserInfo) {
diff --git a/media/src/main/java/androidx/media/VolumeProviderCompat.java b/media/src/main/java/androidx/media/VolumeProviderCompat.java
index eea4c7b..6d835d6 100644
--- a/media/src/main/java/androidx/media/VolumeProviderCompat.java
+++ b/media/src/main/java/androidx/media/VolumeProviderCompat.java
@@ -39,7 +39,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // used by mediarouter
@IntDef({VOLUME_CONTROL_FIXED, VOLUME_CONTROL_RELATIVE, VOLUME_CONTROL_ABSOLUTE})
@Retention(RetentionPolicy.SOURCE)
public @interface ControlType {}
diff --git a/media/src/main/java/androidx/media/app/NotificationCompat.java b/media/src/main/java/androidx/media/app/NotificationCompat.java
index 9e6af2e..e1fe44d 100644
--- a/media/src/main/java/androidx/media/app/NotificationCompat.java
+++ b/media/src/main/java/androidx/media/app/NotificationCompat.java
@@ -16,7 +16,7 @@
package androidx.media.app;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.core.app.NotificationCompat.COLOR_DEFAULT;
import android.app.Notification;
@@ -204,7 +204,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
@Override
public void apply(NotificationBuilderWithBuilderAccessor builder) {
if (Build.VERSION.SDK_INT >= 21) {
@@ -229,7 +229,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
@Override
public RemoteViews makeContentView(NotificationBuilderWithBuilderAccessor builder) {
if (Build.VERSION.SDK_INT >= 21) {
@@ -297,7 +297,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
@Override
public RemoteViews makeBigContentView(NotificationBuilderWithBuilderAccessor builder) {
if (Build.VERSION.SDK_INT >= 21) {
@@ -382,7 +382,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
@Override
public void apply(NotificationBuilderWithBuilderAccessor builder) {
if (Build.VERSION.SDK_INT >= 24) {
@@ -396,7 +396,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
@Override
public RemoteViews makeContentView(NotificationBuilderWithBuilderAccessor builder) {
if (Build.VERSION.SDK_INT >= 24) {
@@ -438,7 +438,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
@Override
public RemoteViews makeBigContentView(NotificationBuilderWithBuilderAccessor builder) {
if (Build.VERSION.SDK_INT >= 24) {
@@ -470,7 +470,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
@Override
public RemoteViews makeHeadsUpContentView(NotificationBuilderWithBuilderAccessor builder) {
if (Build.VERSION.SDK_INT >= 24) {
diff --git a/media2/session/api/1.1.0-alpha01.txt b/media2/session/api/1.1.0-alpha01.txt
index 4d96ad8..d16e2fb 100644
--- a/media2/session/api/1.1.0-alpha01.txt
+++ b/media2/session/api/1.1.0-alpha01.txt
@@ -190,7 +190,6 @@
method public java.util.List<androidx.media2.session.MediaSession.ControllerInfo!> getConnectedControllers();
method public String getId();
method public androidx.media2.common.SessionPlayer getPlayer();
- method public android.support.v4.media.session.MediaSessionCompat getSessionCompat();
method public androidx.media2.session.SessionToken getToken();
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
method public void setAllowedCommands(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommandGroup);
diff --git a/media2/session/api/current.txt b/media2/session/api/current.txt
index 4d96ad8..d16e2fb 100644
--- a/media2/session/api/current.txt
+++ b/media2/session/api/current.txt
@@ -190,7 +190,6 @@
method public java.util.List<androidx.media2.session.MediaSession.ControllerInfo!> getConnectedControllers();
method public String getId();
method public androidx.media2.common.SessionPlayer getPlayer();
- method public android.support.v4.media.session.MediaSessionCompat getSessionCompat();
method public androidx.media2.session.SessionToken getToken();
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
method public void setAllowedCommands(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommandGroup);
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaSession.java b/media2/session/src/main/java/androidx/media2/session/MediaSession.java
index d601bd7..90664f5 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaSession.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaSession.java
@@ -386,12 +386,10 @@
}
/**
- * Gets the {@link MediaSessionCompat} for this session to handle incoming commands from the
- * {@link android.support.v4.media.session.MediaSessionCompat} for legacy support.
- *
- * @return MediaSessionCompat
+ * @hide
+ * @return Bundle
*/
- @NonNull
+ @RestrictTo(LIBRARY_GROUP_PREFIX)
public MediaSessionCompat getSessionCompat() {
return mImpl.getSessionCompat();
}
diff --git a/media2/session/src/main/java/androidx/media2/session/SessionTokenImplBase.java b/media2/session/src/main/java/androidx/media2/session/SessionTokenImplBase.java
index b0296b7..1db4c73 100644
--- a/media2/session/src/main/java/androidx/media2/session/SessionTokenImplBase.java
+++ b/media2/session/src/main/java/androidx/media2/session/SessionTokenImplBase.java
@@ -156,7 +156,7 @@
}
@Override
- @NonNull
+ @Nullable
public Bundle getExtras() {
return mExtras;
}
diff --git a/navigation/navigation-common/api/api_lint.ignore b/navigation/navigation-common/api/api_lint.ignore
index 59abda6..7fcf560 100644
--- a/navigation/navigation-common/api/api_lint.ignore
+++ b/navigation/navigation-common/api/api_lint.ignore
@@ -1,11 +1,3 @@
// Baseline format: 1.0
KotlinOperator: androidx.navigation.NavType#get(android.os.Bundle, String):
Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.navigation.NavType.ParcelableArrayType#get(android.os.Bundle, String):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.navigation.NavType.ParcelableType#get(android.os.Bundle, String):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.navigation.NavType.SerializableArrayType#get(android.os.Bundle, String):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.navigation.NavType.SerializableType#get(android.os.Bundle, String):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
diff --git a/navigation/navigation-fragment-ktx/build.gradle b/navigation/navigation-fragment-ktx/build.gradle
index b588ff0..dd016f5 100644
--- a/navigation/navigation-fragment-ktx/build.gradle
+++ b/navigation/navigation-fragment-ktx/build.gradle
@@ -51,6 +51,7 @@
androidTestImplementation(TRUTH)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy)
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy)
+ androidTestImplementation project(':internal-testutils')
}
androidx {
diff --git a/navigation/navigation-fragment-ktx/src/androidTest/AndroidManifest.xml b/navigation/navigation-fragment-ktx/src/androidTest/AndroidManifest.xml
index 70a80b1..151e7a8 100644
--- a/navigation/navigation-fragment-ktx/src/androidTest/AndroidManifest.xml
+++ b/navigation/navigation-fragment-ktx/src/androidTest/AndroidManifest.xml
@@ -19,5 +19,6 @@
package="androidx.navigation.fragment.ktx">
<application>
<activity android:name="androidx.navigation.fragment.TestActivity" />
+ <activity android:name="androidx.navigation.fragment.NavGraphActivity" />
</application>
</manifest>
diff --git a/navigation/navigation-fragment-ktx/src/androidTest/java/androidx/navigation/fragment/NavGraphViewModelLazyTest.kt b/navigation/navigation-fragment-ktx/src/androidTest/java/androidx/navigation/fragment/NavGraphViewModelLazyTest.kt
index b124750..5a7eff9 100644
--- a/navigation/navigation-fragment-ktx/src/androidTest/java/androidx/navigation/fragment/NavGraphViewModelLazyTest.kt
+++ b/navigation/navigation-fragment-ktx/src/androidTest/java/androidx/navigation/fragment/NavGraphViewModelLazyTest.kt
@@ -22,19 +22,25 @@
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.testing.launchFragmentInContainer
+import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelStore
import androidx.navigation.NavHostController
import androidx.navigation.Navigation
+import androidx.navigation.findNavController
+import androidx.navigation.fragment.ktx.test.R
import androidx.navigation.navGraphViewModels
import androidx.navigation.navigation
import androidx.navigation.plusAssign
import androidx.navigation.testing.TestNavigator
import androidx.navigation.testing.test
+import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
+import androidx.testutils.withActivity
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -57,21 +63,82 @@
Navigation.setViewNavController(fragment.requireView(), navController)
}
val navGraph = navController.navigatorProvider.navigation(
- id = GRAPH_ID,
- startDestination = DESTINATION_ID
+ id = R.id.vm_graph,
+ startDestination = R.id.start_destination
) {
- test(DESTINATION_ID)
+ test(R.id.start_destination)
}
navController.setGraph(navGraph, null)
scenario.onFragment { fragment ->
assertThat(fragment.viewModel).isNotNull()
+ assertThat(fragment.savedStateViewModel).isNotNull()
+ }
+ }
+
+ @Test
+ fun sameViewModelAcrossFragments() {
+ with(ActivityScenario.launch(NavGraphActivity::class.java)) {
+ val navController = withActivity { findNavController(R.id.nav_host_fragment) }
+ val firstFragment: TestVMFragment = withActivity {
+ val navHostFragment = supportFragmentManager
+ .findFragmentById(R.id.nav_host_fragment)!!
+ navHostFragment.childFragmentManager.primaryNavigationFragment as TestVMFragment
+ }
+ val viewModel = firstFragment.viewModel
+ val savedStateViewModel = firstFragment.savedStateViewModel
+ assertThat(viewModel).isNotNull()
+ assertThat(savedStateViewModel).isNotNull()
+
+ // First assert that we don't have any default value since the
+ // navigation graph wasn't sent any arguments
+ val initialState: String? = savedStateViewModel.savedStateHandle["test"]
+ assertThat(initialState).isNull()
+
+ // Now set arguments
+ savedStateViewModel.savedStateHandle.set("test", "test")
+
+ // Navigate to the second destination and ensure it
+ // gets the same ViewModels and data
+ navController.navigate(R.id.second_destination)
+ val secondFragment: TestVMFragment = withActivity {
+ val navHostFragment = supportFragmentManager
+ .findFragmentById(R.id.nav_host_fragment)!!
+ navHostFragment.childFragmentManager.primaryNavigationFragment as TestVMFragment
+ }
+ assertThat(secondFragment.viewModel)
+ .isSameInstanceAs(viewModel)
+ assertThat(secondFragment.savedStateViewModel)
+ .isSameInstanceAs(savedStateViewModel)
+ val savedValue: String? = secondFragment.savedStateViewModel
+ .savedStateHandle["test"]
+ assertThat(savedValue).isEqualTo("test")
+
+ // Now recreate the Activity and ensure that when we
+ // first request the nav graph ViewModel on the second destination
+ // that we get the same ViewModel and data back
+ recreate()
+ val recreatedFragment: TestVMFragment = withActivity {
+ val navHostFragment = supportFragmentManager
+ .findFragmentById(R.id.nav_host_fragment)!!
+ navHostFragment.childFragmentManager.primaryNavigationFragment as TestVMFragment
+ }
+ assertThat(recreatedFragment.viewModel)
+ .isSameInstanceAs(viewModel)
+ assertThat(recreatedFragment.savedStateViewModel)
+ .isSameInstanceAs(savedStateViewModel)
+ val recreatedValue: String? = recreatedFragment.savedStateViewModel
+ .savedStateHandle["test"]
+ assertThat(recreatedValue).isEqualTo("test")
}
}
}
+class NavGraphActivity : FragmentActivity(R.layout.activity_nav_graph)
+
class TestVMFragment : Fragment() {
- val viewModel: TestViewModel by navGraphViewModels(GRAPH_ID)
+ val viewModel: TestViewModel by navGraphViewModels(R.id.vm_graph)
+ val savedStateViewModel: TestSavedStateViewModel by navGraphViewModels(R.id.vm_graph)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -82,6 +149,4 @@
}
class TestViewModel : ViewModel()
-
-private const val GRAPH_ID = 1
-private const val DESTINATION_ID = 2
\ No newline at end of file
+class TestSavedStateViewModel(val savedStateHandle: SavedStateHandle) : ViewModel()
diff --git a/navigation/navigation-fragment-ktx/src/androidTest/res/layout/activity_nav_graph.xml b/navigation/navigation-fragment-ktx/src/androidTest/res/layout/activity_nav_graph.xml
new file mode 100644
index 0000000..b8cdfcfb
--- /dev/null
+++ b/navigation/navigation-fragment-ktx/src/androidTest/res/layout/activity_nav_graph.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2019 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.
+ -->
+
+<fragment
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/nav_host_fragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:name="androidx.navigation.fragment.NavHostFragment"
+ app:defaultNavHost="true"
+ app:navGraph="@navigation/vm_graph" />
diff --git a/navigation/navigation-fragment-ktx/src/androidTest/res/navigation/vm_graph.xml b/navigation/navigation-fragment-ktx/src/androidTest/res/navigation/vm_graph.xml
new file mode 100644
index 0000000..b8daf2b
--- /dev/null
+++ b/navigation/navigation-fragment-ktx/src/androidTest/res/navigation/vm_graph.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2019 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.
+ -->
+
+<navigation
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/vm_graph"
+ app:startDestination="@+id/start_destination">
+ <fragment
+ android:id="@+id/start_destination"
+ android:name="androidx.navigation.fragment.TestVMFragment">
+ <argument
+ android:name="test"
+ android:defaultValue="first"/>
+ </fragment>
+ <fragment
+ android:id="@+id/second_destination"
+ android:name="androidx.navigation.fragment.TestVMFragment">
+ <argument
+ android:name="test"
+ android:defaultValue="second"/>
+ </fragment>
+</navigation>
diff --git a/navigation/navigation-fragment-ktx/src/main/java/androidx/navigation/NavGraphViewModelLazy.kt b/navigation/navigation-fragment-ktx/src/main/java/androidx/navigation/NavGraphViewModelLazy.kt
index b4c0f16..75e7122 100644
--- a/navigation/navigation-fragment-ktx/src/main/java/androidx/navigation/NavGraphViewModelLazy.kt
+++ b/navigation/navigation-fragment-ktx/src/main/java/androidx/navigation/NavGraphViewModelLazy.kt
@@ -20,6 +20,7 @@
import androidx.annotation.MainThread
import androidx.fragment.app.Fragment
import androidx.fragment.app.createViewModelLazy
+import androidx.lifecycle.HasDefaultViewModelProviderFactory
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStore
@@ -52,8 +53,14 @@
@IdRes navGraphId: Int,
noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null
): Lazy<VM> {
- val storeProducer: () -> ViewModelStore = {
- findNavController().getViewModelStoreOwner(navGraphId).viewModelStore
+ val viewModelStoreOwner by lazy {
+ findNavController().getViewModelStoreOwner(navGraphId)
}
- return createViewModelLazy(VM::class, storeProducer, factoryProducer)
+ val storeProducer: () -> ViewModelStore = {
+ viewModelStoreOwner.viewModelStore
+ }
+ return createViewModelLazy(VM::class, storeProducer, {
+ factoryProducer?.invoke() ?: (viewModelStoreOwner as HasDefaultViewModelProviderFactory)
+ .defaultViewModelProviderFactory
+ })
}
\ No newline at end of file
diff --git a/navigation/navigation-runtime/build.gradle b/navigation/navigation-runtime/build.gradle
index 9afb788..2624b7c 100644
--- a/navigation/navigation-runtime/build.gradle
+++ b/navigation/navigation-runtime/build.gradle
@@ -30,6 +30,8 @@
api(project(":navigation:navigation-common"))
api(project(":activity:activity"))
api(project(":lifecycle:lifecycle-viewmodel"))
+ api("androidx.savedstate:savedstate:1.0.0-rc01")
+ api(project(":lifecycle:lifecycle-viewmodel-savedstate"))
androidTestImplementation(project(":navigation:navigation-testing"))
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
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 3b2698d..297e3d8 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
@@ -23,6 +23,8 @@
import android.os.Parcel
import android.os.Parcelable
import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStore
import androidx.navigation.test.R
@@ -912,6 +914,39 @@
}
@Test
+ fun testGetViewModelStoreOwnerSavedStateViewModel() {
+ val hostStore = ViewModelStore()
+ val navController = createNavController()
+ navController.setViewModelStore(hostStore)
+ val navGraph = navController.navigatorProvider.navigation(
+ id = 1,
+ startDestination = R.id.start_test
+ ) {
+ test(R.id.start_test)
+ }
+ navController.setGraph(navGraph, null)
+
+ val owner = navController.getViewModelStoreOwner(navGraph.id)
+ assertThat(owner).isNotNull()
+ val viewModelProvider = ViewModelProvider(owner)
+ val viewModel = viewModelProvider[TestSavedStateViewModel::class.java]
+ assertThat(viewModel).isNotNull()
+ viewModel.savedStateHandle.set("test", "test")
+
+ val savedState = navController.saveState()
+ val restoredNavController = createNavController()
+ restoredNavController.setViewModelStore(hostStore)
+ restoredNavController.restoreState(savedState)
+ restoredNavController.graph = navGraph
+
+ val restoredOwner = navController.getViewModelStoreOwner(navGraph.id)
+ val restoredViewModel = ViewModelProvider(
+ restoredOwner)[TestSavedStateViewModel::class.java]
+ val restoredState: String? = restoredViewModel.savedStateHandle.get("test")
+ assertThat(restoredState).isEqualTo("test")
+ }
+
+ @Test
fun testSaveRestoreGetViewModelStoreOwner() {
val hostStore = ViewModelStore()
val navController = createNavController()
@@ -990,6 +1025,8 @@
class TestAndroidViewModel(application: Application) : AndroidViewModel(application)
+class TestSavedStateViewModel(val savedStateHandle: SavedStateHandle) : ViewModel()
+
/**
* [TestNavigator] that helps with testing saving and restoring state.
*/
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntry.java b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntry.java
index 0b9c4e5d..f226265 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntry.java
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntry.java
@@ -23,40 +23,65 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.HasDefaultViewModelProviderFactory;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.SavedStateViewModelFactory;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStore;
import androidx.lifecycle.ViewModelStoreOwner;
+import androidx.savedstate.SavedStateRegistry;
+import androidx.savedstate.SavedStateRegistryController;
+import androidx.savedstate.SavedStateRegistryOwner;
import java.util.UUID;
/**
* Representation of an entry in the back stack of a {@link NavController}.
*/
-final class NavBackStackEntry implements ViewModelStoreOwner, HasDefaultViewModelProviderFactory {
+final class NavBackStackEntry implements
+ LifecycleOwner,
+ ViewModelStoreOwner, HasDefaultViewModelProviderFactory,
+ SavedStateRegistryOwner {
private final Context mContext;
private final NavDestination mDestination;
private final Bundle mArgs;
+ private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
+ private final SavedStateRegistryController mSavedStateRegistryController =
+ SavedStateRegistryController.create(this);
// Internal unique name for this navBackStackEntry;
@NonNull
final UUID mId;
private NavControllerViewModel mNavControllerViewModel;
+ private ViewModelProvider.Factory mDefaultFactory;
NavBackStackEntry(@NonNull Context context,
@NonNull NavDestination destination, @Nullable Bundle args,
+ @Nullable LifecycleOwner navControllerLifecyleOwner,
@Nullable NavControllerViewModel navControllerViewModel) {
- this(context, destination, args, navControllerViewModel, UUID.randomUUID());
+ this(context, destination, args,
+ navControllerLifecyleOwner, navControllerViewModel,
+ UUID.randomUUID(), null);
}
NavBackStackEntry(@NonNull Context context,
@NonNull NavDestination destination, @Nullable Bundle args,
+ @Nullable LifecycleOwner navControllerLifecyleOwner,
@Nullable NavControllerViewModel navControllerViewModel,
- @NonNull UUID uuid) {
+ @NonNull UUID uuid, @Nullable Bundle savedState) {
mContext = context;
mId = uuid;
mDestination = destination;
mArgs = args;
mNavControllerViewModel = navControllerViewModel;
+ mSavedStateRegistryController.performRestore(savedState);
+ if (navControllerLifecyleOwner != null) {
+ mLifecycle.setCurrentState(navControllerLifecyleOwner.getLifecycle()
+ .getCurrentState());
+ } else {
+ mLifecycle.setCurrentState(Lifecycle.State.CREATED);
+ }
}
/**
@@ -83,6 +108,16 @@
@NonNull
@Override
+ public Lifecycle getLifecycle() {
+ return mLifecycle;
+ }
+
+ void handleLifecycleEvent(Lifecycle.Event event) {
+ mLifecycle.handleLifecycleEvent(event);
+ }
+
+ @NonNull
+ @Override
public ViewModelStore getViewModelStore() {
return mNavControllerViewModel.getViewModelStore(mId);
}
@@ -90,7 +125,22 @@
@NonNull
@Override
public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
- return ViewModelProvider.AndroidViewModelFactory.getInstance(
- (Application) mContext.getApplicationContext());
+ if (mDefaultFactory == null) {
+ mDefaultFactory = new SavedStateViewModelFactory(
+ (Application) mContext.getApplicationContext(),
+ this,
+ mArgs);
+ }
+ return mDefaultFactory;
+ }
+
+ @NonNull
+ @Override
+ public SavedStateRegistry getSavedStateRegistry() {
+ return mSavedStateRegistryController.getSavedStateRegistry();
+ }
+
+ void saveState(@NonNull Bundle outBundle) {
+ mSavedStateRegistryController.performSave(outBundle);
}
}
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntryState.java b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntryState.java
index a39a6e1..5dd3b7e 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntryState.java
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntryState.java
@@ -32,11 +32,14 @@
private final UUID mUUID;
private final int mDestinationId;
private final Bundle mArgs;
+ private final Bundle mSavedState;
NavBackStackEntryState(NavBackStackEntry entry) {
mUUID = entry.mId;
mDestinationId = entry.getDestination().getId();
mArgs = entry.getArguments();
+ mSavedState = new Bundle();
+ entry.saveState(mSavedState);
}
@SuppressWarnings("WeakerAccess")
@@ -44,6 +47,7 @@
mUUID = UUID.fromString(in.readString());
mDestinationId = in.readInt();
mArgs = in.readBundle(getClass().getClassLoader());
+ mSavedState = in.readBundle(getClass().getClassLoader());
}
@NonNull
@@ -60,6 +64,11 @@
return mArgs;
}
+ @NonNull
+ Bundle getSavedState() {
+ return mSavedState;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -70,6 +79,7 @@
parcel.writeString(mUUID.toString());
parcel.writeInt(mDestinationId);
parcel.writeBundle(mArgs);
+ parcel.writeBundle(mSavedState);
}
public static final Parcelable.Creator<NavBackStackEntryState> CREATOR =
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
index 112e24e..9686153 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
@@ -33,6 +33,9 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.TaskStackBuilder;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleEventObserver;
+import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelStore;
import androidx.lifecycle.ViewModelStoreOwner;
@@ -79,12 +82,14 @@
private final Context mContext;
private Activity mActivity;
private NavInflater mInflater;
- private NavGraph mGraph;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ NavGraph mGraph;
private Bundle mNavigatorStateToRestore;
private Parcelable[] mBackStackToRestore;
private boolean mDeepLinkHandled;
- private final Deque<NavBackStackEntry> mBackStack = new ArrayDeque<>();
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ final Deque<NavBackStackEntry> mBackStack = new ArrayDeque<>();
private LifecycleOwner mLifecycleOwner;
private NavControllerViewModel mViewModel;
@@ -94,6 +99,17 @@
private final CopyOnWriteArrayList<OnDestinationChangedListener>
mOnDestinationChangedListeners = new CopyOnWriteArrayList<>();
+ private final LifecycleObserver mLifecycleObserver = new LifecycleEventObserver() {
+ @Override
+ public void onStateChanged(@NonNull LifecycleOwner source,
+ @NonNull Lifecycle.Event event) {
+ if (mGraph != null) {
+ for (NavBackStackEntry entry : mBackStack) {
+ entry.handleLifecycleEvent(event);
+ }
+ }
+ }
+ };
private final OnBackPressedCallback mOnBackPressedCallback =
new OnBackPressedCallback(false) {
@Override
@@ -276,6 +292,7 @@
for (Navigator<?> navigator : popOperations) {
if (navigator.popBackStack()) {
NavBackStackEntry entry = mBackStack.removeLast();
+ entry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
if (mViewModel != null) {
mViewModel.clear(entry.mId);
}
@@ -480,8 +497,10 @@
if (args != null) {
args.setClassLoader(mContext.getClassLoader());
}
- mBackStack.add(new NavBackStackEntry(mContext, node, args, mViewModel,
- state.getUUID()));
+ NavBackStackEntry entry = new NavBackStackEntry(mContext, node, args,
+ mLifecycleOwner, mViewModel,
+ state.getUUID(), state.getSavedState());
+ mBackStack.add(entry);
}
updateOnBackPressedCallbackEnabled();
mBackStackToRestore = null;
@@ -865,7 +884,9 @@
}
// The mGraph should always be on the back stack after you navigate()
if (mBackStack.isEmpty()) {
- mBackStack.add(new NavBackStackEntry(mContext, mGraph, finalArgs, mViewModel));
+ NavBackStackEntry entry = new NavBackStackEntry(mContext, mGraph, finalArgs,
+ mLifecycleOwner, mViewModel);
+ mBackStack.add(entry);
}
// Now ensure all intermediate NavGraphs are put on the back stack
// to ensure that global actions work.
@@ -874,15 +895,16 @@
while (destination != null && findDestination(destination.getId()) == null) {
NavGraph parent = destination.getParent();
if (parent != null) {
- hierarchy.addFirst(new NavBackStackEntry(mContext, parent, finalArgs,
- mViewModel));
+ NavBackStackEntry entry = new NavBackStackEntry(mContext, parent, finalArgs,
+ mLifecycleOwner, mViewModel);
+ hierarchy.addFirst(entry);
}
destination = parent;
}
mBackStack.addAll(hierarchy);
// And finally, add the new destination with its default args
NavBackStackEntry newBackStackEntry = new NavBackStackEntry(mContext, newDest,
- newDest.addInDefaultArgs(finalArgs), mViewModel);
+ newDest.addInDefaultArgs(finalArgs), mLifecycleOwner, mViewModel);
mBackStack.add(newBackStackEntry);
}
updateOnBackPressedCallbackEnabled();
@@ -1004,6 +1026,7 @@
void setLifecycleOwner(@NonNull LifecycleOwner owner) {
mLifecycleOwner = owner;
+ mLifecycleOwner.getLifecycle().addObserver(mLifecycleObserver);
}
void setOnBackPressedDispatcher(@NonNull OnBackPressedDispatcher dispatcher) {
diff --git a/paging/common/api/api_lint.ignore b/paging/common/api/api_lint.ignore
index dec4b46..678d58d 100644
--- a/paging/common/api/api_lint.ignore
+++ b/paging/common/api/api_lint.ignore
@@ -23,10 +23,6 @@
Parameter p references hidden type class androidx.paging.PositionalDataSource.InitialResult.
-KotlinOperator: androidx.paging.PagedList#get(int):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
-
-
RegistrationName: androidx.paging.DataSource#addInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback):
Callback methods should be named register/unregister; was addInvalidatedCallback
RegistrationName: androidx.paging.DataSource#addInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit>):
diff --git a/paging/runtime/api/api_lint.ignore b/paging/runtime/api/api_lint.ignore
index 68bf03a..24e84aa 100644
--- a/paging/runtime/api/api_lint.ignore
+++ b/paging/runtime/api/api_lint.ignore
@@ -2,4 +2,4 @@
DocumentExceptions: androidx.paging.AsyncPagedListDiffer#getItem(int):
Method AsyncPagedListDiffer.getItem appears to be throwing java.lang.IndexOutOfBoundsException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
DocumentExceptions: androidx.paging.AsyncPagedListDiffer#submitList(androidx.paging.PagedList<T>, Runnable):
- Method AsyncPagedListDiffer.submitList appears to be throwing java.lang.IllegalArgumentException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
+ Method AsyncPagedListDiffer.submitList appears to be throwing java.lang.IllegalStateException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
index 74cd10a..0d1f6d9 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
@@ -5140,11 +5140,13 @@
void dispatchOnScrolled(int hresult, int vresult) {
mDispatchScrollCounter++;
- // Pass the current scrollX/scrollY values; no actual change in these properties occurred
- // but some general-purpose code may choose to respond to changes this way.
+ // Pass the current scrollX/scrollY values as current values. No actual change in these
+ // properties occurred. Pass negative hresult and vresult as old values so that
+ // postSendViewScrolledAccessibilityEventCallback(l - oldl, t - oldt) in onScrollChanged
+ // sends the scrolled accessibility event correctly.
final int scrollX = getScrollX();
final int scrollY = getScrollY();
- onScrollChanged(scrollX, scrollY, scrollX, scrollY);
+ onScrollChanged(scrollX, scrollY, scrollX - hresult, scrollY - vresult);
// Pass the real deltas to onScrolled, the RecyclerView-specific method.
onScrolled(hresult, vresult);
@@ -11354,7 +11356,9 @@
@Override
public String toString() {
- final StringBuilder sb = new StringBuilder("ViewHolder{"
+ String className =
+ getClass().isAnonymousClass() ? "ViewHolder" : getClass().getSimpleName();
+ final StringBuilder sb = new StringBuilder(className + "{"
+ Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId
+ ", oldPos=" + mOldPosition + ", pLpos:" + mPreLayoutPosition);
if (isScrap()) {
diff --git a/room/common/api/2.2.0-alpha02.ignore b/room/common/api/2.2.0-alpha02.ignore
index 64ce5cf..51b51d5 100644
--- a/room/common/api/2.2.0-alpha02.ignore
+++ b/room/common/api/2.2.0-alpha02.ignore
@@ -1,16 +1,4 @@
// Baseline format: 1.0
-AddedAbstractMethod: androidx.room.ColumnInfo#defaultValue():
- Added method androidx.room.ColumnInfo.defaultValue()
-AddedAbstractMethod: androidx.room.Delete#entity():
- Added method androidx.room.Delete.entity()
-AddedAbstractMethod: androidx.room.Insert#entity():
- Added method androidx.room.Insert.entity()
-AddedAbstractMethod: androidx.room.Relation#associateBy():
- Added method androidx.room.Relation.associateBy()
-AddedAbstractMethod: androidx.room.Update#entity():
- Added method androidx.room.Update.entity()
-
-
ChangedType: androidx.room.Database#entities():
Method androidx.room.Database.entities has changed return type from Class[] to Class<?>[]
ChangedType: androidx.room.Database#views():
diff --git a/security/crypto/api/api_lint.ignore b/security/crypto/api/api_lint.ignore
index 2635915..0b681f2 100644
--- a/security/crypto/api/api_lint.ignore
+++ b/security/crypto/api/api_lint.ignore
@@ -5,10 +5,6 @@
Context is distinct, so it must be the first argument (method `create`)
-KotlinOperator: androidx.security.crypto.EncryptedSharedPreferences#contains(String):
- Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
-
-
RegistrationName: androidx.security.crypto.EncryptedSharedPreferences#registerOnSharedPreferenceChangeListener(android.content.SharedPreferences.OnSharedPreferenceChangeListener):
Listener methods should be named add/remove; was registerOnSharedPreferenceChangeListener
RegistrationName: androidx.security.crypto.EncryptedSharedPreferences#unregisterOnSharedPreferenceChangeListener(android.content.SharedPreferences.OnSharedPreferenceChangeListener):
diff --git a/settings.gradle b/settings.gradle
index e953056..8e9171e 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -66,6 +66,8 @@
includeProject(":benchmark:integration-tests:startup-benchmark", "benchmark/integration-tests/startup-benchmark")
includeProject(":biometric", "biometric")
includeProject(":browser", "browser")
+includeProject(":buildSrc-tests", "buildSrc-tests")
+includeProject(":buildSrc-tests:lint-checks", "buildSrc-tests/lint-checks")
includeProject(":camera:camera-camera2", "camera/camera-camera2")
includeProject(":camera:camera-core", "camera/camera-core")
includeProject(":camera:camera-extensions", "camera/camera-extensions")
diff --git a/ui/integration-tests/benchmark/build.gradle b/ui/integration-tests/benchmark/build.gradle
index ab94dc5..44c1326 100644
--- a/ui/integration-tests/benchmark/build.gradle
+++ b/ui/integration-tests/benchmark/build.gradle
@@ -61,4 +61,3 @@
useIR = true
}
}
-
diff --git a/ui/integration-tests/benchmark/src/androidTest/AndroidManifest.xml b/ui/integration-tests/benchmark/src/androidTest/AndroidManifest.xml
index 1496a9d..3369493 100644
--- a/ui/integration-tests/benchmark/src/androidTest/AndroidManifest.xml
+++ b/ui/integration-tests/benchmark/src/androidTest/AndroidManifest.xml
@@ -14,13 +14,16 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<manifest
- xmlns:android="http://schemas.android.com/apk/res/android"
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="androidx.ui.benchmark.test">
- <!-- Important: disable debuggable for accurate performance results -->
+ <!--
+ ~ Important: disable debuggable for accurate performance results
+ ~ requestLegacyExternalStorage to enable legacy JSON reporting when targeting Q
+ -->
<application
+ android:requestLegacyExternalStorage="true"
android:debuggable="false"
tools:replace="android:debuggable">
<activity android:name="android.app.Activity"/>
diff --git a/ui/ui-animation/src/main/java/androidx/ui/animation/Transition.kt b/ui/ui-animation/src/main/java/androidx/ui/animation/Transition.kt
index c1a49d5..f3f6dd7 100644
--- a/ui/ui-animation/src/main/java/androidx/ui/animation/Transition.kt
+++ b/ui/ui-animation/src/main/java/androidx/ui/animation/Transition.kt
@@ -46,7 +46,7 @@
fun <T> Transition(
definition: TransitionDefinition<T>,
toState: T,
- @Children children: @Composable() (state: TransitionState) -> Unit
+ children: @Composable() (state: TransitionState) -> Unit
) {
if (transitionsEnabled) {
// TODO: This null is workaround for b/132148894
diff --git a/ui/ui-core/api/api_lint.ignore b/ui/ui-core/api/api_lint.ignore
index a0c8d75..c321320 100644
--- a/ui/ui-core/api/api_lint.ignore
+++ b/ui/ui-core/api/api_lint.ignore
@@ -106,17 +106,17 @@
KotlinOperator: androidx.ui.engine.geometry.RRect#contains(androidx.ui.engine.geometry.Offset):
- Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
+ Note that adding the `operator` keyword would allow calling this method using operator syntax
KotlinOperator: androidx.ui.engine.geometry.Rect#contains(androidx.ui.engine.geometry.Offset):
- Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
+ Note that adding the `operator` keyword would allow calling this method using operator syntax
KotlinOperator: androidx.ui.engine.geometry.Size#contains(androidx.ui.engine.geometry.Offset):
- Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
+ Note that adding the `operator` keyword would allow calling this method using operator syntax
KotlinOperator: androidx.ui.painting.Path#contains(androidx.ui.engine.geometry.Offset):
- Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
+ Note that adding the `operator` keyword would allow calling this method using operator syntax
KotlinOperator: androidx.ui.text.TextRange#contains(androidx.ui.text.TextRange):
- Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
+ Note that adding the `operator` keyword would allow calling this method using operator syntax
KotlinOperator: androidx.ui.text.TextRange#contains(int):
- Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
+ Note that adding the `operator` keyword would allow calling this method using operator syntax
MethodNameUnits: androidx.ui.core.Durations#inSeconds(androidx.ui.core.Duration):
diff --git a/ui/ui-foundation/api/1.0.0-alpha01.txt b/ui/ui-foundation/api/1.0.0-alpha01.txt
index 5edc08e..ec7e422 100644
--- a/ui/ui-foundation/api/1.0.0-alpha01.txt
+++ b/ui/ui-foundation/api/1.0.0-alpha01.txt
@@ -29,43 +29,40 @@
}
-package androidx.ui.foundation.gestures {
+package androidx.ui.foundation.animation {
- public final class AnchorsFlingConfig implements androidx.ui.foundation.gestures.FlingConfig {
- ctor public AnchorsFlingConfig(java.util.List<java.lang.Float> animationAnchors, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, androidx.animation.AnimationBuilder<java.lang.Float> animationBuilder, androidx.animation.DecayAnimation decayAnimation);
- method public java.util.List<java.lang.Float> component1();
+ public final class AnimatedFloatDragController implements androidx.ui.foundation.gestures.DragValueController {
+ ctor public AnimatedFloatDragController(float initialValue, androidx.ui.foundation.animation.FlingConfig? flingConfig);
+ method public androidx.animation.AnimatedFloat getAnimatedFloat();
+ method public float getCurrentValue();
+ method public void onDrag(float target);
+ method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
+ method public void setBounds(float min, float max);
+ property public final androidx.animation.AnimatedFloat animatedFloat;
+ property public float currentValue;
+ }
+
+ public final class FlingConfig {
+ ctor public FlingConfig(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
+ ctor public FlingConfig();
+ method public androidx.animation.DecayAnimation component1();
method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? component2();
- method public androidx.animation.AnimationBuilder<java.lang.Float> component3();
- method public androidx.animation.DecayAnimation component4();
- method public androidx.ui.foundation.gestures.AnchorsFlingConfig copy(java.util.List<java.lang.Float> animationAnchors, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, androidx.animation.AnimationBuilder<java.lang.Float> animationBuilder, androidx.animation.DecayAnimation decayAnimation);
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
- method public java.util.List<java.lang.Float> getAnimationAnchors();
- method public androidx.animation.AnimationBuilder<java.lang.Float> getAnimationBuilder();
+ method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> component3();
+ method public androidx.ui.foundation.animation.FlingConfig copy(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
+ method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> getAdjustTarget();
method public androidx.animation.DecayAnimation getDecayAnimation();
method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? getOnAnimationFinished();
}
- public final class AnimatedDraggableKt {
- ctor public AnimatedDraggableKt();
- method public static void AnimatedDraggable(androidx.ui.foundation.gestures.DragDirection dragDirection, float startValue, float minValue = Float.MIN_VALUE, float maxValue = Float.MAX_VALUE, androidx.ui.foundation.gestures.FlingConfig? flingConfig = null, kotlin.jvm.functions.Function1<? super androidx.animation.BaseAnimatedValue<java.lang.Float>,kotlin.Unit> children);
+ public final class FlingConfigKt {
+ ctor public FlingConfigKt();
+ method public static androidx.ui.foundation.animation.FlingConfig AnchorsFlingConfig(java.util.List<java.lang.Float> anchors, androidx.animation.AnimationBuilder<java.lang.Float> animationBuilder = PhysicsBuilder(), kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished = null, androidx.animation.DecayAnimation decayAnimation = ExponentialDecay());
+ method public static void fling(androidx.animation.AnimatedFloat, androidx.ui.foundation.animation.FlingConfig config, float startVelocity);
}
- public final class DecayFlingConfig implements androidx.ui.foundation.gestures.FlingConfig {
- ctor public DecayFlingConfig(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onFlingFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
- method public androidx.animation.DecayAnimation component1();
- method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? component2();
- method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> component3();
- method public androidx.ui.foundation.gestures.DecayFlingConfig copy(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onFlingFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
- method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> getAdjustTarget();
- method public androidx.animation.DecayAnimation getDecayAnimation();
- method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? getOnFlingFinished();
- }
+}
- public final class DefaultFlingConfig implements androidx.ui.foundation.gestures.FlingConfig {
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
- field public static final androidx.ui.foundation.gestures.DefaultFlingConfig! INSTANCE;
- }
+package androidx.ui.foundation.gestures {
public abstract sealed class DragDirection {
}
@@ -78,8 +75,33 @@
field public static final androidx.ui.foundation.gestures.DragDirection.Vertical! INSTANCE;
}
- public interface FlingConfig {
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
+ public interface DragValueController {
+ method public float getCurrentValue();
+ method public void onDrag(float target);
+ method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
+ method public void setBounds(float min, float max);
+ property public abstract float currentValue;
+ }
+
+ public final class DraggableCallback {
+ ctor public DraggableCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onDragStarted, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onDragSettled);
+ ctor public DraggableCallback();
+ }
+
+ public final class DraggableKt {
+ ctor public DraggableKt();
+ method public static void Draggable(androidx.ui.foundation.gestures.DragDirection dragDirection, float minValue = Float.MIN_VALUE, float maxValue = Float.MAX_VALUE, androidx.ui.foundation.gestures.DragValueController valueController = +memo(minValue, {
+ <init>(minValue)
+}), androidx.ui.foundation.gestures.DraggableCallback? callback = null, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> children);
+ }
+
+ public final class FloatDragValueController implements androidx.ui.foundation.gestures.DragValueController {
+ ctor public FloatDragValueController(float initialValue);
+ method public float getCurrentValue();
+ method public void onDrag(float target);
+ method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
+ method public void setBounds(float min, float max);
+ property public float currentValue;
}
}
diff --git a/ui/ui-foundation/api/current.txt b/ui/ui-foundation/api/current.txt
index 5edc08e..ec7e422 100644
--- a/ui/ui-foundation/api/current.txt
+++ b/ui/ui-foundation/api/current.txt
@@ -29,43 +29,40 @@
}
-package androidx.ui.foundation.gestures {
+package androidx.ui.foundation.animation {
- public final class AnchorsFlingConfig implements androidx.ui.foundation.gestures.FlingConfig {
- ctor public AnchorsFlingConfig(java.util.List<java.lang.Float> animationAnchors, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, androidx.animation.AnimationBuilder<java.lang.Float> animationBuilder, androidx.animation.DecayAnimation decayAnimation);
- method public java.util.List<java.lang.Float> component1();
+ public final class AnimatedFloatDragController implements androidx.ui.foundation.gestures.DragValueController {
+ ctor public AnimatedFloatDragController(float initialValue, androidx.ui.foundation.animation.FlingConfig? flingConfig);
+ method public androidx.animation.AnimatedFloat getAnimatedFloat();
+ method public float getCurrentValue();
+ method public void onDrag(float target);
+ method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
+ method public void setBounds(float min, float max);
+ property public final androidx.animation.AnimatedFloat animatedFloat;
+ property public float currentValue;
+ }
+
+ public final class FlingConfig {
+ ctor public FlingConfig(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
+ ctor public FlingConfig();
+ method public androidx.animation.DecayAnimation component1();
method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? component2();
- method public androidx.animation.AnimationBuilder<java.lang.Float> component3();
- method public androidx.animation.DecayAnimation component4();
- method public androidx.ui.foundation.gestures.AnchorsFlingConfig copy(java.util.List<java.lang.Float> animationAnchors, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, androidx.animation.AnimationBuilder<java.lang.Float> animationBuilder, androidx.animation.DecayAnimation decayAnimation);
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
- method public java.util.List<java.lang.Float> getAnimationAnchors();
- method public androidx.animation.AnimationBuilder<java.lang.Float> getAnimationBuilder();
+ method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> component3();
+ method public androidx.ui.foundation.animation.FlingConfig copy(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
+ method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> getAdjustTarget();
method public androidx.animation.DecayAnimation getDecayAnimation();
method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? getOnAnimationFinished();
}
- public final class AnimatedDraggableKt {
- ctor public AnimatedDraggableKt();
- method public static void AnimatedDraggable(androidx.ui.foundation.gestures.DragDirection dragDirection, float startValue, float minValue = Float.MIN_VALUE, float maxValue = Float.MAX_VALUE, androidx.ui.foundation.gestures.FlingConfig? flingConfig = null, kotlin.jvm.functions.Function1<? super androidx.animation.BaseAnimatedValue<java.lang.Float>,kotlin.Unit> children);
+ public final class FlingConfigKt {
+ ctor public FlingConfigKt();
+ method public static androidx.ui.foundation.animation.FlingConfig AnchorsFlingConfig(java.util.List<java.lang.Float> anchors, androidx.animation.AnimationBuilder<java.lang.Float> animationBuilder = PhysicsBuilder(), kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished = null, androidx.animation.DecayAnimation decayAnimation = ExponentialDecay());
+ method public static void fling(androidx.animation.AnimatedFloat, androidx.ui.foundation.animation.FlingConfig config, float startVelocity);
}
- public final class DecayFlingConfig implements androidx.ui.foundation.gestures.FlingConfig {
- ctor public DecayFlingConfig(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onFlingFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
- method public androidx.animation.DecayAnimation component1();
- method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? component2();
- method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> component3();
- method public androidx.ui.foundation.gestures.DecayFlingConfig copy(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onFlingFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
- method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> getAdjustTarget();
- method public androidx.animation.DecayAnimation getDecayAnimation();
- method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? getOnFlingFinished();
- }
+}
- public final class DefaultFlingConfig implements androidx.ui.foundation.gestures.FlingConfig {
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
- field public static final androidx.ui.foundation.gestures.DefaultFlingConfig! INSTANCE;
- }
+package androidx.ui.foundation.gestures {
public abstract sealed class DragDirection {
}
@@ -78,8 +75,33 @@
field public static final androidx.ui.foundation.gestures.DragDirection.Vertical! INSTANCE;
}
- public interface FlingConfig {
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
+ public interface DragValueController {
+ method public float getCurrentValue();
+ method public void onDrag(float target);
+ method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
+ method public void setBounds(float min, float max);
+ property public abstract float currentValue;
+ }
+
+ public final class DraggableCallback {
+ ctor public DraggableCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onDragStarted, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onDragSettled);
+ ctor public DraggableCallback();
+ }
+
+ public final class DraggableKt {
+ ctor public DraggableKt();
+ method public static void Draggable(androidx.ui.foundation.gestures.DragDirection dragDirection, float minValue = Float.MIN_VALUE, float maxValue = Float.MAX_VALUE, androidx.ui.foundation.gestures.DragValueController valueController = +memo(minValue, {
+ <init>(minValue)
+}), androidx.ui.foundation.gestures.DraggableCallback? callback = null, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> children);
+ }
+
+ public final class FloatDragValueController implements androidx.ui.foundation.gestures.DragValueController {
+ ctor public FloatDragValueController(float initialValue);
+ method public float getCurrentValue();
+ method public void onDrag(float target);
+ method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
+ method public void setBounds(float min, float max);
+ property public float currentValue;
}
}
diff --git a/ui/ui-foundation/api/restricted_1.0.0-alpha01.txt b/ui/ui-foundation/api/restricted_1.0.0-alpha01.txt
index 5edc08e..ec7e422 100644
--- a/ui/ui-foundation/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-foundation/api/restricted_1.0.0-alpha01.txt
@@ -29,43 +29,40 @@
}
-package androidx.ui.foundation.gestures {
+package androidx.ui.foundation.animation {
- public final class AnchorsFlingConfig implements androidx.ui.foundation.gestures.FlingConfig {
- ctor public AnchorsFlingConfig(java.util.List<java.lang.Float> animationAnchors, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, androidx.animation.AnimationBuilder<java.lang.Float> animationBuilder, androidx.animation.DecayAnimation decayAnimation);
- method public java.util.List<java.lang.Float> component1();
+ public final class AnimatedFloatDragController implements androidx.ui.foundation.gestures.DragValueController {
+ ctor public AnimatedFloatDragController(float initialValue, androidx.ui.foundation.animation.FlingConfig? flingConfig);
+ method public androidx.animation.AnimatedFloat getAnimatedFloat();
+ method public float getCurrentValue();
+ method public void onDrag(float target);
+ method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
+ method public void setBounds(float min, float max);
+ property public final androidx.animation.AnimatedFloat animatedFloat;
+ property public float currentValue;
+ }
+
+ public final class FlingConfig {
+ ctor public FlingConfig(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
+ ctor public FlingConfig();
+ method public androidx.animation.DecayAnimation component1();
method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? component2();
- method public androidx.animation.AnimationBuilder<java.lang.Float> component3();
- method public androidx.animation.DecayAnimation component4();
- method public androidx.ui.foundation.gestures.AnchorsFlingConfig copy(java.util.List<java.lang.Float> animationAnchors, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, androidx.animation.AnimationBuilder<java.lang.Float> animationBuilder, androidx.animation.DecayAnimation decayAnimation);
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
- method public java.util.List<java.lang.Float> getAnimationAnchors();
- method public androidx.animation.AnimationBuilder<java.lang.Float> getAnimationBuilder();
+ method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> component3();
+ method public androidx.ui.foundation.animation.FlingConfig copy(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
+ method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> getAdjustTarget();
method public androidx.animation.DecayAnimation getDecayAnimation();
method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? getOnAnimationFinished();
}
- public final class AnimatedDraggableKt {
- ctor public AnimatedDraggableKt();
- method public static void AnimatedDraggable(androidx.ui.foundation.gestures.DragDirection dragDirection, float startValue, float minValue = Float.MIN_VALUE, float maxValue = Float.MAX_VALUE, androidx.ui.foundation.gestures.FlingConfig? flingConfig = null, kotlin.jvm.functions.Function1<? super androidx.animation.BaseAnimatedValue<java.lang.Float>,kotlin.Unit> children);
+ public final class FlingConfigKt {
+ ctor public FlingConfigKt();
+ method public static androidx.ui.foundation.animation.FlingConfig AnchorsFlingConfig(java.util.List<java.lang.Float> anchors, androidx.animation.AnimationBuilder<java.lang.Float> animationBuilder = PhysicsBuilder(), kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished = null, androidx.animation.DecayAnimation decayAnimation = ExponentialDecay());
+ method public static void fling(androidx.animation.AnimatedFloat, androidx.ui.foundation.animation.FlingConfig config, float startVelocity);
}
- public final class DecayFlingConfig implements androidx.ui.foundation.gestures.FlingConfig {
- ctor public DecayFlingConfig(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onFlingFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
- method public androidx.animation.DecayAnimation component1();
- method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? component2();
- method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> component3();
- method public androidx.ui.foundation.gestures.DecayFlingConfig copy(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onFlingFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
- method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> getAdjustTarget();
- method public androidx.animation.DecayAnimation getDecayAnimation();
- method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? getOnFlingFinished();
- }
+}
- public final class DefaultFlingConfig implements androidx.ui.foundation.gestures.FlingConfig {
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
- field public static final androidx.ui.foundation.gestures.DefaultFlingConfig! INSTANCE;
- }
+package androidx.ui.foundation.gestures {
public abstract sealed class DragDirection {
}
@@ -78,8 +75,33 @@
field public static final androidx.ui.foundation.gestures.DragDirection.Vertical! INSTANCE;
}
- public interface FlingConfig {
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
+ public interface DragValueController {
+ method public float getCurrentValue();
+ method public void onDrag(float target);
+ method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
+ method public void setBounds(float min, float max);
+ property public abstract float currentValue;
+ }
+
+ public final class DraggableCallback {
+ ctor public DraggableCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onDragStarted, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onDragSettled);
+ ctor public DraggableCallback();
+ }
+
+ public final class DraggableKt {
+ ctor public DraggableKt();
+ method public static void Draggable(androidx.ui.foundation.gestures.DragDirection dragDirection, float minValue = Float.MIN_VALUE, float maxValue = Float.MAX_VALUE, androidx.ui.foundation.gestures.DragValueController valueController = +memo(minValue, {
+ <init>(minValue)
+}), androidx.ui.foundation.gestures.DraggableCallback? callback = null, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> children);
+ }
+
+ public final class FloatDragValueController implements androidx.ui.foundation.gestures.DragValueController {
+ ctor public FloatDragValueController(float initialValue);
+ method public float getCurrentValue();
+ method public void onDrag(float target);
+ method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
+ method public void setBounds(float min, float max);
+ property public float currentValue;
}
}
diff --git a/ui/ui-foundation/api/restricted_current.txt b/ui/ui-foundation/api/restricted_current.txt
index 5edc08e..ec7e422 100644
--- a/ui/ui-foundation/api/restricted_current.txt
+++ b/ui/ui-foundation/api/restricted_current.txt
@@ -29,43 +29,40 @@
}
-package androidx.ui.foundation.gestures {
+package androidx.ui.foundation.animation {
- public final class AnchorsFlingConfig implements androidx.ui.foundation.gestures.FlingConfig {
- ctor public AnchorsFlingConfig(java.util.List<java.lang.Float> animationAnchors, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, androidx.animation.AnimationBuilder<java.lang.Float> animationBuilder, androidx.animation.DecayAnimation decayAnimation);
- method public java.util.List<java.lang.Float> component1();
+ public final class AnimatedFloatDragController implements androidx.ui.foundation.gestures.DragValueController {
+ ctor public AnimatedFloatDragController(float initialValue, androidx.ui.foundation.animation.FlingConfig? flingConfig);
+ method public androidx.animation.AnimatedFloat getAnimatedFloat();
+ method public float getCurrentValue();
+ method public void onDrag(float target);
+ method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
+ method public void setBounds(float min, float max);
+ property public final androidx.animation.AnimatedFloat animatedFloat;
+ property public float currentValue;
+ }
+
+ public final class FlingConfig {
+ ctor public FlingConfig(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
+ ctor public FlingConfig();
+ method public androidx.animation.DecayAnimation component1();
method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? component2();
- method public androidx.animation.AnimationBuilder<java.lang.Float> component3();
- method public androidx.animation.DecayAnimation component4();
- method public androidx.ui.foundation.gestures.AnchorsFlingConfig copy(java.util.List<java.lang.Float> animationAnchors, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, androidx.animation.AnimationBuilder<java.lang.Float> animationBuilder, androidx.animation.DecayAnimation decayAnimation);
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
- method public java.util.List<java.lang.Float> getAnimationAnchors();
- method public androidx.animation.AnimationBuilder<java.lang.Float> getAnimationBuilder();
+ method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> component3();
+ method public androidx.ui.foundation.animation.FlingConfig copy(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
+ method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> getAdjustTarget();
method public androidx.animation.DecayAnimation getDecayAnimation();
method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? getOnAnimationFinished();
}
- public final class AnimatedDraggableKt {
- ctor public AnimatedDraggableKt();
- method public static void AnimatedDraggable(androidx.ui.foundation.gestures.DragDirection dragDirection, float startValue, float minValue = Float.MIN_VALUE, float maxValue = Float.MAX_VALUE, androidx.ui.foundation.gestures.FlingConfig? flingConfig = null, kotlin.jvm.functions.Function1<? super androidx.animation.BaseAnimatedValue<java.lang.Float>,kotlin.Unit> children);
+ public final class FlingConfigKt {
+ ctor public FlingConfigKt();
+ method public static androidx.ui.foundation.animation.FlingConfig AnchorsFlingConfig(java.util.List<java.lang.Float> anchors, androidx.animation.AnimationBuilder<java.lang.Float> animationBuilder = PhysicsBuilder(), kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished = null, androidx.animation.DecayAnimation decayAnimation = ExponentialDecay());
+ method public static void fling(androidx.animation.AnimatedFloat, androidx.ui.foundation.animation.FlingConfig config, float startVelocity);
}
- public final class DecayFlingConfig implements androidx.ui.foundation.gestures.FlingConfig {
- ctor public DecayFlingConfig(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onFlingFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
- method public androidx.animation.DecayAnimation component1();
- method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? component2();
- method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> component3();
- method public androidx.ui.foundation.gestures.DecayFlingConfig copy(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onFlingFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
- method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> getAdjustTarget();
- method public androidx.animation.DecayAnimation getDecayAnimation();
- method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? getOnFlingFinished();
- }
+}
- public final class DefaultFlingConfig implements androidx.ui.foundation.gestures.FlingConfig {
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
- field public static final androidx.ui.foundation.gestures.DefaultFlingConfig! INSTANCE;
- }
+package androidx.ui.foundation.gestures {
public abstract sealed class DragDirection {
}
@@ -78,8 +75,33 @@
field public static final androidx.ui.foundation.gestures.DragDirection.Vertical! INSTANCE;
}
- public interface FlingConfig {
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
+ public interface DragValueController {
+ method public float getCurrentValue();
+ method public void onDrag(float target);
+ method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
+ method public void setBounds(float min, float max);
+ property public abstract float currentValue;
+ }
+
+ public final class DraggableCallback {
+ ctor public DraggableCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onDragStarted, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onDragSettled);
+ ctor public DraggableCallback();
+ }
+
+ public final class DraggableKt {
+ ctor public DraggableKt();
+ method public static void Draggable(androidx.ui.foundation.gestures.DragDirection dragDirection, float minValue = Float.MIN_VALUE, float maxValue = Float.MAX_VALUE, androidx.ui.foundation.gestures.DragValueController valueController = +memo(minValue, {
+ <init>(minValue)
+}), androidx.ui.foundation.gestures.DraggableCallback? callback = null, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> children);
+ }
+
+ public final class FloatDragValueController implements androidx.ui.foundation.gestures.DragValueController {
+ ctor public FloatDragValueController(float initialValue);
+ method public float getCurrentValue();
+ method public void onDrag(float target);
+ method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
+ method public void setBounds(float min, float max);
+ property public float currentValue;
}
}
diff --git a/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/ui/foundation/demos/AnimatedDraggableActivity.kt b/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/ui/foundation/demos/AnimatedDraggableActivity.kt
index d33545a..5f277c2 100644
--- a/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/ui/foundation/demos/AnimatedDraggableActivity.kt
+++ b/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/ui/foundation/demos/AnimatedDraggableActivity.kt
@@ -19,8 +19,12 @@
import android.app.Activity
import android.os.Bundle
import androidx.compose.composer
+import androidx.ui.core.dp
import androidx.ui.core.setContent
-import androidx.ui.foundation.samples.AnimatedDraggableSample
+import androidx.ui.foundation.samples.AnchoredDraggableSample
+import androidx.ui.foundation.samples.DraggableSample
+import androidx.ui.layout.Column
+import androidx.ui.layout.HeightSpacer
import androidx.ui.layout.Wrap
class AnimatedDraggableActivity : Activity() {
@@ -29,7 +33,11 @@
super.onCreate(savedInstanceState)
setContent {
Wrap {
- AnimatedDraggableSample()
+ Column {
+ DraggableSample()
+ HeightSpacer(100.dp)
+ AnchoredDraggableSample()
+ }
}
}
}
diff --git a/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/AnimatedDraggableSamples.kt b/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/AnimatedDraggableSamples.kt
deleted file mode 100644
index 3714185..0000000
--- a/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/AnimatedDraggableSamples.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2019 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.ui.foundation.samples
-
-import androidx.annotation.Sampled
-import androidx.compose.Composable
-import androidx.compose.composer
-import androidx.compose.unaryPlus
-import androidx.ui.core.dp
-import androidx.ui.core.withDensity
-import androidx.ui.foundation.ColoredRect
-import androidx.ui.foundation.gestures.AnchorsFlingConfig
-import androidx.ui.foundation.gestures.AnimatedDraggable
-import androidx.ui.foundation.gestures.DragDirection
-import androidx.ui.foundation.shape.DrawShape
-import androidx.ui.foundation.shape.RectangleShape
-import androidx.ui.graphics.Color
-import androidx.ui.layout.Alignment
-import androidx.ui.layout.Container
-import androidx.ui.layout.Padding
-
-@Sampled
-@Composable
-fun AnimatedDraggableSample() {
- // Composable that users can drag over 300 dp. There are 3 anchors
- // and the value will gravitate to 0, 150 or 300 dp
- val max = 300.dp
- val min = 0.dp
- val (minPx, maxPx) = +withDensity {
- min.toPx().value to max.toPx().value
- }
-
- AnimatedDraggable(
- dragDirection = DragDirection.Horizontal,
- startValue = minPx,
- minValue = minPx,
- maxValue = maxPx,
- // Specify an anchored behavior for the fling with anchors at max, min and center.
- flingConfig = AnchorsFlingConfig(listOf(minPx, maxPx / 2, maxPx))
- ) { dragValue ->
-
- // dragValue is the AnimatedFloat with current value in progress
- // of dragging or animating
- val draggedDp = +withDensity {
- dragValue.value.toDp()
- }
- val squareSize = 50.dp
-
- // Draw a seekbar-like widget that has a black background
- // with a red square that moves along the drag
- Container(width = max + squareSize, alignment = Alignment.CenterLeft) {
- DrawShape(RectangleShape, Color.Black)
- Padding(left = draggedDp) {
- ColoredRect(Color.Red, width = squareSize, height = squareSize)
- }
- }
- }
-}
\ No newline at end of file
diff --git a/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/DraggableSamples.kt b/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/DraggableSamples.kt
new file mode 100644
index 0000000..8706d04
--- /dev/null
+++ b/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/DraggableSamples.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2019 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.ui.foundation.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.Composable
+import androidx.compose.composer
+import androidx.compose.memo
+import androidx.compose.unaryPlus
+import androidx.ui.animation.animatedFloat
+import androidx.ui.core.dp
+import androidx.ui.core.withDensity
+import androidx.ui.foundation.ColoredRect
+import androidx.ui.foundation.animation.AnchorsFlingConfig
+import androidx.ui.foundation.animation.AnimatedFloatDragController
+import androidx.ui.foundation.gestures.DragDirection
+import androidx.ui.foundation.gestures.Draggable
+import androidx.ui.foundation.shape.DrawShape
+import androidx.ui.foundation.shape.RectangleShape
+import androidx.ui.graphics.Color
+import androidx.ui.layout.Alignment
+import androidx.ui.layout.Container
+import androidx.ui.layout.Padding
+
+@Sampled
+@Composable
+fun DraggableSample() {
+ // Composable that users can drag over 300 dp.
+ val max = 300.dp
+ val min = 0.dp
+ val (minPx, maxPx) = +withDensity {
+ min.toPx().value to max.toPx().value
+ }
+ Draggable(DragDirection.Horizontal, minPx, maxPx) { dragValue ->
+ // dragValue is the current value in progress of dragging
+ val draggedDp = +withDensity {
+ dragValue.toDp()
+ }
+ val squareSize = 50.dp
+
+ // Draw a seekbar-like widget that has a black background
+ // with a red square that moves along the drag
+ Container(width = max + squareSize, alignment = Alignment.CenterLeft) {
+ DrawShape(RectangleShape, Color.Black)
+ Padding(left = draggedDp) {
+ ColoredRect(Color.Red, width = squareSize, height = squareSize)
+ }
+ }
+ }
+}
+
+@Sampled
+@Composable
+fun AnchoredDraggableSample() {
+ // Composable that users can drag over 300 dp. There are 3 anchors
+ // and the value will gravitate to 0, 150 or 300 dp
+ val max = 300.dp
+ val min = 0.dp
+ val (minPx, maxPx) = +withDensity {
+ min.toPx().value to max.toPx().value
+ }
+ // define anchors and related animation controller
+ val anchors = listOf(minPx, maxPx, maxPx / 2)
+ val flingConfig = +memo { AnchorsFlingConfig(anchors) }
+ val dragController = +memo { AnimatedFloatDragController(minPx, flingConfig) }
+
+ Draggable(
+ dragDirection = DragDirection.Horizontal,
+ valueController = dragController,
+ minValue = minPx,
+ maxValue = maxPx
+ ) { dragValue ->
+ // dragValue is the current value in progress
+ // of dragging or animation
+ val draggedDp = +withDensity {
+ dragValue.toDp()
+ }
+ val squareSize = 50.dp
+ // Draw a seekbar-like widget that has a black background
+ // with a red square that moves along the drag
+ Container(width = max + squareSize, alignment = Alignment.CenterLeft) {
+ DrawShape(RectangleShape, Color.Black)
+ Padding(left = draggedDp) {
+ ColoredRect(Color.Red, width = squareSize, height = squareSize)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/Clickable.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/Clickable.kt
index 1730ce1..bee68c7 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/Clickable.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/Clickable.kt
@@ -40,7 +40,7 @@
fun Clickable(
onClick: (() -> Unit)? = null,
consumeDownOnStart: Boolean = false,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
Semantics(
button = true,
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/DeterminateProgressIndicator.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/DeterminateProgressIndicator.kt
index 1182bfe..a40b834 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/DeterminateProgressIndicator.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/DeterminateProgressIndicator.kt
@@ -36,7 +36,7 @@
@Composable
fun DeterminateProgressIndicator(
@FloatRange(from = 0.0, to = 1.0) progress: Float,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
if (progress !in 0f..1f) {
throw IllegalArgumentException("Progress must be between 0.0 and 1.0")
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/Dialog.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/Dialog.kt
index 8ccc5b6..b6ad191 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/Dialog.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/Dialog.kt
@@ -47,7 +47,7 @@
* @param children The content to be displayed inside the dialog.
*/
@Composable
-fun Dialog(onCloseRequest: () -> Unit, @Children children: @Composable() () -> Unit) {
+fun Dialog(onCloseRequest: () -> Unit, children: @Composable() () -> Unit) {
val context = +ambient(ContextAmbient)
val dialog = +memo { DialogWrapper(context, onCloseRequest) }
@@ -72,7 +72,7 @@
setContentView(frameLayout)
}
- fun setContent(@Children children: @Composable() () -> Unit) {
+ fun setContent(children: @Composable() () -> Unit) {
frameLayout.setContent(children)
}
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/animation/AnimatedFloatDragController.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/animation/AnimatedFloatDragController.kt
new file mode 100644
index 0000000..c47f27e
--- /dev/null
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/animation/AnimatedFloatDragController.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2019 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.ui.foundation.animation
+
+import androidx.animation.AnimatedFloat
+import androidx.animation.ValueHolder
+import androidx.compose.Model
+import androidx.ui.foundation.gestures.DragValueController
+import androidx.ui.foundation.gestures.Draggable
+import androidx.ui.lerp
+
+/**
+ * Controller that proxy all dragging events to [AnimatedFloat] and [FlingConfig]
+ *
+ * It makes it possible to have animation support for [Draggable] composable
+ * as well as to have fling animation after drag has ended, which is defined by [FlingConfig]
+ *
+ * @param initialValue initial value for AnimatedFloat to set it up
+ * @param flingConfig sets behavior of the fling after drag has ended.
+ * Default is null, which means no fling will occur no matter the velocity
+ */
+class AnimatedFloatDragController(
+ initialValue: Float,
+ private val flingConfig: FlingConfig? = null
+) : DragValueController {
+
+ val animatedFloat = AnimatedFloat(AnimValueHolder(initialValue, ::lerp))
+
+ override val currentValue
+ get() = animatedFloat.value
+
+ override fun setBounds(min: Float, max: Float) = animatedFloat.setBounds(min, max)
+
+ override fun onDrag(target: Float) {
+ animatedFloat.snapTo(target)
+ }
+
+ override fun onDragEnd(velocity: Float, onValueSettled: (Float) -> Unit) {
+ if (flingConfig != null) {
+ val config =
+ flingConfig.copy(onAnimationFinished = { value: Float, cancelled: Boolean ->
+ if (!cancelled) onValueSettled(value)
+ flingConfig.onAnimationFinished?.invoke(value, cancelled)
+ })
+ animatedFloat.fling(config, velocity)
+ } else {
+ onValueSettled(animatedFloat.value)
+ }
+ }
+}
+
+@Model
+private class AnimValueHolder<T>(
+ override var value: T,
+ override val interpolator: (T, T, Float) -> T
+) : ValueHolder<T>
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/animation/FlingConfig.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/animation/FlingConfig.kt
new file mode 100644
index 0000000..52e3d5e
--- /dev/null
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/animation/FlingConfig.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2019 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.ui.foundation.animation
+
+import androidx.animation.AnimatedFloat
+import androidx.animation.AnimationBuilder
+import androidx.animation.DecayAnimation
+import androidx.animation.ExponentialDecay
+import androidx.animation.PhysicsBuilder
+import androidx.animation.TargetAnimation
+import androidx.animation.fling
+import kotlin.math.abs
+
+/**
+ * Class to specify fling behavior.
+ *
+ * When drag has ended, this class specifies what to do given the velocity
+ * with which drag ended and AnimatedFloat instance to perform fling on and read current value.
+ *
+ * Config that provides natural fling with customizable behaviour
+ * e.g fling friction or result target adjustment.
+ *
+ * The most common Decay animation is [ExponentialDecay].
+ *
+ * If you want to only be able to drag/animate between predefined set of values,
+ * consider using [AnchorsFlingConfig] function to generate such behaviour.
+ *
+ * @param decayAnimation the animation to control fling behaviour
+ * @param onAnimationFinished callback to be invoked when fling finishes by decay
+ * or being interrupted by gesture input.
+ * Consider second boolean param "cancelled" to know what happened.
+ * @param adjustTarget callback to be called at the start of fling
+ * so the final value for fling can be adjusted
+ */
+data class FlingConfig(
+ val decayAnimation: DecayAnimation = ExponentialDecay(),
+ val onAnimationFinished: ((finalValue: Float, cancelled: Boolean) -> Unit)? = null,
+ val adjustTarget: (Float) -> TargetAnimation? = { null }
+)
+
+/**
+ * Starts a fling animation with the specified starting velocity and fling configuration.
+ *
+ * @param config configuration that specifies fling behaviour
+ * @param startVelocity Starting velocity of the fling animation
+ */
+fun AnimatedFloat.fling(config: FlingConfig, startVelocity: Float) {
+ fling(
+ startVelocity,
+ config.decayAnimation,
+ config.adjustTarget,
+ { config.onAnimationFinished?.invoke(value, it) }
+ )
+}
+
+/**
+ * Create fling config with anchors will make sure that after drag has ended,
+ * the value will be animated to one of the points from the predefined list.
+ *
+ * It takes velocity into account, though value will be animated to the closest
+ * point in provided list considering velocity.
+ *
+ * @param animationAnchors set of anchors to animate to
+ * @param onAnimationFinished callback to be invoked when animation value reaches desired anchor
+ * or fling being interrupted by gesture input.
+ * Consider the second boolean param "cancelled" to know what happened.
+ * @param animationBuilder animation which will be used for animations
+ * @param decayAnimation decay animation to be used to calculate closest point in the anchors set
+ * considering velocity.
+ */
+fun AnchorsFlingConfig(
+ anchors: List<Float>,
+ animationBuilder: AnimationBuilder<Float> = PhysicsBuilder(),
+ onAnimationFinished: ((finalValue: Float, cancelled: Boolean) -> Unit)? = null,
+ decayAnimation: DecayAnimation = ExponentialDecay()
+): FlingConfig {
+ val adjustTarget: (Float) -> TargetAnimation? = { target ->
+ val point = anchors.minBy { abs(it - target) }
+ val adjusted = point ?: target
+ TargetAnimation(adjusted, animationBuilder)
+ }
+ return FlingConfig(decayAnimation, onAnimationFinished, adjustTarget)
+}
\ No newline at end of file
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/AnimatedDraggable.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/AnimatedDraggable.kt
deleted file mode 100644
index 39e47e1..0000000
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/AnimatedDraggable.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright 2019 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.ui.foundation.gestures
-
-import androidx.animation.AnimatedFloat
-import androidx.animation.BaseAnimatedValue
-import androidx.compose.Composable
-import androidx.compose.composer
-import androidx.compose.unaryPlus
-import androidx.ui.animation.animatedFloat
-import androidx.ui.core.PxPosition
-import androidx.ui.core.gesture.DragGestureDetector
-import androidx.ui.core.gesture.DragObserver
-import androidx.ui.core.px
-
-/**
- * Component that provides drag, fling and animation logic for one [Float] value.
- *
- * The common usecase for this component is when you need to be able to drag/scroll something
- * on the screen and also one or more of the following:
- * 1. Fling support when scrolling/dragging ends with velocity.
- * 2. Stable anchors points support for dragging value,
- * e.g. be able to drag between only predefined set of values.
- * 3. Automatic animation of draggable value, e.g emulate drag by click.
- *
- * @see [FlingConfig] to control anchors or fling intensity.
- *
- * This component provides high-level API and ownership for [AnimatedFloat]
- * and returns it as a parameter for its children.
- *
- * If you need only drag support without animations, consider using [DragGestureDetector] instead.
- *
- * If you need only animations without gesture support, consider using [animatedFloat] instead.
- *
- * @sample androidx.ui.foundation.samples.AnimatedDraggableSample
- *
- * @param dragDirection direction in which drag should be happening
- * @param startValue value to set as initial for draggable/animating value in this component
- * @param minValue lower bound for draggable/animating value in this component
- * @param maxValue upper bound for draggable/animating value in this component
- * Either [DragDirection.Vertical] or [DragDirection.Horizontal]
- * @param flingConfig sets behavior of the fling after drag has ended.
- * Default is null, which means no drag will occur no matter the velocity
- */
-@Composable
-fun AnimatedDraggable(
- dragDirection: DragDirection,
- startValue: Float,
- minValue: Float = Float.MIN_VALUE,
- maxValue: Float = Float.MAX_VALUE,
- flingConfig: FlingConfig? = null,
- children: @Composable() (BaseAnimatedValue<Float>) -> Unit
-) {
- val animFloat = (+animatedFloat(startValue)).apply {
- setBounds(minValue, maxValue)
- }
- DragGestureDetector(
- canDrag = { direction ->
- dragDirection.isDraggableInDirection(direction, minValue, animFloat.value, maxValue)
- },
- dragObserver = object : DragObserver {
-
- override fun onDrag(dragDistance: PxPosition): PxPosition {
- val projected = dragDirection.project(dragDistance)
- val newValue = (animFloat.value + projected).coerceIn(minValue, maxValue)
- val consumed = newValue - animFloat.value
- animFloat.snapTo(newValue)
- val fractionConsumed = if (projected == 0f) 0f else consumed / projected
- return PxPosition(
- dragDirection.xProjection(dragDistance.x).px * fractionConsumed,
- dragDirection.yProjection(dragDistance.y).px * fractionConsumed
- )
- }
-
- override fun onStop(velocity: PxPosition) {
- val projected = dragDirection.project(velocity)
- flingConfig?.fling(animFloat, projected)
- }
- }
- ) {
- children(animFloat)
- }
-}
-
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/DragDirection.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/DragDirection.kt
index abd6b48..4c16ec7 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/DragDirection.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/DragDirection.kt
@@ -21,7 +21,7 @@
import androidx.ui.core.PxPosition
/**
- * Draggable Direction specifies the direction in which you can drag an [AnimatedDraggable].
+ * Draggable Direction specifies the direction in which you can drag an [Draggable].
* It can be either [Horizontal] or [Vertical].
*/
sealed class DragDirection {
@@ -39,7 +39,7 @@
internal open fun project(pos: PxPosition) = xProjection(pos.x) + yProjection(pos.y)
/**
- * Horizontal direction of dragging in [AnimatedDraggable].
+ * Horizontal direction of dragging in [Draggable].
*/
object Horizontal : DragDirection() {
internal override val xProjection: (Px) -> Float = { it.value }
@@ -60,7 +60,7 @@
}
/**
- * Vertical direction of dragging in [AnimatedDraggable].
+ * Vertical direction of dragging in [Draggable].
*/
object Vertical : DragDirection() {
internal override val xProjection: (Px) -> Float = { 0f }
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/DragValueController.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/DragValueController.kt
new file mode 100644
index 0000000..fcf668f
--- /dev/null
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/DragValueController.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2019 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.ui.foundation.gestures
+
+import androidx.compose.Model
+
+/**
+ * Interface that defines behaviour of value that is dragged via [Draggable]
+ */
+interface DragValueController {
+ /**
+ * Current Float value of controlled value
+ */
+ val currentValue: Float
+
+ /**
+ * Perform drag to [target] on controlled value
+ *
+ * @param target the resulted position for controlled value
+ */
+ fun onDrag(target: Float)
+
+ /**
+ * Perform finishing activities when drag has ended with given velocity
+ *
+ * This is a good place to start fling or consume velocity in some other way
+ * if you need to do so.
+ *
+ * Callback passed to this function *must* be called after controlled value reached it's final
+ * destination, e.g. it might be an immediate drag end or the end of the fling.
+ *
+ * @param velocity the velocity value when drag has ended
+ * @param onValueSettled callback to call after fling has ended and controlled value settled
+ */
+ fun onDragEnd(velocity: Float, onValueSettled: (Float) -> Unit)
+
+ /**
+ * Set bounds for controlled value
+ *
+ * @param min lower bound for dragging
+ * @param max upper bound for dragging
+ */
+ fun setBounds(min: Float, max: Float)
+}
+
+@Model
+private class FloatValueHolder(var inner: Float)
+
+/**
+ * Simple [DragValueController] that backs up single [Float] value with no fling support
+ *
+ * @param initialValue the initial value to set for controlled Float value
+ */
+class FloatDragValueController(initialValue: Float) : DragValueController {
+ override val currentValue: Float
+ get() = value.inner
+
+ private val value = FloatValueHolder(initialValue)
+ private var minBound = Float.MIN_VALUE
+ private var maxBound = Float.MAX_VALUE
+
+ override fun onDrag(target: Float) {
+ this.value.inner = target.coerceIn(minBound, maxBound)
+ }
+
+ override fun onDragEnd(velocity: Float, onValueSettled: (Float) -> Unit) {
+ onValueSettled(currentValue)
+ }
+
+ override fun setBounds(min: Float, max: Float) {
+ val changed = minBound != min || maxBound != max
+ minBound = min
+ maxBound = max
+ if (changed) value.inner = value.inner.coerceIn(minBound, maxBound)
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/Draggable.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/Draggable.kt
new file mode 100644
index 0000000..5fbcfc9
--- /dev/null
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/Draggable.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2019 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.ui.foundation.gestures
+
+import androidx.compose.Composable
+import androidx.compose.composer
+import androidx.compose.memo
+import androidx.compose.unaryPlus
+import androidx.ui.core.PxPosition
+import androidx.ui.foundation.animation.AnimatedFloatDragController
+import androidx.ui.foundation.animation.AnchorsFlingConfig
+import androidx.ui.core.gesture.DragGestureDetector
+import androidx.ui.core.gesture.DragObserver
+import androidx.ui.core.px
+
+/**
+ * Component that provides high-level drag functionality reflected in one value
+ *
+ * The common usecase for this component is when you need to be able to drag/scroll something
+ * on the screen and represent it as one value via [DragValueController].
+ *
+ * If you need to control the whole dragging flow, consider using [DragGestureDetector] instead.
+ *
+ * @sample androidx.ui.foundation.samples.DraggableSample
+ *
+ * By using [AnimatedFloatDragController] with [AnchorsFlingConfig] you can achieve behaviour
+ * when value is gravitating to predefined set of points after drag has ended.
+ *
+ * @sample androidx.ui.foundation.samples.AnchoredDraggableSample
+ *
+ * @param dragDirection direction in which drag should be happening.
+ * Either [DragDirection.Vertical] or [DragDirection.Horizontal]
+ * @param minValue lower bound for draggable value in this component
+ * @param maxValue upper bound for draggable value in this component
+ * @param valueController controller to control value and how it will consume drag events,
+ * such as drag, fling or change of dragging bounds. The default is [FloatDragValueController],
+ * which provides simple move-as-much-as-user-drags login with no fling support.
+ * @param callback callback to react to drag events
+ */
+@Composable
+fun Draggable(
+ dragDirection: DragDirection,
+ minValue: Float = Float.MIN_VALUE,
+ maxValue: Float = Float.MAX_VALUE,
+ valueController: DragValueController = +memo(minValue) { FloatDragValueController(minValue) },
+ callback: DraggableCallback? = null,
+ children: @Composable() (Float) -> Unit
+) {
+ fun current() = valueController.currentValue
+ +memo(valueController, minValue, maxValue) {
+ valueController.setBounds(minValue, maxValue)
+ }
+ DragGestureDetector(
+ canDrag = { direction ->
+ dragDirection.isDraggableInDirection(direction, minValue, current(), maxValue)
+ },
+ dragObserver = object : DragObserver {
+
+ override fun onDrag(dragDistance: PxPosition): PxPosition {
+ callback?.notifyDrag()
+ val projected = dragDirection.project(dragDistance)
+ val newValue = (current() + projected).coerceIn(minValue, maxValue)
+ val consumed = newValue - current()
+ valueController.onDrag(newValue)
+ val fractionConsumed = if (projected == 0f) 0f else consumed / projected
+ return PxPosition(
+ dragDirection.xProjection(dragDistance.x).px * fractionConsumed,
+ dragDirection.yProjection(dragDistance.y).px * fractionConsumed
+ )
+ }
+
+ override fun onStop(velocity: PxPosition) {
+ val projected = dragDirection.project(velocity)
+ valueController.onDragEnd(projected) {
+ callback?.notifyFinished(it)
+ }
+ }
+ }
+ ) {
+ children(current())
+ }
+}
+
+
+class DraggableCallback(
+ private val onDragStarted: () -> Unit = {},
+ private val onDragSettled: (Float) -> Unit = {}
+) {
+ private var startNotified: Boolean = false
+ internal fun notifyDrag() {
+ if (!startNotified) {
+ startNotified = true
+ onDragStarted()
+ }
+ }
+
+ internal fun notifyFinished(final: Float) {
+ startNotified = false
+ onDragSettled(final)
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/FlingConfig.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/FlingConfig.kt
deleted file mode 100644
index f2b41be..0000000
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/FlingConfig.kt
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright 2019 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.ui.foundation.gestures
-
-import androidx.animation.AnimatedFloat
-import androidx.animation.AnimationBuilder
-import androidx.animation.DecayAnimation
-import androidx.animation.ExponentialDecay
-import androidx.animation.PhysicsBuilder
-import androidx.animation.TargetAnimation
-import androidx.animation.fling
-import kotlin.math.abs
-
-/**
- * Interface to specify fling behavior in [AnimatedDraggable].
- *
- * When drag has ended, this class specifies what to do given the velocity
- * with which drag ended and AnimatedFloat instance to perform fling on and read current value.
- *
- * If you need natural fling support, use [DefaultFlingConfig] or
- * [DecayFlingConfig] to control how much friction is applied to the fling
- *
- * If you want to only be able to drag/animate between predefined set of values,
- * consider using [AnchorsFlingConfig].
- *
- */
-interface FlingConfig {
- fun fling(value: AnimatedFloat, startVelocity: Float)
-}
-
-/**
- * Fling config with anchors will make sure that after drag has ended,
- * the value will be animated to one of the points from the predefined list.
- *
- * It takes velocity into account, though value will be animated to the closest
- * point in provided list considering velocity.
- *
- * @see ExponentialDecay to understand when to pass your own decayAnimation.
- *
- * @param animationAnchors set of anchors to animate to
- * @param onAnimationFinished callback to be invoked when animation value reaches desired anchor
- * or fling being interrupted by gesture input.
- * Consider the second boolean param "cancelled" to know what happened.
- * @param animationBuilder animation which will be used for animations
- * @param decayAnimation decay animation to be used to calculate closest point in the anchors set
- * considering velocity.
- */
-data class AnchorsFlingConfig(
- val animationAnchors: List<Float>,
- val onAnimationFinished: ((finishValue: Float, cancelled: Boolean) -> Unit)? = null,
- val animationBuilder: AnimationBuilder<Float> = PhysicsBuilder(),
- val decayAnimation: DecayAnimation = ExponentialDecay()
-) : FlingConfig {
-
- private val adjust: (Float) -> TargetAnimation? = { target ->
- val point = animationAnchors.minBy { abs(it - target) }
- val adjusted = point ?: target
- TargetAnimation(adjusted, animationBuilder)
- }
-
- override fun fling(
- value: AnimatedFloat,
- startVelocity: Float
- ) {
- value.fling(
- startVelocity,
- decayAnimation,
- adjust,
- onAnimationFinished?.let {
- { cancelled: Boolean -> it.invoke(value.value, cancelled) }
- }
- )
- }
-}
-
-/**
- * Config that provides natural fling with customizable decay behavior
- * e.g fling friction or velocity threshold. Natural fling config doesn't
- * specify where this fling will end.
- *
- * The most common Decay animation is [ExponentialDecay].
- *
- * @param decayAnimation the animation to control fling behaviour
- * @param onFlingFinished callback to be invoked when fling finishes by decay
- * or being interrupted by gesture input.
- * Consider second boolean param "cancelled" to know what happened.
- * @param adjustTarget callback to be called at the start of fling
- * so the final value for fling can be adjusted
- */
-data class DecayFlingConfig(
- val decayAnimation: DecayAnimation,
- val onFlingFinished: ((finishValue: Float, cancelled: Boolean) -> Unit)? = null,
- val adjustTarget: (Float) -> TargetAnimation? = { null }
-) : FlingConfig {
- override fun fling(value: AnimatedFloat, startVelocity: Float) {
- value.fling(
- startVelocity,
- decayAnimation,
- adjustTarget = adjustTarget,
- onFinished = onFlingFinished?.let {
- { cancelled: Boolean -> it.invoke(value.value, cancelled) }
- }
- )
- }
-}
-
-/**
- * Default fling config sets decay animation to [ExponentialDecay] to provide natural fling
- * and no calls no callback when fling finishes.
- */
-object DefaultFlingConfig : FlingConfig {
- override fun fling(value: AnimatedFloat, startVelocity: Float) {
- value.fling(startVelocity, ExponentialDecay())
- }
-}
\ No newline at end of file
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/selection/MutuallyExclusiveSetItem.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/selection/MutuallyExclusiveSetItem.kt
index af44074..a199158 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/selection/MutuallyExclusiveSetItem.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/selection/MutuallyExclusiveSetItem.kt
@@ -37,7 +37,7 @@
fun MutuallyExclusiveSetItem(
selected: Boolean,
onClick: () -> Unit,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
// TODO: when semantics can be merged, we should make this use Clickable internally rather
// than duplicating logic
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/selection/Toggleable.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/selection/Toggleable.kt
index 6531828..a9f6f7b 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/selection/Toggleable.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/selection/Toggleable.kt
@@ -28,7 +28,7 @@
fun Toggleable(
value: ToggleableState = ToggleableState.Checked,
onToggle: (() -> Unit)? = null,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val actions = if (onToggle != null) {
listOf(SemanticsAction(SemanticsActionType.Tap, onToggle))
diff --git a/ui/ui-framework/api/1.0.0-alpha01.txt b/ui/ui-framework/api/1.0.0-alpha01.txt
index c7820fa..0a535b2 100644
--- a/ui/ui-framework/api/1.0.0-alpha01.txt
+++ b/ui/ui-framework/api/1.0.0-alpha01.txt
@@ -32,11 +32,6 @@
method public androidx.ui.text.TextStyle? getTextStyle();
}
- public final class InputFieldKt {
- ctor public InputFieldKt();
- method public static void InputField(androidx.ui.input.EditorState value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorState,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
- }
-
public final class IntrinsicMeasurementsReceiver implements androidx.ui.core.DensityReceiver {
method public androidx.ui.core.Density getDensity();
method public androidx.ui.core.IntPx maxIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
@@ -151,6 +146,11 @@
method public static androidx.compose.Ambient<java.lang.String> getTestTagAmbient();
}
+ public final class TextFieldKt {
+ ctor public TextFieldKt();
+ method public static void TextField(androidx.ui.input.EditorModel value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorModel,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
+ }
+
public final class TextKt {
ctor public TextKt();
method public static void CurrentTextStyleProvider(androidx.ui.text.TextStyle value, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -449,28 +449,91 @@
method public void applyBrush(androidx.ui.painting.Paint p);
}
+ public final class VectorAsset {
+ method public androidx.ui.core.Px getDefaultHeight();
+ method public androidx.ui.core.Px getDefaultWidth();
+ method public String getName();
+ method public androidx.ui.core.vectorgraphics.VectorGroup getRoot();
+ method public float getViewportHeight();
+ method public float getViewportWidth();
+ }
+
+ public final class VectorAssetBuilder {
+ ctor public VectorAssetBuilder(String name, androidx.ui.core.Px defaultWidth, androidx.ui.core.Px defaultHeight, float viewportWidth, float viewportHeight);
+ method public androidx.ui.core.vectorgraphics.VectorAssetBuilder addPath(Object? pathData, String name = "", Object fill = EmptyBrush, float fillAlpha = 1.0f, Object stroke = EmptyBrush, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.ui.painting.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f);
+ method public androidx.ui.core.vectorgraphics.VectorAsset build();
+ method public void ensureNotConsumed();
+ method public androidx.ui.core.Px getDefaultHeight();
+ method public androidx.ui.core.Px getDefaultWidth();
+ method public String getName();
+ method public float getViewportHeight();
+ method public float getViewportWidth();
+ method public androidx.ui.core.vectorgraphics.VectorAssetBuilder popGroup();
+ method public androidx.ui.core.vectorgraphics.VectorAssetBuilder pushGroup(String name = "", float rotate = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translationX = 0.0f, float translationY = 0.0f, Object? clipPathData = EmptyPath);
+ }
+
+ public final class VectorAssetKt {
+ ctor public VectorAssetKt();
+ method public static void DrawVector(androidx.ui.core.vectorgraphics.VectorAsset vectorImage);
+ }
+
+ public final class VectorGroup extends androidx.ui.core.vectorgraphics.VectorNode implements java.lang.Iterable<androidx.ui.core.vectorgraphics.VectorNode> kotlin.jvm.internal.markers.KMappedMarker {
+ ctor public VectorGroup(String name, float rotation, float pivotX, float pivotY, float scaleX, float scaleY, float translationX, float translationY, Object? clipPathData);
+ ctor public VectorGroup();
+ method public operator androidx.ui.core.vectorgraphics.VectorNode get(int index);
+ method public Object? getClipPathData();
+ method public String getName();
+ method public float getPivotX();
+ method public float getPivotY();
+ method public float getRotation();
+ method public float getScaleX();
+ method public float getScaleY();
+ method public int getSize();
+ method public float getTranslationX();
+ method public float getTranslationY();
+ method public java.util.Iterator<androidx.ui.core.vectorgraphics.VectorNode> iterator();
+ property public final int size;
+ }
+
public final class VectorKt {
ctor public VectorKt();
+ method public static void Group(String name = "", float rotation = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translationX = 0.0f, float translationY = 0.0f, Object? clipPathData = EmptyPath, kotlin.jvm.functions.Function0<kotlin.Unit> childNodes);
+ method public static void Path(Object? pathData, String name = "", Object fill = EmptyBrush, float fillAlpha = 1.0f, Object stroke = EmptyBrush, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.ui.painting.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f);
+ method public static void Vector(String name = "", float viewportWidth, float viewportHeight, androidx.ui.core.Px defaultWidth = Px(viewportWidth), androidx.ui.core.Px defaultHeight = Px(viewportHeight), kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static androidx.ui.core.vectorgraphics.PathNode![] addPathNodes(String? pathStr);
method public static android.view.View? adoptVectorGraphic(Object? parent, Object? child);
method public static androidx.ui.painting.StrokeCap getDefaultStrokeLineCap();
method public static androidx.ui.painting.StrokeJoin getDefaultStrokeLineJoin();
method public static androidx.ui.core.vectorgraphics.PathNode![] getEmptyPath();
- method public static void group(String name = "", float rotate = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translateX = 0.0f, float translateY = 0.0f, Object? clipPathData = EmptyPath, kotlin.jvm.functions.Function0<kotlin.Unit> childNodes);
- method public static void path(Object? pathData, String name = "", Object fill = EmptyBrush, float fillAlpha = 1.0f, Object stroke = EmptyBrush, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.ui.painting.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f);
- method public static void vector(String name = "", float viewportWidth, float viewportHeight, float defaultWidth = viewportWidth, float defaultHeight = viewportHeight, kotlin.jvm.functions.Function0<kotlin.Unit> children);
field public static final float DefaultAlpha = 1.0f;
field public static final String DefaultGroupName = "";
field public static final String DefaultPathName = "";
field public static final float DefaultPivotX = 0.0f;
field public static final float DefaultPivotY = 0.0f;
- field public static final float DefaultRotate = 0.0f;
+ field public static final float DefaultRotation = 0.0f;
field public static final float DefaultScaleX = 1.0f;
field public static final float DefaultScaleY = 1.0f;
field public static final float DefaultStrokeLineMiter = 4.0f;
field public static final float DefaultStrokeLineWidth = 0.0f;
- field public static final float DefaultTranslateX = 0.0f;
- field public static final float DefaultTranslateY = 0.0f;
+ field public static final float DefaultTranslationX = 0.0f;
+ field public static final float DefaultTranslationY = 0.0f;
+ }
+
+ public abstract sealed class VectorNode {
+ }
+
+ public final class VectorPath extends androidx.ui.core.vectorgraphics.VectorNode {
+ ctor public VectorPath(String name, Object? pathData, Object fill, float fillAlpha, Object stroke, float strokeAlpha, float strokeLineWidth, androidx.ui.painting.StrokeCap strokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin, float strokeLineMiter);
+ method public Object getFill();
+ method public float getFillAlpha();
+ method public String getName();
+ method public Object? getPathData();
+ method public Object getStroke();
+ method public float getStrokeAlpha();
+ method public androidx.ui.painting.StrokeCap getStrokeLineCap();
+ method public androidx.ui.painting.StrokeJoin getStrokeLineJoin();
+ method public float getStrokeLineMiter();
+ method public float getStrokeLineWidth();
}
}
@@ -479,7 +542,12 @@
public final class VectorResourceKt {
ctor public VectorResourceKt();
- method public static void vectorResource(android.content.res.Resources res, int resId);
+ method public static void VectorResource(int resId);
+ method public static androidx.ui.core.vectorgraphics.VectorAsset loadVectorResource(android.content.res.Resources.Theme? theme = null, android.content.res.Resources res, int resId) throws org.xmlpull.v1.XmlPullParserException;
+ }
+
+ public final class XmlVectorParserKt {
+ ctor public XmlVectorParserKt();
}
}
diff --git a/ui/ui-framework/api/current.txt b/ui/ui-framework/api/current.txt
index c7820fa..0a535b2 100644
--- a/ui/ui-framework/api/current.txt
+++ b/ui/ui-framework/api/current.txt
@@ -32,11 +32,6 @@
method public androidx.ui.text.TextStyle? getTextStyle();
}
- public final class InputFieldKt {
- ctor public InputFieldKt();
- method public static void InputField(androidx.ui.input.EditorState value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorState,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
- }
-
public final class IntrinsicMeasurementsReceiver implements androidx.ui.core.DensityReceiver {
method public androidx.ui.core.Density getDensity();
method public androidx.ui.core.IntPx maxIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
@@ -151,6 +146,11 @@
method public static androidx.compose.Ambient<java.lang.String> getTestTagAmbient();
}
+ public final class TextFieldKt {
+ ctor public TextFieldKt();
+ method public static void TextField(androidx.ui.input.EditorModel value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorModel,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
+ }
+
public final class TextKt {
ctor public TextKt();
method public static void CurrentTextStyleProvider(androidx.ui.text.TextStyle value, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -449,28 +449,91 @@
method public void applyBrush(androidx.ui.painting.Paint p);
}
+ public final class VectorAsset {
+ method public androidx.ui.core.Px getDefaultHeight();
+ method public androidx.ui.core.Px getDefaultWidth();
+ method public String getName();
+ method public androidx.ui.core.vectorgraphics.VectorGroup getRoot();
+ method public float getViewportHeight();
+ method public float getViewportWidth();
+ }
+
+ public final class VectorAssetBuilder {
+ ctor public VectorAssetBuilder(String name, androidx.ui.core.Px defaultWidth, androidx.ui.core.Px defaultHeight, float viewportWidth, float viewportHeight);
+ method public androidx.ui.core.vectorgraphics.VectorAssetBuilder addPath(Object? pathData, String name = "", Object fill = EmptyBrush, float fillAlpha = 1.0f, Object stroke = EmptyBrush, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.ui.painting.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f);
+ method public androidx.ui.core.vectorgraphics.VectorAsset build();
+ method public void ensureNotConsumed();
+ method public androidx.ui.core.Px getDefaultHeight();
+ method public androidx.ui.core.Px getDefaultWidth();
+ method public String getName();
+ method public float getViewportHeight();
+ method public float getViewportWidth();
+ method public androidx.ui.core.vectorgraphics.VectorAssetBuilder popGroup();
+ method public androidx.ui.core.vectorgraphics.VectorAssetBuilder pushGroup(String name = "", float rotate = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translationX = 0.0f, float translationY = 0.0f, Object? clipPathData = EmptyPath);
+ }
+
+ public final class VectorAssetKt {
+ ctor public VectorAssetKt();
+ method public static void DrawVector(androidx.ui.core.vectorgraphics.VectorAsset vectorImage);
+ }
+
+ public final class VectorGroup extends androidx.ui.core.vectorgraphics.VectorNode implements java.lang.Iterable<androidx.ui.core.vectorgraphics.VectorNode> kotlin.jvm.internal.markers.KMappedMarker {
+ ctor public VectorGroup(String name, float rotation, float pivotX, float pivotY, float scaleX, float scaleY, float translationX, float translationY, Object? clipPathData);
+ ctor public VectorGroup();
+ method public operator androidx.ui.core.vectorgraphics.VectorNode get(int index);
+ method public Object? getClipPathData();
+ method public String getName();
+ method public float getPivotX();
+ method public float getPivotY();
+ method public float getRotation();
+ method public float getScaleX();
+ method public float getScaleY();
+ method public int getSize();
+ method public float getTranslationX();
+ method public float getTranslationY();
+ method public java.util.Iterator<androidx.ui.core.vectorgraphics.VectorNode> iterator();
+ property public final int size;
+ }
+
public final class VectorKt {
ctor public VectorKt();
+ method public static void Group(String name = "", float rotation = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translationX = 0.0f, float translationY = 0.0f, Object? clipPathData = EmptyPath, kotlin.jvm.functions.Function0<kotlin.Unit> childNodes);
+ method public static void Path(Object? pathData, String name = "", Object fill = EmptyBrush, float fillAlpha = 1.0f, Object stroke = EmptyBrush, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.ui.painting.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f);
+ method public static void Vector(String name = "", float viewportWidth, float viewportHeight, androidx.ui.core.Px defaultWidth = Px(viewportWidth), androidx.ui.core.Px defaultHeight = Px(viewportHeight), kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static androidx.ui.core.vectorgraphics.PathNode![] addPathNodes(String? pathStr);
method public static android.view.View? adoptVectorGraphic(Object? parent, Object? child);
method public static androidx.ui.painting.StrokeCap getDefaultStrokeLineCap();
method public static androidx.ui.painting.StrokeJoin getDefaultStrokeLineJoin();
method public static androidx.ui.core.vectorgraphics.PathNode![] getEmptyPath();
- method public static void group(String name = "", float rotate = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translateX = 0.0f, float translateY = 0.0f, Object? clipPathData = EmptyPath, kotlin.jvm.functions.Function0<kotlin.Unit> childNodes);
- method public static void path(Object? pathData, String name = "", Object fill = EmptyBrush, float fillAlpha = 1.0f, Object stroke = EmptyBrush, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.ui.painting.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f);
- method public static void vector(String name = "", float viewportWidth, float viewportHeight, float defaultWidth = viewportWidth, float defaultHeight = viewportHeight, kotlin.jvm.functions.Function0<kotlin.Unit> children);
field public static final float DefaultAlpha = 1.0f;
field public static final String DefaultGroupName = "";
field public static final String DefaultPathName = "";
field public static final float DefaultPivotX = 0.0f;
field public static final float DefaultPivotY = 0.0f;
- field public static final float DefaultRotate = 0.0f;
+ field public static final float DefaultRotation = 0.0f;
field public static final float DefaultScaleX = 1.0f;
field public static final float DefaultScaleY = 1.0f;
field public static final float DefaultStrokeLineMiter = 4.0f;
field public static final float DefaultStrokeLineWidth = 0.0f;
- field public static final float DefaultTranslateX = 0.0f;
- field public static final float DefaultTranslateY = 0.0f;
+ field public static final float DefaultTranslationX = 0.0f;
+ field public static final float DefaultTranslationY = 0.0f;
+ }
+
+ public abstract sealed class VectorNode {
+ }
+
+ public final class VectorPath extends androidx.ui.core.vectorgraphics.VectorNode {
+ ctor public VectorPath(String name, Object? pathData, Object fill, float fillAlpha, Object stroke, float strokeAlpha, float strokeLineWidth, androidx.ui.painting.StrokeCap strokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin, float strokeLineMiter);
+ method public Object getFill();
+ method public float getFillAlpha();
+ method public String getName();
+ method public Object? getPathData();
+ method public Object getStroke();
+ method public float getStrokeAlpha();
+ method public androidx.ui.painting.StrokeCap getStrokeLineCap();
+ method public androidx.ui.painting.StrokeJoin getStrokeLineJoin();
+ method public float getStrokeLineMiter();
+ method public float getStrokeLineWidth();
}
}
@@ -479,7 +542,12 @@
public final class VectorResourceKt {
ctor public VectorResourceKt();
- method public static void vectorResource(android.content.res.Resources res, int resId);
+ method public static void VectorResource(int resId);
+ method public static androidx.ui.core.vectorgraphics.VectorAsset loadVectorResource(android.content.res.Resources.Theme? theme = null, android.content.res.Resources res, int resId) throws org.xmlpull.v1.XmlPullParserException;
+ }
+
+ public final class XmlVectorParserKt {
+ ctor public XmlVectorParserKt();
}
}
diff --git a/ui/ui-framework/api/restricted_1.0.0-alpha01.txt b/ui/ui-framework/api/restricted_1.0.0-alpha01.txt
index c7820fa..0a535b2 100644
--- a/ui/ui-framework/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-framework/api/restricted_1.0.0-alpha01.txt
@@ -32,11 +32,6 @@
method public androidx.ui.text.TextStyle? getTextStyle();
}
- public final class InputFieldKt {
- ctor public InputFieldKt();
- method public static void InputField(androidx.ui.input.EditorState value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorState,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
- }
-
public final class IntrinsicMeasurementsReceiver implements androidx.ui.core.DensityReceiver {
method public androidx.ui.core.Density getDensity();
method public androidx.ui.core.IntPx maxIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
@@ -151,6 +146,11 @@
method public static androidx.compose.Ambient<java.lang.String> getTestTagAmbient();
}
+ public final class TextFieldKt {
+ ctor public TextFieldKt();
+ method public static void TextField(androidx.ui.input.EditorModel value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorModel,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
+ }
+
public final class TextKt {
ctor public TextKt();
method public static void CurrentTextStyleProvider(androidx.ui.text.TextStyle value, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -449,28 +449,91 @@
method public void applyBrush(androidx.ui.painting.Paint p);
}
+ public final class VectorAsset {
+ method public androidx.ui.core.Px getDefaultHeight();
+ method public androidx.ui.core.Px getDefaultWidth();
+ method public String getName();
+ method public androidx.ui.core.vectorgraphics.VectorGroup getRoot();
+ method public float getViewportHeight();
+ method public float getViewportWidth();
+ }
+
+ public final class VectorAssetBuilder {
+ ctor public VectorAssetBuilder(String name, androidx.ui.core.Px defaultWidth, androidx.ui.core.Px defaultHeight, float viewportWidth, float viewportHeight);
+ method public androidx.ui.core.vectorgraphics.VectorAssetBuilder addPath(Object? pathData, String name = "", Object fill = EmptyBrush, float fillAlpha = 1.0f, Object stroke = EmptyBrush, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.ui.painting.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f);
+ method public androidx.ui.core.vectorgraphics.VectorAsset build();
+ method public void ensureNotConsumed();
+ method public androidx.ui.core.Px getDefaultHeight();
+ method public androidx.ui.core.Px getDefaultWidth();
+ method public String getName();
+ method public float getViewportHeight();
+ method public float getViewportWidth();
+ method public androidx.ui.core.vectorgraphics.VectorAssetBuilder popGroup();
+ method public androidx.ui.core.vectorgraphics.VectorAssetBuilder pushGroup(String name = "", float rotate = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translationX = 0.0f, float translationY = 0.0f, Object? clipPathData = EmptyPath);
+ }
+
+ public final class VectorAssetKt {
+ ctor public VectorAssetKt();
+ method public static void DrawVector(androidx.ui.core.vectorgraphics.VectorAsset vectorImage);
+ }
+
+ public final class VectorGroup extends androidx.ui.core.vectorgraphics.VectorNode implements java.lang.Iterable<androidx.ui.core.vectorgraphics.VectorNode> kotlin.jvm.internal.markers.KMappedMarker {
+ ctor public VectorGroup(String name, float rotation, float pivotX, float pivotY, float scaleX, float scaleY, float translationX, float translationY, Object? clipPathData);
+ ctor public VectorGroup();
+ method public operator androidx.ui.core.vectorgraphics.VectorNode get(int index);
+ method public Object? getClipPathData();
+ method public String getName();
+ method public float getPivotX();
+ method public float getPivotY();
+ method public float getRotation();
+ method public float getScaleX();
+ method public float getScaleY();
+ method public int getSize();
+ method public float getTranslationX();
+ method public float getTranslationY();
+ method public java.util.Iterator<androidx.ui.core.vectorgraphics.VectorNode> iterator();
+ property public final int size;
+ }
+
public final class VectorKt {
ctor public VectorKt();
+ method public static void Group(String name = "", float rotation = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translationX = 0.0f, float translationY = 0.0f, Object? clipPathData = EmptyPath, kotlin.jvm.functions.Function0<kotlin.Unit> childNodes);
+ method public static void Path(Object? pathData, String name = "", Object fill = EmptyBrush, float fillAlpha = 1.0f, Object stroke = EmptyBrush, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.ui.painting.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f);
+ method public static void Vector(String name = "", float viewportWidth, float viewportHeight, androidx.ui.core.Px defaultWidth = Px(viewportWidth), androidx.ui.core.Px defaultHeight = Px(viewportHeight), kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static androidx.ui.core.vectorgraphics.PathNode![] addPathNodes(String? pathStr);
method public static android.view.View? adoptVectorGraphic(Object? parent, Object? child);
method public static androidx.ui.painting.StrokeCap getDefaultStrokeLineCap();
method public static androidx.ui.painting.StrokeJoin getDefaultStrokeLineJoin();
method public static androidx.ui.core.vectorgraphics.PathNode![] getEmptyPath();
- method public static void group(String name = "", float rotate = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translateX = 0.0f, float translateY = 0.0f, Object? clipPathData = EmptyPath, kotlin.jvm.functions.Function0<kotlin.Unit> childNodes);
- method public static void path(Object? pathData, String name = "", Object fill = EmptyBrush, float fillAlpha = 1.0f, Object stroke = EmptyBrush, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.ui.painting.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f);
- method public static void vector(String name = "", float viewportWidth, float viewportHeight, float defaultWidth = viewportWidth, float defaultHeight = viewportHeight, kotlin.jvm.functions.Function0<kotlin.Unit> children);
field public static final float DefaultAlpha = 1.0f;
field public static final String DefaultGroupName = "";
field public static final String DefaultPathName = "";
field public static final float DefaultPivotX = 0.0f;
field public static final float DefaultPivotY = 0.0f;
- field public static final float DefaultRotate = 0.0f;
+ field public static final float DefaultRotation = 0.0f;
field public static final float DefaultScaleX = 1.0f;
field public static final float DefaultScaleY = 1.0f;
field public static final float DefaultStrokeLineMiter = 4.0f;
field public static final float DefaultStrokeLineWidth = 0.0f;
- field public static final float DefaultTranslateX = 0.0f;
- field public static final float DefaultTranslateY = 0.0f;
+ field public static final float DefaultTranslationX = 0.0f;
+ field public static final float DefaultTranslationY = 0.0f;
+ }
+
+ public abstract sealed class VectorNode {
+ }
+
+ public final class VectorPath extends androidx.ui.core.vectorgraphics.VectorNode {
+ ctor public VectorPath(String name, Object? pathData, Object fill, float fillAlpha, Object stroke, float strokeAlpha, float strokeLineWidth, androidx.ui.painting.StrokeCap strokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin, float strokeLineMiter);
+ method public Object getFill();
+ method public float getFillAlpha();
+ method public String getName();
+ method public Object? getPathData();
+ method public Object getStroke();
+ method public float getStrokeAlpha();
+ method public androidx.ui.painting.StrokeCap getStrokeLineCap();
+ method public androidx.ui.painting.StrokeJoin getStrokeLineJoin();
+ method public float getStrokeLineMiter();
+ method public float getStrokeLineWidth();
}
}
@@ -479,7 +542,12 @@
public final class VectorResourceKt {
ctor public VectorResourceKt();
- method public static void vectorResource(android.content.res.Resources res, int resId);
+ method public static void VectorResource(int resId);
+ method public static androidx.ui.core.vectorgraphics.VectorAsset loadVectorResource(android.content.res.Resources.Theme? theme = null, android.content.res.Resources res, int resId) throws org.xmlpull.v1.XmlPullParserException;
+ }
+
+ public final class XmlVectorParserKt {
+ ctor public XmlVectorParserKt();
}
}
diff --git a/ui/ui-framework/api/restricted_current.txt b/ui/ui-framework/api/restricted_current.txt
index c7820fa..0a535b2 100644
--- a/ui/ui-framework/api/restricted_current.txt
+++ b/ui/ui-framework/api/restricted_current.txt
@@ -32,11 +32,6 @@
method public androidx.ui.text.TextStyle? getTextStyle();
}
- public final class InputFieldKt {
- ctor public InputFieldKt();
- method public static void InputField(androidx.ui.input.EditorState value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorState,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
- }
-
public final class IntrinsicMeasurementsReceiver implements androidx.ui.core.DensityReceiver {
method public androidx.ui.core.Density getDensity();
method public androidx.ui.core.IntPx maxIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
@@ -151,6 +146,11 @@
method public static androidx.compose.Ambient<java.lang.String> getTestTagAmbient();
}
+ public final class TextFieldKt {
+ ctor public TextFieldKt();
+ method public static void TextField(androidx.ui.input.EditorModel value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorModel,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
+ }
+
public final class TextKt {
ctor public TextKt();
method public static void CurrentTextStyleProvider(androidx.ui.text.TextStyle value, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -449,28 +449,91 @@
method public void applyBrush(androidx.ui.painting.Paint p);
}
+ public final class VectorAsset {
+ method public androidx.ui.core.Px getDefaultHeight();
+ method public androidx.ui.core.Px getDefaultWidth();
+ method public String getName();
+ method public androidx.ui.core.vectorgraphics.VectorGroup getRoot();
+ method public float getViewportHeight();
+ method public float getViewportWidth();
+ }
+
+ public final class VectorAssetBuilder {
+ ctor public VectorAssetBuilder(String name, androidx.ui.core.Px defaultWidth, androidx.ui.core.Px defaultHeight, float viewportWidth, float viewportHeight);
+ method public androidx.ui.core.vectorgraphics.VectorAssetBuilder addPath(Object? pathData, String name = "", Object fill = EmptyBrush, float fillAlpha = 1.0f, Object stroke = EmptyBrush, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.ui.painting.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f);
+ method public androidx.ui.core.vectorgraphics.VectorAsset build();
+ method public void ensureNotConsumed();
+ method public androidx.ui.core.Px getDefaultHeight();
+ method public androidx.ui.core.Px getDefaultWidth();
+ method public String getName();
+ method public float getViewportHeight();
+ method public float getViewportWidth();
+ method public androidx.ui.core.vectorgraphics.VectorAssetBuilder popGroup();
+ method public androidx.ui.core.vectorgraphics.VectorAssetBuilder pushGroup(String name = "", float rotate = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translationX = 0.0f, float translationY = 0.0f, Object? clipPathData = EmptyPath);
+ }
+
+ public final class VectorAssetKt {
+ ctor public VectorAssetKt();
+ method public static void DrawVector(androidx.ui.core.vectorgraphics.VectorAsset vectorImage);
+ }
+
+ public final class VectorGroup extends androidx.ui.core.vectorgraphics.VectorNode implements java.lang.Iterable<androidx.ui.core.vectorgraphics.VectorNode> kotlin.jvm.internal.markers.KMappedMarker {
+ ctor public VectorGroup(String name, float rotation, float pivotX, float pivotY, float scaleX, float scaleY, float translationX, float translationY, Object? clipPathData);
+ ctor public VectorGroup();
+ method public operator androidx.ui.core.vectorgraphics.VectorNode get(int index);
+ method public Object? getClipPathData();
+ method public String getName();
+ method public float getPivotX();
+ method public float getPivotY();
+ method public float getRotation();
+ method public float getScaleX();
+ method public float getScaleY();
+ method public int getSize();
+ method public float getTranslationX();
+ method public float getTranslationY();
+ method public java.util.Iterator<androidx.ui.core.vectorgraphics.VectorNode> iterator();
+ property public final int size;
+ }
+
public final class VectorKt {
ctor public VectorKt();
+ method public static void Group(String name = "", float rotation = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translationX = 0.0f, float translationY = 0.0f, Object? clipPathData = EmptyPath, kotlin.jvm.functions.Function0<kotlin.Unit> childNodes);
+ method public static void Path(Object? pathData, String name = "", Object fill = EmptyBrush, float fillAlpha = 1.0f, Object stroke = EmptyBrush, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.ui.painting.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f);
+ method public static void Vector(String name = "", float viewportWidth, float viewportHeight, androidx.ui.core.Px defaultWidth = Px(viewportWidth), androidx.ui.core.Px defaultHeight = Px(viewportHeight), kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static androidx.ui.core.vectorgraphics.PathNode![] addPathNodes(String? pathStr);
method public static android.view.View? adoptVectorGraphic(Object? parent, Object? child);
method public static androidx.ui.painting.StrokeCap getDefaultStrokeLineCap();
method public static androidx.ui.painting.StrokeJoin getDefaultStrokeLineJoin();
method public static androidx.ui.core.vectorgraphics.PathNode![] getEmptyPath();
- method public static void group(String name = "", float rotate = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translateX = 0.0f, float translateY = 0.0f, Object? clipPathData = EmptyPath, kotlin.jvm.functions.Function0<kotlin.Unit> childNodes);
- method public static void path(Object? pathData, String name = "", Object fill = EmptyBrush, float fillAlpha = 1.0f, Object stroke = EmptyBrush, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.ui.painting.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f);
- method public static void vector(String name = "", float viewportWidth, float viewportHeight, float defaultWidth = viewportWidth, float defaultHeight = viewportHeight, kotlin.jvm.functions.Function0<kotlin.Unit> children);
field public static final float DefaultAlpha = 1.0f;
field public static final String DefaultGroupName = "";
field public static final String DefaultPathName = "";
field public static final float DefaultPivotX = 0.0f;
field public static final float DefaultPivotY = 0.0f;
- field public static final float DefaultRotate = 0.0f;
+ field public static final float DefaultRotation = 0.0f;
field public static final float DefaultScaleX = 1.0f;
field public static final float DefaultScaleY = 1.0f;
field public static final float DefaultStrokeLineMiter = 4.0f;
field public static final float DefaultStrokeLineWidth = 0.0f;
- field public static final float DefaultTranslateX = 0.0f;
- field public static final float DefaultTranslateY = 0.0f;
+ field public static final float DefaultTranslationX = 0.0f;
+ field public static final float DefaultTranslationY = 0.0f;
+ }
+
+ public abstract sealed class VectorNode {
+ }
+
+ public final class VectorPath extends androidx.ui.core.vectorgraphics.VectorNode {
+ ctor public VectorPath(String name, Object? pathData, Object fill, float fillAlpha, Object stroke, float strokeAlpha, float strokeLineWidth, androidx.ui.painting.StrokeCap strokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin, float strokeLineMiter);
+ method public Object getFill();
+ method public float getFillAlpha();
+ method public String getName();
+ method public Object? getPathData();
+ method public Object getStroke();
+ method public float getStrokeAlpha();
+ method public androidx.ui.painting.StrokeCap getStrokeLineCap();
+ method public androidx.ui.painting.StrokeJoin getStrokeLineJoin();
+ method public float getStrokeLineMiter();
+ method public float getStrokeLineWidth();
}
}
@@ -479,7 +542,12 @@
public final class VectorResourceKt {
ctor public VectorResourceKt();
- method public static void vectorResource(android.content.res.Resources res, int resId);
+ method public static void VectorResource(int resId);
+ method public static androidx.ui.core.vectorgraphics.VectorAsset loadVectorResource(android.content.res.Resources.Theme? theme = null, android.content.res.Resources res, int resId) throws org.xmlpull.v1.XmlPullParserException;
+ }
+
+ public final class XmlVectorParserKt {
+ ctor public XmlVectorParserKt();
}
}
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/MultipleCollect.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/MultipleCollect.kt
index 443fcd6..2ca3de7 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/MultipleCollect.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/MultipleCollect.kt
@@ -63,7 +63,7 @@
fun HeaderFooterLayout(
header: @Composable() () -> Unit,
footer: @Composable() () -> Unit,
- @Children content: @Composable() () -> Unit
+ content: @Composable() () -> Unit
) {
@Suppress("USELESS_CAST")
Layout(
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL1.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL1.kt
index 74904e6..a24fa7b 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL1.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL1.kt
@@ -144,7 +144,7 @@
onPress: SemanticAction<PxPosition> = SemanticAction(defaultParam = PxPosition.Origin) { },
onRelease: SemanticAction<Unit> = SemanticAction(defaultParam = Unit) { },
onCancel: SemanticAction<Unit> = SemanticAction(defaultParam = Unit) { },
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
PressGestureDetector(
onPress = { onPress.action(ActionParam(ActionCaller.PointerInput, it)) },
@@ -165,7 +165,7 @@
fun Semantics(
@Suppress("UNUSED_PARAMETER") properties: Set<SemanticProperty<out Any>> = setOf(),
actions: Set<SemanticAction<out Any?>> = setOf(),
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
Column {
MaterialTheme {
@@ -275,7 +275,7 @@
* children or make them visible.
*/
@Composable
-private fun Collapsable(@Children children: @Composable() () -> Unit) {
+private fun Collapsable(children: @Composable() () -> Unit) {
val collapsedState = +state { CollapseMode.Collapsed }
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL2.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL2.kt
index a1aa21f..031632f 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL2.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL2.kt
@@ -28,7 +28,7 @@
label: String = "",
visibility: Visibility = Visibility.Undefined,
actions: Set<SemanticAction<out Any?>> = setOf(),
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val propertySet = mutableSetOf<SemanticProperty<out Any>>()
@@ -62,7 +62,7 @@
defaultParam: T,
types: Set<ActionType> = setOf(),
action: (ActionParam<T>) -> Unit,
- @Children block: @Composable() (SemanticAction<T>) -> Unit
+ block: @Composable() (SemanticAction<T>) -> Unit
) {
val semanticAction = SemanticAction<T>(phrase, defaultParam, types, action)
block.invoke(semanticAction)
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL3.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL3.kt
index 9b5eed4..565ab99 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL3.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL3.kt
@@ -29,7 +29,7 @@
@Composable
fun ClickInteraction(
click: SemanticActionBuilder<Unit>.() -> Unit,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val clickAction = SemanticActionBuilder(phrase = "Click", defaultParam = Unit)
.apply(click)
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/VectorGraphicsActivity.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/VectorGraphicsActivity.kt
index e6288ff..1153956 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/VectorGraphicsActivity.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/VectorGraphicsActivity.kt
@@ -17,38 +17,39 @@
package androidx.ui.framework.demos
import android.app.Activity
-import android.graphics.Color
import android.os.Bundle
import android.widget.LinearLayout
import androidx.ui.core.vectorgraphics.adoptVectorGraphic
-import androidx.ui.core.vectorgraphics.compat.vectorResource
-import androidx.ui.core.vectorgraphics.group
-import androidx.ui.core.vectorgraphics.path
-import androidx.ui.core.vectorgraphics.vector
+import androidx.ui.core.vectorgraphics.Group
+import androidx.ui.core.vectorgraphics.Path
+import androidx.ui.core.vectorgraphics.Vector
import androidx.ui.core.vectorgraphics.PathBuilder
import androidx.ui.core.vectorgraphics.PathDelegate
import androidx.compose.Composable
import androidx.compose.composer
import androidx.compose.registerAdapter
import androidx.compose.setViewContent
+import androidx.ui.core.ContextAmbient
+import androidx.ui.core.Px
+import androidx.ui.core.vectorgraphics.compat.VectorResource
+import androidx.ui.graphics.Color
class VectorGraphicsActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- val res = getResources()
setViewContent {
composer.registerAdapter { parent, child ->
adoptVectorGraphic(parent, child)
}
LinearLayout(orientation = LinearLayout.VERTICAL) {
- vectorResource(
- res = res,
- resId = androidx.ui.framework.demos.R.drawable.ic_crane
- )
- vectorShape()
+ // TODO Make composition of components with Android Views automatically wire the appropriate ambients
+ ContextAmbient.Provider(value = this@VectorGraphicsActivity) {
+ VectorResource(resId = androidx.ui.framework.demos.R.drawable.ic_crane)
+ vectorShape()
+ }
}
}
}
@@ -57,28 +58,28 @@
fun vectorShape() {
val viewportWidth = 300.0f
val viewportHeight = 300.0f
- vector(
+ Vector(
name = "vectorShape",
- defaultWidth = 300.0f,
- defaultHeight = 300.0f,
+ defaultWidth = Px(300.0f),
+ defaultHeight = Px(300.0f),
viewportWidth = viewportWidth,
viewportHeight = viewportHeight
) {
- group(
+ Group(
scaleX = 0.75f,
scaleY = 0.75f,
- rotate = 45.0f,
+ rotation = 45.0f,
pivotX = (viewportWidth / 2),
pivotY = (viewportHeight / 2)
) {
backgroundPath(vectorWidth = viewportWidth, vectorHeight = viewportHeight)
stripePath(vectorWidth = viewportWidth, vectorHeight = viewportHeight)
- group(
- translateX = 50.0f,
- translateY = 50.0f,
+ Group(
+ translationX = 50.0f,
+ translationY = 50.0f,
pivotX = (viewportWidth / 2),
pivotY = (viewportHeight / 2),
- rotate = 25.0f
+ rotation = 25.0f
) {
val pathData = PathDelegate {
moveTo(viewportWidth / 2 - 100, viewportHeight / 2 - 100)
@@ -87,7 +88,7 @@
horizontalLineToRelative(-200.0f)
close()
}
- path(fill = Color.MAGENTA, pathData = pathData)
+ Path(fill = Color.Magenta, pathData = pathData)
}
}
}
@@ -102,7 +103,7 @@
close()
}
- path(fill = Color.CYAN, pathData = background)
+ Path(fill = Color.Cyan, pathData = background)
}
@Composable
@@ -111,7 +112,7 @@
stripe(vectorWidth, vectorHeight, 10)
}
- path(stroke = Color.BLUE, pathData = stripeDelegate)
+ Path(stroke = Color.Blue, pathData = stripeDelegate)
}
private fun PathBuilder.stripe(vectorWidth: Float, vectorHeight: Float, numLines: Int) {
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/NestedScrollingDemo.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/NestedScrollingDemo.kt
index dc3feee..4074e44 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/NestedScrollingDemo.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/NestedScrollingDemo.kt
@@ -80,7 +80,7 @@
* A very simple ScrollView like implementation that allows for vertical scrolling.
*/
@Composable
-private fun Draggable(@Children children: @Composable() () -> Unit) {
+private fun Draggable(children: @Composable() () -> Unit) {
val offset = +state { 0.px }
val maxOffset = +state { 0.px }
@@ -215,7 +215,7 @@
* A simple composable that arranges it's children as vertical list of items.
*/
@Composable
-private fun Column(@Children children: @Composable() () -> Unit) {
+private fun Column(children: @Composable() () -> Unit) {
Layout(children) { measurables, constraints ->
var height = 0.ipx
val placeables = measurables.map {
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/SimpleComposables.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/SimpleComposables.kt
index e49a48e..dc07bc9 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/SimpleComposables.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/SimpleComposables.kt
@@ -35,7 +35,7 @@
* A simple layout composable that matches the size of it's parent layout.
*/
@Composable
-internal fun MatchParent(@Children children: @Composable() () -> Unit) {
+internal fun MatchParent(children: @Composable() () -> Unit) {
Layout({
children()
}, { _, constraints ->
@@ -44,7 +44,7 @@
}
@Composable
-internal fun Center(@Children children: @Composable() () -> Unit) {
+internal fun Center(children: @Composable() () -> Unit) {
Layout(children) { measurables, constraints ->
val placeable = measurables.first().measure(constraints)
layout(constraints.maxWidth, constraints.maxHeight) {
@@ -59,7 +59,7 @@
* A simple composable that pads items by [padding].
*/
@Composable
-private fun Padding(padding: Dp?, @Children children: @Composable() () -> Unit) {
+private fun Padding(padding: Dp?, children: @Composable() () -> Unit) {
Layout(children) { measurables, constraints ->
val paddingPx = padding?.toIntPx() ?: 0.ipx
val doublePadding = paddingPx * 2
@@ -90,7 +90,7 @@
* A simple composable that draws a border around it's children.
*/
@Composable
-private fun Border(color: Color, width: Dp, @Children children: @Composable() () -> Unit) {
+private fun Border(color: Color, width: Dp, children: @Composable() () -> Unit) {
Layout(
children = {
children()
@@ -140,7 +140,7 @@
width: Dp,
height: Dp,
padding: Dp,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val borderWidth: Dp = 2.dp
diff --git a/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/AndroidLayoutDrawTest.kt b/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/AndroidLayoutDrawTest.kt
index 2318041..3c79ab3 100644
--- a/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/AndroidLayoutDrawTest.kt
+++ b/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/AndroidLayoutDrawTest.kt
@@ -553,7 +553,7 @@
fun FixedSizeRow(
width: IntPx,
height: IntPx,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
Layout(children = children, layoutBlock = { measurables, constraints ->
val resolvedWidth = width.coerceIn(constraints.minWidth, constraints.maxWidth)
@@ -936,7 +936,7 @@
}
@Composable
-fun AtLeastSize(size: IntPx, @Children children: @Composable() () -> Unit) {
+fun AtLeastSize(size: IntPx, children: @Composable() () -> Unit) {
Layout(
layoutBlock = { measurables, constraints ->
val newConstraints = Constraints(
@@ -964,7 +964,7 @@
}
@Composable
-fun Align(@Children children: @Composable() () -> Unit) {
+fun Align(children: @Composable() () -> Unit) {
Layout(
layoutBlock = { measurables, constraints ->
val newConstraints = Constraints(
@@ -992,7 +992,7 @@
}
@Composable
-fun Padding(size: IntPx, @Children children: @Composable() () -> Unit) {
+fun Padding(size: IntPx, children: @Composable() () -> Unit) {
Layout(
layoutBlock = { measurables, constraints ->
val totalDiff = size * 2
@@ -1024,7 +1024,7 @@
fun TwoMeasureLayout(
size: IntPx,
latch: CountDownLatch,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
Layout(children = children) { measurables, _ ->
val testConstraints = Constraints()
@@ -1046,7 +1046,7 @@
}
@Composable
-fun Position(size: IntPx, offset: OffsetModel, @Children children: @Composable() () -> Unit) {
+fun Position(size: IntPx, offset: OffsetModel, children: @Composable() () -> Unit) {
Layout(children) { measurables, constraints ->
val placeables = measurables.map { m ->
m.measure(constraints)
diff --git a/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/TextLayoutTest.kt b/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/TextLayoutTest.kt
index e31200b..9f42391 100644
--- a/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/TextLayoutTest.kt
+++ b/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/TextLayoutTest.kt
@@ -163,7 +163,7 @@
assertTrue(intrinsicsLatch.await(1, TimeUnit.SECONDS))
}
- private fun show(@Children composable: @Composable() () -> Unit) {
+ private fun show(composable: @Composable() () -> Unit) {
val runnable: Runnable = object : Runnable {
override fun run() {
activity.setContent {
diff --git a/ui/ui-framework/src/androidTest/java/androidx/ui/core/vectorgraphics/compat/XmlVectorParserTest.kt b/ui/ui-framework/src/androidTest/java/androidx/ui/core/vectorgraphics/compat/XmlVectorParserTest.kt
new file mode 100644
index 0000000..6c5ff53
--- /dev/null
+++ b/ui/ui-framework/src/androidTest/java/androidx/ui/core/vectorgraphics/compat/XmlVectorParserTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2019 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.ui.core.vectorgraphics.compat
+
+import android.util.Log
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.ui.core.Px
+import androidx.ui.core.vectorgraphics.PathCommand
+import androidx.ui.core.vectorgraphics.PathNode
+import androidx.ui.core.vectorgraphics.VectorPath
+import androidx.ui.framework.test.R
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class XmlVectorParserTest {
+
+ @Test
+ fun testParseXml() {
+ val res = InstrumentationRegistry.getInstrumentation().targetContext.resources
+ val asset = loadVectorResource(null, res, R.drawable.test_compose_vector)
+ val density = res.displayMetrics.density
+ val expectedSize = Px(density*24)
+ assertEquals(expectedSize, asset.defaultWidth)
+ assertEquals(expectedSize, asset.defaultHeight)
+ assertEquals(24.0f, asset.viewportWidth)
+ assertEquals(24.0f, asset.viewportHeight)
+ assertEquals(1, asset.root.size)
+
+ val node = asset.root.iterator().next() as VectorPath
+ assertEquals(0xFFFF0000.toInt(), node.fill as Int)
+
+ @Suppress("UNCHECKED_CAST")
+ val path = node.pathData as Array<PathNode>
+ assertEquals(3, path.size)
+ assertEquals(PathCommand.MoveTo, path.get(0).command)
+ assertEquals(20.0f, path.get(0).args[0])
+ assertEquals(10.0f, path.get(0).args[1])
+
+ assertEquals(PathCommand.RelativeLineTo, path.get(1).command)
+ assertEquals(6, path.get(1).args.size)
+ assertEquals(10.0f, path.get(1).args[0])
+ assertEquals(0.0f, path.get(1).args[1])
+ assertEquals(0.0f, path.get(1).args[2])
+ assertEquals(10.0f, path.get(1).args[3])
+ assertEquals(-10.0f, path.get(1).args[4])
+ assertEquals(0.0f, path.get(1).args[5])
+
+ assertEquals(PathCommand.RelativeClose, path.get(2).command)
+ assertEquals(0, path.get(2).args.size)
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-framework/src/androidTest/res/drawable/test_compose_vector.xml b/ui/ui-framework/src/androidTest/res/drawable/test_compose_vector.xml
new file mode 100644
index 0000000..05beeb3
--- /dev/null
+++ b/ui/ui-framework/src/androidTest/res/drawable/test_compose_vector.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FFFF0000"
+ android:pathData="M20,10, l10,0 0,10 -10, 0z"/>
+</vector>
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/Clip.kt b/ui/ui-framework/src/main/java/androidx/ui/core/Clip.kt
index be6e0ff..294325e 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/Clip.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/Clip.kt
@@ -27,7 +27,7 @@
* @param shape the [Shape] used for clipping.
*/
@Composable
-fun Clip(shape: Shape, @Children children: @Composable() () -> Unit) {
+fun Clip(shape: Shape, children: @Composable() () -> Unit) {
<RepaintBoundaryNode name=null shape=shape clipToShape=true>
children()
</RepaintBoundaryNode>
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/Layout.kt b/ui/ui-framework/src/main/java/androidx/ui/core/Layout.kt
index 4686cc3..168452a 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/Layout.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/Layout.kt
@@ -516,7 +516,7 @@
* Please note that using this widget might be a performance hit, so please use with care.
*/
@Composable
-fun WithConstraints(@Children children: @Composable() (Constraints) -> Unit) {
+fun WithConstraints(children: @Composable() (Constraints) -> Unit) {
val ref = +compositionReference()
val context = +ambient(ContextAmbient)
@@ -592,7 +592,7 @@
@Composable
fun OnChildPositioned(
onPositioned: (coordinates: LayoutCoordinates) -> Unit,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
<DataNode key=OnChildPositionedKey value=onPositioned>
<children/>
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/Opacity.kt b/ui/ui-framework/src/main/java/androidx/ui/core/Opacity.kt
index 7399e1a..bc1881a 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/Opacity.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/Opacity.kt
@@ -33,7 +33,7 @@
@Composable
fun Opacity(
@FloatRange(from = 0.0, to = 1.0) opacity: Float,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
<RepaintBoundaryNode name=null opacity=opacity>
children()
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/ParentData.kt b/ui/ui-framework/src/main/java/androidx/ui/core/ParentData.kt
index 8ae7c49..081b3daf 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/ParentData.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/ParentData.kt
@@ -53,7 +53,7 @@
* expanded, which are inflexible and which are flexible.
*/
@Composable
-fun ParentData(data: Any, @Children children: @Composable() () -> Unit) {
+fun ParentData(data: Any, children: @Composable() () -> Unit) {
<DataNode key=ParentDataKey value=data>
<children/>
</DataNode>
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/PointerInputWrapper.kt b/ui/ui-framework/src/main/java/androidx/ui/core/PointerInputWrapper.kt
index ec66af4..d6e0e77 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/PointerInputWrapper.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/PointerInputWrapper.kt
@@ -24,7 +24,7 @@
@Composable
fun PointerInputWrapper(
pointerInputHandler: PointerInputHandler = { event, _ -> event },
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
// Hide the internals of PointerInputNode
<PointerInputNode pointerInputHandler>
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/RepaintBoundary.kt b/ui/ui-framework/src/main/java/androidx/ui/core/RepaintBoundary.kt
index 757e740..cbf1760 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/RepaintBoundary.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/RepaintBoundary.kt
@@ -31,7 +31,7 @@
* @param children The contained children.
*/
@Composable
-fun RepaintBoundary(name: String? = null, @Children children: @Composable() () -> Unit) {
+fun RepaintBoundary(name: String? = null, children: @Composable() () -> Unit) {
<RepaintBoundaryNode name=name>
<children/>
</RepaintBoundaryNode>
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/Semantics.kt b/ui/ui-framework/src/main/java/androidx/ui/core/Semantics.kt
index baa4ff3..dad6816 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/Semantics.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/Semantics.kt
@@ -124,7 +124,7 @@
textDirection: TextDirection? = null,
testTag: String? = null,
actions: List<SemanticsAction<*>> = emptyList(),
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val providedTestTag = +ambient(TestTagAmbient)
<SemanticsComponentNode
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/TestTagProvider.kt b/ui/ui-framework/src/main/java/androidx/ui/core/TestTagProvider.kt
index e69bf03..a75a022 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/TestTagProvider.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/TestTagProvider.kt
@@ -27,6 +27,6 @@
// Implementation with ambients now for only one semantics inside.
// replace with mergeable semantics later
@Composable
-fun TestTag(tag: String, @Children children: @Composable() () -> Unit) {
+fun TestTag(tag: String, children: @Composable() () -> Unit) {
TestTagAmbient.Provider(value = tag, children = children)
}
\ No newline at end of file
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/Text.kt b/ui/ui-framework/src/main/java/androidx/ui/core/Text.kt
index 7e1680cb..4d0fd2a 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/Text.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/Text.kt
@@ -97,7 +97,7 @@
/**
* Composable TextSpan attached after [text].
*/
- @Children child: @Composable TextSpanScope.() -> Unit
+ child: @Composable TextSpanScope.() -> Unit
) {
val rootTextSpan = +memo(text) { TextSpan(text = text) }
val ref = +compositionReference()
@@ -321,7 +321,7 @@
* styled explicitly.
*/
@Composable
-fun CurrentTextStyleProvider(value: TextStyle, @Children children: @Composable() () -> Unit) {
+fun CurrentTextStyleProvider(value: TextStyle, children: @Composable() () -> Unit) {
val style = +ambient(CurrentTextStyleAmbient)
val mergedStyle = style.merge(value)
CurrentTextStyleAmbient.Provider(value = mergedStyle) {
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/InputField.kt b/ui/ui-framework/src/main/java/androidx/ui/core/TextField.kt
similarity index 89%
rename from ui/ui-framework/src/main/java/androidx/ui/core/InputField.kt
rename to ui/ui-framework/src/main/java/androidx/ui/core/TextField.kt
index 5da2696..1af15fc50 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/InputField.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/TextField.kt
@@ -28,7 +28,7 @@
import androidx.ui.core.input.FocusManager
import androidx.ui.graphics.Color
import androidx.ui.input.EditProcessor
-import androidx.ui.input.EditorState
+import androidx.ui.input.EditorModel
import androidx.ui.input.ImeAction
import androidx.ui.input.KeyboardType
import androidx.ui.text.TextPainter
@@ -44,35 +44,35 @@
/**
* The composition background color
*
- * @see EditorState.composition
+ * @see EditorModel.composition
*/
val compositionColor: Color = Color(alpha = 0xFF, red = 0xB0, green = 0xE0, blue = 0xE6),
/**
* The selection background color
*
- * @see EditorState.selection
+ * @see EditorModel.selection
*/
// TODO(nona): share with Text.DEFAULT_SELECTION_COLOR
val selectionColor: Color = Color(alpha = 0x66, red = 0x33, green = 0xB5, blue = 0xE5)
)
/**
- * A default implementation of InputField
+ * A default implementation of TextField
*
- * To make InputField work with platoform input service, you must keep the editor state and update
+ * To make TextField work with platoform input service, you must keep the editor state and update
* in [onValueChagne] callback.
*
* Example:
- * var state = +state { EditorState() }
- * InputField(
+ * var state = +state { EditorModel() }
+ * TextField(
* value = state.value,
* onValueChange = { state.value = it })
*/
@Composable
-fun InputField(
+fun TextField(
/** Initial editor state value */
- value: EditorState,
+ value: EditorModel,
/** The editor style */
editorStyle: EditorStyle,
@@ -96,7 +96,7 @@
imeAction: ImeAction = ImeAction.Unspecified,
/** Called when the InputMethodService update the editor state */
- onValueChange: (EditorState) -> Unit = {},
+ onValueChange: (EditorModel) -> Unit = {},
/** Called when the InputMethod requested an IME action */
onImeActionPerformed: (ImeAction) -> Unit = {},
@@ -116,7 +116,7 @@
val processor = +memo { EditProcessor() }
val mergedStyle = style.merge(editorStyle.textStyle)
val (visualText, offsetMap) = +memo(value, visualTransformation) {
- InputFieldDelegate.applyVisualFilter(value, visualTransformation)
+ TextFieldDelegate.applyVisualFilter(value, visualTransformation)
}
val textPainter = +memo(visualText, mergedStyle, density, resourceLoader) {
// TODO(nona): Add parameter for text direction, softwrap, etc.
@@ -137,7 +137,7 @@
onPress = { },
onFocus = {
hasFocus.value = true
- InputFieldDelegate.onFocus(
+ TextFieldDelegate.onFocus(
textInputService,
value,
processor,
@@ -147,7 +147,7 @@
onImeActionPerformed)
coords.value?.let { coords ->
textInputService?.let { textInputService ->
- InputFieldDelegate.notifyFocusedRect(
+ TextFieldDelegate.notifyFocusedRect(
value,
textPainter,
coords,
@@ -160,14 +160,14 @@
},
onBlur = {
hasFocus.value = false
- InputFieldDelegate.onBlur(
+ TextFieldDelegate.onBlur(
textInputService,
processor,
onValueChange)
},
- onDragAt = { InputFieldDelegate.onDragAt(it) },
+ onDragAt = { TextFieldDelegate.onDragAt(it) },
onRelease = {
- InputFieldDelegate.onRelease(
+ TextFieldDelegate.onRelease(
it,
textPainter,
processor,
@@ -184,7 +184,7 @@
// TODO(nona): notify focused rect in onPreDraw equivalent callback for
// supporting multiline text.
coords.value = it
- InputFieldDelegate.notifyFocusedRect(
+ TextFieldDelegate.notifyFocusedRect(
value,
textPainter,
it,
@@ -194,7 +194,7 @@
)
}
}
- Draw { canvas, _ -> InputFieldDelegate.draw(
+ Draw { canvas, _ -> TextFieldDelegate.draw(
canvas,
value,
offsetMap,
@@ -203,7 +203,7 @@
editorStyle) }
},
layoutBlock = { _, constraints ->
- InputFieldDelegate.layout(textPainter, constraints).let {
+ TextFieldDelegate.layout(textPainter, constraints).let {
layout(it.first, it.second) {}
}
}
@@ -221,7 +221,7 @@
onRelease: (PxPosition) -> Unit,
onFocus: () -> Unit,
onBlur: () -> Unit,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val focused = +state { false }
val focusManager = +ambient(FocusManagerAmbient)
@@ -294,7 +294,7 @@
onPress: (PxPosition) -> Unit,
onDragAt: (PxPosition) -> Unit,
onRelease: (PxPosition) -> Unit,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val tracker = +state { DragEventTracker() }
PressGestureDetector(
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/InputFieldDelegate.kt b/ui/ui-framework/src/main/java/androidx/ui/core/TextFieldDelegate.kt
similarity index 94%
rename from ui/ui-framework/src/main/java/androidx/ui/core/InputFieldDelegate.kt
rename to ui/ui-framework/src/main/java/androidx/ui/core/TextFieldDelegate.kt
index 1c70f23..9d66efa 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/InputFieldDelegate.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/TextFieldDelegate.kt
@@ -20,7 +20,7 @@
import androidx.ui.engine.geometry.Rect
import androidx.ui.input.EditOperation
import androidx.ui.input.EditProcessor
-import androidx.ui.input.EditorState
+import androidx.ui.input.EditorModel
import androidx.ui.input.FinishComposingTextEditOp
import androidx.ui.input.ImeAction
import androidx.ui.input.KeyboardType
@@ -30,7 +30,7 @@
import androidx.ui.text.AnnotatedString
import androidx.ui.text.TextPainter
-internal class InputFieldDelegate {
+internal class TextFieldDelegate {
companion object {
/**
* Process text layout with given constraint.
@@ -80,7 +80,7 @@
@JvmStatic
fun draw(
canvas: Canvas,
- value: EditorState,
+ value: EditorModel,
offsetMap: OffsetMap,
textPainter: TextPainter,
hasFocus: Boolean,
@@ -117,7 +117,7 @@
*/
@JvmStatic
fun notifyFocusedRect(
- value: EditorState,
+ value: EditorModel,
textPainter: TextPainter,
layoutCoordinates: LayoutCoordinates,
textInputService: TextInputService,
@@ -158,7 +158,7 @@
internal fun onEditCommand(
ops: List<EditOperation>,
editProcessor: EditProcessor,
- onValueChange: (EditorState) -> Unit
+ onValueChange: (EditorModel) -> Unit
) {
onValueChange(editProcessor.onEditCommands(ops))
}
@@ -171,7 +171,7 @@
@JvmStatic
fun onDragAt(position: PxPosition) {
// TODO(nona): Implement this function
- Log.d("InputFieldDelegate", "onDrag: $position")
+ Log.d("TextFieldDelegate", "onDrag: $position")
}
/**
@@ -191,7 +191,7 @@
textPainter: TextPainter,
editProcessor: EditProcessor,
offsetMap: OffsetMap,
- onValueChange: (EditorState) -> Unit,
+ onValueChange: (EditorModel) -> Unit,
textInputService: TextInputService?,
hasFocus: Boolean
) {
@@ -219,15 +219,15 @@
@JvmStatic
fun onFocus(
textInputService: TextInputService?,
- value: EditorState,
+ value: EditorModel,
editProcessor: EditProcessor,
keyboardType: KeyboardType,
imeAction: ImeAction,
- onValueChange: (EditorState) -> Unit,
+ onValueChange: (EditorModel) -> Unit,
onImeActionPerformed: (ImeAction) -> Unit
) {
textInputService?.startInput(
- initState = value,
+ initModel = value,
keyboardType = keyboardType,
imeAction = imeAction,
onEditCommand = { onEditCommand(it, editProcessor, onValueChange) },
@@ -245,21 +245,21 @@
fun onBlur(
textInputService: TextInputService?,
editProcessor: EditProcessor,
- onValueChange: (EditorState) -> Unit
+ onValueChange: (EditorModel) -> Unit
) {
onEditCommand(listOf(FinishComposingTextEditOp()), editProcessor, onValueChange)
textInputService?.stopInput()
}
/**
- * Helper function of applying visual transformation method to the EditorState.
+ * Helper function of applying visual transformation method to the EditorModel.
*
* @param value An editor state
* @param visualTransformation A visual transformation
*/
@JvmStatic
fun applyVisualFilter(
- value: EditorState,
+ value: EditorModel,
visualTransformation: VisualTransformation?
): TransformedText {
val annotatedString = AnnotatedString(value.text)
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/TextSpanCompose.kt b/ui/ui-framework/src/main/java/androidx/ui/core/TextSpanCompose.kt
index 1af1687..8d8a56f 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/TextSpanCompose.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/TextSpanCompose.kt
@@ -256,7 +256,7 @@
fun TextSpanScope.Span(
text: String? = null,
style: TextStyle? = null,
- @Children child: @Composable TextSpanScope.() -> Unit
+ child: @Composable TextSpanScope.() -> Unit
) {
TextSpan(text = text, style = style) {
child()
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/Wrapper.kt b/ui/ui-framework/src/main/java/androidx/ui/core/Wrapper.kt
index 45f178a..ed51529 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/Wrapper.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/Wrapper.kt
@@ -47,7 +47,7 @@
* [Activity.setContent] or [ViewGroup.setContent] extensions.
*/
@Composable
-fun ComposeView(@Children children: @Composable() () -> Unit) {
+fun ComposeView(children: @Composable() () -> Unit) {
val rootRef = +memo { Ref<AndroidCraneView>() }
<AndroidCraneView ref=rootRef>
@@ -100,7 +100,7 @@
* @param content Composable that will be the content of the activity.
*/
fun Activity.setContent(
- @Children content: @Composable() () -> Unit
+ content: @Composable() () -> Unit
): CompositionContext? {
val craneView = window.decorView
.findViewById<ViewGroup>(android.R.id.content)
@@ -123,7 +123,7 @@
* @param content Composable that will be the content of the view.
*/
fun ViewGroup.setContent(
- @Children content: @Composable() () -> Unit
+ content: @Composable() () -> Unit
): CompositionContext? {
val craneView =
if (childCount > 0) { getChildAt(0) as? AndroidCraneView } else { removeAllViews(); null }
@@ -144,7 +144,7 @@
craneView: AndroidCraneView,
context: Context,
coroutineContext: CoroutineContext,
- @Children content: @Composable() () -> Unit
+ content: @Composable() () -> Unit
) {
// TODO(nona): Tie the focus manger lifecycle to Window, otherwise FocusManager won't work
// with nested AndroidCraneView case
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/DragGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/DragGestureDetector.kt
index 95cc8eb..0692d57 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/DragGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/DragGestureDetector.kt
@@ -100,7 +100,7 @@
fun DragGestureDetector(
canDrag: ((Direction) -> Boolean)? = null,
dragObserver: DragObserver? = null,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val recognizer = +memo { DragGestureRecognizer() }
recognizer.canDrag = canDrag
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/LongPressGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/LongPressGestureDetector.kt
index a3f894c..d269572 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/LongPressGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/LongPressGestureDetector.kt
@@ -46,7 +46,7 @@
@Composable
fun LongPressGestureDetector(
onLongPress: (PxPosition) -> Unit,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val recognizer =
+memo { LongPressGestureRecognizer(onLongPress, +ambient(CoroutineContextAmbient)) }
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressGestureDetector.kt
index 6d8beb2..e91a1e34 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressGestureDetector.kt
@@ -26,7 +26,7 @@
onPress: ((PxPosition) -> Unit)? = null,
onRelease: (() -> Unit)? = null,
onCancel: (() -> Unit)? = null,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
PressIndicatorGestureDetector(onStart = onPress, onCancel = onCancel) {
PressReleasedGestureDetector(onRelease = onRelease, consumeDownOnStart = false) {
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressIndicatorGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressIndicatorGestureDetector.kt
index 7705035..423b917 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressIndicatorGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressIndicatorGestureDetector.kt
@@ -53,7 +53,7 @@
onStart: ((PxPosition) -> Unit)? = null,
onStop: (() -> Unit)? = null,
onCancel: (() -> Unit)? = null,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val recognizer = +memo { PressIndicatorGestureRecognizer() }
recognizer.onStart = onStart
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressReleasedGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressReleasedGestureDetector.kt
index a524b3f..99a0daa 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressReleasedGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressReleasedGestureDetector.kt
@@ -52,7 +52,7 @@
fun PressReleasedGestureDetector(
onRelease: (() -> Unit)? = null,
consumeDownOnStart: Boolean = true,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val recognizer = +memo { PressReleaseGestureRecognizer() }
recognizer.onRelease = onRelease
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionContainer.kt b/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionContainer.kt
index a3bd5e5..6dfb42c 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionContainer.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionContainer.kt
@@ -44,7 +44,7 @@
onSelectionChange: (Selection?) -> Unit,
/** Selection mode. The default mode is Vertical. */
mode: SelectionMode = SelectionMode.Vertical,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val manager = +memo { SelectionManager() }
// TODO (qqd): After selection widget is fully implemented, evaluate if the following 2 items
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/Vector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/Vector.kt
index 9fd6bb1..fcf26ac 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/Vector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/Vector.kt
@@ -34,17 +34,18 @@
import androidx.compose.Composable
import androidx.compose.Emittable
import androidx.compose.composer
+import androidx.ui.core.Px
import androidx.ui.painting.withSave
import androidx.ui.painting.Path as PaintingPath
const val DefaultGroupName = ""
-const val DefaultRotate = 0.0f
+const val DefaultRotation = 0.0f
const val DefaultPivotX = 0.0f
const val DefaultPivotY = 0.0f
const val DefaultScaleX = 1.0f
const val DefaultScaleY = 1.0f
-const val DefaultTranslateX = 0.0f
-const val DefaultTranslateY = 0.0f
+const val DefaultTranslationX = 0.0f
+const val DefaultTranslationY = 0.0f
val EmptyPath = emptyArray<PathNode>()
@@ -80,50 +81,55 @@
}
@Composable
-fun vector(
+fun Vector(
name: String = "",
viewportWidth: Float,
viewportHeight: Float,
- defaultWidth: Float = viewportWidth,
- defaultHeight: Float = viewportHeight,
- @Children children: @Composable() () -> Unit
+ defaultWidth: Px = Px(viewportWidth),
+ defaultHeight: Px = Px(viewportHeight),
+ children: @Composable() () -> Unit
) {
- <Vector name defaultWidth defaultHeight viewportWidth viewportHeight>
+ <VectorComponent
+ name
+ defaultWidth = defaultWidth.value
+ defaultHeight = defaultHeight.value
+ viewportWidth
+ viewportHeight>
children()
- </Vector>
+ </VectorComponent>
}
@Composable
-fun group(
+fun Group(
name: String = DefaultGroupName,
- rotate: Float = DefaultRotate,
+ rotation: Float = DefaultRotation,
pivotX: Float = DefaultPivotX,
pivotY: Float = DefaultPivotY,
scaleX: Float = DefaultScaleX,
scaleY: Float = DefaultScaleY,
- translateX: Float = DefaultTranslateX,
- translateY: Float = DefaultTranslateY,
+ translationX: Float = DefaultTranslationX,
+ translationY: Float = DefaultTranslationY,
clipPathData: PathData = EmptyPath,
- @Children childNodes: @Composable() () -> Unit
+ childNodes: @Composable() () -> Unit
) {
val clipPathNodes = createPath(clipPathData)
- <Group
+ <GroupComponent
name
- rotate
+ rotation
pivotX
pivotY
scaleX
scaleY
- translateX
- translateY
+ translationX
+ translationY
clipPathNodes>
childNodes()
- </Group>
+ </GroupComponent>
}
@Composable
-fun path(
+fun Path(
pathData: PathData,
name: String = DefaultPathName,
fill: BrushType = EmptyBrush,
@@ -139,7 +145,7 @@
val fillBrush: Brush = obtainBrush(fill)
val strokeBrush: Brush = obtainBrush(stroke)
- <Path
+ <PathComponent
name
pathNodes
fill=fillBrush
@@ -156,7 +162,7 @@
abstract fun draw(canvas: Canvas)
}
-private class Vector(
+private class VectorComponent(
val name: String = "",
val viewportWidth: Float,
val viewportHeight: Float,
@@ -164,7 +170,7 @@
val defaultHeight: Float = viewportHeight
) : VNode(), Emittable {
- private val root = Group(this@Vector.name).apply {
+ private val root = GroupComponent(this@VectorComponent.name).apply {
pivotX = 0.0f
pivotY = 0.0f
scaleX = defaultWidth / viewportWidth
@@ -218,7 +224,7 @@
}
}
-private class Path(val name: String) : VNode(), Emittable {
+private class PathComponent(val name: String) : VNode(), Emittable {
var fill: Brush = EmptyBrush
set(value) {
@@ -383,7 +389,7 @@
}
}
-private class Group(val name: String = DefaultGroupName) : VNode(), Emittable {
+private class GroupComponent(val name: String = DefaultGroupName) : VNode(), Emittable {
private var groupMatrix: Matrix? = null
@@ -425,7 +431,7 @@
}
}
- var rotate: Float = DefaultRotate
+ var rotation: Float = DefaultRotation
set(value) {
field = value
isMatrixDirty = true
@@ -455,13 +461,13 @@
isMatrixDirty = true
}
- var translateX: Float = DefaultTranslateX
+ var translationX: Float = DefaultTranslationX
set(value) {
field = value
isMatrixDirty = true
}
- var translateY: Float = DefaultTranslateY
+ var translationY: Float = DefaultTranslationY
set(value) {
field = value
isMatrixDirty = true
@@ -482,9 +488,9 @@
reset()
postTranslate(-pivotX, -pivotY)
postScale(scaleX, scaleY)
- postRotate(rotate, 0f, 0f)
- postTranslate(translateX + pivotX,
- translateY + pivotY)
+ postRotate(rotation, 0f, 0f)
+ postTranslate(translationX + pivotX,
+ translationY + pivotY)
}
}
@@ -579,7 +585,7 @@
// Temporary glue logic to wrap a Vector asset into an ImageView
fun adoptVectorGraphic(parent: Any?, child: Any?): View? {
- return if (parent is ViewGroup && child is Vector) {
+ return if (parent is ViewGroup && child is VectorComponent) {
val imageView = ImageView(parent.context)
imageView.scaleType = ImageView.ScaleType.FIT_CENTER
imageView.setImageDrawable(VectorGraphicDrawable(child))
@@ -589,7 +595,7 @@
}
}
-private class VectorGraphicDrawable(private val vector: Vector) : Drawable() {
+private class VectorGraphicDrawable(private val vector: VectorComponent) : Drawable() {
override fun getIntrinsicWidth(): Int = Math.round(vector.defaultWidth)
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/VectorAsset.kt b/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/VectorAsset.kt
new file mode 100644
index 0000000..95f732b
--- /dev/null
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/VectorAsset.kt
@@ -0,0 +1,420 @@
+/*
+ * Copyright 2019 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.ui.core.vectorgraphics
+
+import androidx.compose.Composable
+import androidx.compose.composer
+import androidx.ui.core.Px
+import androidx.ui.painting.StrokeCap
+import androidx.ui.painting.StrokeJoin
+import java.util.Stack
+import java.util.function.Consumer
+
+/**
+ * Builder used to construct a Vector graphic tree.
+ * This is useful for caching the result of expensive operations used to construct
+ * a vector graphic for compose.
+ * For example, the vector graphic could be serialized and downloaded from a server and represented
+ * internally in a VectorAsset before it is composed through [DrawVector]
+ * The generated VectorAsset is recommended to be memoized across composition calls to avoid
+ * doing redundant work
+ **/
+class VectorAssetBuilder(
+
+ /**
+ * Name of the vector asset
+ */
+ val name: String = DefaultGroupName,
+
+ /**
+ * Intrinsic width of the Vector in pixels
+ */
+ val defaultWidth: Px,
+
+ /**
+ * Intrinsic height of the Vector in pixels
+ */
+ val defaultHeight: Px,
+
+ /**
+ * Used to define the width of the viewport space. Viewport is basically the virtual canvas
+ * where the paths are drawn on.
+ */
+ val viewportWidth: Float,
+
+ /**
+ * Used to define the height of the viewport space. Viewport is basically the virtual canvas
+ * where the paths are drawn on.
+ */
+ val viewportHeight: Float
+) {
+
+ private val nodes = Stack<VectorGroup>()
+
+ private var root = VectorGroup()
+ private var isConsumed = false
+
+ private var currentGroup: VectorGroup = root
+ private set
+ get() = nodes.peek()
+
+ init {
+ nodes.add(root)
+ }
+
+ /**
+ * Create a new group and push it to the front of the stack of VectorAsset nodes
+ * @return This VectorAssetBuilder instance as a convenience for chaining calls
+ */
+ fun pushGroup(
+ name: String = DefaultGroupName,
+ rotate: Float = DefaultRotation,
+ pivotX: Float = DefaultPivotX,
+ pivotY: Float = DefaultPivotY,
+ scaleX: Float = DefaultScaleX,
+ scaleY: Float = DefaultScaleY,
+ translationX: Float = DefaultTranslationX,
+ translationY: Float = DefaultTranslationY,
+ clipPathData: PathData = EmptyPath
+ ): VectorAssetBuilder {
+ ensureNotConsumed()
+ val group = VectorGroup(
+ name,
+ rotate,
+ pivotX,
+ pivotY,
+ scaleX,
+ scaleY,
+ translationX,
+ translationY,
+ clipPathData
+ )
+ nodes.add(group)
+ currentGroup.addNode(group)
+ return this
+ }
+
+ /**
+ * Pops the topmost VectorGroup from this VectorAssetBuilder. This is used to indicate
+ * that no additional VectorAsset nodes will be added to the current VectorGroup
+ * @return This VectorAssetBuilder instance as a convenience for chaining calls
+ */
+ fun popGroup(): VectorAssetBuilder {
+ ensureNotConsumed()
+ nodes.pop()
+ return this
+ }
+
+ /**
+ * Add a path to the VectorAsset graphic. This represents a leaf node in the VectorAsset graphics
+ * tree structure
+ * @return This VectorAssetBuilder instance as a convenience for chaining calls
+ */
+ fun addPath(
+ pathData: PathData,
+ name: String = DefaultPathName,
+ fill: BrushType = EmptyBrush,
+ fillAlpha: Float = DefaultAlpha,
+ stroke: BrushType = EmptyBrush,
+ strokeAlpha: Float = DefaultAlpha,
+ strokeLineWidth: Float = DefaultStrokeLineWidth,
+ strokeLineCap: StrokeCap = DefaultStrokeLineCap,
+ strokeLineJoin: StrokeJoin = DefaultStrokeLineJoin,
+ strokeLineMiter: Float = DefaultStrokeLineMiter
+ ): VectorAssetBuilder {
+ ensureNotConsumed()
+ currentGroup.addNode(
+ VectorPath(
+ name,
+ pathData,
+ fill,
+ fillAlpha,
+ stroke,
+ strokeAlpha,
+ strokeLineWidth,
+ strokeLineCap,
+ strokeLineJoin,
+ strokeLineMiter
+ )
+ )
+ return this
+ }
+
+ /**
+ * Construct a VectorAsset. This concludes the creation process of a VectorAsset graphic
+ * This builder cannot be re-used to create additional VectorAsset instances
+ * @return Thew newly created VectorAsset instance
+ */
+ fun build(): VectorAsset {
+ ensureNotConsumed()
+ val vectorImage = VectorAsset(
+ name,
+ defaultWidth,
+ defaultHeight,
+ viewportWidth,
+ viewportHeight,
+ root
+ )
+
+ // reset state in case this builder is used again
+ nodes.clear()
+ root = VectorGroup()
+ nodes.add(root)
+
+ isConsumed = true
+
+ return vectorImage
+ }
+
+ /**
+ * Throws IllegalStateException if the VectorAssetBuilder is already been consumed
+ */
+ fun ensureNotConsumed() {
+ if (isConsumed) {
+ throw IllegalStateException("VectorAssetBuilder is single use, create " +
+ "a new instance to create a new VectorAsset")
+ }
+ }
+}
+
+sealed class VectorNode
+
+/**
+ * Vector graphics object that is generated as a result of [VectorAssetBuilder]]
+ * It can be composed and rendered by passing it as an argument to [DrawVector]
+ */
+class VectorAsset internal constructor(
+
+ /**
+ * Name of the Vector asset
+ */
+ val name: String,
+
+ /**
+ * Intrinsic width of the vector asset in pixels
+ */
+ val defaultWidth: Px,
+
+ /**
+ * Intrinsic height of the vector asset in pixels
+ */
+ val defaultHeight: Px,
+
+ /**
+ * Used to define the width of the viewport space. Viewport is basically the virtual canvas
+ * where the paths are drawn on.
+ */
+ val viewportWidth: Float,
+
+ /**
+ * Used to define the height of the viewport space. Viewport is basically the virtual canvas
+ * where the paths are drawn on.
+ */
+ val viewportHeight: Float,
+
+ /**
+ * Root group of the vector asset that contains all the child groups and paths
+ */
+ val root: VectorGroup
+)
+
+/**
+ * Defines a group of paths or subgroups, plus transformation information.
+ * The transformations are defined in the same coordinates as the viewport.
+ * The transformations are applied in the order of scale, rotate then translate.
+ */
+class VectorGroup(
+ /**
+ * Name of the corresponding group
+ */
+ val name: String = DefaultGroupName,
+
+ /**
+ * Rotation of the group in degrees
+ */
+ val rotation: Float = DefaultRotation,
+
+ /**
+ * X coordinate of the pivot point to rotate or scale the group
+ */
+ val pivotX: Float = DefaultPivotX,
+
+ /**
+ * Y coordinate of the pivot point to rotate or scale the group
+ */
+ val pivotY: Float = DefaultPivotY,
+
+ /**
+ * Scale factor in the X-axis to apply to the group
+ */
+ val scaleX: Float = DefaultScaleX,
+
+ /**
+ * Scale factor in the Y-axis to apply to the group
+ */
+ val scaleY: Float = DefaultScaleY,
+
+ /**
+ * Translation in virtual pixels to apply along the x-axis
+ */
+ val translationX: Float = DefaultTranslationX,
+
+ /**
+ * Translation in virtual pixels to apply along the y-axis
+ */
+ val translationY: Float = DefaultTranslationY,
+
+ /**
+ * Path information used to clip the content within the group
+ */
+ val clipPathData: PathData = EmptyPath
+
+) : VectorNode(), Iterable<VectorNode> {
+
+ private val children = ArrayList<VectorNode>()
+
+ internal fun addNode(node: VectorNode) {
+ children.add(node)
+ }
+
+ val size: Int
+ get() = children.size
+
+ operator fun get(index: Int): VectorNode {
+ return children[index]
+ }
+
+ override fun iterator(): Iterator<VectorNode> {
+ return object: Iterator<VectorNode> {
+
+ val it = children.iterator()
+
+ override fun hasNext(): Boolean = it.hasNext()
+
+ override fun next(): VectorNode = it.next()
+ }
+ }
+}
+
+/**
+ * Leaf node of a Vector graphics tree. This specifies a path shape and parameters
+ * to color and style the the shape itself
+ */
+class VectorPath(
+ /**
+ * Name of the corresponding path
+ */
+ val name: String = DefaultPathName,
+
+ /**
+ * Path information to render the shape of the path
+ */
+ val pathData: PathData,
+
+ /**
+ * Specifies the color or gradient used to fill the path
+ */
+ val fill: BrushType = EmptyBrush,
+
+ /**
+ * Opacity to fill the path
+ */
+ val fillAlpha: Float = DefaultAlpha,
+
+ /**
+ * Specifies the color or gradient used to fill the stroke
+ */
+ val stroke: BrushType = EmptyBrush,
+
+ /**
+ * Opacity to stroke the path
+ */
+ val strokeAlpha: Float = DefaultAlpha,
+
+ /**
+ * Width of the line to stroke the path
+ */
+ val strokeLineWidth: Float = DefaultStrokeLineWidth,
+
+ /**
+ * Specifies the linecap for a stroked path, either butt, round, or square. The default is butt.
+ */
+ val strokeLineCap: StrokeCap = DefaultStrokeLineCap,
+
+ /**
+ * Specifies the linejoin for a stroked path, either miter, round or bevel. The default is miter
+ */
+ val strokeLineJoin: StrokeJoin = DefaultStrokeLineJoin,
+
+ /**
+ * Specifies the miter limit for a stroked path, the default is 4
+ */
+ val strokeLineMiter: Float = DefaultStrokeLineMiter
+) : VectorNode()
+
+/**
+ * Composes a vector graphic into the composition tree based on the specification
+ * provided by given [VectorAsset]
+ */
+@Composable
+fun DrawVector(vectorImage: VectorAsset) {
+ Vector(
+ name = vectorImage.name,
+ viewportWidth = vectorImage.viewportWidth,
+ viewportHeight = vectorImage.viewportHeight,
+ defaultWidth = vectorImage.defaultWidth,
+ defaultHeight = vectorImage.defaultHeight) {
+ RenderVectorGroup(group = vectorImage.root)
+ }
+}
+
+/**
+ * Recursive method for creating the vector graphic composition by traversing
+ * the tree structure
+ */
+@Composable
+private fun RenderVectorGroup(group: VectorGroup) {
+ for (vectorNode in group) {
+ if (vectorNode is VectorPath) {
+ Path(
+ pathData = vectorNode.pathData,
+ name = vectorNode.name,
+ fill = vectorNode.fill,
+ fillAlpha = vectorNode.fillAlpha,
+ stroke = vectorNode.stroke,
+ strokeAlpha = vectorNode.strokeAlpha,
+ strokeLineWidth = vectorNode.strokeLineWidth,
+ strokeLineCap = vectorNode.strokeLineCap,
+ strokeLineJoin = vectorNode.strokeLineJoin,
+ strokeLineMiter = vectorNode.strokeLineMiter
+ )
+ } else if (vectorNode is VectorGroup) {
+ Group(
+ name = vectorNode.name,
+ rotation = vectorNode.rotation,
+ scaleX = vectorNode.scaleX,
+ scaleY = vectorNode.scaleY,
+ translationX = vectorNode.translationX,
+ translationY = vectorNode.translationY,
+ pivotX = vectorNode.pivotX,
+ pivotY = vectorNode.pivotY,
+ clipPathData = vectorNode.clipPathData) {
+ RenderVectorGroup(group = vectorNode)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/compat/VectorResource.kt b/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/compat/VectorResource.kt
index c129e97..a886ad0 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/compat/VectorResource.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/compat/VectorResource.kt
@@ -16,395 +16,41 @@
package androidx.ui.core.vectorgraphics.compat
+import androidx.compose.composer
import android.annotation.SuppressLint
import android.content.res.Resources
-import android.content.res.TypedArray
-import android.util.AttributeSet
-import android.util.Log
import android.util.Xml
-import androidx.core.content.res.TypedArrayUtils
-import androidx.ui.core.vectorgraphics.addPathNodes
-import androidx.ui.core.vectorgraphics.group
-import androidx.ui.core.vectorgraphics.path
-import androidx.ui.core.vectorgraphics.vector
-import androidx.ui.painting.StrokeCap
-import androidx.ui.painting.StrokeJoin
-import androidx.ui.core.vectorgraphics.DefaultPivotX
-import androidx.ui.core.vectorgraphics.DefaultPivotY
-import androidx.ui.core.vectorgraphics.DefaultRotate
-import androidx.ui.core.vectorgraphics.DefaultScaleX
-import androidx.ui.core.vectorgraphics.DefaultScaleY
-import androidx.ui.core.vectorgraphics.DefaultTranslateX
-import androidx.ui.core.vectorgraphics.DefaultTranslateY
-import androidx.ui.core.vectorgraphics.EmptyBrush
-import androidx.ui.core.vectorgraphics.EmptyPath
-import androidx.ui.core.vectorgraphics.PathNode
-import androidx.compose.Children
import androidx.compose.Composable
-import androidx.compose.composer
-import org.xmlpull.v1.XmlPullParser
+import androidx.compose.ambient
+import androidx.compose.memo
+import androidx.compose.unaryPlus
+import androidx.ui.core.ContextAmbient
+import androidx.ui.core.vectorgraphics.DrawVector
+import androidx.ui.core.vectorgraphics.VectorAsset
import org.xmlpull.v1.XmlPullParserException
-import java.io.IOException
-
-private val LINECAP_BUTT = 0
-private val LINECAP_ROUND = 1
-private val LINECAP_SQUARE = 2
-
-private val LINEJOIN_MITER = 0
-private val LINEJOIN_ROUND = 1
-private val LINEJOIN_BEVEL = 2
-
-private val FILL_TYPE_WINDING = 0
-
-private val SHAPE_CLIP_PATH = "clip-VPath"
-private val SHAPE_GROUP = "group"
-private val SHAPE_PATH = "path"
-
-private val LOGTAG = "VectorGraphicCreator"
-
-private fun getStrokeLineCap(id: Int, defValue: StrokeCap = StrokeCap.butt): StrokeCap =
- when (id) {
- LINECAP_BUTT -> StrokeCap.butt
- LINECAP_ROUND -> StrokeCap.round
- LINECAP_SQUARE -> StrokeCap.square
- else -> defValue
- }
-
-private fun getStrokeLineJoin(id: Int, defValue: StrokeJoin = StrokeJoin.miter): StrokeJoin =
- when (id) {
- LINEJOIN_MITER -> StrokeJoin.miter
- LINEJOIN_ROUND -> StrokeJoin.round
- LINEJOIN_BEVEL -> StrokeJoin.bevel
- else -> defValue
- }
@Composable
+fun VectorResource(resId: Int) {
+ val context = +ambient(ContextAmbient)
+ val res = context.resources
+ val theme = context.theme
+ val vectorImage = +memo(resId) {
+ loadVectorResource(theme, res, resId)
+ }
+ DrawVector(vectorImage = vectorImage)
+}
+
+@Throws(XmlPullParserException::class)
@SuppressWarnings("RestrictedApi")
-private fun inflateGroup(
- a: TypedArray,
- parser: XmlPullParser,
- @Suppress("UNUSED_PARAMETER") theme: Resources.Theme?,
- @Children childNodes: @Composable() () -> Unit
-) {
- // Account for any configuration changes.
- // mChangingConfigurations |= Utils.getChangingConfigurations(a);
+fun loadVectorResource(theme: Resources.Theme? = null, res: Resources, resId: Int): VectorAsset {
- // Extract the theme attributes, if any.
- // mThemeAttrs = null // TODO TINT THEME Not supported yet a.extractThemeAttrs();
-
- // This is added in API 11
- val rotate = TypedArrayUtils.getNamedFloat(
- a, parser, "rotation",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_ROTATION,
- DefaultRotate
- )
-
- val pivotX = a.getFloat(
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_PIVOT_X,
- DefaultPivotX
- )
- val pivotY = a.getFloat(
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_PIVOT_Y,
- DefaultPivotY
- )
-
- // This is added in API 11
- val scaleX = TypedArrayUtils.getNamedFloat(
- a, parser, "scaleX",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_SCALE_X,
- DefaultScaleX
- )
-
- // This is added in API 11
- val scaleY = TypedArrayUtils.getNamedFloat(
- a, parser, "scaleY",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_SCALE_Y,
- DefaultScaleY
- )
-
- val translateX = TypedArrayUtils.getNamedFloat(
- a, parser, "translateX",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_TRANSLATE_X,
- DefaultTranslateX
- )
- val translateY = TypedArrayUtils.getNamedFloat(
- a, parser, "translateY",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_TRANSLATE_Y,
- DefaultTranslateY
- )
-
- val name: String =
- a.getString(AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_NAME) ?: ""
-
- parser.next()
-
- // TODO parse clip path
- val clipPathData = EmptyPath
- group(
- name = name,
- rotate = rotate,
- scaleX = scaleX,
- scaleY = scaleY,
- translateX = translateX,
- translateY = translateY,
- pivotX = pivotX,
- pivotY = pivotY,
- clipPathData = clipPathData
- ) {
- childNodes()
- }
-}
-
-@Suppress("UNUSED_PARAMETER")
-@Composable
-private fun inflateClip(
- a: TypedArray,
- parser: XmlPullParser,
- theme: Resources.Theme?,
- @Children childNodes: @Composable() () -> Unit
-) {
-// var pathName: String? =
-// a.getString(AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH_NAME)
-// if (pathName == null) {
-// pathName = ""
-// }
-// val pathData = a.getString(AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH_PATH_DATA)
-
- // TODO (njawad) finish parsing clip paths from xml resources
-}
-
-@Composable
-@SuppressWarnings("RestrictedApi")
-private fun inflatePath(a: TypedArray, parser: XmlPullParser, theme: Resources.Theme?) {
- val hasPathData = TypedArrayUtils.hasAttribute(parser, "pathData")
- if (!hasPathData) {
- // If there is no pathData in the VPath tag, then this is an empty VPath,
- // nothing need to be drawn.
- Log.v("VectorPath", "no path data available skipping path")
- return
- }
-
- val name: String = a.getString(AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_NAME) ?: ""
-
- val pathStr = a.getString(AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_PATH_DATA)
-
- val pathData: Array<PathNode> = addPathNodes(pathStr)
-
- val fillColor = TypedArrayUtils.getNamedComplexColor(
- a, parser, theme, "fillColor",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_FILL_COLOR, 0
- )
- // TODO(njawad): restore these when they are used
-/* val fillAlpha = TypedArrayUtils.getNamedFloat(
- a, parser, "fillAlpha",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_FILL_ALPHA, 1.0f
- )
- val lineCap = TypedArrayUtils.getNamedInt(
- a, parser, "strokeLineCap",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_LINE_CAP, -1
- )
- val strokeLineCap =
- getStrokeLineCap(lineCap, StrokeCap.butt)
- val lineJoin = TypedArrayUtils.getNamedInt(
- a, parser, "strokeLineJoin",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_LINE_JOIN, -1
- )
- val strokeLineJoin =
- getStrokeLineJoin(lineJoin, StrokeJoin.bevel)
- val strokeMiterlimit = TypedArrayUtils.getNamedFloat(
- a, parser, "strokeMiterLimit",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_MITER_LIMIT,
- 1.0f
- )*/
- val strokeColor = TypedArrayUtils.getNamedComplexColor(
- a, parser, theme, "strokeColor",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_COLOR, 0
- )
- val strokeAlpha = TypedArrayUtils.getNamedFloat(
- a, parser, "strokeAlpha",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_ALPHA, 1.0f
- )
- val strokeLineWidth = TypedArrayUtils.getNamedFloat(
- a, parser, "strokeWidth",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_WIDTH, 1.0f
- )
- /*val trimPathEnd = TypedArrayUtils.getNamedFloat(
- a, parser, "trimPathEnd",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_END, 1.0f
- )
- val trimPathOffset = TypedArrayUtils.getNamedFloat(
- a, parser, "trimPathOffset",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_OFFSET,
- 0.0f
- )
- val trimPathStart = TypedArrayUtils.getNamedFloat(
- a, parser, "trimPathStart",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_START,
- 0.0f
- )
- val fillRule = TypedArrayUtils.getNamedInt(
- a, parser, "fillType",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_FILLTYPE,
- FILL_TYPE_WINDING
- )*/
-
- // TODO update path with additional params
- path(
- name = name,
- fill = if (fillColor.willDraw()) fillColor.color else EmptyBrush,
- strokeAlpha = strokeAlpha,
- strokeLineWidth = strokeLineWidth,
- stroke = if (strokeColor.willDraw()) strokeColor.color else EmptyBrush,
- pathData = pathData
- )
-}
-
-@Composable
-@SuppressWarnings("RestrictedApi")
-private fun inflateInner(
- res: Resources,
- attrs: AttributeSet,
- theme: Resources.Theme?,
- innerDepth: Int,
- parser: XmlPullParser
-) {
- var eventType = parser.getEventType()
-
- // Parse everything until the end of the VectorGraphic element.
- while (eventType != XmlPullParser.END_DOCUMENT &&
- (parser.getDepth() >= innerDepth || eventType != XmlPullParser.END_TAG)
- ) {
- if (eventType == XmlPullParser.START_TAG) {
- val tagName = parser.getName()
- if (SHAPE_PATH.equals(tagName)) {
- Log.v("VectorPath", "parsing path...")
- val a = TypedArrayUtils.obtainAttributes(
- res, theme, attrs,
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH
- )
- inflatePath(a = a, parser = parser, theme = theme)
- a.recycle()
- } else if (SHAPE_CLIP_PATH.equals(tagName)) {
- val a = TypedArrayUtils.obtainAttributes(
- res, theme, attrs,
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH
- )
- inflateClip(a = a, parser = parser, theme = theme) {
- inflateInner(
- res = res,
- parser = parser,
- attrs = attrs,
- theme = theme,
- innerDepth = innerDepth
- )
- }
- a.recycle()
- } else if (SHAPE_GROUP.equals(tagName)) {
- val a = TypedArrayUtils.obtainAttributes(
- res, theme, attrs,
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP
- )
- inflateGroup(a = a, parser = parser, theme = theme) {
- inflateInner(
- res = res,
- parser = parser,
- attrs = attrs,
- theme = theme,
- innerDepth = innerDepth
- )
- }
- a.recycle()
- }
- }
- eventType = parser.next()
- }
-}
-
-@Composable
-@SuppressWarnings("RestrictedApi")
-private fun inflate(
- res: Resources,
- parser: XmlPullParser,
- attrs: AttributeSet,
- theme: Resources.Theme?
-) {
- val innerDepth = parser.getDepth() + 1
-
- val vectorAttrs = TypedArrayUtils.obtainAttributes(
- res, theme, attrs,
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_TYPE_ARRAY
- )
-
- // TODO (njawad) handle mirroring here
-// state.mAutoMirrored = TypedArrayUtils.getNamedBoolean(a, parser, "autoMirrored",
-// AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_AUTO_MIRRORED, state.mAutoMirrored)
-
- val viewportWidth = TypedArrayUtils.getNamedFloat(
- vectorAttrs, parser, "viewportWidth",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_VIEWPORT_WIDTH,
- 0.0f
- )
-
- val viewportHeight = TypedArrayUtils.getNamedFloat(
- vectorAttrs, parser, "viewportHeight",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_VIEWPORT_HEIGHT,
- 0.0f
- )
-
- if (viewportWidth <= 0) {
- throw XmlPullParserException(
- vectorAttrs.getPositionDescription() +
- "VectorGraphic requires viewportWidth > 0"
- )
- } else if (viewportHeight <= 0) {
- throw XmlPullParserException(
- vectorAttrs.getPositionDescription() +
- "VectorGraphic requires viewportHeight > 0"
- )
- }
-
- val defaultWidth = vectorAttrs.getDimension(
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_WIDTH, 0.0f
- )
- val defaultHeight = vectorAttrs.getDimension(
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_HEIGHT, 0.0f
- )
-
- vector(
- name = "vector",
- defaultWidth = defaultWidth,
- defaultHeight = defaultHeight,
- viewportWidth = viewportWidth,
- viewportHeight = viewportHeight
- ) {
- inflateInner(
- res = res,
- attrs = attrs,
- theme = theme,
- innerDepth = innerDepth,
- parser = parser
- )
- }
-}
-
-@Composable
-fun vectorResource(res: Resources, resId: Int) {
@SuppressLint("ResourceType") val parser = res.getXml(resId)
val attrs = Xml.asAttributeSet(parser)
- var type: Int
- try {
- type = parser.next()
- while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {
- // Empty loop
- type = parser.next()
- }
- if (type != XmlPullParser.START_TAG) {
- throw XmlPullParserException("No start tag found")
- }
- inflate(res = res, parser = parser, attrs = attrs, theme = null)
- } catch (e: XmlPullParserException) {
- Log.e(LOGTAG, "parser error", e)
- } catch (e: IOException) {
- Log.e(LOGTAG, "parser error", e)
+ val builder = parser.seekToStartTag().createVectorImageBuilder(res, theme, attrs)
+
+ while (!parser.isAtEnd()) {
+ parser.parseCurrentVectorNode(res, attrs, theme, builder)
+ parser.next()
}
-}
+ return builder.build()
+}
\ No newline at end of file
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/compat/XmlVectorParser.kt b/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/compat/XmlVectorParser.kt
new file mode 100644
index 0000000..121a723
--- /dev/null
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/compat/XmlVectorParser.kt
@@ -0,0 +1,394 @@
+/*
+ * Copyright 2019 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.ui.core.vectorgraphics.compat
+
+import android.content.res.Resources
+import android.util.AttributeSet
+import androidx.core.content.res.TypedArrayUtils
+import androidx.ui.core.Px
+import androidx.ui.core.vectorgraphics.DefaultPivotX
+import androidx.ui.core.vectorgraphics.DefaultPivotY
+import androidx.ui.core.vectorgraphics.DefaultRotation
+import androidx.ui.core.vectorgraphics.DefaultScaleX
+import androidx.ui.core.vectorgraphics.DefaultScaleY
+import androidx.ui.core.vectorgraphics.DefaultTranslationX
+import androidx.ui.core.vectorgraphics.DefaultTranslationY
+import androidx.ui.core.vectorgraphics.EmptyBrush
+import androidx.ui.core.vectorgraphics.EmptyPath
+import androidx.ui.core.vectorgraphics.PathNode
+import androidx.ui.core.vectorgraphics.VectorAssetBuilder
+import androidx.ui.core.vectorgraphics.addPathNodes
+import androidx.ui.painting.StrokeCap
+import androidx.ui.painting.StrokeJoin
+import org.xmlpull.v1.XmlPullParser
+import org.xmlpull.v1.XmlPullParserException
+
+private val LINECAP_BUTT = 0
+private val LINECAP_ROUND = 1
+private val LINECAP_SQUARE = 2
+
+private val LINEJOIN_MITER = 0
+private val LINEJOIN_ROUND = 1
+private val LINEJOIN_BEVEL = 2
+
+private val FILL_TYPE_WINDING = 0
+
+private val SHAPE_CLIP_PATH = "clip-VPath"
+private val SHAPE_GROUP = "group"
+private val SHAPE_PATH = "path"
+
+private fun getStrokeLineCap(id: Int, defValue: StrokeCap = StrokeCap.butt): StrokeCap =
+ when (id) {
+ LINECAP_BUTT -> StrokeCap.butt
+ LINECAP_ROUND -> StrokeCap.round
+ LINECAP_SQUARE -> StrokeCap.square
+ else -> defValue
+ }
+
+private fun getStrokeLineJoin(id: Int, defValue: StrokeJoin = StrokeJoin.miter): StrokeJoin =
+ when (id) {
+ LINEJOIN_MITER -> StrokeJoin.miter
+ LINEJOIN_ROUND -> StrokeJoin.round
+ LINEJOIN_BEVEL -> StrokeJoin.bevel
+ else -> defValue
+ }
+
+internal fun XmlPullParser.isAtEnd(): Boolean =
+ eventType == XmlPullParser.END_DOCUMENT ||
+ (depth < 1 && eventType == XmlPullParser.END_TAG)
+
+internal fun XmlPullParser.parseCurrentVectorNode(
+ res: Resources,
+ attrs: AttributeSet,
+ theme: Resources.Theme? = null,
+ builder: VectorAssetBuilder
+) {
+ when (eventType) {
+ XmlPullParser.START_TAG -> {
+ when (name) {
+ SHAPE_PATH -> {
+ parsePath(res, theme, attrs, builder)
+ }
+ SHAPE_CLIP_PATH -> {
+ // TODO parse clipping paths
+ }
+ SHAPE_GROUP -> {
+ parseGroup(res, theme, attrs, builder)
+ }
+ }
+ }
+ XmlPullParser.END_TAG -> {
+ if (SHAPE_GROUP == name) {
+ builder.popGroup()
+ }
+ }
+ }
+}
+
+/**
+ * Helper method to seek to the first tag within the VectorDrawable xml asset
+ */
+@Throws(XmlPullParserException::class)
+internal fun XmlPullParser.seekToStartTag(): XmlPullParser {
+ var type = next()
+ while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {
+ // Empty loop
+ type = next()
+ }
+ if (type != XmlPullParser.START_TAG) {
+ throw XmlPullParserException("No start tag found")
+ }
+ return this
+}
+
+@SuppressWarnings("RestrictedApi")
+internal fun XmlPullParser.createVectorImageBuilder(
+ res: Resources,
+ theme: Resources.Theme?,
+ attrs: AttributeSet
+): VectorAssetBuilder {
+ val vectorAttrs = TypedArrayUtils.obtainAttributes(
+ res,
+ theme,
+ attrs,
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_TYPE_ARRAY
+ )
+
+ // TODO (njawad) handle mirroring here
+// state.mAutoMirrored = TypedArrayUtils.getNamedBoolean(a, parser, "autoMirrored",
+// AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_AUTO_MIRRORED, state.mAutoMirrored)
+
+ val viewportWidth = TypedArrayUtils.getNamedFloat(
+ vectorAttrs,
+ this,
+ "viewportWidth",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_VIEWPORT_WIDTH,
+ 0.0f
+ )
+
+ val viewportHeight = TypedArrayUtils.getNamedFloat(
+ vectorAttrs,
+ this,
+ "viewportHeight",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_VIEWPORT_HEIGHT,
+ 0.0f
+ )
+
+ if (viewportWidth <= 0) {
+ throw XmlPullParserException(
+ vectorAttrs.getPositionDescription() +
+ "<VectorGraphic> tag requires viewportWidth > 0"
+ )
+ } else if (viewportHeight <= 0) {
+ throw XmlPullParserException(
+ vectorAttrs.getPositionDescription() +
+ "<VectorGraphic> tag requires viewportHeight > 0"
+ )
+ }
+
+ val defaultWidth = vectorAttrs.getDimension(
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_WIDTH, 0.0f
+ )
+ val defaultHeight = vectorAttrs.getDimension(
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_HEIGHT, 0.0f
+ )
+
+ vectorAttrs.recycle()
+
+ return VectorAssetBuilder(
+ defaultWidth = Px(defaultWidth),
+ defaultHeight = Px(defaultHeight),
+ viewportWidth = viewportWidth,
+ viewportHeight = viewportHeight
+ )
+}
+
+@Throws(IllegalArgumentException::class)
+@SuppressWarnings("RestrictedApi")
+internal fun XmlPullParser.parsePath(
+ res: Resources,
+ theme: Resources.Theme?,
+ attrs: AttributeSet,
+ builder: VectorAssetBuilder
+) {
+ val a = TypedArrayUtils.obtainAttributes(
+ res,
+ theme,
+ attrs,
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH
+ )
+
+ val hasPathData = TypedArrayUtils.hasAttribute(this, "pathData")
+ if (!hasPathData) {
+ // If there is no pathData in the VPath tag, then this is an empty VPath,
+ // nothing need to be drawn.
+ throw IllegalArgumentException("No path data available")
+ }
+
+ val name: String = a.getString(AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_NAME) ?: ""
+
+ val pathStr = a.getString(AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_PATH_DATA)
+
+ val pathData: Array<PathNode> = addPathNodes(pathStr)
+
+ val fillColor = TypedArrayUtils.getNamedComplexColor(
+ a,
+ this,
+ theme,
+ "fillColor",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_FILL_COLOR, 0
+ )
+ val fillAlpha = TypedArrayUtils.getNamedFloat(
+ a,
+ this,
+ "fillAlpha",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_FILL_ALPHA, 1.0f
+ )
+ val lineCap = TypedArrayUtils.getNamedInt(
+ a,
+ this,
+ "strokeLineCap",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_LINE_CAP, -1
+ )
+ val strokeLineCap = getStrokeLineCap(lineCap, StrokeCap.butt)
+ val lineJoin = TypedArrayUtils.getNamedInt(
+ a,
+ this,
+ "strokeLineJoin",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_LINE_JOIN, -1
+ )
+ val strokeLineJoin = getStrokeLineJoin(lineJoin, StrokeJoin.bevel)
+ val strokeMiterLimit = TypedArrayUtils.getNamedFloat(
+ a,
+ this,
+ "strokeMiterLimit",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_MITER_LIMIT,
+ 1.0f
+ )
+ val strokeColor = TypedArrayUtils.getNamedComplexColor(
+ a,
+ this,
+ theme,
+ "strokeColor",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_COLOR, 0
+ )
+ val strokeAlpha = TypedArrayUtils.getNamedFloat(
+ a,
+ this,
+ "strokeAlpha",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_ALPHA, 1.0f
+ )
+ val strokeLineWidth = TypedArrayUtils.getNamedFloat(
+ a,
+ this,
+ "strokeWidth",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_WIDTH, 1.0f
+ )
+
+ // TODO (njawad) handle trim paths + fill rule
+// val trimPathEnd = TypedArrayUtils.getNamedFloat(
+// a, this, "trimPathEnd",
+// AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_END, 1.0f
+// )
+// val trimPathOffset = TypedArrayUtils.getNamedFloat(
+// a, this, "trimPathOffset",
+// AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_OFFSET,
+// 0.0f
+// )
+// val trimPathStart = TypedArrayUtils.getNamedFloat(
+// a, this, "trimPathStart",
+// AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_START,
+// 0.0f
+// )
+// val fillRule = TypedArrayUtils.getNamedInt(
+// a, this, "fillType",
+// AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_FILLTYPE,
+// FILL_TYPE_WINDING
+// )
+
+ a.recycle()
+
+ @Suppress("IMPLICIT_CAST_TO_ANY")
+ val fillBrush = if (fillColor.willDraw()) fillColor.color else EmptyBrush
+
+ @Suppress("IMPLICIT_CAST_TO_ANY")
+ val strokeBrush = if (strokeColor.willDraw()) strokeColor.color else EmptyBrush
+
+ builder.addPath(
+ pathData,
+ name,
+ fillBrush,
+ fillAlpha,
+ strokeBrush,
+ strokeAlpha,
+ strokeLineWidth,
+ strokeLineCap,
+ strokeLineJoin,
+ strokeMiterLimit)
+}
+
+@SuppressWarnings("RestrictedApi")
+internal fun XmlPullParser.parseGroup(
+ res: Resources,
+ theme: Resources.Theme?,
+ attrs: AttributeSet,
+ builder: VectorAssetBuilder
+) {
+ val a = TypedArrayUtils.obtainAttributes(
+ res,
+ theme,
+ attrs,
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP
+ )
+
+ // Account for any configuration changes.
+ // mChangingConfigurations |= Utils.getChangingConfigurations(a);
+
+ // Extract the theme attributes, if any.
+ // mThemeAttrs = null // TODO TINT THEME Not supported yet a.extractThemeAttrs();
+
+ // This is added in API 11
+ val rotate = TypedArrayUtils.getNamedFloat(
+ a,
+ this,
+ "rotation",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_ROTATION,
+ DefaultRotation
+ )
+
+ val pivotX = a.getFloat(
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_PIVOT_X,
+ DefaultPivotX
+ )
+ val pivotY = a.getFloat(
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_PIVOT_Y,
+ DefaultPivotY
+ )
+
+ // This is added in API 11
+ val scaleX = TypedArrayUtils.getNamedFloat(
+ a,
+ this,
+ "scaleX",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_SCALE_X,
+ DefaultScaleX
+ )
+
+ // This is added in API 11
+ val scaleY = TypedArrayUtils.getNamedFloat(
+ a,
+ this,
+ "scaleY",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_SCALE_Y,
+ DefaultScaleY
+ )
+
+ val translateX = TypedArrayUtils.getNamedFloat(
+ a,
+ this,
+ "translationX",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_TRANSLATE_X,
+ DefaultTranslationX
+ )
+ val translateY = TypedArrayUtils.getNamedFloat(
+ a,
+ this,
+ "translationY",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_TRANSLATE_Y,
+ DefaultTranslationY
+ )
+
+ val name: String =
+ a.getString(AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_NAME) ?: ""
+
+ // TODO parse clip path
+ val clipPathData = EmptyPath
+
+ a.recycle()
+
+ builder.pushGroup(
+ name,
+ rotate,
+ scaleX,
+ scaleY,
+ translateX,
+ translateY,
+ pivotX,
+ pivotY,
+ clipPathData
+ )
+}
diff --git a/ui/ui-framework/src/test/java/androidx/ui/core/InputFieldDelegateTest.kt b/ui/ui-framework/src/test/java/androidx/ui/core/TextFieldDelegateTest.kt
similarity index 87%
rename from ui/ui-framework/src/test/java/androidx/ui/core/InputFieldDelegateTest.kt
rename to ui/ui-framework/src/test/java/androidx/ui/core/TextFieldDelegateTest.kt
index f934c88..4657c23 100644
--- a/ui/ui-framework/src/test/java/androidx/ui/core/InputFieldDelegateTest.kt
+++ b/ui/ui-framework/src/test/java/androidx/ui/core/TextFieldDelegateTest.kt
@@ -21,7 +21,7 @@
import androidx.ui.input.CommitTextEditOp
import androidx.ui.input.EditOperation
import androidx.ui.input.EditProcessor
-import androidx.ui.input.EditorState
+import androidx.ui.input.EditorModel
import androidx.ui.input.FinishComposingTextEditOp
import androidx.ui.input.ImeAction
import androidx.ui.input.KeyboardType
@@ -49,12 +49,12 @@
import org.junit.runners.JUnit4
@RunWith(JUnit4::class)
-class InputFieldDelegateTest {
+class TextFieldDelegateTest {
private lateinit var canvas: Canvas
private lateinit var painter: TextPainter
private lateinit var processor: EditProcessor
- private lateinit var onValueChange: (EditorState) -> Unit
+ private lateinit var onValueChange: (EditorModel) -> Unit
private lateinit var onEditorActionPerformed: (Any) -> Unit
private lateinit var textInputService: TextInputService
private lateinit var layoutCoordinates: LayoutCoordinates
@@ -106,10 +106,10 @@
val selection = TextRange(0, 1)
val selectionColor = Color.Blue
- InputFieldDelegate.draw(
+ TextFieldDelegate.draw(
canvas = canvas,
textPainter = painter,
- value = EditorState(text = "Hello, World", selection = selection),
+ value = EditorModel(text = "Hello, World", selection = selection),
editorStyle = EditorStyle(selectionColor = selectionColor),
hasFocus = true,
offsetMap = identityOffsetMap
@@ -126,10 +126,10 @@
fun draw_cursor_test() {
val cursor = TextRange(1, 1)
- InputFieldDelegate.draw(
+ TextFieldDelegate.draw(
canvas = canvas,
textPainter = painter,
- value = EditorState(text = "Hello, World", selection = cursor),
+ value = EditorModel(text = "Hello, World", selection = cursor),
editorStyle = EditorStyle(),
hasFocus = true,
offsetMap = identityOffsetMap
@@ -144,10 +144,10 @@
fun dont_draw_cursor_test() {
val cursor = TextRange(1, 1)
- InputFieldDelegate.draw(
+ TextFieldDelegate.draw(
canvas = canvas,
textPainter = painter,
- value = EditorState(text = "Hello, World", selection = cursor),
+ value = EditorModel(text = "Hello, World", selection = cursor),
editorStyle = EditorStyle(),
hasFocus = false,
offsetMap = identityOffsetMap
@@ -165,10 +165,10 @@
val cursor = TextRange(1, 1)
- InputFieldDelegate.draw(
+ TextFieldDelegate.draw(
canvas = canvas,
textPainter = painter,
- value = EditorState(text = "Hello, World", selection = cursor,
+ value = EditorModel(text = "Hello, World", selection = cursor,
composition = composition),
editorStyle = EditorStyle(compositionColor = compositionColor),
hasFocus = true,
@@ -184,11 +184,11 @@
@Test
fun test_on_edit_command() {
val ops = listOf(CommitTextEditOp("Hello, World", 1))
- val dummyEditorState = EditorState(text = "Hello, World", selection = TextRange(1, 1))
+ val dummyEditorState = EditorModel(text = "Hello, World", selection = TextRange(1, 1))
whenever(processor.onEditCommands(ops)).thenReturn(dummyEditorState)
- InputFieldDelegate.onEditCommand(ops, processor, onValueChange)
+ TextFieldDelegate.onEditCommand(ops, processor, onValueChange)
verify(onValueChange, times(1)).invoke(eq(dummyEditorState))
}
@@ -197,7 +197,7 @@
fun test_on_release() {
val position = PxPosition(100.px, 200.px)
val offset = 10
- val dummyEditorState = EditorState(text = "Hello, World", selection = TextRange(1, 1))
+ val dummyEditorState = EditorModel(text = "Hello, World", selection = TextRange(1, 1))
whenever(painter.getOffsetForPosition(position)).thenReturn(offset)
@@ -205,7 +205,7 @@
whenever(processor.onEditCommands(captor.capture())).thenReturn(dummyEditorState)
- InputFieldDelegate.onRelease(
+ TextFieldDelegate.onRelease(
position,
painter,
processor,
@@ -227,7 +227,7 @@
val offset = 10
whenever(painter.getOffsetForPosition(position)).thenReturn(offset)
- InputFieldDelegate.onRelease(
+ TextFieldDelegate.onRelease(
position,
painter,
processor,
@@ -244,10 +244,10 @@
fun test_draw_order() {
val canvas: Canvas = mock()
- InputFieldDelegate.draw(
+ TextFieldDelegate.draw(
canvas = canvas,
textPainter = painter,
- value = EditorState(
+ value = EditorModel(
text = "Hello, World", selection = TextRange(1, 1),
composition = TextRange(1, 3)
),
@@ -264,8 +264,8 @@
@Test
fun on_focus() {
- val dummyEditorState = EditorState(text = "Hello, World", selection = TextRange(1, 1))
- InputFieldDelegate.onFocus(textInputService, dummyEditorState, processor,
+ val dummyEditorState = EditorModel(text = "Hello, World", selection = TextRange(1, 1))
+ TextFieldDelegate.onFocus(textInputService, dummyEditorState, processor,
KeyboardType.Text, ImeAction.Unspecified, onValueChange, onEditorActionPerformed)
verify(textInputService).startInput(
eq(dummyEditorState),
@@ -280,9 +280,9 @@
fun on_blur() {
val captor = argumentCaptor<List<EditOperation>>()
- whenever(processor.onEditCommands(captor.capture())).thenReturn(EditorState())
+ whenever(processor.onEditCommands(captor.capture())).thenReturn(EditorModel())
- InputFieldDelegate.onBlur(textInputService, processor, onValueChange)
+ TextFieldDelegate.onBlur(textInputService, processor, onValueChange)
assertEquals(1, captor.allValues.size)
assertEquals(1, captor.firstValue.size)
@@ -296,8 +296,8 @@
whenever(painter.getBoundingBox(any())).thenReturn(dummyRect)
val dummyPoint = PxPosition(5.px, 6.px)
whenever(layoutCoordinates.localToRoot(any())).thenReturn(dummyPoint)
- val dummyEditorState = EditorState(text = "Hello, World", selection = TextRange(1, 1))
- InputFieldDelegate.notifyFocusedRect(
+ val dummyEditorState = EditorModel(text = "Hello, World", selection = TextRange(1, 1))
+ TextFieldDelegate.notifyFocusedRect(
dummyEditorState,
painter,
layoutCoordinates,
@@ -310,8 +310,8 @@
@Test
fun notify_focused_rect_without_focus() {
- val dummyEditorState = EditorState(text = "Hello, World", selection = TextRange(1, 1))
- InputFieldDelegate.notifyFocusedRect(
+ val dummyEditorState = EditorModel(text = "Hello, World", selection = TextRange(1, 1))
+ TextFieldDelegate.notifyFocusedRect(
dummyEditorState,
painter,
layoutCoordinates,
@@ -328,8 +328,8 @@
whenever(painter.getBoundingBox(any())).thenReturn(dummyRect)
val dummyPoint = PxPosition(5.px, 6.px)
whenever(layoutCoordinates.localToRoot(any())).thenReturn(dummyPoint)
- val dummyEditorState = EditorState(text = "Hello, World", selection = TextRange(12, 12))
- InputFieldDelegate.notifyFocusedRect(
+ val dummyEditorState = EditorModel(text = "Hello, World", selection = TextRange(12, 12))
+ TextFieldDelegate.notifyFocusedRect(
dummyEditorState,
painter,
layoutCoordinates,
@@ -346,8 +346,8 @@
whenever(painter.preferredLineHeight).thenReturn(dummyHeight)
val dummyPoint = PxPosition(5.px, 6.px)
whenever(layoutCoordinates.localToRoot(any())).thenReturn(dummyPoint)
- val dummyEditorState = EditorState(text = "", selection = TextRange(0, 0))
- InputFieldDelegate.notifyFocusedRect(
+ val dummyEditorState = EditorModel(text = "", selection = TextRange(0, 0))
+ TextFieldDelegate.notifyFocusedRect(
dummyEditorState,
painter,
layoutCoordinates,
@@ -375,7 +375,7 @@
whenever(painter.resourceLoader).thenReturn(mock())
whenever(painter.height).thenReturn(512.0f)
- val res = InputFieldDelegate.layout(painter, constraints)
+ val res = TextFieldDelegate.layout(painter, constraints)
assertEquals(1024.px.round(), res.first)
assertEquals(512.px.round(), res.second)
@@ -387,10 +387,10 @@
val selection = TextRange(1, 3)
val selectionColor = Color.Blue
- InputFieldDelegate.draw(
+ TextFieldDelegate.draw(
canvas = canvas,
textPainter = painter,
- value = EditorState(text = "Hello, World", selection = selection),
+ value = EditorModel(text = "Hello, World", selection = selection),
editorStyle = EditorStyle(selectionColor = selectionColor),
hasFocus = true,
offsetMap = skippingOffsetMap
@@ -410,11 +410,11 @@
fun check_notify_rect_uses_offset_map() {
val dummyRect = Rect(0f, 1f, 2f, 3f)
val dummyPoint = PxPosition(5.px, 6.px)
- val dummyEditorState = EditorState(text = "Hello, World", selection = TextRange(1, 3))
+ val dummyEditorState = EditorModel(text = "Hello, World", selection = TextRange(1, 3))
whenever(painter.getBoundingBox(any())).thenReturn(dummyRect)
whenever(layoutCoordinates.localToRoot(any())).thenReturn(dummyPoint)
- InputFieldDelegate.notifyFocusedRect(
+ TextFieldDelegate.notifyFocusedRect(
dummyEditorState,
painter,
layoutCoordinates,
@@ -430,7 +430,7 @@
fun check_on_release_uses_offset_map() {
val position = PxPosition(100.px, 200.px)
val offset = 10
- val dummyEditorState = EditorState(text = "Hello, World", selection = TextRange(1, 1))
+ val dummyEditorState = EditorModel(text = "Hello, World", selection = TextRange(1, 1))
whenever(painter.getOffsetForPosition(position)).thenReturn(offset)
@@ -438,7 +438,7 @@
whenever(processor.onEditCommands(captor.capture())).thenReturn(dummyEditorState)
- InputFieldDelegate.onRelease(
+ TextFieldDelegate.onRelease(
position,
painter,
processor,
@@ -459,8 +459,8 @@
@Test
fun use_identity_mapping_if_visual_transformation_is_null() {
- val (visualText, offsetMap) = InputFieldDelegate.applyVisualFilter(
- EditorState(text = "Hello, World"),
+ val (visualText, offsetMap) = TextFieldDelegate.applyVisualFilter(
+ EditorModel(text = "Hello, World"),
null)
assertEquals("Hello, World", visualText.text)
diff --git a/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/ComplexLayoutDemos.kt b/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/ComplexLayoutDemos.kt
index 52c2f1c..568e0a2 100644
--- a/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/ComplexLayoutDemos.kt
+++ b/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/ComplexLayoutDemos.kt
@@ -227,7 +227,7 @@
}
@Composable
-fun SingleCompositionRow(@Children children: @Composable() () -> Unit) {
+fun SingleCompositionRow(children: @Composable() () -> Unit) {
Layout(layoutBlock = { measurables, constraints ->
val placeables = measurables.map {
it.measure(constraints.copy(minWidth = 0.ipx, maxWidth = IntPx.Infinity))
@@ -246,7 +246,7 @@
}
@Composable
-fun SingleCompositionColumn(@Children children: @Composable() () -> Unit) {
+fun SingleCompositionColumn(children: @Composable() () -> Unit) {
Layout(layoutBlock = { measurables, constraints ->
val placeables = measurables.map {
it.measure(constraints.copy(minHeight = 0.ipx, maxHeight = IntPx.Infinity))
@@ -306,7 +306,7 @@
}
@Composable
-fun SingleCompositionRowWithIntrinsics(@Children children: @Composable() () -> Unit) {
+fun SingleCompositionRowWithIntrinsics(children: @Composable() () -> Unit) {
ComplexLayout(children = children, block = {
layout { measurables, constraints ->
val placeables = measurables.map { measurable ->
diff --git a/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/LayoutActivity.kt b/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/LayoutActivity.kt
index 1b94f79..c42418b 100644
--- a/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/LayoutActivity.kt
+++ b/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/LayoutActivity.kt
@@ -53,7 +53,7 @@
width: Dp? = null,
height: Dp? = null,
color: Color,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
Wrap {
DrawRectangle(color = color)
diff --git a/ui/ui-layout/src/androidTest/AndroidManifest.xml b/ui/ui-layout/src/androidTest/AndroidManifest.xml
index 463edf8..b1e8fb7 100644
--- a/ui/ui-layout/src/androidTest/AndroidManifest.xml
+++ b/ui/ui-layout/src/androidTest/AndroidManifest.xml
@@ -17,7 +17,12 @@
xmlns:tools="http://schemas.android.com/tools"
package="androidx.ui.layout">
+ <!--
+ ~ Important: disable debuggable for accurate performance results
+ ~ requestLegacyExternalStorage to enable legacy JSON reporting when targeting Q
+ -->
<application
+ android:requestLegacyExternalStorage="true"
android:debuggable="false"
tools:ignore="HardcodedDebugMode"
tools:replace="android:debuggable">
diff --git a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/FlexTest.kt b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/FlexTest.kt
index d68feb4..6221fed 100644
--- a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/FlexTest.kt
+++ b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/FlexTest.kt
@@ -145,9 +145,12 @@
val root = findAndroidCraneView()
waitForDraw(root)
- assertEquals(PxSize(root.width.px / 3, childrenHeight.toPx()), childSize[0])
assertEquals(
- PxSize(root.width.px * 2 / 3, (heightDp.toPx() * 2).round().toPx()),
+ PxSize((root.width.px / 3).round().toPx(), childrenHeight.toPx()),
+ childSize[0]
+ )
+ assertEquals(
+ PxSize((root.width.px * 2 / 3).round().toPx(), (heightDp.toPx() * 2).round().toPx()),
childSize[1]
)
assertEquals(
diff --git a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/IntrinsicTest.kt b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/IntrinsicTest.kt
index dd375d36..556d6b4 100644
--- a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/IntrinsicTest.kt
+++ b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/IntrinsicTest.kt
@@ -500,7 +500,7 @@
maxIntrinsicWidth: Dp,
minIntrinsicHeight: Dp,
height: Dp, maxIntrinsicHeight: Dp,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
ComplexLayout(children) {
layout { _, constraints ->
diff --git a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/LayoutTest.kt b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/LayoutTest.kt
index 0042948..ea2810b 100644
--- a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/LayoutTest.kt
+++ b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/LayoutTest.kt
@@ -70,7 +70,7 @@
activityTestRule.runOnUiThread(runnable)
}
- internal fun show(@Children composable: @Composable() () -> Unit) {
+ internal fun show(composable: @Composable() () -> Unit) {
val runnable: Runnable = object : Runnable {
override fun run() {
activity.setContent(composable)
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Align.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Align.kt
index 07144eb..888706f 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Align.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Align.kt
@@ -69,7 +69,7 @@
* For a widget that does alignment and tries to be the same size as its child, see [Wrap].
*/
@Composable
-fun Align(alignment: Alignment, @Children children: @Composable() () -> Unit) {
+fun Align(alignment: Alignment, children: @Composable() () -> Unit) {
Layout(layoutBlock = { measurables, constraints ->
val measurable = measurables.firstOrNull()
// The child cannot be larger than our max constraints, but we ignore min constraints.
@@ -112,6 +112,6 @@
* }
*/
@Composable
-fun Center(@Children children: @Composable() () -> Unit) {
+fun Center(children: @Composable() () -> Unit) {
Align(alignment = Alignment.Center, children = children)
}
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/AspectRatio.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/AspectRatio.kt
index 8117ad6..2856b4b 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/AspectRatio.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/AspectRatio.kt
@@ -51,7 +51,7 @@
@Composable
fun AspectRatio(
aspectRatio: Float,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
Layout(children) { measurables, constraints ->
val size = listOf(
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/ConstrainedBox.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/ConstrainedBox.kt
index e30c9b3..7ad2633 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/ConstrainedBox.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/ConstrainedBox.kt
@@ -38,7 +38,7 @@
@Composable
fun ConstrainedBox(
constraints: DpConstraints,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
ComplexLayout(children) {
layout { measurables, incomingConstraints ->
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Container.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Container.kt
index 9027e93..dc48a4b 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Container.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Container.kt
@@ -61,7 +61,7 @@
constraints: DpConstraints = DpConstraints(),
width: Dp? = null,
height: Dp? = null,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
trace("UI:Container") {
Layout(children = children, layoutBlock = { measurables, incomingConstraints ->
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Flex.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Flex.kt
index 4860313..21a9a37 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Flex.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Flex.kt
@@ -178,7 +178,7 @@
mainAxisSize: FlexSize = FlexSize.Max,
crossAxisAlignment: CrossAxisAlignment = CrossAxisAlignment.Center,
crossAxisSize: FlexSize = FlexSize.Min,
- @Children block: @Composable() () -> Unit
+ block: @Composable() () -> Unit
) {
FlexRow(
mainAxisAlignment = mainAxisAlignment,
@@ -214,7 +214,7 @@
mainAxisSize: FlexSize = FlexSize.Max,
crossAxisAlignment: CrossAxisAlignment = CrossAxisAlignment.Center,
crossAxisSize: FlexSize = FlexSize.Min,
- @Children block: @Composable() () -> Unit
+ block: @Composable() () -> Unit
) {
FlexColumn(
mainAxisAlignment = mainAxisAlignment,
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Intrinsic.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Intrinsic.kt
index 58b8205..3bd2915 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Intrinsic.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Intrinsic.kt
@@ -42,7 +42,7 @@
* the [ConstrainedBox]s to use the same width.
*/
@Composable
-fun MinIntrinsicWidth(@Children children: @Composable() () -> Unit) {
+fun MinIntrinsicWidth(children: @Composable() () -> Unit) {
ComplexLayout(children) {
layout { measurables, constraints ->
val measurable = measurables.firstOrNull()
@@ -87,7 +87,7 @@
* the divider to use the same height.
*/
@Composable
-fun MinIntrinsicHeight(@Children children: @Composable() () -> Unit) {
+fun MinIntrinsicHeight(children: @Composable() () -> Unit) {
ComplexLayout(children) {
layout { measurables, constraints ->
val measurable = measurables.firstOrNull()
@@ -132,7 +132,7 @@
* The sample is a layout containing three widgets having the same width as the widest one.
*/
@Composable
-fun MaxIntrinsicWidth(@Children children: @Composable() () -> Unit) {
+fun MaxIntrinsicWidth(children: @Composable() () -> Unit) {
ComplexLayout(children) {
layout { measurables, constraints ->
val measurable = measurables.firstOrNull()
@@ -177,7 +177,7 @@
* and the divider to use the same height.
*/
@Composable
-fun MaxIntrinsicHeight(@Children children: @Composable() () -> Unit) {
+fun MaxIntrinsicHeight(children: @Composable() () -> Unit) {
ComplexLayout(children) {
layout { measurables, constraints ->
val measurable = measurables.firstOrNull()
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Padding.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Padding.kt
index 8e8bbe45..79225a6 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Padding.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Padding.kt
@@ -53,7 +53,7 @@
@Composable
fun Padding(
padding: EdgeInsets,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
Layout(layoutBlock = { measurables, constraints ->
val measurable = measurables.firstOrNull()
@@ -98,7 +98,7 @@
top: Dp = 0.dp,
right: Dp = 0.dp,
bottom: Dp = 0.dp,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
Padding(
padding = EdgeInsets(left = left, top = top, right = right, bottom = bottom),
@@ -121,7 +121,7 @@
@Composable
fun Padding(
padding: Dp,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
Padding(padding = EdgeInsets(padding), children = children)
}
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Scroller.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Scroller.kt
index 69bfe04..677be31 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Scroller.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Scroller.kt
@@ -52,7 +52,7 @@
private fun VerticalDragGestureDetector(
max: Px = Px.Infinity,
offsetChange: (Px) -> Unit,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val offset = +state { 0.px }
DragGestureDetector(
@@ -105,7 +105,7 @@
onScrollChanged: (position: Px, maxPosition: Px) -> Unit = { position, _ ->
scrollerPosition.position = position
},
- @Children child: @Composable() () -> Unit
+ child: @Composable() () -> Unit
) {
val maxPosition = +state { 0.px }
Layout(children = {
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Wrap.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Wrap.kt
index 5a08cf8..3846114 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Wrap.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Wrap.kt
@@ -35,7 +35,7 @@
* For a widget that does alignment and tries to be as large as possible, see [Align].
*/
@Composable
-fun Wrap(alignment: Alignment = Alignment.TopLeft, @Children children: @Composable() () -> Unit) {
+fun Wrap(alignment: Alignment = Alignment.TopLeft, children: @Composable() () -> Unit) {
Layout(layoutBlock = { measurables, constraints ->
val measurable = measurables.firstOrNull()
// The child cannot be larger than our max constraints, but we ignore min constraints.
diff --git a/ui/ui-material/api/1.0.0-alpha01.txt b/ui/ui-material/api/1.0.0-alpha01.txt
index eba63b6..df0d16a 100644
--- a/ui/ui-material/api/1.0.0-alpha01.txt
+++ b/ui/ui-material/api/1.0.0-alpha01.txt
@@ -240,6 +240,14 @@
}
+package androidx.ui.material.internal {
+
+ public final class AnchoredControllerByStateKt {
+ ctor public AnchoredControllerByStateKt();
+ }
+
+}
+
package androidx.ui.material.ripple {
public final class DefaultRippleEffectFactory implements androidx.ui.material.ripple.RippleEffectFactory {
diff --git a/ui/ui-material/api/current.txt b/ui/ui-material/api/current.txt
index eba63b6..df0d16a 100644
--- a/ui/ui-material/api/current.txt
+++ b/ui/ui-material/api/current.txt
@@ -240,6 +240,14 @@
}
+package androidx.ui.material.internal {
+
+ public final class AnchoredControllerByStateKt {
+ ctor public AnchoredControllerByStateKt();
+ }
+
+}
+
package androidx.ui.material.ripple {
public final class DefaultRippleEffectFactory implements androidx.ui.material.ripple.RippleEffectFactory {
diff --git a/ui/ui-material/api/restricted_1.0.0-alpha01.txt b/ui/ui-material/api/restricted_1.0.0-alpha01.txt
index eba63b6..df0d16a 100644
--- a/ui/ui-material/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-material/api/restricted_1.0.0-alpha01.txt
@@ -240,6 +240,14 @@
}
+package androidx.ui.material.internal {
+
+ public final class AnchoredControllerByStateKt {
+ ctor public AnchoredControllerByStateKt();
+ }
+
+}
+
package androidx.ui.material.ripple {
public final class DefaultRippleEffectFactory implements androidx.ui.material.ripple.RippleEffectFactory {
diff --git a/ui/ui-material/api/restricted_current.txt b/ui/ui-material/api/restricted_current.txt
index eba63b6..df0d16a 100644
--- a/ui/ui-material/api/restricted_current.txt
+++ b/ui/ui-material/api/restricted_current.txt
@@ -240,6 +240,14 @@
}
+package androidx.ui.material.internal {
+
+ public final class AnchoredControllerByStateKt {
+ ctor public AnchoredControllerByStateKt();
+ }
+
+}
+
package androidx.ui.material.ripple {
public final class DefaultRippleEffectFactory implements androidx.ui.material.ripple.RippleEffectFactory {
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionControlsActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionControlsActivity.kt
index a0eaee6..ded34be 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionControlsActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionControlsActivity.kt
@@ -157,11 +157,11 @@
val (checked, onChecked) = +state { false }
val (checked2, onChecked2) = +state { false }
val (checked3, onChecked3) = +state { true }
- val (checked4, onChecked4) = +state { true }
+ val (checked4, _) = +state { true }
Switch(checked = checked, onCheckedChange = onChecked)
Switch(checked = checked2, onCheckedChange = onChecked2, color = customColor)
Switch(checked = checked3, onCheckedChange = onChecked3, color = customColor2)
- Switch(checked = checked4, onCheckedChange = onChecked4, color = customColor3)
+ Switch(checked = checked4, onCheckedChange = {}, color = customColor3)
}
}
@@ -177,4 +177,4 @@
RadioButton(selected = false, color = customColor, onSelect = null)
}
}
-}
+}
\ No newline at end of file
diff --git a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/MissingMaterialComponents.kt b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/MissingMaterialComponents.kt
index bc83b65..ff66edf 100644
--- a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/MissingMaterialComponents.kt
+++ b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/MissingMaterialComponents.kt
@@ -31,7 +31,7 @@
*/
@Composable
-fun Scaffold(appBar: @Composable() () -> Unit, @Children children: @Composable() () -> Unit) {
+fun Scaffold(appBar: @Composable() () -> Unit, children: @Composable() () -> Unit) {
FlexColumn {
inflexible {
appBar()
diff --git a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyTheme.kt b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyTheme.kt
index e80124f..f1d3435 100644
--- a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyTheme.kt
+++ b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyTheme.kt
@@ -36,7 +36,7 @@
val rallyBlue = Color(0xFF72DEFF.toInt())
@Composable
-fun RallyTheme(@Children children: @Composable() () -> Unit) {
+fun RallyTheme(children: @Composable() () -> Unit) {
val colors = MaterialColors(
primary = rallyGreen,
surface = Color(0xFF26282F.toInt()),
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/RadioGroupUiTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/RadioGroupUiTest.kt
index d43cd7a..67f7537 100644
--- a/ui/ui-material/src/androidTest/java/androidx/ui/material/RadioGroupUiTest.kt
+++ b/ui/ui-material/src/androidTest/java/androidx/ui/material/RadioGroupUiTest.kt
@@ -63,7 +63,7 @@
private val options = listOf(itemOne, itemTwo, itemThree)
@Composable
- fun VerticalRadioGroupforTests(@Children children: @Composable() RadioGroupScope.() -> Unit) {
+ fun VerticalRadioGroupforTests(children: @Composable() RadioGroupScope.() -> Unit) {
RadioGroup {
Column {
children(p1 = this)
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/RippleEffectTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/RippleEffectTest.kt
index d7707a2..173bb9f 100644
--- a/ui/ui-material/src/androidTest/java/androidx/ui/material/RippleEffectTest.kt
+++ b/ui/ui-material/src/androidTest/java/androidx/ui/material/RippleEffectTest.kt
@@ -192,7 +192,7 @@
private fun RippleCallback(
onRippleDrawn: (Matrix4) -> Unit = {},
onDispose: () -> Unit = {},
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val theme = RippleTheme(testRippleEffect(onRippleDrawn, onDispose)) { Color(0) }
CurrentRippleTheme.Provider(value = theme, children = children)
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Button.kt b/ui/ui-material/src/main/java/androidx/ui/material/Button.kt
index 9ece28d..5137a35 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Button.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Button.kt
@@ -74,7 +74,7 @@
color: Color = +themeColor { primary },
border: Border? = null,
elevation: Dp = 0.dp,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val textStyle = +themeTextStyle { button }
Surface(shape = shape, color = color, border = border, elevation = elevation) {
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt b/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt
index 557ee14..547149d8 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt
@@ -19,8 +19,10 @@
import androidx.animation.PhysicsBuilder
import androidx.compose.Composable
import androidx.compose.composer
+import androidx.compose.memo
import androidx.compose.onCommit
import androidx.compose.unaryPlus
+import androidx.ui.animation.animatedFloat
import androidx.ui.core.Dp
import androidx.ui.core.IntPx
import androidx.ui.core.Layout
@@ -34,9 +36,11 @@
import androidx.ui.core.withDensity
import androidx.ui.foundation.Clickable
import androidx.ui.foundation.ColoredRect
-import androidx.ui.foundation.gestures.AnchorsFlingConfig
-import androidx.ui.foundation.gestures.AnimatedDraggable
+import androidx.ui.foundation.animation.AnchorsFlingConfig
+import androidx.ui.foundation.gestures.Draggable
+import androidx.ui.foundation.animation.AnimatedFloatDragController
import androidx.ui.foundation.gestures.DragDirection
+import androidx.ui.foundation.gestures.DraggableCallback
import androidx.ui.layout.Alignment
import androidx.ui.layout.Container
import androidx.ui.layout.DpConstraints
@@ -120,32 +124,26 @@
val maxValue = 0f
val valueByState = if (drawerState == DrawerState.Opened) maxValue else minValue
- val flingConfig = AnchorsFlingConfig(
- listOf(minValue, maxValue),
- onAnimationFinished = { finalValue, cancelled ->
- if (!cancelled) {
- onStateChange(
- if (finalValue <= minValue) DrawerState.Closed else DrawerState.Opened
- )
- }
- },
- animationBuilder = AnimationBuilder
- )
+ val callback = DraggableCallback(onDragSettled = {
+ onStateChange(if (it <= minValue) DrawerState.Closed else DrawerState.Opened)
+ })
+ val flingConfig = AnchorsFlingConfig(listOf(minValue, maxValue), AnimationBuilder)
+ val controller = +memo { AnimatedFloatDragController(valueByState, flingConfig) }
+ +onCommit(valueByState) {
+ controller.animatedFloat.animateTo(valueByState, AnimationBuilder)
+ }
- AnimatedDraggable(
+ Draggable(
dragDirection = DragDirection.Horizontal,
- startValue = valueByState,
minValue = minValue,
maxValue = maxValue,
- flingConfig = flingConfig
- ) { animatedValue ->
- +onCommit(valueByState) {
- animatedValue.animateTo(valueByState, AnimationBuilder)
- }
- val fraction = calculateFraction(minValue, maxValue, animatedValue.value)
+ valueController = controller,
+ callback = callback
+ ) { value ->
+ val fraction = calculateFraction(minValue, maxValue, value)
val scrimAlpha = fraction * ScrimDefaultOpacity
val dpOffset = +withDensity {
- animatedValue.value.toDp()
+ value.toDp()
}
Stack {
@@ -208,31 +206,30 @@
)
val valueByState = if (drawerState == DrawerState.Opened) openedValue else maxValue
val anchors = listOf(minValue, maxValue, openedValue)
+ val callback = DraggableCallback(onDragSettled = {
+ onStateChange(if (it >= maxValue) DrawerState.Closed else DrawerState.Opened)
+ })
- val onAnimationFinished = { finalValue: Float, cancelled: Boolean ->
- if (!cancelled) {
- onStateChange(
- if (finalValue >= maxValue) DrawerState.Closed else DrawerState.Opened
- )
- }
+ val flingConfig = AnchorsFlingConfig(anchors, AnimationBuilder)
+
+ val controller = +memo { AnimatedFloatDragController(valueByState, flingConfig) }
+ +onCommit(valueByState) {
+ controller.animatedFloat.animateTo(valueByState, AnimationBuilder)
}
- AnimatedDraggable(
+ Draggable(
dragDirection = DragDirection.Vertical,
- startValue = valueByState,
minValue = minValue,
maxValue = maxValue,
- flingConfig = AnchorsFlingConfig(anchors, onAnimationFinished, AnimationBuilder)
- ) { animatedValue ->
- +onCommit(valueByState) {
- animatedValue.animateTo(valueByState, AnimationBuilder)
- }
+ valueController = controller,
+ callback = callback
+ ) { value ->
// as we scroll "from height to 0" backwards, (1 - fraction) will reverse it
val fractionToOpened =
- 1 - max(0f, calculateFraction(openedValue, maxValue, animatedValue.value))
+ 1 - max(0f, calculateFraction(openedValue, maxValue, value))
val scrimAlpha = fractionToOpened * ScrimDefaultOpacity
val dpOffset = +withDensity {
- animatedValue.value.toDp()
+ value.toDp()
}
Stack {
aligned(Alignment.TopLeft) {
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/FloatingActionButton.kt b/ui/ui-material/src/main/java/androidx/ui/material/FloatingActionButton.kt
index 3ce7acf..019eb31 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/FloatingActionButton.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/FloatingActionButton.kt
@@ -64,7 +64,7 @@
shape: Shape = CircleShape,
color: Color = +themeColor { primary },
elevation: Dp = 0.dp, // TODO(Andrey) add the default elevation when it ready b/123215187
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
Button(color = color, onClick = onClick, shape = shape, elevation = elevation) {
Container(constraints = DpConstraints(minWidth = minSize, minHeight = minSize)) {
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt b/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt
index 296208b..76ce952 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt
@@ -57,7 +57,6 @@
fun MaterialTheme(
colors: MaterialColors = MaterialColors(),
typography: MaterialTypography = MaterialTypography(),
- @Children
children: @Composable() () -> Unit
) {
Colors.Provider(value = colors) {
@@ -222,7 +221,7 @@
* guidelines for descendants
*/
@Composable
-fun MaterialRippleTheme(@Children children: @Composable() () -> Unit) {
+fun MaterialRippleTheme(children: @Composable() () -> Unit) {
val materialColors = +ambient(Colors)
val defaultTheme = +memo {
RippleTheme(
@@ -287,7 +286,7 @@
* Applies the default [Shape]s for all the surfaces.
*/
@Composable
-fun MaterialButtonShapeTheme(@Children children: @Composable() () -> Unit) {
+fun MaterialButtonShapeTheme(children: @Composable() () -> Unit) {
val value = +withDensity {
Shapes(
button = RoundedCornerShape(CornerSizes(4.dp))
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/ProgressIndicator.kt b/ui/ui-material/src/main/java/androidx/ui/material/ProgressIndicator.kt
index a8485a0..e2da5bc 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/ProgressIndicator.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/ProgressIndicator.kt
@@ -282,7 +282,7 @@
}
@Composable
-private fun CircularIndicatorContainer(@Children children: @Composable() () -> Unit) {
+private fun CircularIndicatorContainer(children: @Composable() () -> Unit) {
Wrap {
Padding(CircularIndicatorPadding) {
Container(width = CircularIndicatorDiameter, height = CircularIndicatorDiameter) {
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/RadioButton.kt b/ui/ui-material/src/main/java/androidx/ui/material/RadioButton.kt
index c3bbe64..ae0f300 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/RadioButton.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/RadioButton.kt
@@ -76,7 +76,7 @@
* onOptionSelected = { ... })
*/
@Composable
-fun RadioGroup(@Children children: @Composable RadioGroupScope.() -> Unit) {
+fun RadioGroup(children: @Composable RadioGroupScope.() -> Unit) {
val scope = +memo { RadioGroupScope() }
children(p1 = scope)
}
@@ -142,7 +142,7 @@
fun RadioGroupItem(
selected: Boolean,
onSelect: () -> Unit,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
Container {
Ripple(bounded = true) {
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Switch.kt b/ui/ui-material/src/main/java/androidx/ui/material/Switch.kt
index 7654959..da22e0f 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Switch.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Switch.kt
@@ -16,27 +16,26 @@
package androidx.ui.material
-import androidx.animation.ColorPropKey
-import androidx.animation.FastOutSlowInEasing
-import androidx.animation.FloatPropKey
-import androidx.animation.TransitionSpec
-import androidx.animation.transitionDefinition
+import androidx.animation.TweenBuilder
import androidx.compose.Composable
import androidx.compose.composer
-import androidx.compose.memo
import androidx.compose.unaryPlus
-import androidx.ui.animation.Transition
import androidx.ui.core.DensityReceiver
import androidx.ui.core.Draw
import androidx.ui.core.PxSize
import androidx.ui.core.dp
+import androidx.ui.core.px
+import androidx.ui.core.withDensity
import androidx.ui.engine.geometry.Offset
+import androidx.ui.foundation.gestures.DragDirection
+import androidx.ui.foundation.gestures.Draggable
import androidx.ui.foundation.selection.Toggleable
import androidx.ui.foundation.selection.ToggleableState
import androidx.ui.graphics.Color
import androidx.ui.layout.Container
import androidx.ui.layout.Padding
import androidx.ui.layout.Wrap
+import androidx.ui.material.internal.anchoredControllerByState
import androidx.ui.material.ripple.Ripple
import androidx.ui.painting.Canvas
import androidx.ui.painting.Paint
@@ -63,9 +62,7 @@
Ripple(bounded = false) {
Toggleable(value = value, onToggle = onCheckedChange?.let { { it(!checked) } }) {
Padding(padding = DefaultSwitchPadding) {
- Container(width = SwitchWidth, height = SwitchHeight) {
- DrawSwitch(checked = checked, checkedThumbColor = color)
- }
+ SwitchImpl(checked, onCheckedChange, color)
}
}
}
@@ -73,49 +70,58 @@
}
@Composable
-private fun DrawSwitch(checked: Boolean, checkedThumbColor: Color) {
- val uncheckedThumbColor = +themeColor { surface }
- val transDef = +memo(checkedThumbColor, uncheckedThumbColor) {
- generateTransitionDefinition(checkedThumbColor, uncheckedThumbColor)
+private fun SwitchImpl(checked: Boolean, onCheckedChange: ((Boolean) -> Unit)?, color: Color) {
+ val minBound = 0f
+ val maxBound = +withDensity { ThumbPathLength.toPx().value }
+ val (controller, callback) =
+ +anchoredControllerByState(
+ state = checked,
+ onStateChange = onCheckedChange ?: {},
+ anchorsToState = listOf(minBound to false, maxBound to true),
+ animationBuilder = AnimationBuilder
+ )
+ Draggable(
+ dragDirection = DragDirection.Horizontal,
+ minValue = minBound,
+ maxValue = maxBound,
+ valueController = controller,
+ callback = callback
+ ) { thumbPosition ->
+ Container(width = SwitchWidth, height = SwitchHeight, expanded = true) {
+ DrawSwitch(
+ checked = checked,
+ checkedThumbColor = color,
+ thumbPosition = thumbPosition
+ )
+ }
}
+}
+
+@Composable
+private fun DrawSwitch(checked: Boolean, checkedThumbColor: Color, thumbPosition: Float) {
+ val thumbColor = if (checked) checkedThumbColor else +themeColor { surface }
val trackColor = if (checked) {
checkedThumbColor.copy(alpha = CheckedTrackOpacity)
} else {
(+themeColor { onSurface }).copy(alpha = UncheckedTrackOpacity)
}
- DrawTrack(color = trackColor)
- Transition(definition = transDef, toState = checked) { state ->
- DrawThumb(
- color = state[ThumbColorProp],
- relativePosition = state[RelativeThumbTranslationProp]
- )
- }
-}
-
-@Composable
-private fun DrawTrack(color: Color) {
Draw { canvas, parentSize ->
- drawTrack(canvas, parentSize, color)
- }
-}
-
-@Composable
-private fun DrawThumb(relativePosition: Float, color: Color) {
- Draw { canvas, parentSize ->
- drawThumb(canvas, parentSize, relativePosition, color)
+ drawTrack(canvas, parentSize, trackColor)
+ drawThumb(canvas, parentSize, thumbPosition, thumbColor)
}
}
private fun DensityReceiver.drawTrack(
canvas: Canvas,
parentSize: PxSize,
- color: Color
+ trackColor: Color
) {
- val paint = Paint()
- paint.isAntiAlias = true
- paint.color = color
- paint.strokeCap = StrokeCap.round
- paint.strokeWidth = TrackStrokeWidth.toPx().value
+ val paint = Paint().apply {
+ isAntiAlias = true
+ color = trackColor
+ strokeCap = StrokeCap.round
+ strokeWidth = TrackStrokeWidth.toPx().value
+ }
val strokeRadius = TrackStrokeWidth / 2
val centerHeight = parentSize.height / 2
@@ -130,50 +136,19 @@
private fun DensityReceiver.drawThumb(
canvas: Canvas,
parentSize: PxSize,
- relativePosition: Float,
- color: Color
+ position: Float,
+ thumbColor: Color
) {
- val paint = Paint()
- paint.isAntiAlias = true
- paint.color = color
-
- val centerHeight = parentSize.height / 2
- val thumbRadius = ThumbDiameter / 2
- val thumbPathWidth = TrackWidth - ThumbDiameter
-
- val start = thumbRadius + thumbPathWidth * relativePosition
- canvas.drawCircle(
- Offset(start.toPx().value, centerHeight.value),
- // TODO(malkov): wierd +1 but necessary in order to properly cover track. Investigate
- thumbRadius.toPx().value + 1,
- paint
- )
-}
-
-private val RelativeThumbTranslationProp = FloatPropKey()
-private val ThumbColorProp = ColorPropKey()
-private const val SwitchAnimationDuration = 100
-
-private fun generateTransitionDefinition(checkedColor: Color, uncheckedColor: Color) =
- transitionDefinition {
- fun <T> TransitionSpec<Boolean>.switchTween() = tween<T> {
- duration = SwitchAnimationDuration
- easing = FastOutSlowInEasing
- }
-
- state(false) {
- this[RelativeThumbTranslationProp] = 0f
- this[ThumbColorProp] = uncheckedColor
- }
- state(true) {
- this[RelativeThumbTranslationProp] = 1f
- this[ThumbColorProp] = checkedColor
- }
- transition {
- RelativeThumbTranslationProp using switchTween()
- ThumbColorProp using switchTween()
- }
+ val paint = Paint().apply {
+ isAntiAlias = true
+ color = thumbColor
}
+ val centerHeight = parentSize.height / 2
+ val thumbRadius = (ThumbDiameter / 2).toPx().value
+ val x = position.px.value + thumbRadius
+
+ canvas.drawCircle(Offset(x, centerHeight.value), thumbRadius, paint)
+}
private val CheckedTrackOpacity = 0.54f
private val UncheckedTrackOpacity = 0.38f
@@ -186,4 +161,7 @@
// TODO(malkov): clarify this padding for Switch
private val DefaultSwitchPadding = 2.dp
private val SwitchWidth = TrackWidth
-private val SwitchHeight = ThumbDiameter
\ No newline at end of file
+private val SwitchHeight = ThumbDiameter
+private val ThumbPathLength = TrackWidth - ThumbDiameter
+
+private val AnimationBuilder = TweenBuilder<Float>().apply { duration = 100 }
\ No newline at end of file
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/internal/AnchoredControllerByState.kt b/ui/ui-material/src/main/java/androidx/ui/material/internal/AnchoredControllerByState.kt
new file mode 100644
index 0000000..c8416a3
--- /dev/null
+++ b/ui/ui-material/src/main/java/androidx/ui/material/internal/AnchoredControllerByState.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2019 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.ui.material.internal
+
+import androidx.animation.AnimationBuilder
+import androidx.compose.composer
+import androidx.compose.effectOf
+import androidx.compose.memo
+import androidx.compose.onCommit
+import androidx.compose.state
+import androidx.ui.foundation.gestures.Draggable
+import androidx.ui.foundation.animation.AnchorsFlingConfig
+import androidx.ui.foundation.animation.AnimatedFloatDragController
+import androidx.ui.foundation.gestures.DragValueController
+import androidx.ui.foundation.gestures.DraggableCallback
+
+/**
+ * Effect to create special version if [AnimatedFloatDragController] that maps
+ * float anchors to states
+ *
+ * Alongside with control that [AnimatedFloatDragController] provides, this component ensures that:
+ * 1. The AnimatedFloat value will be in sync with call site state
+ * 2. When the anchor is reached, [onStateChange] will be called with state mapped to this anchor
+ * 3. When the anchor is reached and [onStateChange] with corresponding state is called, but
+ * call site didn't update state to the reached one for some reason,
+ * this component performs rollback to the previous (correct) state.
+ * 4. When new [state] is provided, component will be animated to state's anchor
+ *
+ * @param T type with which state is represented
+ * @param state current state to represent Float value with
+ * @param onStateChange callback to update call site's state
+ * @param anchorsToState pairs of anchors to states to map anchors to state and vise versa
+ * @param animationBuilder animation which will be used for animations
+ * @return pair of controller and callback to pass to [Draggable].
+ */
+// TODO(malkov/tianliu) (figure our how to make it better and make public)
+internal fun <T> anchoredControllerByState(
+ state: T,
+ onStateChange: (T) -> Unit,
+ anchorsToState: List<Pair<Float, T>>,
+ animationBuilder: AnimationBuilder<Float>
+) = effectOf<Pair<DragValueController, DraggableCallback>> {
+ val anchors = +memo(anchorsToState) { anchorsToState.map { it.first } }
+ val currentValue = anchorsToState.firstOrNull { it.second == state }!!.first
+
+ // This state is to force this component to be recomposed and trigger +onCommit below
+ // This is needed to stay in sync with drag state that caller side holds
+ val forceAnimationCheck = +state { true }
+ val callback = DraggableCallback(onDragSettled = { finalValue ->
+ val newState = anchorsToState.firstOrNull { it.first == finalValue }?.second
+ if (newState != null && newState != state) {
+ onStateChange(newState)
+ forceAnimationCheck.value = !forceAnimationCheck.value
+ }
+ })
+
+ val controller = +memo(anchorsToState, animationBuilder) {
+ AnimatedFloatDragController(currentValue, AnchorsFlingConfig(anchors, animationBuilder))
+ }
+ +onCommit(currentValue, forceAnimationCheck.value) {
+ controller.animatedFloat.animateTo(currentValue, animationBuilder)
+ }
+ controller to callback
+}
\ No newline at end of file
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/ripple/Ripple.kt b/ui/ui-material/src/main/java/androidx/ui/material/ripple/Ripple.kt
index 139a554..382e2ab 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/ripple/Ripple.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/ripple/Ripple.kt
@@ -60,7 +60,7 @@
fun Ripple(
bounded: Boolean,
radius: Dp? = null,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val density = +ambientDensity()
val rippleSurface = +ambientRippleSurface()
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/ripple/RippleSurface.kt b/ui/ui-material/src/main/java/androidx/ui/material/ripple/RippleSurface.kt
index 48d547f..aa0dd8c 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/ripple/RippleSurface.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/ripple/RippleSurface.kt
@@ -87,7 +87,7 @@
@Composable
fun RippleSurface(
color: Color?,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val owner = +memo { RippleSurfaceOwnerImpl() }
owner.backgroundColor = color
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/surface/Card.kt b/ui/ui-material/src/main/java/androidx/ui/material/surface/Card.kt
index 52f1f13f..544666b 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/surface/Card.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/surface/Card.kt
@@ -49,7 +49,7 @@
color: Color = +themeColor { surface },
border: Border? = null,
elevation: Dp = 0.dp,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
// TODO(Andrey: This currently adds no logic on top of Surface, I just reserve the name
// for now. We will see what will be the additional Card specific logic later.
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/surface/Surface.kt b/ui/ui-material/src/main/java/androidx/ui/material/surface/Surface.kt
index 0981820..8a75fb3 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/surface/Surface.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/surface/Surface.kt
@@ -84,7 +84,7 @@
color: Color = +themeColor { surface },
border: Border? = null,
elevation: Dp = 0.dp,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
SurfaceLayout {
DrawShadow(shape = shape, elevation = elevation)
@@ -110,7 +110,7 @@
* TODO("Andrey: Should be replaced with some basic layout implementation when we have it")
*/
@Composable
-private fun SurfaceLayout(@Children children: @Composable() () -> Unit) {
+private fun SurfaceLayout(children: @Composable() () -> Unit) {
Layout(children = children, layoutBlock = { measurables, constraints ->
if (measurables.size > 1) {
throw IllegalStateException("Surface can have only one direct measurable child!")
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/surface/TransparentSurface.kt b/ui/ui-material/src/main/java/androidx/ui/material/surface/TransparentSurface.kt
index 29aea48..f68b95c 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/surface/TransparentSurface.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/surface/TransparentSurface.kt
@@ -38,7 +38,7 @@
@Composable
fun TransparentSurface(
shape: Shape = RectangleShape,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
Surface(shape = shape, children = children, color = Color.Transparent)
}
diff --git a/ui/ui-platform/src/main/java/androidx/ui/core/input/TextInputServiceAndroid.kt b/ui/ui-platform/src/main/java/androidx/ui/core/input/TextInputServiceAndroid.kt
index 0989de0..e77e070 100644
--- a/ui/ui-platform/src/main/java/androidx/ui/core/input/TextInputServiceAndroid.kt
+++ b/ui/ui-platform/src/main/java/androidx/ui/core/input/TextInputServiceAndroid.kt
@@ -24,7 +24,7 @@
import android.view.inputmethod.InputMethodManager
import androidx.ui.engine.geometry.Rect
import androidx.ui.input.EditOperation
-import androidx.ui.input.EditorState
+import androidx.ui.input.EditorModel
import androidx.ui.input.ImeAction
import androidx.ui.input.InputEventListener
import androidx.ui.input.KeyboardType
@@ -86,14 +86,14 @@
fun isEditorFocused(): Boolean = editorHasFocus
override fun startInput(
- initState: EditorState,
+ initModel: EditorModel,
keyboardType: KeyboardType,
imeAction: ImeAction,
onEditCommand: (List<EditOperation>) -> Unit,
onImeActionPerformed: (ImeAction) -> Unit
) {
editorHasFocus = true
- state = initState.toInputState()
+ state = initModel.toInputState()
this.keyboardType = keyboardType
this.imeAction = imeAction
this.onEditCommand = onEditCommand
@@ -118,8 +118,8 @@
imm.showSoftInput(view, 0)
}
- override fun onStateUpdated(state: EditorState) {
- this.state = state.toInputState()
+ override fun onStateUpdated(model: EditorModel) {
+ this.state = model.toInputState()
ic?.updateInputState(this.state, imm, view)
}
@@ -178,7 +178,7 @@
}
}
-private fun EditorState.toInputState(): InputState =
+private fun EditorModel.toInputState(): InputState =
InputState(
text = text, // TODO(nona): call toString once AnnotatedString is in use.
selection = selection,
diff --git a/ui/ui-platform/src/test/java/androidx/ui/core/input/TextInputServiceAndroidTest.kt b/ui/ui-platform/src/test/java/androidx/ui/core/input/TextInputServiceAndroidTest.kt
index 575460d..0d32cf82 100644
--- a/ui/ui-platform/src/test/java/androidx/ui/core/input/TextInputServiceAndroidTest.kt
+++ b/ui/ui-platform/src/test/java/androidx/ui/core/input/TextInputServiceAndroidTest.kt
@@ -22,7 +22,7 @@
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import androidx.test.filters.SmallTest
-import androidx.ui.input.EditorState
+import androidx.ui.input.EditorModel
import androidx.ui.input.ImeAction
import androidx.ui.input.KeyboardType
import com.nhaarman.mockitokotlin2.eq
@@ -54,7 +54,7 @@
@Test
fun test_fill_editor_info_text() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Text,
ImeAction.Unspecified,
onEditCommand = {},
@@ -71,7 +71,7 @@
@Test
fun test_fill_editor_info_ascii() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Ascii,
ImeAction.Unspecified,
onEditCommand = {},
@@ -89,7 +89,7 @@
@Test
fun test_fill_editor_info_number() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Number,
ImeAction.Unspecified,
onEditCommand = {},
@@ -106,7 +106,7 @@
@Test
fun test_fill_editor_info_phone() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Phone,
ImeAction.Unspecified,
onEditCommand = {},
@@ -123,7 +123,7 @@
@Test
fun test_fill_editor_info_uri() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Uri,
ImeAction.Unspecified,
onEditCommand = {},
@@ -141,7 +141,7 @@
@Test
fun test_fill_editor_info_email() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Email,
ImeAction.Unspecified,
onEditCommand = {},
@@ -159,7 +159,7 @@
@Test
fun test_fill_editor_info_password() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Password,
ImeAction.Unspecified,
onEditCommand = {},
@@ -177,7 +177,7 @@
@Test
fun test_fill_editor_info_number_password() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.NumberPassword,
ImeAction.Unspecified,
onEditCommand = {},
@@ -195,7 +195,7 @@
@Test
fun test_fill_editor_info_action_none() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Ascii,
ImeAction.NoAction,
onEditCommand = {},
@@ -213,7 +213,7 @@
@Test
fun test_fill_editor_info_action_go() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Ascii,
ImeAction.Go,
onEditCommand = {},
@@ -231,7 +231,7 @@
@Test
fun test_fill_editor_info_action_next() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Ascii,
ImeAction.Next,
onEditCommand = {},
@@ -249,7 +249,7 @@
@Test
fun test_fill_editor_info_action_previous() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Ascii,
ImeAction.Previous,
onEditCommand = {},
@@ -267,7 +267,7 @@
@Test
fun test_fill_editor_info_action_search() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Ascii,
ImeAction.Search,
onEditCommand = {},
@@ -285,7 +285,7 @@
@Test
fun test_fill_editor_info_action_send() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Ascii,
ImeAction.Send,
onEditCommand = {},
@@ -303,7 +303,7 @@
@Test
fun test_fill_editor_info_action_done() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Ascii,
ImeAction.Done,
onEditCommand = {},
diff --git a/ui/ui-text/api/1.0.0-alpha01.txt b/ui/ui-text/api/1.0.0-alpha01.txt
index 59a09a0..a37b2f2 100644
--- a/ui/ui-text/api/1.0.0-alpha01.txt
+++ b/ui/ui-text/api/1.0.0-alpha01.txt
@@ -1,8 +1,8 @@
// Signature format: 3.0
package androidx.ui.input {
- public final class EditorState {
- ctor public EditorState(String text, androidx.ui.text.TextRange selection);
+ public final class EditorModel {
+ ctor public EditorModel(String text, androidx.ui.text.TextRange selection);
method public androidx.ui.text.TextRange? getComposition();
method public androidx.ui.text.TextRange getSelection();
method public String getText();
@@ -38,10 +38,12 @@
package androidx.ui.text {
public final class AnnotatedString {
- ctor public AnnotatedString(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles);
+ ctor public AnnotatedString(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> paragraphStyles);
method public String component1();
method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> component2();
- method public androidx.ui.text.AnnotatedString copy(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles);
+ method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> component3();
+ method public androidx.ui.text.AnnotatedString copy(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> paragraphStyles);
+ method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> getParagraphStyles();
method public String getText();
method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> getTextStyles();
}
@@ -76,6 +78,10 @@
method public String _canonicalizeRegionCode(String regionCode);
}
+ public final class MultiParagraphKt {
+ ctor public MultiParagraphKt();
+ }
+
public interface Paragraph {
method public float getBaseline();
method public androidx.ui.engine.geometry.Rect getCursorRect(int offset);
@@ -127,6 +133,7 @@
method public androidx.ui.text.style.TextAlign? getTextAlign();
method public androidx.ui.text.style.TextDirection? getTextDirection();
method public androidx.ui.text.style.TextIndent? getTextIndent();
+ method public androidx.ui.text.ParagraphStyle merge(androidx.ui.text.ParagraphStyle? other = null);
}
public final class TextPainter {
diff --git a/ui/ui-text/api/api_lint.ignore b/ui/ui-text/api/api_lint.ignore
index 577c67f..26f0fc9 100644
--- a/ui/ui-text/api/api_lint.ignore
+++ b/ui/ui-text/api/api_lint.ignore
@@ -48,12 +48,8 @@
KotlinOperator: androidx.ui.text.font.FontFamily#contains(androidx.ui.text.font.Font):
- Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.ui.text.font.FontFamily#get(int):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
+ Note that adding the `operator` keyword would allow calling this method using operator syntax
KotlinOperator: androidx.ui.text.font.FontFamilyList#contains(androidx.ui.text.font.FontFamily):
- Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.ui.text.font.FontFamilyList#get(int):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
+ Note that adding the `operator` keyword would allow calling this method using operator syntax
KotlinOperator: androidx.ui.text.style.TextDecoration#contains(androidx.ui.text.style.TextDecoration):
- Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
+ Note that adding the `operator` keyword would allow calling this method using operator syntax
diff --git a/ui/ui-text/api/current.txt b/ui/ui-text/api/current.txt
index 59a09a0..a37b2f2 100644
--- a/ui/ui-text/api/current.txt
+++ b/ui/ui-text/api/current.txt
@@ -1,8 +1,8 @@
// Signature format: 3.0
package androidx.ui.input {
- public final class EditorState {
- ctor public EditorState(String text, androidx.ui.text.TextRange selection);
+ public final class EditorModel {
+ ctor public EditorModel(String text, androidx.ui.text.TextRange selection);
method public androidx.ui.text.TextRange? getComposition();
method public androidx.ui.text.TextRange getSelection();
method public String getText();
@@ -38,10 +38,12 @@
package androidx.ui.text {
public final class AnnotatedString {
- ctor public AnnotatedString(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles);
+ ctor public AnnotatedString(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> paragraphStyles);
method public String component1();
method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> component2();
- method public androidx.ui.text.AnnotatedString copy(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles);
+ method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> component3();
+ method public androidx.ui.text.AnnotatedString copy(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> paragraphStyles);
+ method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> getParagraphStyles();
method public String getText();
method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> getTextStyles();
}
@@ -76,6 +78,10 @@
method public String _canonicalizeRegionCode(String regionCode);
}
+ public final class MultiParagraphKt {
+ ctor public MultiParagraphKt();
+ }
+
public interface Paragraph {
method public float getBaseline();
method public androidx.ui.engine.geometry.Rect getCursorRect(int offset);
@@ -127,6 +133,7 @@
method public androidx.ui.text.style.TextAlign? getTextAlign();
method public androidx.ui.text.style.TextDirection? getTextDirection();
method public androidx.ui.text.style.TextIndent? getTextIndent();
+ method public androidx.ui.text.ParagraphStyle merge(androidx.ui.text.ParagraphStyle? other = null);
}
public final class TextPainter {
diff --git a/ui/ui-text/api/restricted_1.0.0-alpha01.txt b/ui/ui-text/api/restricted_1.0.0-alpha01.txt
index cb841ce..d02f68726 100644
--- a/ui/ui-text/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-text/api/restricted_1.0.0-alpha01.txt
@@ -8,8 +8,8 @@
- public final class EditorState {
- ctor public EditorState(String text, androidx.ui.text.TextRange selection);
+ public final class EditorModel {
+ ctor public EditorModel(String text, androidx.ui.text.TextRange selection);
method public androidx.ui.text.TextRange? getComposition();
method public androidx.ui.text.TextRange getSelection();
method public String getText();
@@ -52,10 +52,12 @@
package androidx.ui.text {
public final class AnnotatedString {
- ctor public AnnotatedString(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles);
+ ctor public AnnotatedString(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> paragraphStyles);
method public String component1();
method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> component2();
- method public androidx.ui.text.AnnotatedString copy(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles);
+ method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> component3();
+ method public androidx.ui.text.AnnotatedString copy(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> paragraphStyles);
+ method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> getParagraphStyles();
method public String getText();
method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> getTextStyles();
}
@@ -90,6 +92,10 @@
method public String _canonicalizeRegionCode(String regionCode);
}
+ public final class MultiParagraphKt {
+ ctor public MultiParagraphKt();
+ }
+
public interface Paragraph {
method public float getBaseline();
method public androidx.ui.engine.geometry.Rect getCursorRect(int offset);
@@ -141,6 +147,7 @@
method public androidx.ui.text.style.TextAlign? getTextAlign();
method public androidx.ui.text.style.TextDirection? getTextDirection();
method public androidx.ui.text.style.TextIndent? getTextIndent();
+ method public androidx.ui.text.ParagraphStyle merge(androidx.ui.text.ParagraphStyle? other = null);
}
public final class TextPainter {
diff --git a/ui/ui-text/api/restricted_current.txt b/ui/ui-text/api/restricted_current.txt
index cb841ce..d02f68726 100644
--- a/ui/ui-text/api/restricted_current.txt
+++ b/ui/ui-text/api/restricted_current.txt
@@ -8,8 +8,8 @@
- public final class EditorState {
- ctor public EditorState(String text, androidx.ui.text.TextRange selection);
+ public final class EditorModel {
+ ctor public EditorModel(String text, androidx.ui.text.TextRange selection);
method public androidx.ui.text.TextRange? getComposition();
method public androidx.ui.text.TextRange getSelection();
method public String getText();
@@ -52,10 +52,12 @@
package androidx.ui.text {
public final class AnnotatedString {
- ctor public AnnotatedString(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles);
+ ctor public AnnotatedString(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> paragraphStyles);
method public String component1();
method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> component2();
- method public androidx.ui.text.AnnotatedString copy(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles);
+ method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> component3();
+ method public androidx.ui.text.AnnotatedString copy(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> paragraphStyles);
+ method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> getParagraphStyles();
method public String getText();
method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> getTextStyles();
}
@@ -90,6 +92,10 @@
method public String _canonicalizeRegionCode(String regionCode);
}
+ public final class MultiParagraphKt {
+ ctor public MultiParagraphKt();
+ }
+
public interface Paragraph {
method public float getBaseline();
method public androidx.ui.engine.geometry.Rect getCursorRect(int offset);
@@ -141,6 +147,7 @@
method public androidx.ui.text.style.TextAlign? getTextAlign();
method public androidx.ui.text.style.TextDirection? getTextDirection();
method public androidx.ui.text.style.TextIndent? getTextIndent();
+ method public androidx.ui.text.ParagraphStyle merge(androidx.ui.text.ParagraphStyle? other = null);
}
public final class TextPainter {
diff --git a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneInputField.kt b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneInputField.kt
index c71370d..b345b40 100644
--- a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneInputField.kt
+++ b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneInputField.kt
@@ -21,9 +21,8 @@
import androidx.compose.state
import androidx.compose.unaryPlus
import androidx.ui.core.EditorStyle
-import androidx.ui.core.InputField
-import androidx.ui.text.TextRange
-import androidx.ui.input.EditorState
+import androidx.ui.core.TextField
+import androidx.ui.input.EditorModel
import androidx.ui.input.ImeAction
import androidx.ui.input.KeyboardType
import androidx.ui.layout.Column
@@ -79,8 +78,8 @@
keyboardType: KeyboardType = KeyboardType.Text,
imeAction: ImeAction = ImeAction.Unspecified
) {
- val state = +state { EditorState() }
- InputField(
+ val state = +state { EditorModel() }
+ TextField(
value = state.value,
keyboardType = keyboardType,
imeAction = imeAction,
diff --git a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneText.kt b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneText.kt
index faaf76d..a0e9bda 100644
--- a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneText.kt
+++ b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneText.kt
@@ -47,6 +47,8 @@
import androidx.ui.text.style.TextOverflow
import androidx.ui.core.Sp
import androidx.ui.core.sp
+import androidx.ui.text.AnnotatedString
+import androidx.ui.text.style.TextIndent
val displayText = "Text Demo"
val displayTextChinese = "文本演示"
@@ -99,6 +101,16 @@
TextDemoComposableTextSpan()
TagLine(tag = "fontSizeScale")
TextDemoFontSizeScale()
+ TagLine(tag = "multiple paragraphs basic")
+ TextDemoParagraph()
+ TagLine(tag = "multiple paragraphs TextAlign")
+ TextDemoParagraphTextAlign()
+ TagLine(tag = "multiple paragraphs line height")
+ TextDemoParagraphLineHeight()
+ TagLine(tag = "multiple paragraphs TextIndent")
+ TextDemoParagraphIndent()
+ TagLine(tag = "multiple paragraphs TextDirection")
+ TextDemoParagraphTextDirection()
}
}
}
@@ -685,3 +697,131 @@
}
}
}
+
+@Composable
+fun TextDemoParagraph() {
+ val text1 = "paragraph1 paragraph1 paragraph1 paragraph1 paragraph1"
+ val text2 = "paragraph2 paragraph2 paragraph2 paragraph2 paragraph2"
+ Text(
+ text = AnnotatedString(
+ text = text1 + text2,
+ textStyles = listOf(),
+ paragraphStyles = listOf(
+ AnnotatedString.Item(ParagraphStyle(), text1.length, text1.length)
+ )
+ ),
+ style = TextStyle(fontSize = fontSize8)
+ )
+}
+
+@Composable
+fun TextDemoParagraphTextAlign() {
+ var text = ""
+ val paragraphStyles = mutableListOf<AnnotatedString.Item<ParagraphStyle>>()
+ TextAlign.values().map { textAlign ->
+ val str = List(4) { "TextAlign.$textAlign" }.joinToString(" ")
+ val paragraphStyle = ParagraphStyle(textAlign = textAlign)
+ Pair(str, paragraphStyle)
+ }.forEach { (str, paragraphStyle) ->
+ paragraphStyles.add(
+ AnnotatedString.Item(
+ paragraphStyle,
+ text.length,
+ text.length + str.length
+ )
+ )
+ text += str
+ }
+
+ Text(
+ text = AnnotatedString(
+ text = text,
+ textStyles = listOf(),
+ paragraphStyles = paragraphStyles
+ ),
+ style = TextStyle(fontSize = fontSize8)
+ )
+}
+
+@Composable
+fun TextDemoParagraphLineHeight() {
+ val text1 = "LineHeight=1.0f LineHeight=1.0f LineHeight=1.0f LineHeight=1.0f"
+ val text2 = "LineHeight=1.5f LineHeight=1.5f LineHeight=1.5f LineHeight=1.5f"
+ val text3 = "LineHeight=3.0f LineHeight=3.0f LineHeight=3.0f LineHeight=3.0f"
+
+ Text(
+ text = AnnotatedString(
+ text = text1 + text2 + text3,
+ textStyles = listOf(),
+ paragraphStyles = listOf(
+ AnnotatedString.Item(
+ ParagraphStyle(lineHeight = 1.0f),
+ 0,
+ text1.length
+ ),
+ AnnotatedString.Item(
+ ParagraphStyle(lineHeight = 1.5f),
+ text1.length,
+ text1.length + text2.length
+ ),
+ AnnotatedString.Item(
+ ParagraphStyle(lineHeight = 2f),
+ text1.length + text2.length,
+ text1.length + text2.length + text3.length
+ )
+ )
+ ),
+ style = TextStyle(fontSize = fontSize8)
+ )
+}
+
+@Composable
+fun TextDemoParagraphIndent() {
+ val text1 = "TextIndent firstLine TextIndent firstLine TextIndent firstLine"
+ val text2 = "TextIndent restLine TextIndent restLine TextIndent restLine"
+
+ Text(
+ text = AnnotatedString(
+ text = text1 + text2,
+ textStyles = listOf(),
+ paragraphStyles = listOf(
+ AnnotatedString.Item(
+ ParagraphStyle(textIndent = TextIndent(firstLine = 100.px)),
+ 0,
+ text1.length
+ ),
+ AnnotatedString.Item(
+ ParagraphStyle(textIndent = TextIndent(restLine = 100.px)),
+ text1.length,
+ text1.length + text2.length
+ )
+ )
+ ),
+ style = TextStyle(fontSize = fontSize8)
+ )
+}
+
+@Composable
+fun TextDemoParagraphTextDirection() {
+ val ltrText = "Hello World! Hello World! Hello World! Hello World! Hello World!"
+ val rtlText = "مرحبا بالعالم مرحبا بالعالم مرحبا بالعالم مرحبا بالعالم مرحبا بالعالم"
+ Text(
+ text = AnnotatedString(
+ text = ltrText + rtlText,
+ textStyles = listOf(),
+ paragraphStyles = listOf(
+ AnnotatedString.Item(
+ ParagraphStyle(textDirection = TextDirection.Ltr),
+ 0,
+ ltrText.length
+ ),
+ AnnotatedString.Item(
+ ParagraphStyle(textDirection = TextDirection.Rtl),
+ ltrText.length,
+ ltrText.length + rtlText.length
+ )
+ )
+ ),
+ style = TextStyle(fontSize = fontSize8)
+ )
+}
\ No newline at end of file
diff --git a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputField.kt b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputField.kt
index 1f46d90..47b3abb 100644
--- a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputField.kt
+++ b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputField.kt
@@ -21,12 +21,16 @@
import androidx.compose.state
import androidx.compose.unaryPlus
import androidx.ui.core.EditorStyle
-import androidx.ui.core.InputField
+import androidx.ui.core.Layout
import androidx.ui.core.OffsetMap
import androidx.ui.core.PasswordVisualTransformation
+import androidx.ui.core.Text
+import androidx.ui.core.TextField
import androidx.ui.core.TransformedText
import androidx.ui.core.VisualTransformation
-import androidx.ui.input.EditorState
+import androidx.ui.core.ipx
+import androidx.ui.graphics.Color
+import androidx.ui.input.EditorModel
import androidx.ui.input.ImeAction
import androidx.ui.input.KeyboardType
import androidx.ui.layout.Column
@@ -220,6 +224,17 @@
keyboardType = KeyboardType.Email,
visualTransformation = emailFilter
)
+
+ TagLine(tag = "Editfield with Hint Text")
+ HintEditText @Composable {
+ Text(
+ text = "Hint Text",
+ style = TextStyle(
+ color = Color(0xFF888888.toInt()),
+ fontSize = fontSize8
+ )
+ )
+ }
}
}
}
@@ -228,11 +243,11 @@
fun VariousEditLine(
keyboardType: KeyboardType = KeyboardType.Text,
imeAction: ImeAction = ImeAction.Unspecified,
- onValueChange: (EditorState, EditorState) -> EditorState = { _, new -> new },
+ onValueChange: (EditorModel, EditorModel) -> EditorModel = { _, new -> new },
visualTransformation: VisualTransformation
) {
- val state = +state { EditorState() }
- InputField(
+ val state = +state { EditorModel() }
+ TextField(
value = state.value,
keyboardType = keyboardType,
imeAction = imeAction,
@@ -241,3 +256,32 @@
editorStyle = EditorStyle(textStyle = TextStyle(fontSize = fontSize8))
)
}
+
+@Composable
+fun HintEditText(hintText: @Composable() () -> Unit) {
+ val state = +state { EditorModel() }
+
+ val inputField = @Composable {
+ TextField(
+ value = state.value,
+ onValueChange = { state.value = it },
+ editorStyle = EditorStyle(textStyle = TextStyle(fontSize = fontSize8))
+ )
+ }
+
+ if (state.value.text.isNotEmpty()) {
+ inputField()
+ } else {
+ Layout(
+ childrenArray = arrayOf(inputField, hintText),
+ layoutBlock = { measurable, constraints ->
+ val inputfieldPlacable = measurable[inputField].first().measure(constraints)
+ val hintTextPlacable = measurable[hintText].first().measure(constraints)
+ layout(inputfieldPlacable.width, inputfieldPlacable.height) {
+ inputfieldPlacable.place(0.ipx, 0.ipx)
+ hintTextPlacable.place(0.ipx, 0.ipx)
+ }
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-text/src/androidTest/java/androidx/ui/text/MultiParagraphIntegrationTest.kt b/ui/ui-text/src/androidTest/java/androidx/ui/text/MultiParagraphIntegrationTest.kt
new file mode 100644
index 0000000..75c21b6
--- /dev/null
+++ b/ui/ui-text/src/androidTest/java/androidx/ui/text/MultiParagraphIntegrationTest.kt
@@ -0,0 +1,1925 @@
+/*
+ * Copyright 2019 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.ui.text
+
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import androidx.test.filters.Suppress
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.ui.core.Density
+import androidx.ui.core.PxPosition
+import androidx.ui.core.Sp
+import androidx.ui.core.px
+import androidx.ui.core.sp
+import androidx.ui.core.withDensity
+import androidx.ui.engine.geometry.Rect
+import androidx.ui.text.style.TextDirection
+import androidx.ui.text.FontTestData.Companion.BASIC_MEASURE_FONT
+import androidx.ui.text.font.FontFamily
+import androidx.ui.text.font.asFontFamily
+import androidx.ui.painting.Path
+import androidx.ui.painting.PathOperation
+import androidx.ui.text.style.TextAlign
+import androidx.ui.text.style.TextIndent
+import com.nhaarman.mockitokotlin2.mock
+import org.hamcrest.Matchers.equalTo
+import org.junit.Assert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+@SmallTest
+class MultiParagraphIntegrationTest {
+ private val fontFamilyMeasureFont = BASIC_MEASURE_FONT.asFontFamily()
+ private val context = InstrumentationRegistry.getInstrumentation().context
+ private val defaultDensity = Density(density = 1f)
+ private val cursorWidth = 4f
+
+ @Test
+ fun empty_string() {
+ withDensity(defaultDensity) {
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val text = ""
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = 100.0f))
+
+ assertThat(paragraph.width, equalTo(100.0f))
+
+ assertThat(paragraph.height, equalTo(fontSizeInPx))
+ // defined in sample_font
+ assertThat(paragraph.baseline, equalTo(fontSizeInPx * 0.8f))
+ assertThat(paragraph.maxIntrinsicWidth, equalTo(0.0f))
+ assertThat(paragraph.minIntrinsicWidth, equalTo(0.0f))
+ // TODO(Migration/siyamed): no baseline query per line?
+ // TODO(Migration/siyamed): no line count?
+ }
+ }
+
+ @Test
+ fun single_line_default_values() {
+ withDensity(defaultDensity) {
+ val fontSize = 50.sp
+ val fontSizeInpx = fontSize.toPx().value
+
+ for (text in arrayOf("xyz", "\u05D0\u05D1\u05D2")) {
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ // width greater than text width - 150
+ paragraph.layout(ParagraphConstraints(width = 200.0f))
+
+ assertThat(text, paragraph.width, equalTo(200.0f))
+ assertThat(text, paragraph.height, equalTo(fontSizeInpx))
+ // defined in sample_font
+ assertThat(text, paragraph.baseline, equalTo(fontSizeInpx * 0.8f))
+ assertThat(
+ text,
+ paragraph.maxIntrinsicWidth,
+ equalTo(fontSizeInpx * text.length)
+ )
+ assertThat(text, paragraph.minIntrinsicWidth, equalTo(0.0f))
+ }
+ }
+ }
+
+ @Test
+ fun line_break_default_values() {
+ withDensity(defaultDensity) {
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+
+ for (text in arrayOf("abcdef", "\u05D0\u05D1\u05D2\u05D3\u05D4\u05D5")) {
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ // 3 chars width
+ paragraph.layout(ParagraphConstraints(width = 3 * fontSizeInPx))
+
+ // 3 chars
+ assertThat(text, paragraph.width, equalTo(3 * fontSizeInPx))
+ // 2 lines, 1 line gap
+ assertThat(
+ text,
+ paragraph.height,
+ equalTo(2 * fontSizeInPx + fontSizeInPx / 5.0f)
+ )
+ // defined in sample_font
+ assertThat(text, paragraph.baseline, equalTo(fontSizeInPx * 0.8f))
+ assertThat(
+ text,
+ paragraph.maxIntrinsicWidth,
+ equalTo(fontSizeInPx * text.length)
+ )
+ assertThat(text, paragraph.minIntrinsicWidth, equalTo(0.0f))
+ }
+ }
+ }
+
+ @Test
+ fun didExceedMaxLines_withMaxLinesSmallerThanTextLines_returnsTrue() {
+ val text = "aaa\naa"
+ val maxLines = text.lines().size - 1
+ val paragraph = simpleMultiParagraph(text = text, maxLines = maxLines)
+
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+ assertThat(paragraph.didExceedMaxLines, equalTo(true))
+ }
+
+ @Test
+ fun didExceedMaxLines_withMaxLinesEqualToTextLines_returnsFalse() {
+ val text = "aaa\naa"
+ val maxLines = text.lines().size
+ val paragraph = simpleMultiParagraph(text = text, maxLines = maxLines)
+
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+ assertThat(paragraph.didExceedMaxLines, equalTo(false))
+ }
+
+ @Test
+ fun didExceedMaxLines_withMaxLinesGreaterThanTextLines_returnsFalse() {
+ val text = "aaa\naa"
+ val maxLines = text.lines().size + 1
+ val paragraph = simpleMultiParagraph(text = text, maxLines = maxLines)
+
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+ assertThat(paragraph.didExceedMaxLines, equalTo(false))
+ }
+
+ @Test
+ fun didExceedMaxLines_withMaxLinesSmallerThanTextLines_withLineWrap_returnsTrue() {
+ withDensity(defaultDensity) {
+ val text = "aa"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val maxLines = 1
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ maxLines = maxLines
+ )
+
+ // One line can only contain 1 character
+ paragraph.layout(ParagraphConstraints(width = fontSizeInPx))
+ assertThat(paragraph.didExceedMaxLines, equalTo(true))
+ }
+ }
+
+ @Test
+ fun didExceedMaxLines_withMaxLinesEqualToTextLines_withLineWrap_returnsFalse() {
+ val text = "a"
+ val maxLines = text.lines().size
+ val paragraph = simpleMultiParagraph(text = text, fontSize = 50.sp, maxLines = maxLines)
+
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+ assertThat(paragraph.didExceedMaxLines, equalTo(false))
+ }
+
+ @Test
+ fun didExceedMaxLines_withMaxLinesGreaterThanTextLines_withLineWrap_returnsFalse() {
+ withDensity(defaultDensity) {
+ val text = "aa"
+ val maxLines = 3
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ maxLines = maxLines
+ )
+
+ // One line can only contain 1 character
+ paragraph.layout(ParagraphConstraints(width = fontSizeInPx))
+ assertThat(paragraph.didExceedMaxLines, equalTo(false))
+ }
+ }
+
+ @Test
+ fun getOffsetForPosition_ltr() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+ // test positions that are 1, fontSize+1, 2fontSize+1 which maps to chars 0, 1, 2 ...
+ for (i in 0..text.length) {
+ val position = PxPosition((i * fontSizeInPx + 1).px, (fontSizeInPx / 2).px)
+ val offset = paragraph.getOffsetForPosition(position)
+ assertThat(
+ "offset at index $i, position $position does not match",
+ offset,
+ equalTo(i)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getOffsetForPosition_rtl() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ // test positions that are 1, fontSize+1, 2fontSize+1 which maps to chars .., 2, 1, 0
+ for (i in 0..text.length) {
+ val position = PxPosition((i * fontSizeInPx + 1).px, (fontSizeInPx / 2).px)
+ val offset = paragraph.getOffsetForPosition(position)
+ assertThat(
+ "offset at index $i, position $position does not match",
+ offset,
+ equalTo(text.length - i)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getOffsetForPosition_ltr_multiline() {
+ withDensity(defaultDensity) {
+ val firstLine = "abc"
+ val secondLine = "def"
+ val text = firstLine + secondLine
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = firstLine.length * fontSizeInPx))
+
+ // test positions are 1, fontSize+1, 2fontSize+1 and always on the second line
+ // which maps to chars 3, 4, 5
+ for (i in 0..secondLine.length) {
+ val position = PxPosition((i * fontSizeInPx + 1).px, (fontSizeInPx * 1.5f).px)
+ val offset = paragraph.getOffsetForPosition(position)
+ assertThat(
+ "offset at index $i, position $position, second line does not match",
+ offset,
+ equalTo(i + firstLine.length)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getOffsetForPosition_rtl_multiline() {
+ withDensity(defaultDensity) {
+ val firstLine = "\u05D0\u05D1\u05D2"
+ val secondLine = "\u05D3\u05D4\u05D5"
+ val text = firstLine + secondLine
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = firstLine.length * fontSizeInPx))
+
+ // test positions are 1, fontSize+1, 2fontSize+1 and always on the second line
+ // which maps to chars 5, 4, 3
+ for (i in 0..secondLine.length) {
+ val position = PxPosition((i * fontSizeInPx + 1).px, (fontSizeInPx * 1.5f).px)
+ val offset = paragraph.getOffsetForPosition(position)
+ assertThat(
+ "offset at index $i, position $position, second line does not match",
+ offset,
+ equalTo(text.length - i)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getOffsetForPosition_ltr_width_outOfBounds() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ // greater than width
+ var position = PxPosition((fontSizeInPx * text.length * 2).px, (fontSizeInPx / 2).px)
+ var offset = paragraph.getOffsetForPosition(position)
+ assertThat(offset, equalTo(text.length))
+
+ // negative
+ position = PxPosition((-1 * fontSizeInPx).px, (fontSizeInPx / 2).px)
+ offset = paragraph.getOffsetForPosition(position)
+ assertThat(offset, equalTo(0))
+ }
+ }
+
+ @Test
+ fun getOffsetForPosition_ltr_height_outOfBounds() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ // greater than height
+ var position = PxPosition((fontSizeInPx / 2).px, (fontSizeInPx * text.length * 2).px)
+ var offset = paragraph.getOffsetForPosition(position)
+ assertThat(offset, equalTo(0))
+
+ // negative
+ position = PxPosition((fontSizeInPx / 2).px, (-1 * fontSizeInPx).px)
+ offset = paragraph.getOffsetForPosition(position)
+ assertThat(offset, equalTo(0))
+ }
+ }
+
+ @Test
+ fun getOffsetForPosition_lineBreak() {
+ withDensity(defaultDensity) {
+ val text = "abc\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ assertThat(
+ paragraph.getOffsetForPosition(PxPosition((3 * fontSizeInPx).px, 0.px)),
+ equalTo(3)
+ )
+
+ assertThat(
+ paragraph.getOffsetForPosition(PxPosition(0.px, (fontSizeInPx * 1.5f).px)),
+ equalTo(4)
+ )
+ }
+ }
+
+ @Test
+ fun getOffsetForPosition_multiple_paragraph() {
+ withDensity(defaultDensity) {
+ val text = "abcdef"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ paragraphStyles = listOf(
+ AnnotatedString.Item(ParagraphStyle(), 0, 3)
+ )
+ )
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ for (i in 0 until 3) {
+ assertThat(
+ paragraph.getOffsetForPosition(PxPosition((i * fontSizeInPx).px, 0.px)),
+ equalTo(i)
+ )
+ }
+
+ for (i in 3 until 6) {
+ assertThat(
+ paragraph.getOffsetForPosition(
+ PxPosition(((i - 3) * fontSizeInPx).px, fontSizeInPx.px)
+ ),
+ equalTo(i)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getBoundingBox_ltr_singleLine() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+ for (i in 0 until text.length) {
+ val box = paragraph.getBoundingBox(i)
+ assertThat(box.left, equalTo(i * fontSizeInPx))
+ assertThat(box.right, equalTo((i + 1) * fontSizeInPx))
+ assertThat(box.top, equalTo(0f))
+ assertThat(box.bottom, equalTo(fontSizeInPx))
+ }
+ }
+ }
+
+ @Test
+ fun getBoundingBox_ltr_multiLines() {
+ withDensity(defaultDensity) {
+ val firstLine = "abc"
+ val secondLine = "def"
+ val text = firstLine + secondLine
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = firstLine.length * fontSizeInPx))
+
+ // test positions are 3, 4, 5 and always on the second line
+ // which maps to chars 3, 4, 5
+ for (i in 0..secondLine.length - 1) {
+ val textPosition = i + firstLine.length
+ val box = paragraph.getBoundingBox(textPosition)
+ assertThat(box.left, equalTo(i * fontSizeInPx))
+ assertThat(box.right, equalTo((i + 1) * fontSizeInPx))
+ assertThat(box.top, equalTo(fontSizeInPx))
+ assertThat(box.bottom, equalTo((2f + 1 / 5f) * fontSizeInPx))
+ }
+ }
+ }
+
+ @Test(expected = java.lang.IndexOutOfBoundsException::class)
+ fun getBoundingBox_ltr_textPosition_negative() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ paragraph.getBoundingBox(-1)
+ }
+ }
+
+ @Suppress
+ @Test(expected = java.lang.IndexOutOfBoundsException::class)
+ fun getBoundingBox_ltr_textPosition_larger_than_length_throw_exception() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ val textPosition = text.length + 1
+ paragraph.getBoundingBox(textPosition)
+ }
+ }
+
+ @Test(expected = java.lang.AssertionError::class)
+ fun getCursorRect_larger_than_length_throw_exception() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ paragraph.getCursorRect(text.length + 1)
+ }
+ }
+
+ @Test(expected = java.lang.AssertionError::class)
+ fun getCursorRect_negative_throw_exception() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ paragraph.getCursorRect(-1)
+ }
+ }
+
+ @Test
+ fun getCursorRect_ltr_singleLine() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ for (i in 0 until text.length) {
+ val cursorRect = paragraph.getCursorRect(i)
+ val cursorXOffset = i * fontSizeInPx
+ assertThat(
+ cursorRect,
+ equalTo(Rect(
+ left = cursorXOffset - cursorWidth / 2,
+ top = 0f,
+ right = cursorXOffset + cursorWidth / 2,
+ bottom = fontSizeInPx
+ ))
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getCursorRect_ltr_multiLines() {
+ withDensity(defaultDensity) {
+ val text = "abcdef"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ val charsPerLine = 3
+
+ paragraph.layout(ParagraphConstraints(width = charsPerLine * fontSizeInPx))
+
+ for (i in 0 until charsPerLine) {
+ val cursorXOffset = i * fontSizeInPx
+ assertThat(
+ paragraph.getCursorRect(i),
+ equalTo(Rect(
+ left = cursorXOffset - cursorWidth / 2,
+ top = 0f,
+ right = cursorXOffset + cursorWidth / 2,
+ bottom = fontSizeInPx
+ ))
+ )
+ }
+
+ for (i in charsPerLine until text.length) {
+ val cursorXOffset = (i % charsPerLine) * fontSizeInPx
+ assertThat(
+ paragraph.getCursorRect(i),
+ equalTo(Rect(
+ left = cursorXOffset - cursorWidth / 2,
+ top = fontSizeInPx,
+ right = cursorXOffset + cursorWidth / 2,
+ bottom = fontSizeInPx * 2.2f
+ ))
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getCursorRect_rtl_singleLine() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ for (i in 0 until text.length) {
+ val cursorXOffset = (text.length - i) * fontSizeInPx
+ assertThat(
+ paragraph.getCursorRect(i),
+ equalTo(Rect(
+ left = cursorXOffset - cursorWidth / 2,
+ top = 0f,
+ right = cursorXOffset + cursorWidth / 2,
+ bottom = fontSizeInPx
+ ))
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getCursorRect_rtl_multiLines() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\u05D0\u05D1\u05D2"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ val charsPerLine = 3
+
+ paragraph.layout(ParagraphConstraints(width = charsPerLine * fontSizeInPx))
+
+ for (i in 0 until charsPerLine) {
+ val cursorXOffset = (charsPerLine - i) * fontSizeInPx
+ assertThat(
+ paragraph.getCursorRect(i),
+ equalTo(Rect(
+ left = cursorXOffset - cursorWidth / 2,
+ top = 0f,
+ right = cursorXOffset + cursorWidth / 2,
+ bottom = fontSizeInPx
+ ))
+ )
+ }
+
+ for (i in charsPerLine until text.length) {
+ val cursorXOffset = (charsPerLine - i % charsPerLine) * fontSizeInPx
+ assertThat(
+ paragraph.getCursorRect(i),
+ equalTo(Rect(
+ left = cursorXOffset - cursorWidth / 2,
+ top = fontSizeInPx,
+ right = cursorXOffset + cursorWidth / 2,
+ bottom = fontSizeInPx * 2.2f
+ ))
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_ltr_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ for (i in 0..text.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(fontSizeInPx * i)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_rtl_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(width - fontSizeInPx * i)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_bidi_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..ltrText.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(fontSizeInPx * i)
+ )
+ }
+
+ for (i in 1 until rtlText.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i + ltrText.length),
+ equalTo(width - fontSizeInPx * i)
+ )
+ }
+
+ assertThat(
+ paragraph.getPrimaryHorizontal(text.length),
+ equalTo(width)
+ )
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_ltr_singleLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(0), equalTo(width))
+
+ for (i in 1 until text.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(fontSizeInPx * i)
+ )
+ }
+
+ assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(0f))
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_rtl_singleLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(0), equalTo(0f))
+
+ for (i in 1 until text.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(width - fontSizeInPx * i)
+ )
+ }
+
+ assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(width))
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_bidi_singleLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..ltrText.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(fontSizeInPx * i)
+ )
+ }
+
+ for (i in 1 until rtlText.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i + ltrText.length),
+ equalTo(width - fontSizeInPx * i)
+ )
+ }
+
+ assertThat(
+ paragraph.getPrimaryHorizontal(text.length),
+ equalTo(width)
+ )
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_bidi_singleLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(0), equalTo(width))
+ // Notice that abc is
+ for (i in 1 until ltrText.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(rtlText.length * fontSizeInPx + i * fontSizeInPx)
+ )
+ }
+
+ for (i in 0..rtlText.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i + ltrText.length),
+ equalTo(rtlText.length * fontSizeInPx - i * fontSizeInPx)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_ltr_newLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "abc\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(0f))
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_rtl_newLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(0f))
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_ltr_newLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val text = "abc\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(width))
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_rtl_newLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(0f))
+ }
+ }
+
+ @Test
+ fun getLineForOffset_singleLine() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.lastIndex) {
+ assertThat(paragraph.getLineForOffset(i), equalTo(0))
+ }
+ }
+ }
+
+ @Test
+ fun getLineForOffset_multiLines() {
+ withDensity(defaultDensity) {
+ val text = "a\nb\nc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.lastIndex) {
+ assertThat(paragraph.getLineForOffset(i), equalTo(i / 2))
+ }
+ }
+ }
+
+ @Test
+ fun getLineForOffset_multiParagraph() {
+ withDensity(defaultDensity) {
+ val text = "abcd"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ paragraphStyles = listOf(AnnotatedString.Item(ParagraphStyle(), 0, 2))
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+ assertThat(paragraph.getLineForOffset(0), equalTo(0))
+ assertThat(paragraph.getLineForOffset(1), equalTo(0))
+ assertThat(paragraph.getLineForOffset(2), equalTo(1))
+ assertThat(paragraph.getLineForOffset(3), equalTo(1))
+ }
+ }
+
+ @Test
+ fun getLineForOffset_emptyParagraph() {
+ withDensity(defaultDensity) {
+ val text = "abcd"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ paragraphStyles = listOf(AnnotatedString.Item(ParagraphStyle(), 2, 2))
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+ assertThat(paragraph.getLineForOffset(0), equalTo(0))
+ assertThat(paragraph.getLineForOffset(1), equalTo(0))
+ // The empty paragraph takes one line
+ assertThat(paragraph.getLineForOffset(2), equalTo(2))
+ assertThat(paragraph.getLineForOffset(3), equalTo(2))
+ }
+ }
+
+ @Test(expected = java.lang.IndexOutOfBoundsException::class)
+ fun getLineForOffset_negativeOffset() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ paragraph.getLineForOffset(-1)
+ }
+ }
+
+ @Test(expected = java.lang.IndexOutOfBoundsException::class)
+ fun getLineForOffset_outOfBoundary() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ paragraph.getLineForOffset(text.length)
+ }
+ }
+
+ @Test
+ fun testGetPathForRange_singleLine() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val expectedPath = Path()
+ val lineLeft = paragraph.getLineLeft(0)
+ val lineRight = paragraph.getLineRight(0)
+ expectedPath.addRect(
+ Rect(
+ lineLeft,
+ 0f,
+ lineRight - fontSizeInPx,
+ fontSizeInPx
+ )
+ )
+
+ // Select "ab"
+ val actualPath = paragraph.getPathForRange(0, 2)
+
+ val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
+ assertThat(diff, equalTo(Rect.zero))
+ }
+ }
+
+ @Test
+ fun testGetPathForRange_multiLines() {
+ withDensity(defaultDensity) {
+ val text = "abc\nabc"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val expectedPath = Path()
+ val firstLineLeft = paragraph.getLineLeft(0)
+ val secondLineLeft = paragraph.getLineLeft(1)
+ val firstLineRight = paragraph.getLineRight(0)
+ val secondLineRight = paragraph.getLineRight(1)
+ expectedPath.addRect(
+ Rect(
+ firstLineLeft + fontSizeInPx,
+ 0f,
+ firstLineRight,
+ fontSizeInPx
+ )
+ )
+ expectedPath.addRect(
+ Rect(
+ secondLineLeft,
+ fontSizeInPx,
+ secondLineRight - fontSizeInPx,
+ paragraph.height
+ )
+ )
+
+ // Select "bc\nab"
+ val actualPath = paragraph.getPathForRange(1, 6)
+
+ val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
+ assertThat(diff, equalTo(Rect.zero))
+ }
+ }
+
+ @Test
+ fun testGetPathForRange_Bidi() {
+ withDensity(defaultDensity) {
+ val textLTR = "Hello"
+ val textRTL = "שלום"
+ val text = textLTR + textRTL
+ val selectionLTRStart = 2
+ val selectionRTLEnd = 2
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val expectedPath = Path()
+ val lineLeft = paragraph.getLineLeft(0)
+ val lineRight = paragraph.getLineRight(0)
+ expectedPath.addRect(
+ Rect(
+ lineLeft + selectionLTRStart * fontSizeInPx,
+ 0f,
+ lineLeft + textLTR.length * fontSizeInPx,
+ fontSizeInPx
+ )
+ )
+ expectedPath.addRect(
+ Rect(
+ lineRight - selectionRTLEnd * fontSizeInPx,
+ 0f,
+ lineRight,
+ fontSizeInPx
+ )
+ )
+
+ // Select "llo..של"
+ val actualPath =
+ paragraph.getPathForRange(selectionLTRStart, textLTR.length + selectionRTLEnd)
+
+ val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
+ assertThat(diff, equalTo(Rect.zero))
+ }
+ }
+
+ @Test
+ fun testGetPathForRange_Start_Equals_End_Returns_Empty_Path() {
+ val text = "abc"
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = 20.sp
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val actualPath = paragraph.getPathForRange(1, 1)
+
+ assertThat(actualPath.getBounds(), equalTo(Rect.zero))
+ }
+
+ @Test
+ fun testGetPathForRange_Empty_Text() {
+ val text = ""
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = 20.sp
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val actualPath = paragraph.getPathForRange(0, 0)
+
+ assertThat(actualPath.getBounds(), equalTo(Rect.zero))
+ }
+
+ @Test
+ fun testGetPathForRange_Surrogate_Pair_Start_Middle_Second_Character_Selected() {
+ withDensity(defaultDensity) {
+ val text = "\uD834\uDD1E\uD834\uDD1F"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val expectedPath = Path()
+ val lineRight = paragraph.getLineRight(0)
+ expectedPath.addRect(Rect(lineRight / 2, 0f, lineRight, fontSizeInPx))
+
+ // Try to select "\uDD1E\uD834\uDD1F", only "\uD834\uDD1F" is selected.
+ val actualPath = paragraph.getPathForRange(1, text.length)
+
+ val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
+ assertThat(diff, equalTo(Rect.zero))
+ }
+ }
+
+ @Test
+ fun testGetPathForRange_Surrogate_Pair_End_Middle_Second_Character_Selected() {
+ withDensity(defaultDensity) {
+ val text = "\uD834\uDD1E\uD834\uDD1F"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val expectedPath = Path()
+ val lineRight = paragraph.getLineRight(0)
+ expectedPath.addRect(Rect(lineRight / 2, 0f, lineRight, fontSizeInPx))
+
+ // Try to select "\uDD1E\uD834", actually "\uD834\uDD1F" is selected.
+ val actualPath = paragraph.getPathForRange(1, text.length - 1)
+
+ val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
+ assertThat(diff, equalTo(Rect.zero))
+ }
+ }
+
+ @Test
+ fun testGetPathForRange_Surrogate_Pair_Start_Middle_End_Same_Character_Returns_Line_Segment() {
+ withDensity(defaultDensity) {
+ val text = "\uD834\uDD1E\uD834\uDD1F"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val expectedPath = Path()
+ val lineRight = paragraph.getLineRight(0)
+ expectedPath.addRect(Rect(lineRight / 2, 0f, lineRight / 2, fontSizeInPx))
+
+ // Try to select "\uDD1E", get vertical line segment after this character.
+ val actualPath = paragraph.getPathForRange(1, 2)
+
+ val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
+ assertThat(diff, equalTo(Rect.zero))
+ }
+ }
+
+ @Test
+ fun testGetPathForRange_Emoji_Sequence() {
+ withDensity(defaultDensity) {
+ val text = "\u1F600\u1F603\u1F604\u1F606"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val expectedPath = Path()
+ val lineLeft = paragraph.getLineLeft(0)
+ val lineRight = paragraph.getLineRight(0)
+ expectedPath.addRect(
+ Rect(
+ lineLeft + fontSizeInPx,
+ 0f,
+ lineRight - fontSizeInPx,
+ fontSizeInPx
+ )
+ )
+
+ // Select "\u1F603\u1F604"
+ val actualPath = paragraph.getPathForRange(1, text.length - 1)
+
+ val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
+ assertThat(diff, equalTo(Rect.zero))
+ }
+ }
+
+ @Test
+ fun testGetPathForRange_Unicode_200D_Return_Line_Segment() {
+ withDensity(defaultDensity) {
+ val text = "\u200D"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val expectedPath = Path()
+ val lineLeft = paragraph.getLineLeft(0)
+ val lineRight = paragraph.getLineRight(0)
+ expectedPath.addRect(Rect(lineLeft, 0f, lineRight, fontSizeInPx))
+
+ val actualPath = paragraph.getPathForRange(0, 1)
+
+ assertThat(lineLeft, equalTo(lineRight))
+ val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
+ assertThat(diff, equalTo(Rect.zero))
+ }
+ }
+
+ @Test
+ fun testGetPathForRange_Unicode_2066_Return_Line_Segment() {
+ withDensity(defaultDensity) {
+ val text = "\u2066"
+ val fontSize = 20f.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val expectedPath = Path()
+ val lineLeft = paragraph.getLineLeft(0)
+ val lineRight = paragraph.getLineRight(0)
+ expectedPath.addRect(Rect(lineLeft, 0f, lineRight, fontSizeInPx))
+
+ val actualPath = paragraph.getPathForRange(0, 1)
+
+ assertThat(lineLeft, equalTo(lineRight))
+ val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
+ assertThat(diff, equalTo(Rect.zero))
+ }
+ }
+
+ @Test
+ fun testGetWordBoundary() {
+ val text = "abc def"
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = 20.sp
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val result = paragraph.getWordBoundary(text.indexOf('a'))
+
+ assertThat(result.start, equalTo(text.indexOf('a')))
+ assertThat(result.end, equalTo(text.indexOf(' ')))
+ }
+
+ @Test
+ fun testGetWordBoundary_Bidi() {
+ val text = "abc \u05d0\u05d1\u05d2 def"
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = 20.sp
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val resultEnglish = paragraph.getWordBoundary(text.indexOf('a'))
+ val resultHebrew = paragraph.getWordBoundary(text.indexOf('\u05d1'))
+
+ assertThat(resultEnglish.start, equalTo(text.indexOf('a')))
+ assertThat(resultEnglish.end, equalTo(text.indexOf(' ')))
+ assertThat(resultHebrew.start, equalTo(text.indexOf('\u05d0')))
+ assertThat(resultHebrew.end, equalTo(text.indexOf('\u05d2') + 1))
+ }
+
+ @Test
+ fun width_default_value() {
+ val paragraph = simpleMultiParagraph()
+
+ assertThat(paragraph.width, equalTo(0.0f))
+ }
+
+ @Test
+ fun height_default_value() {
+ val paragraph = simpleMultiParagraph()
+
+ assertThat(paragraph.height, equalTo(0.0f))
+ }
+
+ @Test
+ fun minIntrinsicWidth_default_value() {
+ val paragraph = simpleMultiParagraph()
+
+ assertThat(paragraph.minIntrinsicWidth, equalTo(0.0f))
+ }
+
+ @Test
+ fun maxIntrinsicWidth_default_value() {
+ val paragraph = simpleMultiParagraph()
+
+ assertThat(paragraph.maxIntrinsicWidth, equalTo(0.0f))
+ }
+
+ @Test
+ fun alphabeticBaseline_default_value() {
+ val paragraph = simpleMultiParagraph()
+
+ assertThat(paragraph.baseline, equalTo(0.0f))
+ }
+
+ @Test
+ fun didExceedMaxLines_default_value() {
+ val paragraph = simpleMultiParagraph()
+
+ assertThat(paragraph.didExceedMaxLines, equalTo(false))
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun paint_throws_exception_if_layout_is_not_called() {
+ val paragraph = simpleMultiParagraph()
+
+ paragraph.paint(mock())
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun getPositionForOffset_throws_exception_if_layout_is_not_called() {
+ val paragraph = simpleMultiParagraph()
+
+ paragraph.getOffsetForPosition(PxPosition.Origin)
+ }
+
+ @Test(expected = AssertionError::class)
+ fun getPathForRange_throws_exception_if_start_larger_than_end() {
+ val text = "ab"
+ val textStart = 0
+ val textEnd = text.length
+ val paragraph = simpleMultiParagraph(text = text)
+
+ paragraph.getPathForRange(textEnd, textStart)
+ }
+
+ @Test(expected = AssertionError::class)
+ fun getPathForRange_throws_exception_if_start_is_smaller_than_zero() {
+ val text = "ab"
+ val textStart = 0
+ val textEnd = text.length
+ val paragraph = simpleMultiParagraph(text = text)
+
+ paragraph.getPathForRange(textStart - 2, textEnd - 1)
+ }
+
+ @Test(expected = AssertionError::class)
+ fun getPathForRange_throws_exception_if_end_is_larger_than_text_length() {
+ val text = "ab"
+ val textStart = 0
+ val textEnd = text.length
+ val paragraph = simpleMultiParagraph(text = text)
+
+ paragraph.getPathForRange(textStart, textEnd + 1)
+ }
+
+ @Test
+ fun textAlign_defaultValue_alignsStart() {
+ withDensity(defaultDensity) {
+ val textLTR = "aa"
+ val textRTL = "\u05D0\u05D0"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+
+ val paragraphLTR = simpleMultiParagraph(
+ text = textLTR,
+ fontSize = fontSize
+ )
+ val layoutLTRWidth = (textLTR.length + 2) * fontSizeInPx
+ paragraphLTR.layout(ParagraphConstraints(width = layoutLTRWidth))
+
+ val paragraphRTL = simpleMultiParagraph(
+ text = textRTL,
+ fontSize = fontSize
+ )
+ val layoutRTLWidth = (textRTL.length + 2) * fontSizeInPx
+ paragraphRTL.layout(ParagraphConstraints(width = layoutRTLWidth))
+
+ // When textAlign is TextAlign.start, LTR aligns to left, RTL aligns to right.
+ assertThat(paragraphLTR.getLineLeft(0), equalTo(0.0f))
+ assertThat(paragraphRTL.getLineRight(0), equalTo(layoutRTLWidth))
+ }
+ }
+
+ @Test
+ fun textAlign_whenAlignLeft_returnsZeroForGetLineLeft() {
+ withDensity(defaultDensity) {
+ val texts = listOf("aa", "\u05D0\u05D0")
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+
+ texts.map { text ->
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textAlign = TextAlign.Left,
+ fontSize = fontSize
+ )
+ val layoutWidth = (text.length + 2) * fontSizeInPx
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
+ assertThat(paragraph.getLineLeft(0), equalTo(0.0f))
+ }
+ }
+ }
+
+ @Test
+ fun textAlign_whenAlignRight_returnsLayoutWidthForGetLineRight() {
+ withDensity(defaultDensity) {
+ val texts = listOf("aa", "\u05D0\u05D0")
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+
+ texts.map { text ->
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textAlign = TextAlign.Right,
+ fontSize = fontSize
+ )
+ val layoutWidth = (text.length + 2) * fontSizeInPx
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
+ assertThat(paragraph.getLineRight(0), equalTo(layoutWidth))
+ }
+ }
+ }
+
+ @Test
+ fun textAlign_whenAlignCenter_textIsCentered() {
+ withDensity(defaultDensity) {
+ val texts = listOf("aa", "\u05D0\u05D0")
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+
+ texts.map { text ->
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textAlign = TextAlign.Center,
+ fontSize = fontSize
+ )
+ val layoutWidth = (text.length + 2) * fontSizeInPx
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+ val textWidth = text.length * fontSizeInPx
+
+ assertThat(
+ paragraph.getLineLeft(0),
+ equalTo(layoutWidth / 2 - textWidth / 2)
+ )
+ assertThat(
+ paragraph.getLineRight(0),
+ equalTo(layoutWidth / 2 + textWidth / 2)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun textAlign_whenAlignStart_withLTR_returnsZeroForGetLineLeft() {
+ withDensity(defaultDensity) {
+ val text = "aa"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val layoutWidth = (text.length + 2) * fontSizeInPx
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textAlign = TextAlign.Start,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
+ assertThat(paragraph.getLineLeft(0), equalTo(0.0f))
+ }
+ }
+
+ @Test
+ fun textAlign_whenAlignEnd_withLTR_returnsLayoutWidthForGetLineRight() {
+ withDensity(defaultDensity) {
+ val text = "aa"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val layoutWidth = (text.length + 2) * fontSizeInPx
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textAlign = TextAlign.End,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
+ assertThat(paragraph.getLineRight(0), equalTo(layoutWidth))
+ }
+ }
+
+ @Test
+ fun textAlign_whenAlignStart_withRTL_returnsLayoutWidthForGetLineRight() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D0"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val layoutWidth = (text.length + 2) * fontSizeInPx
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textAlign = TextAlign.Start,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
+ assertThat(paragraph.getLineRight(0), equalTo(layoutWidth))
+ }
+ }
+
+ @Test
+ fun textAlign_whenAlignEnd_withRTL_returnsZeroForGetLineLeft() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D0"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val layoutWidth = (text.length + 2) * fontSizeInPx
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textAlign = TextAlign.End,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
+ assertThat(paragraph.getLineLeft(0), equalTo(0.0f))
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 28)
+ // We have to test justification above API 28 because of this bug b/68009059, where devices
+ // before API 28 may have an extra space at the end of line.
+ fun textAlign_whenAlignJustify_justifies() {
+ withDensity(defaultDensity) {
+ val text = "a a a"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val layoutWidth = ("a a".length + 1) * fontSizeInPx
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textAlign = TextAlign.Justify,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
+ assertThat(paragraph.getLineLeft(0), equalTo(0.0f))
+ assertThat(paragraph.getLineRight(0), equalTo(layoutWidth))
+ // Last line should align start
+ assertThat(paragraph.getLineLeft(1), equalTo(0.0f))
+ }
+ }
+
+ @Test
+ fun textDirection_whenLTR_dotIsOnRight() {
+ withDensity(defaultDensity) {
+ val text = "a.."
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val layoutWidth = text.length * fontSizeInPx
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textDirection = TextDirection.Ltr,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+ // The position of the last character in display order.
+ val position = PxPosition(("a.".length * fontSizeInPx + 1).px, (fontSizeInPx / 2).px)
+ val charIndex = paragraph.getOffsetForPosition(position)
+ assertThat(charIndex, equalTo(2))
+ }
+ }
+
+ @Test
+ fun textDirection_whenRTL_dotIsOnLeft() {
+ withDensity(defaultDensity) {
+ val text = "a.."
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val layoutWidth = text.length * fontSizeInPx
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textDirection = TextDirection.Rtl,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+ // The position of the first character in display order.
+ val position = PxPosition((fontSizeInPx / 2 + 1).px, (fontSizeInPx / 2).px)
+ val charIndex = paragraph.getOffsetForPosition(position)
+ assertThat(charIndex, equalTo(2))
+ }
+ }
+
+ @Test
+ fun textDirection_whenDefault_withoutStrongChar_directionIsLTR() {
+ withDensity(defaultDensity) {
+ val text = "..."
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val layoutWidth = text.length * fontSizeInPx
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+ for (i in 0..text.length) {
+ // The position of the i-th character in display order.
+ val position = PxPosition((i * fontSizeInPx + 1).px, (fontSizeInPx / 2).px)
+ val charIndex = paragraph.getOffsetForPosition(position)
+ assertThat(charIndex, equalTo(i))
+ }
+ }
+ }
+
+ @Test
+ fun textDirection_whenDefault_withFirstStrongCharLTR_directionIsLTR() {
+ withDensity(defaultDensity) {
+ val text = "a\u05D0."
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val layoutWidth = text.length * fontSizeInPx
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+ for (i in 0 until text.length) {
+ // The position of the i-th character in display order.
+ val position = PxPosition((i * fontSizeInPx + 1).px, (fontSizeInPx / 2).px)
+ val charIndex = paragraph.getOffsetForPosition(position)
+ assertThat(charIndex, equalTo(i))
+ }
+ }
+ }
+
+ @Test
+ fun textDirection_whenDefault_withFirstStrongCharRTL_directionIsRTL() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0a."
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val layoutWidth = text.length * fontSizeInPx
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+ // The first character in display order should be '.'
+ val position = PxPosition((fontSizeInPx / 2 + 1).px, (fontSizeInPx / 2).px)
+ val index = paragraph.getOffsetForPosition(position)
+ assertThat(index, equalTo(2))
+ }
+ }
+
+ @Test
+ fun lineHeight_returnsSameAsGiven() {
+ withDensity(defaultDensity) {
+ val text = "abcdefgh"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ // Make the layout 4 lines
+ val layoutWidth = text.length * fontSizeInPx / 4
+ val lineHeight = 1.5f
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ lineHeight = lineHeight
+ )
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
+ assertThat(paragraph.lineCount, equalTo(4))
+ // TODO(Migration/haoyuchang): Due to bug b/120530738, the height of the first line is
+ // wrong in the framework. Will fix it when the lineHeight in TextSpan is implemented.
+ for (i in 1 until paragraph.lineCount - 1) {
+ val actualHeight = paragraph.getLineHeight(i)
+ // In the sample_font.ttf, the height of the line should be
+ // fontSize + 0.2f * fontSize(line gap)
+ assertThat(
+ "line number $i",
+ actualHeight,
+ equalTo(1.2f * fontSizeInPx * lineHeight)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun lineHeight_hasNoEffectOnLastLine() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val layoutWidth = (text.length - 1) * fontSizeInPx
+ val lineHeight = 1.5f
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ lineHeight = lineHeight
+ )
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
+ val lastLine = paragraph.lineCount - 1
+ // In the sample_font.ttf, the height of the line should be
+ // fontSize + 0.2 * fontSize(line gap)
+ assertThat(paragraph.getLineHeight(lastLine), equalTo(1.2f * fontSizeInPx))
+ }
+ }
+
+ @Test
+ fun textIndent_onSingleLine() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val indent = 20.0f
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textIndent = TextIndent(firstLine = indent.px),
+ fontSize = fontSize,
+ fontFamily = fontFamilyMeasureFont
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ // This position should point to the first character 'a' if indent is applied.
+ // Otherwise this position will point to the second character 'b'.
+ val position = PxPosition((indent + 1).px, (fontSizeInPx / 2).px)
+ // The offset corresponding to the position should be the first char 'a'.
+ assertThat(paragraph.getOffsetForPosition(position), equalTo(0))
+ }
+ }
+
+ @Test
+ fun textIndent_onFirstLine() {
+ withDensity(defaultDensity) {
+ val text = "abcdef"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val indent = 20.0f
+ val paragraphWidth = "abcd".length * fontSizeInPx
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textIndent = TextIndent(firstLine = indent.px),
+ fontSize = fontSize,
+ fontFamily = fontFamilyMeasureFont
+ )
+ paragraph.layout(ParagraphConstraints(width = paragraphWidth))
+
+ assertThat(paragraph.lineCount, equalTo(2))
+ // This position should point to the first character of the first line if indent is
+ // applied. Otherwise this position will point to the second character of the second line.
+ val position = PxPosition((indent + 1).px, (fontSizeInPx / 2).px)
+ // The offset corresponding to the position should be the first char 'a'.
+ assertThat(paragraph.getOffsetForPosition(position), equalTo(0))
+ }
+ }
+
+ @Test
+ fun textIndent_onRestLine() {
+ withDensity(defaultDensity) {
+ val text = "abcde"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val indent = 20.0f
+ val paragraphWidth = "abc".length * fontSizeInPx
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textIndent = TextIndent(
+ firstLine = 0.px,
+ restLine = indent.px
+ ),
+ fontSize = fontSize,
+ fontFamily = fontFamilyMeasureFont
+ )
+ paragraph.layout(ParagraphConstraints(width = paragraphWidth))
+
+ // This position should point to the first character of the second line if indent is
+ // applied. Otherwise this position will point to the second character of the second line.
+ val position = PxPosition((indent + 1).px, (fontSizeInPx / 2 + fontSizeInPx).px)
+ // The offset corresponding to the position should be the 'd' in the second line.
+ assertThat(
+ paragraph.getOffsetForPosition(position),
+ equalTo("abcd".length - 1)
+ )
+ }
+ }
+
+ private fun simpleMultiParagraph(
+ text: String = "",
+ textIndent: TextIndent? = null,
+ textAlign: TextAlign? = null,
+ textDirection: TextDirection? = null,
+ fontSize: Sp? = null,
+ maxLines: Int? = null,
+ lineHeight: Float? = null,
+ textStyles: List<AnnotatedString.Item<TextStyle>> = listOf(),
+ paragraphStyles: List<AnnotatedString.Item<ParagraphStyle>> = listOf(),
+ fontFamily: FontFamily = fontFamilyMeasureFont,
+ locale: Locale? = null,
+ textStyle: TextStyle? = null,
+ density: Density? = null
+ ): MultiParagraph {
+ return MultiParagraph(
+ annotatedString = AnnotatedString(
+ text = text,
+ textStyles = textStyles,
+ paragraphStyles = paragraphStyles
+ ),
+ textStyle = TextStyle(
+ fontFamily = fontFamily,
+ fontSize = fontSize,
+ locale = locale
+ ).merge(textStyle),
+ paragraphStyle = ParagraphStyle(
+ textIndent = textIndent,
+ textAlign = textAlign,
+ textDirection = textDirection,
+ lineHeight = lineHeight
+ ),
+ maxLines = maxLines,
+ density = density ?: defaultDensity,
+ resourceLoader = TestFontResourceLoader(context)
+ )
+ }
+}
diff --git a/ui/ui-text/src/androidTest/java/androidx/ui/text/ParagraphIntegrationTest.kt b/ui/ui-text/src/androidTest/java/androidx/ui/text/ParagraphIntegrationTest.kt
index 841eb1b..9718437 100644
--- a/ui/ui-text/src/androidTest/java/androidx/ui/text/ParagraphIntegrationTest.kt
+++ b/ui/ui-text/src/androidTest/java/androidx/ui/text/ParagraphIntegrationTest.kt
@@ -62,6 +62,8 @@
private val resourceLoader = TestFontResourceLoader(context)
+ private val cursorWidth = 4f
+
@Test
fun empty_string() {
withDensity(defaultDensity) {
@@ -430,6 +432,566 @@
}
}
+ @Test(expected = java.lang.AssertionError::class)
+ fun getCursorRect_larger_than_length_throw_exception() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ paragraph.getCursorRect(text.length + 1)
+ }
+ }
+
+ @Test(expected = java.lang.AssertionError::class)
+ fun getCursorRect_negative_throw_exception() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ paragraph.getCursorRect(-1)
+ }
+ }
+
+ @Test
+ fun getCursorRect_ltr_singleLine() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ for (i in 0 until text.length) {
+ val cursorRect = paragraph.getCursorRect(i)
+ val cursorXOffset = i * fontSizeInPx
+ assertThat(
+ cursorRect,
+ equalTo(Rect(
+ left = cursorXOffset - cursorWidth / 2,
+ top = 0f,
+ right = cursorXOffset + cursorWidth / 2,
+ bottom = fontSizeInPx
+ ))
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getCursorRect_ltr_multiLines() {
+ withDensity(defaultDensity) {
+ val text = "abcdef"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+ val charsPerLine = 3
+
+ paragraph.layout(ParagraphConstraints(width = charsPerLine * fontSizeInPx))
+
+ for (i in 0 until charsPerLine) {
+ val cursorXOffset = i * fontSizeInPx
+ assertThat(
+ paragraph.getCursorRect(i),
+ equalTo(Rect(
+ left = cursorXOffset - cursorWidth / 2,
+ top = 0f,
+ right = cursorXOffset + cursorWidth / 2,
+ bottom = fontSizeInPx
+ ))
+ )
+ }
+
+ for (i in charsPerLine until text.length) {
+ val cursorXOffset = (i % charsPerLine) * fontSizeInPx
+ assertThat(
+ paragraph.getCursorRect(i),
+ equalTo(Rect(
+ left = cursorXOffset - cursorWidth / 2,
+ top = fontSizeInPx,
+ right = cursorXOffset + cursorWidth / 2,
+ bottom = fontSizeInPx * 2.2f
+ ))
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getCursorRect_ltr_newLine() {
+ withDensity(defaultDensity) {
+ val text = "abc\ndef"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ // Cursor before '\n'
+ assertThat(
+ paragraph.getCursorRect(3),
+ equalTo(Rect(
+ left = 3 * fontSizeInPx - cursorWidth / 2,
+ top = 0f,
+ right = 3 * fontSizeInPx + cursorWidth / 2,
+ bottom = fontSizeInPx
+ ))
+ )
+
+ // Cursor after '\n'
+ assertThat(
+ paragraph.getCursorRect(4),
+ equalTo(Rect(
+ left = -cursorWidth / 2,
+ top = fontSizeInPx,
+ right = cursorWidth / 2,
+ bottom = fontSizeInPx * 2.2f
+ ))
+ )
+ }
+ }
+
+ @Test
+ fun getCursorRect_ltr_newLine_last_char() {
+ withDensity(defaultDensity) {
+ val text = "abc\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ // Cursor before '\n'
+ assertThat(
+ paragraph.getCursorRect(3),
+ equalTo(Rect(
+ left = 3 * fontSizeInPx - cursorWidth / 2,
+ top = 0f,
+ right = 3 * fontSizeInPx + cursorWidth / 2,
+ bottom = fontSizeInPx
+ ))
+ )
+
+ // Cursor after '\n'
+ assertThat(
+ paragraph.getCursorRect(4),
+ equalTo(Rect(
+ left = -cursorWidth / 2,
+ top = fontSizeInPx,
+ right = cursorWidth / 2,
+ bottom = fontSizeInPx * 2.2f
+ ))
+ )
+ }
+ }
+
+ @Test
+ fun getCursorRect_rtl_singleLine() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ for (i in 0 until text.length) {
+ val cursorXOffset = (text.length - i) * fontSizeInPx
+ assertThat(
+ paragraph.getCursorRect(i),
+ equalTo(Rect(
+ left = cursorXOffset - cursorWidth / 2,
+ top = 0f,
+ right = cursorXOffset + cursorWidth / 2,
+ bottom = fontSizeInPx
+ ))
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getCursorRect_rtl_multiLines() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\u05D0\u05D1\u05D2"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+ val charsPerLine = 3
+
+ paragraph.layout(ParagraphConstraints(width = charsPerLine * fontSizeInPx))
+
+ for (i in 0 until charsPerLine) {
+ val cursorXOffset = (charsPerLine - i) * fontSizeInPx
+ assertThat(
+ paragraph.getCursorRect(i),
+ equalTo(Rect(
+ left = cursorXOffset - cursorWidth / 2,
+ top = 0f,
+ right = cursorXOffset + cursorWidth / 2,
+ bottom = fontSizeInPx
+ ))
+ )
+ }
+
+ for (i in charsPerLine until text.length) {
+ val cursorXOffset = (charsPerLine - i % charsPerLine) * fontSizeInPx
+ assertThat(
+ paragraph.getCursorRect(i),
+ equalTo(Rect(
+ left = cursorXOffset - cursorWidth / 2,
+ top = fontSizeInPx,
+ right = cursorXOffset + cursorWidth / 2,
+ bottom = fontSizeInPx * 2.2f
+ ))
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getCursorRect_rtl_newLine() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\n\u05D0\u05D1\u05D2"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = 3 * fontSizeInPx))
+
+ // Cursor before '\n'
+ assertThat(
+ paragraph.getCursorRect(3),
+ equalTo(Rect(
+ left = 0 - cursorWidth / 2,
+ top = 0f,
+ right = 0 + cursorWidth / 2,
+ bottom = fontSizeInPx
+ ))
+ )
+
+ // Cursor after '\n'
+ assertThat(
+ paragraph.getCursorRect(4),
+ equalTo(Rect(
+ left = 3 * fontSizeInPx - cursorWidth / 2,
+ top = fontSizeInPx,
+ right = 3 * fontSizeInPx + cursorWidth / 2,
+ bottom = fontSizeInPx * 2.2f
+ ))
+ )
+ }
+ }
+
+ @Test
+ fun getCursorRect_rtl_newLine_last_char() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = 3 * fontSizeInPx))
+
+ // Cursor before '\n'
+ assertThat(
+ paragraph.getCursorRect(3),
+ equalTo(Rect(
+ left = 0 - cursorWidth / 2,
+ top = 0f,
+ right = 0 + cursorWidth / 2,
+ bottom = fontSizeInPx
+ ))
+ )
+
+ // Cursor after '\n'
+ assertThat(
+ paragraph.getCursorRect(4),
+ equalTo(Rect(
+ left = -cursorWidth / 2,
+ top = fontSizeInPx,
+ right = +cursorWidth / 2,
+ bottom = fontSizeInPx * 2.2f
+ ))
+ )
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_ltr_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ for (i in 0..text.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(fontSizeInPx * i)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_rtl_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(width - fontSizeInPx * i)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_bidi_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..ltrText.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(fontSizeInPx * i)
+ )
+ }
+
+ for (i in 1 until rtlText.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i + ltrText.length),
+ equalTo(width - fontSizeInPx * i)
+ )
+ }
+
+ assertThat(
+ paragraph.getPrimaryHorizontal(text.length),
+ equalTo(width)
+ )
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_ltr_singleLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(0), equalTo(width))
+
+ for (i in 1 until text.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(fontSizeInPx * i)
+ )
+ }
+
+ assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(0f))
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_rtl_singleLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(0), equalTo(0f))
+
+ for (i in 1 until text.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(width - fontSizeInPx * i)
+ )
+ }
+
+ assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(width))
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_bidi_singleLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..ltrText.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(fontSizeInPx * i)
+ )
+ }
+
+ for (i in 1 until rtlText.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i + ltrText.length),
+ equalTo(width - fontSizeInPx * i)
+ )
+ }
+
+ assertThat(
+ paragraph.getPrimaryHorizontal(text.length),
+ equalTo(width)
+ )
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_bidi_singleLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(0), equalTo(width))
+ for (i in 1 until ltrText.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(rtlText.length * fontSizeInPx + i * fontSizeInPx)
+ )
+ }
+
+ for (i in 0..rtlText.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i + ltrText.length),
+ equalTo(rtlText.length * fontSizeInPx - i * fontSizeInPx)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_ltr_newLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "abc\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(0f))
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_rtl_newLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(0f))
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_ltr_newLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val text = "abc\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(width))
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_rtl_newLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(0f))
+ }
+ }
+
@Test
fun locale_withCJK_shouldNotDrawSame() {
withDensity(defaultDensity) {
diff --git a/ui/ui-text/src/androidTest/java/androidx/ui/text/TextPainterIntegrationTest.kt b/ui/ui-text/src/androidTest/java/androidx/ui/text/TextPainterIntegrationTest.kt
index 41f1cf3..3b17aae 100644
--- a/ui/ui-text/src/androidTest/java/androidx/ui/text/TextPainterIntegrationTest.kt
+++ b/ui/ui-text/src/androidTest/java/androidx/ui/text/TextPainterIntegrationTest.kt
@@ -26,7 +26,6 @@
import androidx.ui.core.px
import androidx.ui.core.sp
import androidx.ui.core.withDensity
-import androidx.ui.engine.geometry.Offset
import androidx.ui.engine.geometry.Rect
import androidx.ui.engine.geometry.Size
import androidx.ui.text.FontTestData.Companion.BASIC_MEASURE_FONT
@@ -291,7 +290,7 @@
textPainter.layout(Constraints(0.ipx, 20.ipx))
- assertThat(textPainter.paragraph).isNotNull()
+ assertThat(textPainter.multiParagraph).isNotNull()
}
@Test
@@ -460,10 +459,10 @@
val defaultSelectionColor = Color(0x6633B5E5)
expectedPaint.color = defaultSelectionColor
- val firstLineLeft = textPainter.paragraph?.getLineLeft(0)
- val secondLineLeft = textPainter.paragraph?.getLineLeft(1)
- val firstLineRight = textPainter.paragraph?.getLineRight(0)
- val secondLineRight = textPainter.paragraph?.getLineRight(1)
+ val firstLineLeft = textPainter.multiParagraph?.getLineLeft(0)
+ val secondLineLeft = textPainter.multiParagraph?.getLineLeft(1)
+ val firstLineRight = textPainter.multiParagraph?.getLineRight(0)
+ val secondLineRight = textPainter.multiParagraph?.getLineRight(1)
expectedCanvas.drawRect(
Rect(firstLineLeft!!, 0f, firstLineRight!!, fontSizeInPx),
expectedPaint
@@ -473,7 +472,7 @@
secondLineLeft!!,
fontSizeInPx,
secondLineRight!!,
- textPainter.paragraph!!.height
+ textPainter.multiParagraph!!.height
),
expectedPaint
)
diff --git a/ui/ui-text/src/main/java/androidx/ui/input/EditProcessor.kt b/ui/ui-text/src/main/java/androidx/ui/input/EditProcessor.kt
index 0d8d5c0..d9367c9 100644
--- a/ui/ui-text/src/main/java/androidx/ui/input/EditProcessor.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/input/EditProcessor.kt
@@ -30,26 +30,26 @@
class EditProcessor {
// The previous editor state we passed back to the user of this class.
- private var mPreviousState: EditorState? = null
+ private var mPreviousModel: EditorModel? = null
// The editing buffer used for applying editor commands from IME.
private var mBuffer: EditingBuffer =
EditingBuffer(initialText = "", initialSelection = TextRange(0, 0))
/**
- * Must be called whenever new editor state arrives.
+ * Must be called whenever new editor model arrives.
*
- * This method updates the internal editing buffer with the given editor state.
+ * This method updates the internal editing buffer with the given editor model.
* This method may tell the IME about the selection offset changes or extracted text changes.
*/
- fun onNewState(state: EditorState, textInputService: TextInputService?) {
- if (mPreviousState !== state) {
+ fun onNewState(model: EditorModel, textInputService: TextInputService?) {
+ if (mPreviousModel !== model) {
mBuffer = EditingBuffer(
- initialText = state.text,
- initialSelection = state.selection)
+ initialText = model.text,
+ initialSelection = model.selection)
}
- textInputService?.onStateUpdated(state)
+ textInputService?.onStateUpdated(model)
}
/**
@@ -58,10 +58,10 @@
* This method updates internal editing buffer with the given edit operations and returns the
* latest editor state representation of the editing buffer.
*/
- fun onEditCommands(ops: List<EditOperation>): EditorState {
+ fun onEditCommands(ops: List<EditOperation>): EditorModel {
ops.forEach { it.process(mBuffer) }
- val newState = EditorState(
+ val newState = EditorModel(
text = mBuffer.toString(),
selection = TextRange(mBuffer.selectionStart, mBuffer.selectionEnd),
composition = if (mBuffer.hasComposition()) {
@@ -70,7 +70,7 @@
null
})
- mPreviousState = newState
+ mPreviousModel = newState
return newState
}
}
\ No newline at end of file
diff --git a/ui/ui-text/src/main/java/androidx/ui/input/EditorState.kt b/ui/ui-text/src/main/java/androidx/ui/input/EditorModel.kt
similarity index 98%
rename from ui/ui-text/src/main/java/androidx/ui/input/EditorState.kt
rename to ui/ui-text/src/main/java/androidx/ui/input/EditorModel.kt
index b8e86aa..363e49a 100644
--- a/ui/ui-text/src/main/java/androidx/ui/input/EditorState.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/input/EditorModel.kt
@@ -26,7 +26,7 @@
* state. TextInputService sends the latest editing state to TextInputClient when the platform input
* service sends some input events.
*/
-class EditorState {
+class EditorModel {
/**
* The text
diff --git a/ui/ui-text/src/main/java/androidx/ui/input/TextInputService.kt b/ui/ui-text/src/main/java/androidx/ui/input/TextInputService.kt
index 382e110..c241f70 100644
--- a/ui/ui-text/src/main/java/androidx/ui/input/TextInputService.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/input/TextInputService.kt
@@ -29,7 +29,7 @@
* Start text input session for given client.
*/
fun startInput(
- initState: EditorState,
+ initModel: EditorModel,
keyboardType: KeyboardType,
imeAction: ImeAction,
onEditCommand: (List<EditOperation>) -> Unit,
@@ -49,9 +49,9 @@
fun showSoftwareKeyboard()
/*
- * Notify the new editor state to IME.
+ * Notify the new editor model to IME.
*/
- fun onStateUpdated(state: EditorState)
+ fun onStateUpdated(model: EditorModel)
/**
* Notify the focused rectangle to the system.
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/AnnotatedString.kt b/ui/ui-text/src/main/java/androidx/ui/text/AnnotatedString.kt
index 97fda37..28140c5 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/AnnotatedString.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/AnnotatedString.kt
@@ -21,8 +21,23 @@
*/
data class AnnotatedString(
val text: String,
- val textStyles: List<Item<TextStyle>> = listOf()
+ val textStyles: List<Item<TextStyle>> = listOf(),
+ val paragraphStyles: List<Item<ParagraphStyle>> = listOf()
) {
+
+ init {
+ var lastStyleEnd = -1
+ for (paragraphStyle in paragraphStyles) {
+ if (paragraphStyle.start < lastStyleEnd) {
+ throw IllegalArgumentException("ParagraphStyle should not overlap")
+ }
+ if (paragraphStyle.end > text.length) {
+ throw IllegalArgumentException("ParagraphStyle range " +
+ "[${paragraphStyle.start}, ${paragraphStyle.end}) is out of boundary")
+ }
+ lastStyleEnd = paragraphStyle.end
+ }
+ }
/**
* The information attached on the text such as a TextStyle.
*
@@ -31,5 +46,11 @@
* @param end The end of the range where [style] takes effect. It's exclusive.
*/
// TODO(haoyuchang): Check some other naming options.
- data class Item<T>(val style: T, val start: Int, val end: Int)
+ data class Item<T>(val style: T, val start: Int, val end: Int) {
+ init {
+ if (start > end) {
+ throw IllegalArgumentException("Reversed range is not supported")
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/MultiParagraph.kt b/ui/ui-text/src/main/java/androidx/ui/text/MultiParagraph.kt
new file mode 100644
index 0000000..830a776
--- /dev/null
+++ b/ui/ui-text/src/main/java/androidx/ui/text/MultiParagraph.kt
@@ -0,0 +1,634 @@
+/*
+ * Copyright 2019 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.ui.text
+
+import androidx.ui.core.Density
+import androidx.ui.core.Px
+import androidx.ui.core.PxPosition
+import androidx.ui.core.px
+import androidx.ui.engine.geometry.Offset
+import androidx.ui.engine.geometry.Rect
+import androidx.ui.painting.Canvas
+import androidx.ui.painting.Path
+import androidx.ui.text.font.Font
+import java.lang.IllegalStateException
+import kotlin.math.max
+
+/**
+ * The class that renders multiple paragraphs at once.
+ *
+ * It's designed to support multiple [ParagraphStyle]s in single text widget.
+ */
+internal class MultiParagraph(
+ val annotatedString: AnnotatedString,
+ private val textStyle: TextStyle = TextStyle(),
+ private val paragraphStyle: ParagraphStyle = ParagraphStyle(),
+ val maxLines: Int? = null,
+ val ellipsis: Boolean? = null,
+ val density: Density,
+ val resourceLoader: Font.ResourceLoader
+) {
+ /**
+ * The minimum width that this paragraph could be without failing to paint
+ * its contents within itself.
+ *
+ * Valid only after [layout] has been called.
+ */
+ val minIntrinsicWidth: Float
+ get() = paragraphInfoList.foldRight(0f) { paragraphInfo, minWidth ->
+ max(paragraphInfo.paragraph.minIntrinsicWidth, minWidth)
+ }
+
+ /**
+ * Returns the smallest width beyond which increasing the width never
+ * decreases the height.
+ *
+ * Valid only after [layout] has been called.
+ */
+ val maxIntrinsicWidth: Float
+ get() = paragraphInfoList.foldRight(0f) { paragraphInfo, maxWidth ->
+ max(paragraphInfo.paragraph.maxIntrinsicWidth, maxWidth)
+ }
+
+ /**
+ * True if there is more vertical content, but the text was truncated, either
+ * because we reached `maxLines` lines of text or because the `maxLines` was
+ * null, `ellipsis` was not null, and one of the lines exceeded the width
+ * constraint.
+ *
+ * See the discussion of the `maxLines` and `ellipsis` arguments at [ParagraphStyle].
+ */
+ var didExceedMaxLines: Boolean = false
+ private set
+
+ /**
+ * The amount of horizontal space this paragraph occupies.
+ *
+ * Valid only after [layout] has been called.
+ */
+ var width: Float = 0f
+ private set
+
+ /**
+ * The amount of vertical space this paragraph occupies.
+ *
+ * Valid only after [layout] has been called.
+ */
+ var height: Float = 0f
+ private set
+
+ /**
+ * The distance from the top of the paragraph to the alphabetic
+ * baseline of the first line, in logical pixels.
+ */
+ val baseline: Float
+ get() = if (paragraphInfoList.isEmpty()) 0f else paragraphInfoList[0].paragraph.baseline
+
+ /** The total number of lines in the text. */
+ var lineCount: Int = 0
+ private set
+
+ private var needLayout = true
+
+ private val paragraphInfoList: List<ParagraphInfo>
+
+ init {
+ val paragraphStyles = fillInParagraphRanges(annotatedString, this.paragraphStyle)
+ this.paragraphInfoList = paragraphStyles.map { (style, start, end) ->
+ // TODO(haoyuchang): Change substring to Paragraph receiving text and range.
+ val textInParagraph = if (start != end) {
+ annotatedString.text.substring(start, end)
+ } else {
+ ""
+ }
+ val textStylesInParagraph = annotatedString.getLocalStyles(start, end)
+
+ // TODO(haoyuchang): remove the top and bottom padding between two paragraphs
+ val paragraph = Paragraph(
+ textInParagraph,
+ this.textStyle,
+ style,
+ textStylesInParagraph,
+ maxLines,
+ ellipsis,
+ density,
+ resourceLoader
+ )
+
+ ParagraphInfo(
+ paragraph = paragraph,
+ startIndex = start,
+ endIndex = end
+ )
+ }
+ }
+
+ /**
+ * Computes the size and position of each glyph in the paragraph.
+ *
+ * The [ParagraphConstraints] control how wide the text is allowed to be.
+ */
+ fun layout(constraints: ParagraphConstraints) {
+ this.needLayout = false
+ this.width = constraints.width
+ this.didExceedMaxLines = false
+
+ var currentLineCount = 0
+ var currentHeight = 0f
+
+ for ((index, paragraphInfo) in paragraphInfoList.withIndex()) {
+ val paragraph = paragraphInfo.paragraph
+ paragraph.layout(constraints)
+
+ paragraphInfo.startLineIndex = currentLineCount
+ paragraphInfo.endLineIndex = currentLineCount + paragraph.lineCount
+ currentLineCount = paragraphInfo.endLineIndex
+
+ paragraphInfo.top = currentHeight.px
+ paragraphInfo.bottom = (currentHeight + paragraph.height).px
+ currentHeight += paragraph.height
+
+ // TODO(haoyuchang): solve the corner case where the ellipsis won't be applied when
+ // currentLineNum == maxLines but there are still more paragraphs
+ if (paragraph.didExceedMaxLines ||
+ (currentLineCount == maxLines && index != this.paragraphInfoList.lastIndex)
+ ) {
+ this.didExceedMaxLines = true
+ break
+ }
+ }
+ this.lineCount = currentLineCount
+ this.height = currentHeight
+ }
+
+ /** Paint the paragraphs to canvas. */
+ fun paint(canvas: Canvas) {
+ assertNeedLayout()
+
+ canvas.save()
+ paragraphInfoList.forEach {
+ it.paragraph.paint(canvas)
+ canvas.translate(0f, it.paragraph.height)
+ }
+ canvas.restore()
+ }
+
+ /** Returns path that enclose the given text range. */
+ fun getPathForRange(start: Int, end: Int): Path {
+ if (start !in 0..end || end > annotatedString.text.length) {
+ throw AssertionError(
+ "Start($start) or End($end) is out of range [0..${annotatedString.text.length})," +
+ " or start > end!"
+ )
+ }
+ assertNeedLayout()
+
+ if (start == end) return Path()
+
+ val paragraphIndex = findParagraphByIndex(paragraphInfoList, start)
+ val path = Path()
+
+ paragraphInfoList.drop(paragraphIndex)
+ .takeWhile { it.startIndex < end }
+ .filterNot { it.startIndex == it.endIndex }
+ .forEach {
+ with(it) {
+ path.addPath(
+ path = paragraph.getPathForRange(
+ start = start.toLocalIndex(),
+ end = end.toLocalIndex()
+ ).toGlobal()
+ )
+ }
+ }
+ return path
+ }
+
+ /** Returns the character offset closest to the given graphical position. */
+ fun getOffsetForPosition(position: PxPosition): Int {
+ assertNeedLayout()
+ val paragraphIndex = when {
+ position.y.value <= 0f -> 0
+ position.y.value >= height -> paragraphInfoList.lastIndex
+ else -> findParagraphByY(paragraphInfoList, position.y)
+ }
+ return with(paragraphInfoList[paragraphIndex]) {
+ if (length == 0) {
+ max(0, startIndex - 1)
+ } else {
+ paragraph.getOffsetForPosition(position.toLocal()).toGlobalIndex()
+ }
+ }
+ }
+
+ /**
+ * Returns the bounding box as Rect of the character for given character offset. Rect
+ * includes the top, bottom, left and right of a character.
+ */
+ fun getBoundingBox(offset: Int): Rect {
+ assertNeedLayout()
+ assertIndexInRange(offset)
+
+ val paragraphIndex = findParagraphByIndex(paragraphInfoList, offset)
+ return with(paragraphInfoList[paragraphIndex]) {
+ val localOffset = offset.toLocalIndex()
+ paragraph.getBoundingBox(localOffset).toGlobal()
+ }
+ }
+
+ /** Get the primary horizontal position for the specified text offset. */
+ fun getPrimaryHorizontal(offset: Int): Float {
+ assertNeedLayout()
+ if (offset !in 0..annotatedString.text.length) {
+ throw AssertionError("offset($offset) is out of bounds " +
+ "(0,${annotatedString.text.length}")
+ }
+
+ val paragraphIndex = if (offset == annotatedString.text.length) {
+ paragraphInfoList.lastIndex
+ } else {
+ findParagraphByIndex(paragraphInfoList, offset)
+ }
+
+ return with(paragraphInfoList[paragraphIndex]) {
+ paragraph.getPrimaryHorizontal(offset.toLocalIndex())
+ }
+ }
+
+ /**
+ * Returns the TextRange of the word at the given character offset. Characters not
+ * part of a word, such as spaces, symbols, and punctuation, have word breaks
+ * on both sides. In such cases, this method will return TextRange(offset, offset+1).
+ * Word boundaries are defined more precisely in Unicode Standard Annex #29
+ * http://www.unicode.org/reports/tr29/#Word_Boundaries
+ */
+ fun getWordBoundary(offset: Int): TextRange {
+ assertNeedLayout()
+ assertIndexInRange(offset)
+
+ val paragraphIndex = findParagraphByIndex(paragraphInfoList, offset)
+
+ return with(paragraphInfoList[paragraphIndex]) {
+ paragraph.getWordBoundary(offset.toLocalIndex()).toGlobal()
+ }
+ }
+
+ /** Returns rectangle of the cursor area. */
+ fun getCursorRect(offset: Int): Rect {
+ assertNeedLayout()
+ if (offset !in 0..annotatedString.text.length) {
+ throw AssertionError("offset($offset) is out of bounds " +
+ "(0,${annotatedString.text.length}")
+ }
+
+ val paragraphIndex = if (offset == annotatedString.text.length) {
+ paragraphInfoList.lastIndex
+ } else {
+ findParagraphByIndex(paragraphInfoList, offset)
+ }
+
+ return with(paragraphInfoList[paragraphIndex]) {
+ paragraph.getCursorRect(offset.toLocalIndex()).toGlobal()
+ }
+ }
+
+ /**
+ * Returns the line number on which the specified text offset appears.
+ * If you ask for a position before 0, you get 0; if you ask for a position
+ * beyond the end of the text, you get the last line.
+ */
+ fun getLineForOffset(offset: Int): Int {
+ assertNeedLayout()
+ assertIndexInRange(offset)
+
+ val paragraphIndex = findParagraphByIndex(paragraphInfoList, offset)
+ return with(paragraphInfoList[paragraphIndex]) {
+ paragraph.getLineForOffset(offset.toLocalIndex()).toGlobalLineIndex()
+ }
+ }
+
+ /** Returns the left x Coordinate of the given line. */
+ fun getLineLeft(lineIndex: Int): Float {
+ assertNeedLayout()
+ assertLineIndexInRange(lineIndex)
+
+ val paragraphIndex = findParagraphByLineIndex(paragraphInfoList, lineIndex)
+
+ return with(paragraphInfoList[paragraphIndex]) {
+ paragraph.getLineLeft(lineIndex.toLocalLineIndex())
+ }
+ }
+
+ /** Returns the right x Coordinate of the given line. */
+ fun getLineRight(lineIndex: Int): Float {
+ assertNeedLayout()
+ assertLineIndexInRange(lineIndex)
+
+ val paragraphIndex = findParagraphByLineIndex(paragraphInfoList, lineIndex)
+
+ return with(paragraphInfoList[paragraphIndex]) {
+ paragraph.getLineRight(lineIndex.toLocalLineIndex())
+ }
+ }
+
+ /** Returns the bottom y coordinate of the given line. */
+ fun getLineBottom(lineIndex: Int): Float {
+ assertNeedLayout()
+ assertLineIndexInRange(lineIndex)
+
+ val paragraphIndex = findParagraphByLineIndex(paragraphInfoList, lineIndex)
+
+ return with(paragraphInfoList[paragraphIndex]) {
+ paragraph.getLineBottom(lineIndex.toLocalLineIndex())
+ }
+ }
+
+ /** Returns the height of the given line. */
+ fun getLineHeight(lineIndex: Int): Float {
+ assertNeedLayout()
+ assertLineIndexInRange(lineIndex)
+
+ val paragraphIndex = findParagraphByLineIndex(paragraphInfoList, lineIndex)
+
+ return with(paragraphInfoList[paragraphIndex]) {
+ paragraph.getLineHeight(lineIndex.toLocalLineIndex())
+ }
+ }
+
+ /** Returns the width of the given line. */
+ fun getLineWidth(lineIndex: Int): Float {
+ assertNeedLayout()
+ assertLineIndexInRange(lineIndex)
+
+ val paragraphIndex = findParagraphByLineIndex(paragraphInfoList, lineIndex)
+
+ return with(paragraphInfoList[paragraphIndex]) {
+ paragraph.getLineWidth(lineIndex.toLocalLineIndex())
+ }
+ }
+
+ private fun assertNeedLayout() {
+ if (needLayout) {
+ throw IllegalStateException("")
+ }
+ }
+
+ private fun assertIndexInRange(offset: Int) {
+ if (offset !in (0 until annotatedString.text.length)) {
+ throw IndexOutOfBoundsException("offset($offset) is out of bounds" +
+ " [0, ${annotatedString.text.length})")
+ }
+ }
+
+ private fun assertLineIndexInRange(lineIndex: Int) {
+ if (lineIndex !in (0 until lineCount)) {
+ throw IndexOutOfBoundsException("lineIndex($lineIndex) is out of bounds" +
+ " [0, $lineIndex)")
+ }
+ }
+}
+
+/**
+ * Given an character index of [MultiParagraph.annotatedString], find the corresponding
+ * [ParagraphInfo] which covers the provided index.
+ *
+ * @param paragraphInfoList The list of [ParagraphInfo] containing the information of each
+ * paragraph in the [MultiParagraph].
+ * @param index The target index in the [MultiParagraph]. It should be in the range of
+ * [0, text.length)
+ * @return The index of the target [ParagraphInfo] in [paragraphInfoList].
+ */
+private fun findParagraphByIndex(paragraphInfoList: List<ParagraphInfo>, index: Int): Int {
+ return paragraphInfoList.binarySearch { paragraphInfo ->
+ when {
+ paragraphInfo.startIndex > index -> 1
+ paragraphInfo.endIndex <= index -> -1
+ else -> 0
+ }
+ }
+}
+
+/**
+ * Given the y graphical position relative to this [MultiParagraph], find the index of the
+ * corresponding [ParagraphInfo] which occupies the provided position.
+ *
+ * @param paragraphInfoList The list of [ParagraphInfo] containing the information of each
+ * paragraph in the [MultiParagraph].
+ * @param y The y coordinate position relative to the [MultiParagraph]. It should be in the range
+ * of [0, [MultiParagraph.height]].
+ * @return The index of the target [ParagraphInfo] in [paragraphInfoList].
+ */
+private fun findParagraphByY(paragraphInfoList: List<ParagraphInfo>, y: Px): Int {
+ return paragraphInfoList.binarySearch { paragraphInfo ->
+ when {
+ paragraphInfo.top > y -> 1
+ paragraphInfo.bottom <= y -> -1
+ else -> 0
+ }
+ }
+}
+
+/**
+ * Given an line index in [MultiParagraph], find the corresponding [ParagraphInfo] which
+ * covers the provided line index.
+ *
+ * @param paragraphInfoList The list of [ParagraphInfo] containing the information of each
+ * paragraph in the [MultiParagraph].
+ * @param lineIndex The target line index in the [MultiParagraph], it should be in the range of
+ * [0, [MultiParagraph.lineCount])
+ * @return The index of the target [ParagraphInfo] in [paragraphInfoList].
+ */
+private fun findParagraphByLineIndex(paragraphInfoList: List<ParagraphInfo>, lineIndex: Int): Int {
+ return paragraphInfoList.binarySearch { paragraphInfo ->
+ when {
+ paragraphInfo.startLineIndex > lineIndex -> 1
+ paragraphInfo.endLineIndex <= lineIndex -> -1
+ else -> 0
+ }
+ }
+}
+
+/**
+ * A helper function used to determine the paragraph boundaries in [MultiParagraph].
+ *
+ * It reads paragraph information from [AnnotatedString.paragraphStyles] where only some parts of
+ * text has [ParagraphStyle] specified, and unspecified parts(gaps between specified paragraphs)
+ * are considered as default paragraph with default [ParagraphStyle].
+ * For example, the following string with a specified paragraph denoted by "[]"
+ * "Hello WorldHi!"
+ * [ ]
+ * The result paragraphs are "Hello World" and "Hi!".
+ *
+ * @param annotatedString: The [AnnotatedString] on which the paragraph boundaries need to be
+ * determined.
+ * @param defaultParagraphStyle The default [ParagraphStyle]. It's used for both unspecified
+ * default paragraphs and specified paragraph. When a specified paragraph's [ParagraphStyle] has
+ * a null attribute, the default one will be used instead.
+ */
+internal fun fillInParagraphRanges(
+ annotatedString: AnnotatedString,
+ defaultParagraphStyle: ParagraphStyle
+): List<AnnotatedString.Item<ParagraphStyle>> {
+ val length = annotatedString.text.length
+ val paragraphStyles = annotatedString.paragraphStyles
+
+ var lastOffset = 0
+ val result = mutableListOf<AnnotatedString.Item<ParagraphStyle>>()
+ for ((style, start, end) in paragraphStyles) {
+ if (start != lastOffset) {
+ result.add(AnnotatedString.Item(defaultParagraphStyle, lastOffset, start))
+ }
+ result.add(AnnotatedString.Item(defaultParagraphStyle.merge(style), start, end))
+ lastOffset = end
+ }
+ if (lastOffset != length) {
+ result.add(AnnotatedString.Item(defaultParagraphStyle, lastOffset, length))
+ }
+ // This is a corner case where annotatedString is an empty string without any ParagraphStyle.
+ // In this case, a dummy ParagraphStyle is created.
+ if (result.isEmpty()) {
+ result.add(AnnotatedString.Item(defaultParagraphStyle, 0, 0))
+ }
+ return result
+}
+
+/**
+ * Helper function used to find the [TextStyle]s in the given paragraph range and also convert the
+ * range of those [TextStyle]s to paragraph local range.
+ *
+ * @param start The start index of the paragraph range, inclusive.
+ * @param end The end index of the paragraph range, exclusive.
+ * @return The list of converted [TextStyle]s in the given paragraph range.
+ */
+private fun AnnotatedString.getLocalStyles(
+ start: Int,
+ end: Int
+): List<AnnotatedString.Item<TextStyle>> {
+ if (start == end) {
+ return listOf()
+ }
+ // If the given range covers the whole AnnotatedString, return textStyles without conversion.
+ if (start == 0 && end >= this.text.length) {
+ return textStyles
+ }
+ return textStyles.filter { it.start < end && it.end > start }
+ .map {
+ AnnotatedString.Item(
+ it.style,
+ it.start.coerceIn(start, end) - start,
+ it.end.coerceIn(start, end) - start
+ )
+ }
+}
+
+/**
+ * This is a helper data structure to store the information of a single [Paragraph] in an
+ * [MultiParagraph]. It's mainly used to convert a global index, lineNumber and [Offset] to the
+ * local ones inside the [paragraph], and vice versa.
+ *
+ * @param paragraph The [Paragraph] object corresponding to this [ParagraphInfo].
+ * @param startIndex The start index of this paragraph in the parent [MultiParagraph], inclusive.
+ * @param endIndex The end index of this paragraph in the parent [MultiParagraph], exclusive.
+ * @param startLineIndex The start line index of this paragraph in the parent [MultiParagraph],
+ * inclusive.
+ * @param endLineIndex The end line index of this paragraph in the parent [MultiParagraph],
+ * exclusive.
+ * @param top The top position of the [paragraph] relative to the parent [MultiParagraph].
+ * @param bottom The bottom position of the [paragraph] relative to the parent [MultiParagraph].
+ */
+internal data class ParagraphInfo(
+ val paragraph: Paragraph,
+ val startIndex: Int,
+ val endIndex: Int,
+ var startLineIndex: Int = -1,
+ var endLineIndex: Int = -1,
+ var top: Px = (-1).px,
+ var bottom: Px = (-1).px
+) {
+
+ /**
+ * The length of the text in the covered by this paragraph.
+ */
+ val length
+ get() = endIndex - startIndex
+
+ /**
+ * Convert an index in the parent [MultiParagraph] to the local index in the [paragraph].
+ */
+ fun Int.toLocalIndex(): Int {
+ return this.coerceIn(startIndex, endIndex) - startIndex
+ }
+
+ /**
+ * Convert a local index in the [paragraph] to the global index in the parent [MultiParagraph].
+ */
+ fun Int.toGlobalIndex(): Int {
+ return this + startIndex
+ }
+
+ /**
+ * Convert a line index in the parent [MultiParagraph] to the local line index in the
+ * [paragraph].
+ *
+ */
+ fun Int.toLocalLineIndex(): Int {
+ return this - startLineIndex
+ }
+
+ /**
+ * Convert a local line index in the [paragraph] to the global line index in the parent
+ * [MultiParagraph].
+ */
+ fun Int.toGlobalLineIndex(): Int {
+ return this + startLineIndex
+ }
+
+ /**
+ * Convert a [PxPosition] relative to the parent [MultiParagraph] to the local [PxPosition]
+ * relative to the [paragraph].
+ */
+ fun PxPosition.toLocal(): PxPosition {
+ return copy(y = y - top)
+ }
+
+ /**
+ * Convert a [Rect] relative to the [paragraph] to the [Rect] relative to the parent
+ * [MultiParagraph].
+ */
+ fun Rect.toGlobal(): Rect {
+ return shift(Offset(dx = 0f, dy = this@ParagraphInfo.top.value))
+ }
+
+ /**
+ * Convert a [Path] relative to the [paragraph] to the [Path] relative to the parent
+ * [MultiParagraph].
+ *
+ * Notice that this function changes the input value.
+ */
+ fun Path.toGlobal(): Path {
+ shift(Offset(dx = 0f, dy = top.value))
+ return this
+ }
+
+ /**
+ * Convert a [TextRange] in to the [paragraph] to the [TextRange] in the parent
+ * [MultiParagraph].
+ */
+ fun TextRange.toGlobal(): TextRange {
+ return TextRange(start = start.toGlobalIndex(), end = end.toGlobalIndex())
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/ParagraphStyle.kt b/ui/ui-text/src/main/java/androidx/ui/text/ParagraphStyle.kt
index dd7c248..4fd974e 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/ParagraphStyle.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/ParagraphStyle.kt
@@ -47,19 +47,20 @@
}
}
// TODO(siyamed) uncomment
-// /**
-// * Returns a new paragraph style that is a combination of this style and the given [other] style.
-// *
-// * If the given paragraph style is null, returns this paragraph style.
-// */
-// fun merge(other: ParagraphStyle? = null): ParagraphStyle {
-// if (other == null) return this
-//
-// return ParagraphStyle(
-// lineHeight = other.lineHeight ?: this.lineHeight,
-// textIndent = other.textIndent ?: this.textIndent,
-// textAlign = other.textAlign ?: this.textAlign,
-// textDirection = other.textDirection ?: this.textDirection
-// )
-// }
+ /**
+ * Returns a new paragraph style that is a combination of this style and the given [other]
+ * style.
+ *
+ * If the given paragraph style is null, returns this paragraph style.
+ */
+ fun merge(other: ParagraphStyle? = null): ParagraphStyle {
+ if (other == null) return this
+
+ return ParagraphStyle(
+ lineHeight = other.lineHeight ?: this.lineHeight,
+ textIndent = other.textIndent ?: this.textIndent,
+ textAlign = other.textAlign ?: this.textAlign,
+ textDirection = other.textDirection ?: this.textDirection
+ )
+ }
}
\ No newline at end of file
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/TextPainter.kt b/ui/ui-text/src/main/java/androidx/ui/text/TextPainter.kt
index 7880c2d..0a5d93a 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/TextPainter.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/TextPainter.kt
@@ -118,7 +118,7 @@
}
@VisibleForTesting
- internal var paragraph: Paragraph? = null
+ internal var multiParagraph: MultiParagraph? = null
private set
@VisibleForTesting
@@ -143,7 +143,7 @@
set(value) {
if (field == value) return
field = value
- paragraph = null
+ multiParagraph = null
needsLayout = true
}
@@ -219,7 +219,7 @@
val minIntrinsicWidth: Float
get() {
assertNeedsLayout("minIntrinsicWidth")
- return applyFloatingPointHack(paragraph!!.minIntrinsicWidth)
+ return applyFloatingPointHack(multiParagraph!!.minIntrinsicWidth)
}
/**
@@ -230,7 +230,7 @@
val maxIntrinsicWidth: Float
get() {
assertNeedsLayout("maxIntrinsicWidth")
- return applyFloatingPointHack(paragraph!!.maxIntrinsicWidth)
+ return applyFloatingPointHack(multiParagraph!!.maxIntrinsicWidth)
}
/**
@@ -281,7 +281,7 @@
val didExceedMaxLines: Boolean
get() {
assertNeedsLayout("didExceedMaxLines")
- return paragraph!!.didExceedMaxLines
+ return multiParagraph!!.didExceedMaxLines
}
/**
@@ -308,25 +308,25 @@
if (!needsLayout && minWidth == lastMinWidth && finalMaxWidth == lastMaxWidth) return
needsLayout = false
- if (paragraph == null) {
- paragraph = Paragraph(
- text = text!!.text,
- style = createTextStyle(),
- paragraphStyle = createParagraphStyle(),
- textStyles = text!!.textStyles,
- maxLines = maxLines,
- ellipsis = isEllipsis,
- density = density,
- resourceLoader = resourceLoader
+
+ if (multiParagraph == null) {
+ multiParagraph = MultiParagraph(
+ text!!,
+ createTextStyle(),
+ paragraphStyle ?: ParagraphStyle(),
+ maxLines,
+ isEllipsis,
+ density,
+ resourceLoader
)
}
lastMinWidth = minWidth
lastMaxWidth = finalMaxWidth
- paragraph!!.layout(ParagraphConstraints(width = finalMaxWidth))
+ multiParagraph!!.layout(ParagraphConstraints(width = finalMaxWidth))
if (minWidth != finalMaxWidth) {
val newWidth = maxIntrinsicWidth.coerceIn(minWidth, finalMaxWidth)
- if (newWidth != paragraph!!.width) {
- paragraph!!.layout(ParagraphConstraints(width = newWidth))
+ if (newWidth != multiParagraph!!.width) {
+ multiParagraph!!.layout(ParagraphConstraints(width = newWidth))
}
}
}
@@ -336,11 +336,11 @@
val didOverflowHeight = didExceedMaxLines
size = constraints.constrain(
- IntPxSize(paragraph!!.width.px.round(), paragraph!!.height.px.round())
+ IntPxSize(multiParagraph!!.width.px.round(), multiParagraph!!.height.px.round())
).let {
Size(it.width.value.toFloat(), it.height.value.toFloat())
}
- val didOverflowWidth = size.width < paragraph!!.width
+ val didOverflowWidth = size.width < multiParagraph!!.width
// TODO(abarth): We're only measuring the sizes of the line boxes here. If
// the glyphs draw outside the line boxes, we might think that there isn't
// visual overflow when there actually is visual overflow. This can become
@@ -356,8 +356,8 @@
resourceLoader = resourceLoader
)
fadeSizePainter.layoutText()
- val fadeWidth = fadeSizePainter.paragraph!!.width
- val fadeHeight = fadeSizePainter.paragraph!!.height
+ val fadeWidth = fadeSizePainter.multiParagraph!!.width
+ val fadeHeight = fadeSizePainter.multiParagraph!!.height
if (didOverflowWidth) {
val (fadeStart, fadeEnd) = if (textDirection == TextDirection.Rtl) {
Pair(fadeWidth, 0.0f)
@@ -426,7 +426,8 @@
}
canvas.clipRect(bounds)
}
- paragraph!!.paint(canvas)
+
+ multiParagraph!!.paint(canvas)
if (hasVisualOverflow) {
if (overflowShader != null) {
val bounds = Rect.fromLTWH(0f, 0f, size.width, size.height)
@@ -452,7 +453,7 @@
fun paintBackground(start: Int, end: Int, color: Color, canvas: Canvas) {
assert(!needsLayout)
if (start == end) return
- val selectionPath = paragraph!!.getPathForRange(start, end)
+ val selectionPath = multiParagraph!!.getPathForRange(start, end)
// TODO(haoyuchang): check if move this paint to parameter is better
canvas.drawPath(selectionPath, Paint().apply { this.color = color })
}
@@ -467,7 +468,7 @@
*/
fun paintCursor(offset: Int, canvas: Canvas) {
assert(!needsLayout)
- val cursorRect = paragraph!!.getCursorRect(offset)
+ val cursorRect = multiParagraph!!.getCursorRect(offset)
canvas.drawRect(cursorRect, Paint().apply { this.color = Color.Black })
}
@@ -479,7 +480,7 @@
@RestrictTo(RestrictTo.Scope.LIBRARY)
fun getLineBottom(lineIndex: Int): Float {
assert(!needsLayout)
- return paragraph!!.getLineBottom(lineIndex)
+ return multiParagraph!!.getLineBottom(lineIndex)
}
/**
@@ -492,7 +493,7 @@
@RestrictTo(RestrictTo.Scope.LIBRARY)
fun getLineForOffset(offset: Int): Int {
assert(!needsLayout)
- return paragraph!!.getLineForOffset(offset)
+ return multiParagraph!!.getLineForOffset(offset)
}
/**
@@ -503,13 +504,13 @@
@RestrictTo(RestrictTo.Scope.LIBRARY)
fun getPrimaryHorizontal(offset: Int): Float {
assert(!needsLayout)
- return paragraph!!.getPrimaryHorizontal(offset)
+ return multiParagraph!!.getPrimaryHorizontal(offset)
}
/** Returns the character offset closest to the given graphical position. */
fun getOffsetForPosition(position: PxPosition): Int {
assert(!needsLayout)
- return paragraph!!.getOffsetForPosition(position)
+ return multiParagraph!!.getOffsetForPosition(position)
}
/**
@@ -523,7 +524,7 @@
@RestrictTo(RestrictTo.Scope.LIBRARY)
fun getBoundingBox(offset: Int): Rect {
assert(!needsLayout)
- return paragraph!!.getBoundingBox(offset)
+ return multiParagraph!!.getBoundingBox(offset)
}
/**
@@ -536,6 +537,6 @@
*/
fun getWordBoundary(offset: Int): TextRange {
assert(!needsLayout)
- return paragraph!!.getWordBoundary(offset)
+ return multiParagraph!!.getWordBoundary(offset)
}
}
diff --git a/ui/ui-text/src/test/java/androidx/ui/input/EditProcessorTest.kt b/ui/ui-text/src/test/java/androidx/ui/input/EditProcessorTest.kt
index 3bdaf143..591e2c4 100644
--- a/ui/ui-text/src/test/java/androidx/ui/input/EditProcessorTest.kt
+++ b/ui/ui-text/src/test/java/androidx/ui/input/EditProcessorTest.kt
@@ -39,8 +39,8 @@
val proc = EditProcessor()
val tis: TextInputService = mock()
- proc.onNewState(EditorState("ABCDE", TextRange(0, 0)), tis)
- val captor = argumentCaptor<EditorState>()
+ proc.onNewState(EditorModel("ABCDE", TextRange(0, 0)), tis)
+ val captor = argumentCaptor<EditorModel>()
verify(tis, times(1)).onStateUpdated(captor.capture())
assertEquals(1, captor.allValues.size)
assertEquals("ABCDE", captor.firstValue.text)
diff --git a/ui/ui-text/src/test/java/androidx/ui/text/MultiParagraphTest.kt b/ui/ui-text/src/test/java/androidx/ui/text/MultiParagraphTest.kt
new file mode 100644
index 0000000..ce472ad
--- /dev/null
+++ b/ui/ui-text/src/test/java/androidx/ui/text/MultiParagraphTest.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2019 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.ui.text
+
+import androidx.ui.text.style.TextAlign
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class MultiParagraphTest {
+ @Test
+ fun `test fillInParagraphRanges`() {
+ val text = "Hello World"
+ val paragraphStyle = ParagraphStyle(textAlign = TextAlign.Center)
+ val paragraphStyles = listOf(AnnotatedString.Item(paragraphStyle, 0, 5))
+ val annotatedString = AnnotatedString(text = text, paragraphStyles = paragraphStyles)
+ val defaultParagraphStyle = ParagraphStyle(lineHeight = 2.0f)
+
+ val paragraphs = fillInParagraphRanges(annotatedString, defaultParagraphStyle)
+
+ assertThat(paragraphs.size).isEqualTo(2)
+
+ assertThat(paragraphs[0].style).isEqualTo(defaultParagraphStyle.merge(paragraphStyle))
+ assertThat(paragraphs[0].start).isEqualTo(0)
+ assertThat(paragraphs[0].end).isEqualTo(5)
+
+ assertThat(paragraphs[1].style).isEqualTo(defaultParagraphStyle)
+ assertThat(paragraphs[1].start).isEqualTo(5)
+ assertThat(paragraphs[1].end).isEqualTo(text.length)
+ }
+
+ @Test
+ fun `test fillInParagraphRanges only string`() {
+ val text = "Hello World"
+ val annotatedString = AnnotatedString(text = text)
+ val defaultParagraphStyle = ParagraphStyle(lineHeight = 2.0f)
+
+ val paragraphs = fillInParagraphRanges(annotatedString, defaultParagraphStyle)
+
+ assertThat(paragraphs.size).isEqualTo(1)
+
+ assertThat(paragraphs[0].style).isEqualTo(defaultParagraphStyle)
+ assertThat(paragraphs[0].start).isEqualTo(0)
+ assertThat(paragraphs[0].end).isEqualTo(text.length)
+ }
+
+ @Test
+ fun `test fillInParagraphRanges empty string`() {
+ val text = ""
+ val annotatedString = AnnotatedString(text = text)
+ val defaultParagraphStyle = ParagraphStyle(lineHeight = 2.0f)
+
+ val paragraphs = fillInParagraphRanges(annotatedString, defaultParagraphStyle)
+
+ assertThat(paragraphs.size).isEqualTo(1)
+
+ assertThat(paragraphs[0].style).isEqualTo(defaultParagraphStyle)
+ assertThat(paragraphs[0].start).isEqualTo(0)
+ assertThat(paragraphs[0].end).isEqualTo(text.length)
+ }
+
+ @Test
+ fun `test fillInParagraphRanges with newLine`() {
+ val text = "Hello\nWorld"
+ val annotatedString = AnnotatedString(text = text)
+ val defaultParagraphStyle = ParagraphStyle(lineHeight = 2.0f)
+
+ val paragraphs = fillInParagraphRanges(annotatedString, defaultParagraphStyle)
+
+ assertThat(paragraphs.size).isEqualTo(1)
+
+ assertThat(paragraphs[0].style).isEqualTo(defaultParagraphStyle)
+ assertThat(paragraphs[0].start).isEqualTo(0)
+ assertThat(paragraphs[0].end).isEqualTo(text.length)
+ }
+
+ @Test
+ fun `test fillInParagraphRanges with only lineFeed`() {
+ val text = "\n"
+ val annotatedString = AnnotatedString(text = text)
+ val defaultParagraphStyle = ParagraphStyle(lineHeight = 2.0f)
+
+ val paragraphs = fillInParagraphRanges(annotatedString, defaultParagraphStyle)
+
+ assertThat(paragraphs.size).isEqualTo(1)
+
+ assertThat(paragraphs[0].style).isEqualTo(defaultParagraphStyle)
+ assertThat(paragraphs[0].start).isEqualTo(0)
+ assertThat(paragraphs[0].end).isEqualTo(1)
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-text/src/test/java/androidx/ui/text/TextPainterTest.kt b/ui/ui-text/src/test/java/androidx/ui/text/TextPainterTest.kt
index 6c07a79..5dcae69 100644
--- a/ui/ui-text/src/test/java/androidx/ui/text/TextPainterTest.kt
+++ b/ui/ui-text/src/test/java/androidx/ui/text/TextPainterTest.kt
@@ -127,7 +127,7 @@
textPainter.text = text
assertThat(textPainter.text).isEqualTo(text)
- assertThat(textPainter.paragraph).isNull()
+ assertThat(textPainter.multiParagraph).isNull()
assertThat(textPainter.needsLayout).isTrue()
}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AccessibilityTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AccessibilityTest.kt
index 050aab8..4736f91 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AccessibilityTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AccessibilityTest.kt
@@ -97,7 +97,7 @@
assertBasicState(initialPage)
var node = AccessibilityNodeInfoCompat.obtain()
- activityTestRule.runOnUiThread {
+ runOnUiThreadSync {
ViewCompat.onInitializeAccessibilityNodeInfo(viewPager, node)
}
var collectionInfo = node.collectionInfo
@@ -127,7 +127,7 @@
val initialPage = viewPager.currentItem
assertBasicState(initialPage)
- activityTestRule.runOnUiThread {
+ runOnUiThreadSync {
viewPager.setOrientation(getOppositeOrientation(config.orientation))
}
assertBasicState(initialPage)
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterDataSetChangeTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterDataSetChangeTest.kt
index face96e..8d4bc8f 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterDataSetChangeTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterDataSetChangeTest.kt
@@ -67,7 +67,7 @@
// Dispatch and wait for data set changes
val layoutChangedLatch = test.viewPager.addWaitForLayoutChangeLatch()
- activityTestRule.runOnUiThread {
+ test.runOnUiThreadSync {
config.actions.forEach { it.perform(adapter) }
}
layoutChangedLatch.await(1, SECONDS)
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterTest.kt
index fc705e5..e696227 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterTest.kt
@@ -18,8 +18,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
-import androidx.testutils.PollingCheck
-import androidx.testutils.waitForExecution
import androidx.viewpager2.widget.AdapterTest.Event.OnPageScrollStateChangedEvent
import androidx.viewpager2.widget.AdapterTest.Event.OnPageScrolledEvent
import androidx.viewpager2.widget.AdapterTest.Event.OnPageSelectedEvent
@@ -33,7 +31,6 @@
import org.junit.Assert.assertThat
import org.junit.Test
import org.junit.runner.RunWith
-import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit.SECONDS
@LargeTest
@@ -56,7 +53,7 @@
test.assertBasicState(0)
test.viewPager.setCurrentItemSync(1, false, 2, SECONDS)
test.assertBasicState(1)
- activityTestRule.runOnUiThread {
+ test.runOnUiThreadSync {
test.viewPager.adapter = test.viewPager.adapter
}
test.assertBasicState(0)
@@ -211,7 +208,7 @@
private fun clearDataSet() {
assertThat(dataSet.size, greaterThan(0))
- modifyDataSet {
+ test.modifyDataSetSync {
val itemCount = dataSet.size
dataSet.clear()
test.viewPager.adapter!!.notifyItemRangeRemoved(0, itemCount)
@@ -221,35 +218,13 @@
private fun fillDataSet() {
assertThat(dataSet.size, equalTo(0))
- modifyDataSet {
+ test.modifyDataSetSync {
dataSet.addAll(stringSequence(pageCount))
test.viewPager.adapter!!.notifyItemRangeInserted(0, pageCount)
}
test.assertBasicState(0)
}
- private fun modifyDataSet(block: () -> Unit) {
- val layoutChangedLatch = test.viewPager.addWaitForLayoutChangeLatch()
- activityTestRule.runOnUiThread {
- block()
- }
- layoutChangedLatch.await(1, SECONDS)
-
- // Let animations run
- val animationLatch = CountDownLatch(1)
- test.viewPager.recyclerView.itemAnimator!!.isRunning {
- animationLatch.countDown()
- }
- animationLatch.await(1, SECONDS)
-
- // Wait until VP2 has stabilized
- activityTestRule.waitForExecution()
- val adapter = test.viewPager.adapter
- if (adapter != null && adapter.itemCount > 0) {
- PollingCheck.waitFor(1000) { test.viewPager.currentCompletelyVisibleItem != -1 }
- }
- }
-
private fun ViewPager2.addNewRecordingCallback(): RecordingCallback {
return RecordingCallback().also { registerOnPageChangeCallback(it) }
}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
index 679943f..1a284f4 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
@@ -146,7 +146,20 @@
field = value
}
- fun runOnUiThread(f: () -> Unit) = activity.runOnUiThread(f)
+ fun runOnUiThreadSync(f: () -> Unit) {
+ var thrownError: Throwable? = null
+ activityTestRule.runOnUiThread {
+ try {
+ f()
+ } catch (t: Throwable) {
+ thrownError = t
+ }
+ }
+ val caughtError = thrownError
+ if (caughtError != null) {
+ throw caughtError
+ }
+ }
val viewPager: ViewPager2 get() = activity.findViewById(R.id.view_pager)
@@ -302,7 +315,7 @@
)
var node = AccessibilityNodeInfo.obtain()
- activityTestRule.runOnUiThread { viewPager.onInitializeAccessibilityNodeInfo(node) }
+ runOnUiThreadSync { viewPager.onInitializeAccessibilityNodeInfo(node) }
@Suppress("DEPRECATION") var standardActions = node.actions
assertThat("scroll backward action expected: $expectScrollBackwardAction",
@@ -374,7 +387,7 @@
fun Context.setAdapterSync(adapterProvider: AdapterProvider) {
lateinit var waitForRenderLatch: CountDownLatch
- activityTestRule.runOnUiThread {
+ runOnUiThreadSync {
waitForRenderLatch = viewPager.addWaitForLayoutChangeLatch()
viewPager.adapter = adapterProvider(activity)
}
@@ -443,6 +456,9 @@
return latch
}
+ val ViewPager2.linearLayoutManager: LinearLayoutManager
+ get() = recyclerView.layoutManager as LinearLayoutManager
+
val ViewPager2.recyclerView: RecyclerView
get() {
return getChildAt(0) as RecyclerView
@@ -452,8 +468,7 @@
get() {
var position = RecyclerView.NO_POSITION
activityTestRule.runOnUiThread {
- position = (recyclerView.layoutManager as LinearLayoutManager)
- .findFirstCompletelyVisibleItemPosition()
+ position = linearLayoutManager.findFirstCompletelyVisibleItemPosition()
}
return position
}
@@ -488,6 +503,21 @@
assertPageActions()
}
+ fun Context.modifyDataSetSync(block: () -> Unit) {
+ val layoutChangedLatch = viewPager.addWaitForLayoutChangeLatch()
+ runOnUiThreadSync {
+ block()
+ }
+ layoutChangedLatch.await(1, TimeUnit.SECONDS)
+
+ // Let animations run
+ val animationLatch = CountDownLatch(1)
+ viewPager.recyclerView.itemAnimator!!.isRunning {
+ animationLatch.countDown()
+ }
+ animationLatch.await(1, TimeUnit.SECONDS)
+ }
+
fun ViewPager2.setCurrentItemSync(
targetPage: Int,
smoothScroll: Boolean,
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/DragWhileSmoothScrollTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/DragWhileSmoothScrollTest.kt
index 1282878..ea4bcf4 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/DragWhileSmoothScrollTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/DragWhileSmoothScrollTest.kt
@@ -17,7 +17,6 @@
package androidx.viewpager2.widget
import android.widget.TextView
-import androidx.recyclerview.widget.LinearLayoutManager
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import androidx.testutils.SwipeToLocation.flingToCenter
@@ -83,7 +82,7 @@
// when we are close enough
val waitTillCloseEnough = test.viewPager.addWaitForDistanceToTarget(config.targetPage,
config.distanceToTargetWhenStartDrag)
- test.runOnUiThread { test.viewPager.setCurrentItem(config.targetPage, true) }
+ test.runOnUiThreadSync { test.viewPager.setCurrentItem(config.targetPage, true) }
waitTillCloseEnough.await(2, SECONDS)
// then perform a swipe
@@ -158,8 +157,8 @@
// Find the view on the UI thread, as RV may be in layout
val pageText = "$pageToSnapTo"
var viewFound = false
- test.activityTestRule.runOnUiThread {
- val llm = test.viewPager.recyclerView.layoutManager as LinearLayoutManager
+ test.runOnUiThreadSync {
+ val llm = test.viewPager.linearLayoutManager
var i = 0
while (!viewFound && i < llm.childCount) {
val view = llm.getChildAt(i++) as TextView
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt
index 874da2c..4a8f194 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt
@@ -322,7 +322,7 @@
@SdkSuppress(minSdkVersion = 16)
fun test_performA11yActionDuringFakeDrag() {
startManualDragDuringFakeDrag(.9f, 1000, interpolator = fastDecelerateInterpolator) {
- activityTestRule.runOnUiThread {
+ test.runOnUiThreadSync {
ViewCompat.performAccessibilityAction(test.viewPager, getNextPageAction(), null)
}
}
@@ -410,12 +410,12 @@
// start smooth scroll
val scrollLatch = test.viewPager.addWaitForFirstScrollEventLatch()
- test.runOnUiThread { test.viewPager.setCurrentItem(settleTarget, true) }
+ test.runOnUiThreadSync { test.viewPager.setCurrentItem(settleTarget, true) }
assertThat(scrollLatch.await(2, SECONDS), equalTo(true))
// start fake drag, but check some preconditions first
var idleLatch: CountDownLatch? = null
- activityTestRule.runOnUiThread {
+ test.runOnUiThreadSync {
val dragDistance = dragDistanceCallback()
val currPosition = test.viewPager.relativeScrollPosition
@@ -481,18 +481,14 @@
}
private fun doIllegalAction(errorMessage: String, action: () -> Unit) {
- val executionLatch = CountDownLatch(1)
var exception: IllegalStateException? = null
- test.runOnUiThread {
+ test.runOnUiThreadSync {
try {
action()
} catch (e: IllegalStateException) {
exception = e
- } finally {
- executionLatch.countDown()
}
}
- assertThat(executionLatch.await(1, SECONDS), equalTo(true))
assertThat(exception, notNullValue())
assertThat(exception!!.message, equalTo(errorMessage))
}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentLifecycleTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentLifecycleTest.kt
index 99e6f4e..51f84084 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentLifecycleTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentLifecycleTest.kt
@@ -184,7 +184,7 @@
// wait for layout to finish after data-set change
val latchLayout = viewPager.addWaitForLayoutChangeLatch()
- activityTestRule.runOnUiThread(it.dataChangeAction)
+ runOnUiThreadSync(it.dataChangeAction)
latchLayout.await(timeoutMs, MILLISECONDS)
// wait for animations to finish after data-set change
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/MutableCollectionsTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/MutableCollectionsTest.kt
index 1e98bbf..ee8c165 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/MutableCollectionsTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/MutableCollectionsTest.kt
@@ -110,7 +110,7 @@
}
private fun Context.setCurrentPageContent(newContent: String) {
- runOnUiThread {
+ runOnUiThreadSync {
PageView.setPageText(PageView.findPageInActivity(activity)!!, newContent)
}
}
@@ -144,7 +144,7 @@
val latch = CountDownLatch(1)
viewPager.viewTreeObserver.addOnGlobalLayoutListener { latch.countDown() }
- runOnUiThread { viewPager.adapter!!.notifyDataSetChanged() }
+ runOnUiThreadSync { viewPager.adapter!!.notifyDataSetChanged() }
latch.await(5, TimeUnit.SECONDS)
}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/OffscreenPageLimitTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/OffscreenPageLimitTest.kt
index 18a3c27..a19d995 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/OffscreenPageLimitTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/OffscreenPageLimitTest.kt
@@ -78,7 +78,7 @@
@LargeTest
fun test() {
test = setUpTest(config.orientation)
- activityTestRule.runOnUiThread {
+ test.runOnUiThreadSync {
test.viewPager.offscreenPageLimit = config.offscreenPageLimit
}
val recorder = test.viewPager.addNewRecordingCallback()
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt
index 81b6edd..d8afaac 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt
@@ -441,7 +441,7 @@
val latch = viewPager.addWaitForScrolledLatch(targetPages.last(), true)
// when
- runOnUiThread {
+ runOnUiThreadSync {
targetPages.forEach {
viewPager.setCurrentItem(it, true)
}
@@ -493,9 +493,9 @@
val idleLatch = viewPager.addWaitForIdleLatch()
// when
- runOnUiThread { viewPager.setCurrentItem(targetPage, true) }
+ runOnUiThreadSync { viewPager.setCurrentItem(targetPage, true) }
scrollLatch.await(2, SECONDS)
- runOnUiThread {
+ runOnUiThreadSync {
viewPager.setCurrentItem(targetPage, false)
callback.markEvent(marker)
}
@@ -609,7 +609,7 @@
val callback = viewPager.addNewRecordingCallback()
// when
- runOnUiThread { viewPager.setCurrentItem(targetPage, true) }
+ runOnUiThreadSync { viewPager.setCurrentItem(targetPage, true) }
delayCallback(viewPager)
recreateActivity(adapterProvider) { newViewPager ->
@@ -718,7 +718,7 @@
pageSwiper.swipeForward(halfPage + 2 * touchSlop, AccelerateInterpolator())
settleLatch.await(2, SECONDS)
var scrollLatch: CountDownLatch? = null
- activityTestRule.runOnUiThread {
+ test.runOnUiThreadSync {
scrollLatch = test.viewPager.addWaitForFirstScrollEventLatch()
}
scrollLatch!!.await(2, SECONDS)
@@ -774,7 +774,7 @@
// when
listOf(2, 2, 0, 0, 1, 2, 1, 0).forEach { targetPage ->
- runOnUiThread { viewPager.setCurrentItem(targetPage, smoothScroll) }
+ runOnUiThreadSync { viewPager.setCurrentItem(targetPage, smoothScroll) }
// poll the viewpager on the ui thread
viewPager.waitUntilSnappedOnTargetByPolling(targetPage)
@@ -816,7 +816,7 @@
// Test SCROLL_STATE_SETTLING
test_getScrollState(test, SCROLL_STATE_SETTLING, 1) {
- test.runOnUiThread { test.viewPager.setCurrentItem(1, true) }
+ test.runOnUiThreadSync { test.viewPager.setCurrentItem(1, true) }
}
// Test SCROLL_STATE_DRAGGING (real drag)
@@ -1020,7 +1020,7 @@
val recorder = test.viewPager.addNewRecordingCallback()
val distanceLatch = test.viewPager.addWaitForDistanceToTarget(targetPage, 1.5f)
- test.runOnUiThread {
+ test.runOnUiThreadSync {
test.viewPager.setCurrentItem(targetPage, true)
}
@@ -1032,6 +1032,99 @@
recorder.assertAllPagesSelected(listOf(targetPage, targetPage + 1))
}
+ @Test
+ fun test_removeFirstVisibleItemWhileScrolling_targetNotBound() {
+ test_removeFirstVisibleItemWhileScrolling(false)
+ }
+
+ @Test
+ fun test_removeFirstVisibleItemWhileScrolling_targetBound() {
+ test_removeFirstVisibleItemWhileScrolling(true)
+ }
+
+ /** @param targetBound If the target page should be bound by RV when modifying the dataset */
+ private fun test_removeFirstVisibleItemWhileScrolling(targetBound: Boolean) {
+ // given
+ val pageCount = 10 // number of pages
+ val initialPage = 0 // page we start at
+ val targetPage = 5 // page we smooth scroll to
+ val removeItemMark = 1 // id of the mark we make when modifying the dataset
+ val bindThreshold = 2 // how many pages from x before x gets bound?
+ val epsilon = 0.001f
+ // start and end of the window of opportunity to modify the dataset
+ val windowStart = targetPage - bindThreshold - if (targetBound) 0 else 1
+ val windowEnd = targetPage - if (targetBound) 0 else bindThreshold
+
+ val test = setUpTest(config.orientation)
+ activityTestRule.runOnUiThread { test.viewPager.offscreenPageLimit = 1 }
+ val dataSet = stringSequence(pageCount).toMutableList()
+ test.setAdapterSync(viewAdapterProvider(dataSet))
+
+ tryNTimes(3, resetBlock = {
+ test.viewPager.setCurrentItemSync(initialPage, false, 2, SECONDS)
+ // VP2 was potentially settling while the RetryException was raised,
+ // in which case we must wait until the IDLE event has been fired
+ activityTestRule.waitForExecution(1)
+ }) {
+ // when we are scrolling to the target
+ val recorder = test.viewPager.addNewRecordingCallback()
+ val distanceLatch = test.viewPager.addWaitForDistanceToTarget(targetPage,
+ targetPage - windowStart - epsilon)
+ val idleLatch = test.viewPager.addWaitForIdleLatch()
+ test.runOnUiThreadSync { test.viewPager.setCurrentItem(targetPage, true) }
+ distanceLatch.await(2, SECONDS)
+
+ // and we remove the first visible item
+ test.modifyDataSetSync {
+ val lastScrollPosition = recorder.scrollEvents.last().let {
+ it.position + it.positionOffset.toDouble()
+ }
+ if (lastScrollPosition >= windowEnd) {
+ throw RetryException("Data set should be modified while scrolling through " +
+ "($windowStart, $windowEnd), but was modified at $lastScrollPosition")
+ }
+ recorder.markEvent(removeItemMark)
+ val position = test.viewPager.linearLayoutManager.findFirstVisibleItemPosition()
+ dataSet.removeAt(position)
+ test.viewPager.adapter!!.notifyItemRemoved(position)
+ }
+ idleLatch.await(2, SECONDS)
+
+ // then
+ val expectedFinalPage = targetPage - if (targetBound) 1 else 0
+ val expectedFinalPageText = expectedFinalPage + 1
+ test.assertBasicState(expectedFinalPage, "$expectedFinalPageText")
+ recorder.apply {
+ val removeItemMarkIx = markerIx(removeItemMark)
+ val expectedSelectEvents = listOf(targetPage).plus(
+ if (targetPage != expectedFinalPage) listOf(expectedFinalPage) else emptyList()
+ )
+ // verify all events
+ assertThat(settlingIx, equalTo(0))
+ assertThat(pageSelectedIx(targetPage), equalTo(1))
+ assertThat(removeItemMarkIx, greaterThan(1))
+ assertThat(idleIx, equalTo(lastIx))
+ assertThat(selectEvents.map { it.position }, equalTo(expectedSelectEvents))
+ assertThat(scrollEventCount, equalTo(eventCount - 3 - expectedSelectEvents.size))
+
+ // verify scroll events _before_ and _after_ the marker
+ val scrollsBeforeMarker = scrollEventsBefore(removeItemMarkIx)
+ val scrollsAfterMarker = scrollEventsAfter(removeItemMarkIx)
+ listOf(scrollsBeforeMarker, scrollsAfterMarker).forEach {
+ it.assertPositionSorted(SortOrder.ASC)
+ it.assertOffsetSorted(SortOrder.ASC)
+ it.assertValueSanity(0, targetPage, test.viewPager.pageSize)
+ }
+ // Only check assertMaxShownPages on scroll events _before_ the marker:
+ // after the data set change, it can scroll an arbitrary number of pages
+ scrollsBeforeMarker.assertMaxShownPages()
+ // Only check assertLastCorrect on scroll events _after_ the marker:
+ // the target is not reached before the data set change
+ scrollsAfterMarker.assertLastCorrect(expectedFinalPage)
+ }
+ }
+ }
+
private fun ViewPager2.addNewRecordingCallback(): RecordingCallback {
return RecordingCallback().also { registerOnPageChangeCallback(it) }
}
@@ -1058,11 +1151,8 @@
val allEvents get() = events
val scrollEvents get() = events.mapNotNull { it as? OnPageScrolledEvent }
- val scrollEventsBeforeSettling
- get() = events.subList(0, settlingIx).mapNotNull { it as? OnPageScrolledEvent }
- val scrollEventsAfterSettling
- get() = events.subList(settlingIx + 1, events.size)
- .mapNotNull { it as? OnPageScrolledEvent }
+ val scrollEventsBeforeSettling get() = scrollEventsBefore(settlingIx)
+ val scrollEventsAfterSettling get() = scrollEventsAfter(settlingIx)
val selectEvents get() = events.mapNotNull { it as? OnPageSelectedEvent }
val stateEvents get() = events.mapNotNull { it as? OnPageScrollStateChangedEvent }
val scrollAndSelectEvents get() = events.mapNotNull {
@@ -1080,6 +1170,15 @@
val draggingIx get() = events.indexOf(OnPageScrollStateChangedEvent(SCROLL_STATE_DRAGGING))
val idleIx get() = events.indexOf(OnPageScrollStateChangedEvent(SCROLL_STATE_IDLE))
val pageSelectedIx: (page: Int) -> Int = { events.indexOf(OnPageSelectedEvent(it)) }
+ val markerIx: (id: Int) -> Int = { events.indexOf(MarkerEvent(it)) }
+
+ val scrollEventsBefore: (ix: Int) -> List<OnPageScrolledEvent> =
+ { scrollEventsBetween(0, it) }
+ val scrollEventsAfter: (ix: Int) -> List<OnPageScrolledEvent> =
+ { scrollEventsBetween(it + 1, events.size) }
+ val scrollEventsBetween: (fromIx: Int, toIx: Int) -> List<OnPageScrolledEvent> = { a, b ->
+ events.subList(a, b).mapNotNull { it as? OnPageScrolledEvent }
+ }
val wasSettleInterrupted: Boolean get() {
val changeToSettlingEvent = OnPageScrollStateChangedEvent(SCROLL_STATE_SETTLING)
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageFillTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageFillTest.kt
index a14a337..75c233e 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageFillTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageFillTest.kt
@@ -66,7 +66,7 @@
}
setUpTest(ORIENTATION_HORIZONTAL).apply {
- runOnUiThread {
+ runOnUiThreadSync {
viewPager.adapter = fixedViewSizeAdapter
try {
viewPager.measure(0, 0)
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageTransformerTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageTransformerTest.kt
index 5981d6b..4d78da2 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageTransformerTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageTransformerTest.kt
@@ -180,8 +180,7 @@
}
private fun ViewPager2.addNewRecordingCallback(): RecordingCallback {
- val layoutManager = recyclerView.layoutManager as LinearLayoutManager
- return RecordingCallback(layoutManager).also {
+ return RecordingCallback(linearLayoutManager).also {
setPageTransformer(it)
registerOnPageChangeCallback(it)
}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SetItemWhileScrollInProgressTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SetItemWhileScrollInProgressTest.kt
index 3f4e84f..8d6159b 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SetItemWhileScrollInProgressTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SetItemWhileScrollInProgressTest.kt
@@ -144,7 +144,7 @@
// when
pageSequence.forEachIndexed { i, targetPage ->
- runOnUiThread {
+ runOnUiThreadSync {
viewPager.setCurrentItem(targetPage, i !in instantScrolls)
viewPager.assertCurrentItemSet(targetPage)
if (currentPage != targetPage) {
@@ -345,6 +345,13 @@
totalPages = 12,
pageSequence = listOf(8, 7, 9, 7, 3, 0, 7, 11, 10, 0),
instantScrolls = setOf(1, 4, 5, 8, 9)
+ ),
+ TestConfig(
+ title = "smooth-instant-long_smooth",
+ orientation = orientation,
+ totalPages = 5,
+ pageSequence = listOf(3, 4, 0),
+ instantScrolls = setOf(1)
)
)
.plus(
@@ -396,4 +403,4 @@
return (0 until seqLen).filter { r.nextFloat() < probability }.toSet()
}
-// endregion
\ No newline at end of file
+// endregion
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SwipeTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SwipeTest.kt
index bc81524..9cacdcd 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SwipeTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SwipeTest.kt
@@ -51,7 +51,7 @@
val modifiedPageValue: String? = stepToNewValue[currentStep]
if (modifiedPageValue != null) {
expectedValues[currentPage] = modifiedPageValue
- runOnUiThread {
+ runOnUiThreadSync {
PageView.setPageText(PageView.findPageInActivity(activity)!!,
modifiedPageValue)
}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/TransientStateFragmentTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/TransientStateFragmentTest.kt
index 4a3337c..ebe5a6a 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/TransientStateFragmentTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/TransientStateFragmentTest.kt
@@ -22,6 +22,7 @@
import androidx.fragment.app.FragmentManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
import org.junit.Test
import org.junit.runner.RunWith
@@ -40,6 +41,7 @@
private val timeoutMs = 3000L
@Test
+ @SdkSuppress(minSdkVersion = 16) /** [View.setHasTransientState] was introduced in API 16 */
fun test_swipeBetweenPages() {
setUpTest(orientation).apply {
val expectedValues = stringSequence(totalPages)
diff --git a/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java b/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java
index a07954e..53ddc92 100644
--- a/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java
+++ b/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java
@@ -60,8 +60,7 @@
* position. If we already have the fragment, or have previously saved its state, we use those.
* <li>{@link RecyclerView.Adapter#onAttachedToWindow} we attach the {@link Fragment} to a
* container.
- * <li>{@link RecyclerView.Adapter#onViewRecycled} and
- * {@link RecyclerView.Adapter#onFailedToRecycleView} we remove, save state, destroy the
+ * <li>{@link RecyclerView.Adapter#onViewRecycled} we remove, save state, destroy the
* {@link Fragment}.
* </ul>
*/
diff --git a/viewpager2/src/main/java/androidx/viewpager2/widget/ScrollEventAdapter.java b/viewpager2/src/main/java/androidx/viewpager2/widget/ScrollEventAdapter.java
index f5e9736..fa9f235 100644
--- a/viewpager2/src/main/java/androidx/viewpager2/widget/ScrollEventAdapter.java
+++ b/viewpager2/src/main/java/androidx/viewpager2/widget/ScrollEventAdapter.java
@@ -125,10 +125,10 @@
}
// Drag is finished (dragging || settling -> idle)
- if (isInAnyDraggingState() && newState == RecyclerView.SCROLL_STATE_IDLE) {
+ if (mAdapterState != STATE_IDLE && newState == RecyclerView.SCROLL_STATE_IDLE) {
boolean dispatchIdle = false;
updateScrollEventValues();
- if (!mScrollHappened) {
+ if (!mScrollHappened && isInAnyDraggingState()) {
// Pages didn't move during drag, so either we're at the start or end of the list,
// or there are no pages at all.
// In the first case, ViewPager's contract requires at least one scroll event.
@@ -137,11 +137,19 @@
dispatchScrolled(mScrollValues.mPosition, 0f, 0);
}
dispatchIdle = true;
- } else if (mScrollValues.mOffsetPx == 0) {
- // Normally we dispatch the selected page and go to idle in onScrolled when
- // mOffsetPx == 0, but in this case the drag was still ongoing when onScrolled was
- // called, so that didn't happen. And since mOffsetPx == 0, there will be no further
- // scroll events, so fire the onPageSelected event and go to idle now.
+ } else if (mScrollHappened && mScrollValues.mOffsetPx == 0) {
+ // Normally we dispatch the selected page and go to idle in onScrolled after we
+ // settled (mOffsetPx == 0), but there are a few exceptions:
+ //
+ // 1) The drag was still ongoing when onScrolled was called, so we didn't know it
+ // was about to end. End it now.
+ // 2) If the adapter data set changes during a smooth scroll, RecyclerView may
+ // settle at a different position, which we don't know about until we're there.
+ // End it now.
+ //
+ // Now RecyclerView is idle and mOffsetPx == 0, so the view has stabilized. Fire
+ // onPageSelected to notify clients of the position settled upon and go to idle.
+ //
// Note that if we _did_ go to idle in that last onScrolled event, this code will
// not be executed because mAdapterState has been reset to STATE_IDLE.
dispatchIdle = true;
diff --git a/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java b/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java
index 5b9f51e..7c71ca7 100644
--- a/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java
+++ b/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java
@@ -512,17 +512,7 @@
mTmpChildRect.bottom);
if (mCurrentItemDirty) {
- RecyclerView.ItemAnimator animator = mRecyclerView.getItemAnimator();
- if (animator == null) {
- updateCurrentItem();
- } else {
- animator.isRunning(new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
- @Override
- public void onAnimationsFinished() {
- updateCurrentItem();
- }
- });
- }
+ updateCurrentItem();
}
}
diff --git a/webkit/integration-tests/testapp/src/main/AndroidManifest.xml b/webkit/integration-tests/testapp/src/main/AndroidManifest.xml
index b999b64..c827c61 100644
--- a/webkit/integration-tests/testapp/src/main/AndroidManifest.xml
+++ b/webkit/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -83,6 +83,9 @@
android:name=".AssetLoaderAjaxActivity"
android:exported="true" />
<activity
+ android:name=".AssetLoaderInternalStorageActivity"
+ android:exported="true" />
+ <activity
android:name=".ForceDarkActivity"
android:theme="@style/AppDayNightTheme"
android:exported="true" />
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderInternalStorageActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderInternalStorageActivity.java
new file mode 100644
index 0000000..a62d043
--- /dev/null
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderInternalStorageActivity.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.androidx.webkit;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.webkit.WebResourceRequest;
+import android.webkit.WebResourceResponse;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.webkit.WebViewAssetLoader;
+import androidx.webkit.WebViewAssetLoader.InternalStoragePathHandler;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * An {@link Activity} to show case a use case of using {@link InternalStoragePathHandler}.
+ */
+public class AssetLoaderInternalStorageActivity extends AppCompatActivity {
+ private static final String DEMO_HTML_CONTENT =
+ "<h3>Successfully loaded html from app files dir!</h3>";
+
+ @NonNull private File mPublicDir;
+ @NonNull private File mDemoFile;
+
+ @NonNull private WebViewAssetLoader mAssetLoader;
+ @NonNull private WebView mWebView;
+
+ private class MyWebViewClient extends WebViewClient {
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ return false;
+ }
+
+ @Override
+ @RequiresApi(21)
+ public WebResourceResponse shouldInterceptRequest(WebView view,
+ WebResourceRequest request) {
+ return mAssetLoader.shouldInterceptRequest(request.getUrl());
+ }
+
+ @Override
+ public WebResourceResponse shouldInterceptRequest(WebView view, String request) {
+ return mAssetLoader.shouldInterceptRequest(Uri.parse(request));
+ }
+ }
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_asset_loader);
+ setTitle(R.string.asset_loader_internal_storage_activity_title);
+ WebkitHelpers.appendWebViewVersionToTitle(this);
+
+ mWebView = findViewById(R.id.webview_asset_loader_webview);
+ mWebView.setWebViewClient(new MyWebViewClient());
+
+ mPublicDir = new File(getFilesDir(), "public");
+ mDemoFile = new File(mPublicDir, "some_text.html");
+
+ // Host "files/public/" in app's data directory under:
+ // http://appassets.androidplatform.net/public_data/...
+ mAssetLoader = new WebViewAssetLoader.Builder()
+ .addPathHandler("/public_data/", new InternalStoragePathHandler(this, mPublicDir))
+ .build();
+
+ // Write the demo file asynchronously and then load the file after it's written.
+ new WriteFileTask(mDemoFile, DEMO_HTML_CONTENT) {
+ @Override
+ protected void onPostExecute(Void result) {
+ Uri path = new Uri.Builder()
+ .scheme("https")
+ .authority(WebViewAssetLoader.DEFAULT_DOMAIN)
+ .appendPath("public_data")
+ .appendPath("some_text.html")
+ .build();
+
+ mWebView.loadUrl(path.toString());
+ }
+ }.execute();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ // Clean the test/demo file for tests.
+ mDemoFile.delete();
+ mPublicDir.delete();
+ }
+
+ // Writes to file asynchronously in the background thread.
+ private class WriteFileTask extends AsyncTask<Void, Void, Void> {
+ @NonNull private final File mFile;
+ @NonNull private final String mContent;
+
+ WriteFileTask(@NonNull File file, @NonNull String content) {
+ mFile = file;
+ mContent = content;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ mFile.getParentFile().mkdirs();
+ try (FileOutputStream fos = new FileOutputStream(mFile)) {
+ fos.write(mContent.getBytes("utf-8"));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return null;
+ }
+
+ }
+}
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderListActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderListActivity.java
index 24628b15..e07539e 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderListActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderListActivity.java
@@ -49,6 +49,9 @@
new MenuListView.MenuItem(
getResources().getString(R.string.asset_loader_ajax_activity_title),
new Intent(activityContext, AssetLoaderAjaxActivity.class)),
+ new MenuListView.MenuItem(
+ getResources().getString(R.string.asset_loader_internal_storage_activity_title),
+ new Intent(activityContext, AssetLoaderInternalStorageActivity.class)),
};
listView.setItems(menuItems);
}
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkActivity.java
index c2c6aea..aa8531b 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkActivity.java
@@ -16,7 +16,6 @@
package com.example.androidx.webkit;
-import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.util.Base64;
@@ -42,7 +41,6 @@
"<html><body><h1>Force Dark Mode is %s </h1></body></html>";
@Override
- @SuppressLint("RestrictedApi")
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -62,7 +60,6 @@
}
}
- @SuppressLint("RestrictedApi")
private void setupWebView(WebView webView, int forceDarkMode) {
webView.setWebViewClient(new WebViewClient());
String formatedDescription;
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/MultiProcessEnabledActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/MultiProcessEnabledActivity.java
index 9a13e2f..ae365cd 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/MultiProcessEnabledActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/MultiProcessEnabledActivity.java
@@ -16,7 +16,6 @@
package com.example.androidx.webkit;
-import android.annotation.SuppressLint;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
@@ -29,7 +28,6 @@
public class MultiProcessEnabledActivity extends AppCompatActivity {
@Override
- @SuppressLint("RestrictedApi")
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_is_multi_process_enabled);
diff --git a/webkit/integration-tests/testapp/src/main/res/values/strings.xml b/webkit/integration-tests/testapp/src/main/res/values/strings.xml
index 881793a..13c5706 100644
--- a/webkit/integration-tests/testapp/src/main/res/values/strings.xml
+++ b/webkit/integration-tests/testapp/src/main/res/values/strings.xml
@@ -22,6 +22,7 @@
<string name="not_updateable_webview">not updatable</string>
<string name="proxy_override_requests_served">Requests served: %d</string>
<string name="asset_loader_ajax_activity_title">Asset Loader AJAX Demo</string>
+ <string name="asset_loader_internal_storage_activity_title">Load from internal storage</string>
<string name="asset_loader_list_activity_title">WebView Asset Loader Demos</string>
<string name="asset_loader_simple_activity_title">Simple Asset Loader</string>
<string name="small_interstitial_activity_title">Small Interstitial</string>
diff --git a/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java b/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
index c4540e0..62f2a98 100644
--- a/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
+++ b/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
@@ -293,7 +293,7 @@
* TODO(amalova): unhide
* @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static final int FORCE_DARK_OFF = 0;
/**
@@ -307,7 +307,7 @@
* TODO(amalova): unhide
* @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static final int FORCE_DARK_AUTO = 1;
/**
@@ -320,7 +320,7 @@
* TODO(amalova): unhide
* @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static final int FORCE_DARK_ON = 2;
/**
@@ -350,7 +350,7 @@
* TODO(amalova): unhide
* @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@SuppressLint("NewApi")
@RequiresFeature(name = WebViewFeature.FORCE_DARK,
enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
@@ -383,7 +383,7 @@
* TODO(amalova): unhide
* @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@SuppressLint("NewApi")
@RequiresFeature(name = WebViewFeature.FORCE_DARK,
enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
diff --git a/webkit/src/main/java/androidx/webkit/WebViewAssetLoader.java b/webkit/src/main/java/androidx/webkit/WebViewAssetLoader.java
index b34d682..f782bcb 100644
--- a/webkit/src/main/java/androidx/webkit/WebViewAssetLoader.java
+++ b/webkit/src/main/java/androidx/webkit/WebViewAssetLoader.java
@@ -234,7 +234,7 @@
* @hide
*/
// TODO(b/132880733) unhide the API when it's ready.
- @RestrictTo(Scope.LIBRARY_GROUP_PREFIX)
+ @RestrictTo(Scope.LIBRARY_GROUP)
public static final class InternalStoragePathHandler implements PathHandler {
/**
* Forbidden subdirectories of {@link Context#getDataDir} that cannot be exposed by this
diff --git a/webkit/src/main/java/androidx/webkit/WebViewCompat.java b/webkit/src/main/java/androidx/webkit/WebViewCompat.java
index e021a09..565d36b 100644
--- a/webkit/src/main/java/androidx/webkit/WebViewCompat.java
+++ b/webkit/src/main/java/androidx/webkit/WebViewCompat.java
@@ -622,7 +622,7 @@
* //TODO(laisminchillo): unhide
* @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@RequiresFeature(name = WebViewFeature.MULTI_PROCESS_QUERY,
enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
public static boolean isMultiProcessEnabled() {
diff --git a/webkit/src/main/java/androidx/webkit/WebViewFeature.java b/webkit/src/main/java/androidx/webkit/WebViewFeature.java
index 5994789..f71c46d 100644
--- a/webkit/src/main/java/androidx/webkit/WebViewFeature.java
+++ b/webkit/src/main/java/androidx/webkit/WebViewFeature.java
@@ -391,7 +391,7 @@
* TODO(cricke): unhide
* @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static final String SUPPRESS_ERROR_PAGE = "SUPPRESS_ERROR_PAGE";
/**
@@ -401,7 +401,7 @@
* TODO(laisminchillo): unhide
* @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static final String MULTI_PROCESS_QUERY = "MULTI_PROCESS_QUERY";
/**
@@ -413,7 +413,7 @@
* TODO(amalova): unhide
* @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static final String FORCE_DARK = "FORCE_DARK";
/**
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
index 620307f..95b6d4c 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
@@ -567,6 +567,7 @@
@Test
public void testConstraintsChanged_withFutureWork() throws InterruptedException {
+ when(mBatteryChargingTracker.getInitialState()).thenReturn(true);
// Use a mocked scheduler in this test.
Scheduler scheduler = mock(Scheduler.class);
doCallRealMethod().when(mWorkManager).rescheduleEligibleWork();
diff --git a/work/workmanager/src/main/java/androidx/work/ListenableWorker.java b/work/workmanager/src/main/java/androidx/work/ListenableWorker.java
index 2203618..41ac5d0 100644
--- a/work/workmanager/src/main/java/androidx/work/ListenableWorker.java
+++ b/work/workmanager/src/main/java/androidx/work/ListenableWorker.java
@@ -180,6 +180,9 @@
* A ListenableWorker is given a maximum of ten minutes to finish its execution and return a
* {@link Result}. After this time has expired, the worker will be signalled to stop and its
* {@link ListenableFuture} will be cancelled.
+ * <p>
+ * The future will also be cancelled if this worker is stopped for any reason
+ * (see {@link #onStopped()}).
*
* @return A {@link ListenableFuture} with the {@link Result} of the computation. If you
* cancel this Future, WorkManager will treat this unit of work as failed.
@@ -222,12 +225,13 @@
}
/**
- * This method is invoked when this Worker has been told to stop. This could happen due
- * to an explicit cancellation signal by the user, or because the system has decided to preempt
- * the task. In these cases, the results of the work will be ignored by WorkManager. All
- * processing in this method should be lightweight - there are no contractual guarantees about
- * which thread will invoke this call, so this should not be a long-running or blocking
- * operation.
+ * This method is invoked when this Worker has been told to stop. At this point, the
+ * {@link ListenableFuture} returned by the instance of {@link #startWork()} is
+ * also cancelled. This could happen due to an explicit cancellation signal by the user, or
+ * because the system has decided to preempt the task. In these cases, the results of the
+ * work will be ignored by WorkManager. All processing in this method should be lightweight
+ * - there are no contractual guarantees about which thread will invoke this call, so this
+ * should not be a long-running or blocking operation.
*/
public void onStopped() {
// Do nothing by default.