Merge "Disable edit text for unchangeable DisplayName (Not part of BluetoothX APIs)" into androidx-main
diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml
index db2e46a..cb49084 100644
--- a/.github/workflows/integration_tests.yml
+++ b/.github/workflows/integration_tests.yml
@@ -25,6 +25,12 @@
gcp-token: ${{ secrets.GCP_SA_KEY }}
github-token: ${{ secrets.GITHUB_TOKEN }}
output-folder: ${{ steps.dirs.outputs.output-dir }}
+ gcp-bucket-name: "androidx-ftl-test-results"
+ gcp-bucket-path: "github-ci-action"
+ log-file: ${{ steps.dirs.outputs.output-dir }}/ftl-logs.txt
+ device-specs: Pixel2.arm:30
+ use-test-config-files: true
+ test-suite-tags: androidx_unit_tests
- uses: actions/upload-artifact@v2
if: always()
with:
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
index 7a4f3c0..1a36ae6 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
@@ -69,6 +69,8 @@
configureKtlintCheckFile()
tasks.register(CheckExternalDependencyLicensesTask.TASK_NAME)
+ maybeRegisterFilterableTask()
+
// If we're running inside Studio, validate the Android Gradle Plugin version.
val expectedAgpVersion = System.getenv("EXPECTED_AGP_VERSION")
if (properties.containsKey("android.injected.invoked.from.ide")) {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/FilteredAnchorTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/FilteredAnchorTask.kt
new file mode 100644
index 0000000..c40a47d
--- /dev/null
+++ b/buildSrc/private/src/main/kotlin/androidx/build/FilteredAnchorTask.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2023 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.build
+
+import androidx.build.FilteredAnchorTask.Companion.GLOBAL_TASK_NAME
+import androidx.build.FilteredAnchorTask.Companion.PROP_PATH_PREFIX
+import androidx.build.FilteredAnchorTask.Companion.PROP_TASK_NAME
+import org.gradle.api.DefaultTask
+import org.gradle.api.GradleException
+import org.gradle.api.Project
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.TaskProvider
+import org.gradle.work.DisableCachingByDefault
+
+@DisableCachingByDefault(because = "This is an anchor task that does no work.")
+abstract class FilteredAnchorTask : DefaultTask() {
+ init {
+ group = "Help"
+ description = "Runs tasks with a name specified by -P$PROP_TASK_NAME= for projects with " +
+ "a path prefix specified by -P$PROP_PATH_PREFIX="
+ }
+
+ @get:Input
+ abstract var pathPrefix: String
+
+ @get:Input
+ abstract var taskName: String
+
+ @TaskAction
+ fun exec() {
+ if (dependsOn.isEmpty()) {
+ throw GradleException("Failed to find any filterable tasks with name \"$taskName\" " +
+ "and path prefixed with \"$pathPrefix\"")
+ }
+ }
+
+ companion object {
+ const val GLOBAL_TASK_NAME = "filterTasks"
+ const val PROP_PATH_PREFIX = "androidx.pathPrefix"
+ const val PROP_TASK_NAME = "androidx.taskName"
+ }
+}
+
+/**
+ * Offers the specified [taskProviders] to the global [FilteredAnchorTask], adding them if they match
+ * the requested path prefix and task name.
+ */
+internal fun Project.addFilterableTasks(vararg taskProviders: TaskProvider<*>?) {
+ if (hasProperty(PROP_PATH_PREFIX) && hasProperty(PROP_TASK_NAME)) {
+ val pathPrefix = properties[PROP_PATH_PREFIX] as String
+ if (relativePathForFiltering().startsWith(pathPrefix)) {
+ val taskName = properties[PROP_TASK_NAME] as String
+ taskProviders.find { taskProvider ->
+ taskName == taskProvider?.name
+ }?.let { taskProvider ->
+ rootProject.tasks.named(GLOBAL_TASK_NAME).configure { task ->
+ task.dependsOn(taskProvider)
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Registers the global [FilteredAnchorTask] if the required command-line properties are set.
+ *
+ * For example, to run `checkApi` for all projects under `core/core/`:
+ * ./gradlew filterTasks -Pandroidx.taskName=checkApi -Pandroidx.pathPrefix=core/core/
+ */
+internal fun Project.maybeRegisterFilterableTask() {
+ if (hasProperty(PROP_TASK_NAME) && hasProperty(PROP_PATH_PREFIX)) {
+ tasks.register(GLOBAL_TASK_NAME, FilteredAnchorTask::class.java) { task ->
+ task.pathPrefix = properties[PROP_PATH_PREFIX] as String
+ task.taskName = properties[PROP_TASK_NAME] as String
+ }
+ }
+}
+
+/**
+ * Returns an AndroidX-relative path for the [Project], inserting the root project directory when
+ * run in a Playground context such that paths are consistent with the AndroidX context.
+ */
+internal fun Project.relativePathForFiltering(): String =
+ if (ProjectLayoutType.isPlayground(project)) {
+ "${rootProject.projectDir.name}/"
+ } else {
+ ""
+ } + "${projectDir.relativeTo(rootDir)}/"
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/buildInfo/CreateLibraryBuildInfoFileTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/buildInfo/CreateLibraryBuildInfoFileTask.kt
index d7c4c07..54597b4 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/buildInfo/CreateLibraryBuildInfoFileTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/buildInfo/CreateLibraryBuildInfoFileTask.kt
@@ -258,12 +258,7 @@
* of the build that is released. Thus, we use frameworks/support to get the sha
*/
fun Project.getFrameworksSupportCommitShaAtHead(): String {
- val gitClient = GitClient.create(
- project.getSupportRootFolder(),
- logger,
- GitClient.getChangeInfoPath(project).get(),
- GitClient.getManifestPath(project).get()
- )
+ val gitClient = GitClient.forProject(project)
return gitClient.getHeadSha(getSupportRootFolder())
}
}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt b/buildSrc/private/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
index feb0a6f..756da81 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
@@ -278,7 +278,7 @@
logger.info("using base commit override $baseCommitOverride")
}
val gitClient = GitClient.create(
- rootProjectDir = parameters.rootDir,
+ projectDir = parameters.rootDir,
logger = logger.toLogger(),
changeInfoPath = parameters.changeInfoPath.get(),
manifestPath = parameters.manifestPath.get()
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/dokka/kmpDocs/DokkaCombinedDocsTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/dokka/kmpDocs/DokkaCombinedDocsTask.kt
index 471f537..bfec803 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/dokka/kmpDocs/DokkaCombinedDocsTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/dokka/kmpDocs/DokkaCombinedDocsTask.kt
@@ -204,12 +204,7 @@
it.additionalDocumentation.set(
project.files("homepage.md")
)
- val gitClient = GitClient.create(
- project.getSupportRootFolder(),
- project.logger,
- GitClient.getChangeInfoPath(project).get(),
- GitClient.getManifestPath(project).get()
- )
+ val gitClient = GitClient.forProject(project)
it.replacementUrl.set(
DokkaUtils.createCsAndroidUrl(
gitClient.getHeadSha(project.getSupportRootFolder())
@@ -253,4 +248,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/gitclient/GitClient.kt b/buildSrc/private/src/main/kotlin/androidx/build/gitclient/GitClient.kt
index 9b1ecee..25c6a81 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/gitclient/GitClient.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/gitclient/GitClient.kt
@@ -19,6 +19,7 @@
import androidx.build.releasenotes.getBuganizerLink
import androidx.build.releasenotes.getChangeIdAOSPLink
import java.io.File
+import java.util.concurrent.ConcurrentHashMap
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.logging.Logger
@@ -83,8 +84,16 @@
fun getManifestPath(project: Project): Provider<String> {
return project.providers.environmentVariable("MANIFEST").orElse("")
}
+ fun forProject(project: Project): GitClient {
+ return create(
+ project.projectDir,
+ project.logger,
+ GitClient.getChangeInfoPath(project).get(),
+ GitClient.getManifestPath(project).get()
+ )
+ }
fun create(
- rootProjectDir: File,
+ projectDir: File,
logger: Logger,
changeInfoPath: String,
manifestPath: String
@@ -107,8 +116,41 @@
"manifest $manifestPath")
return ChangeInfoGitClient(changeInfoText, manifestText)
}
+ val gitRoot = findGitDirInParentFilepath(projectDir)
+ check(gitRoot != null) {
+ "Could not find .git dir for $projectDir"
+ }
logger.info("UsingGitRunnerGitClient")
- return GitRunnerGitClient(rootProjectDir, logger)
+ return GitRunnerGitClient(gitRoot, logger)
+ }
+ }
+}
+
+data class MultiGitClient(
+ val logger: Logger,
+ val changeInfoPath: String,
+ val manifestPath: String
+) {
+ // Map from the root of the git repository to a GitClient for that repository
+ // In AndroidX this directory could be frameworks/support, external/noto-fonts, or others
+ @Transient // We don't want Gradle to persist GitClient in the configuration cache
+ val cache: MutableMap<File, GitClient> = ConcurrentHashMap()
+
+ fun getGitClient(projectDir: File): GitClient {
+ return cache.getOrPut(
+ key = projectDir
+ ) {
+ GitClient.create(projectDir, logger, changeInfoPath, manifestPath)
+ }
+ }
+
+ companion object {
+ fun create(project: Project): MultiGitClient {
+ return MultiGitClient(
+ project.logger,
+ GitClient.getChangeInfoPath(project).get(),
+ GitClient.getManifestPath(project).get()
+ )
}
}
}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/gitclient/GitRunnerGitClient.kt b/buildSrc/private/src/main/kotlin/androidx/build/gitclient/GitRunnerGitClient.kt
index 2057187..342d2e5 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/gitclient/GitRunnerGitClient.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/gitclient/GitRunnerGitClient.kt
@@ -66,17 +66,6 @@
?.firstOrNull()
}
- private fun findGitDirInParentFilepath(filepath: File): File? {
- var curDirectory: File = filepath
- while (curDirectory.path != "/") {
- if (File("$curDirectory/.git").exists()) {
- return curDirectory
- }
- curDirectory = curDirectory.parentFile
- }
- return null
- }
-
private fun parseCommitLogString(
commitLogString: String,
commitStartDelimiter: String,
@@ -214,3 +203,17 @@
const val GIT_LOG_CMD_PREFIX = "git log --name-only"
}
}
+
+/**
+ * Finds the git directory containing the given File by checking parent directories
+ */
+internal fun findGitDirInParentFilepath(filepath: File): File? {
+ var curDirectory: File = filepath
+ while (curDirectory.path != "/") {
+ if (File("$curDirectory/.git").exists()) {
+ return curDirectory
+ }
+ curDirectory = curDirectory.parentFile
+ }
+ return null
+}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt b/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt
index c8458bc..63e1edb 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt
@@ -17,7 +17,7 @@
package androidx.build.metalava
import androidx.build.AndroidXExtension
-import androidx.build.ProjectLayoutType.Companion.isPlayground
+import androidx.build.addFilterableTasks
import androidx.build.addToBuildOnServer
import androidx.build.addToCheckTask
import androidx.build.checkapi.ApiBaselinesLocation
@@ -26,6 +26,7 @@
import androidx.build.getSuppressCompatibilityOptInPathPrefixes
import androidx.build.getSuppressCompatibilityOptOutPathPrefixes
import androidx.build.java.JavaCompileInputs
+import androidx.build.relativePathForFiltering
import androidx.build.uptodatedness.cacheEvenIfNoOutputs
import com.android.build.gradle.tasks.ProcessLibraryManifest
import org.gradle.api.Project
@@ -113,7 +114,7 @@
}
}
- project.tasks.register(
+ val updateApiLintBaseline = project.tasks.register(
"updateApiLintBaseline",
UpdateApiLintBaselineTask::class.java
) { task ->
@@ -189,7 +190,7 @@
// Make sure it always runs *after* the updateApi task.
ignoreApiChanges?.configure { it.mustRunAfter(updateApi) }
- project.tasks.register("regenerateApis") { task ->
+ val regenerateApis = project.tasks.register("regenerateApis") { task ->
task.group = "API"
task.description = "Regenerates current and historic API .txt files using the " +
"corresponding prebuilt and the latest Metalava, then updates API ignore files"
@@ -200,6 +201,14 @@
project.addToCheckTask(checkApi)
project.addToBuildOnServer(checkApi)
+ project.addFilterableTasks(
+ ignoreApiChanges,
+ updateApiLintBaseline,
+ checkApi,
+ regenerateOldApis,
+ updateApi,
+ regenerateApis,
+ )
}
private fun applyInputs(inputs: JavaCompileInputs, task: MetalavaTask) {
@@ -214,11 +223,7 @@
* Returns whether the project has been opted-in to the Suppress Compatibility migration.
*/
internal fun Project.isOptedInToSuppressCompatibilityMigration(): Boolean {
- val dir = if (isPlayground(project)) {
- "${rootProject.projectDir.name}/"
- } else {
- ""
- } + "${projectDir.relativeTo(rootDir)}/"
+ val dir = relativePathForFiltering()
return getSuppressCompatibilityOptOutPathPrefixes().none { pathPrefix ->
dir.startsWith(pathPrefix)
} && getSuppressCompatibilityOptInPathPrefixes().any { pathPrefix ->
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/sbom/Sbom.kt b/buildSrc/private/src/main/kotlin/androidx/build/sbom/Sbom.kt
index 6849ca4..8889212 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/sbom/Sbom.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/sbom/Sbom.kt
@@ -37,7 +37,11 @@
return when (configurationName) {
BundleInsideHelper.CONFIGURATION_NAME -> true
"shadowed" -> true
- "compileClasspath" -> appliesShadowPlugin()
+ // compileClasspath is included by the Shadow plugin by default but projects that
+ // declare a "shadowed" configuration exclude the "compileClasspath" configuration from
+ // the shadowJar task
+ "compileClasspath" ->
+ appliesShadowPlugin() && project.configurations.findByName("shadowed") == null
EXPORT_INSPECTOR_DEPENDENCIES -> true
IMPORT_INSPECTOR_DEPENDENCIES -> true
else -> false
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/LibraryType.kt b/buildSrc/public/src/main/kotlin/androidx/build/LibraryType.kt
index b6c5c29..51a2301 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/LibraryType.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/LibraryType.kt
@@ -36,17 +36,33 @@
*
* The possible values of LibraryType are as follows:
* PUBLISHED_LIBRARY: a conventional library, published, sourced, documented, and versioned.
- * SAMPLES: a library of samples, published as additional properties to a conventional library,
- * including published source. Documented in a special way, not API tracked.
+ * PUBLISHED_TEST_LIBRARY: PUBLISHED_LIBRARY, but allows calling @VisibleForTesting API. Used for
+ * libraries that allow developers to test code that uses your library. Often provides test fakes.
+ * PUBLISHED_NATIVE_LIBRARY: PUBLISHED_LIBRARY, but uses native API tracking instead of Java
+ * INTERNAL_TEST_LIBRARY: unpublished, untracked, undocumented. Used in internal tests. Usually
+ * contains integration tests, but is _not_ an app. Runs device tests.
+ * INTERNAL_HOST_TEST_LIBRARY: as INTERNAL_TEST_LIBRARY, but runs host tests instead. Avoid mixing
+ * host tests and device tests in the same library, for performance / test-result-caching reasons.
+ * SAMPLES: a library containing sample code referenced in your library's documentation with
+ * @sampled, published as a documentation-related supplement to a conventional library.
* LINT: a library of lint rules for using a conventional library. Published through lintPublish as
- * part of an AAR, not published standalone.
- * COMPILER_PLUGIN: a tool that modifies the kotlin or java compiler. Used only while compiling.
+ * part of an AAR, not published standalone.
+ * COMPILER_DAEMON: a tool that modifies the kotlin or java compiler. Used only while compiling. Has
+ * no API and does not publish source jars, but does release to maven.
+ * COMPILER_DAEMON_TEST: a compiler plugin that is not published at all, for internal-only use.
+ * COMPILER_PLUGIN: as COMPILER_DAEMON, but is compatible with JDK 11.
* GRADLE_PLUGIN: a library that is a gradle plugin.
* ANNOTATION_PROCESSOR: a library consisting of an annotation processor. Used only while compiling.
+ * ANNOTATION_PROCESSOR_UTILS: contains reference code for understanding an annotation processor.
+ * Publishes source jars, but does not track API.
* OTHER_CODE_PROCESSOR: a library that algorithmically generates and/or alters code
* but not through hooking into custom annotations or the kotlin compiler.
* For example, navigation:safe-args-generator or Jetifier.
+ * IDE_PLUGIN: a library that should only ever be downloaded by studio. Unfortunately, we don't
+ * yet have a good way to track API for these. b/281843422
* UNSET: a library that has not yet been migrated to using LibraryType. Should never be used.
+ * APP: an app, such as an example app or integration testsapp. Should never be used; apps should
+ * not apply the AndroidX plugin or have an androidx block in their build.gradle files.
*
* TODO: potential future LibraryTypes:
* KOTLIN_ONLY_LIBRARY: like PUBLISHED_LIBRARY, but not intended for use from java. ktx and compose.
@@ -82,7 +98,10 @@
val OTHER_CODE_PROCESSOR = OtherCodeProcessor()
val IDE_PLUGIN = IdePlugin()
val UNSET = Unset()
+ @Deprecated("Do not use an androidx block for apps/testapps, only for libraries")
+ val APP = UNSET
+ @Suppress("DEPRECATION")
private val allTypes = mapOf(
"PUBLISHED_LIBRARY" to PUBLISHED_LIBRARY,
"PUBLISHED_TEST_LIBRARY" to PUBLISHED_TEST_LIBRARY,
@@ -99,7 +118,8 @@
"ANNOTATION_PROCESSOR_UTILS" to ANNOTATION_PROCESSOR_UTILS,
"OTHER_CODE_PROCESSOR" to OTHER_CODE_PROCESSOR,
"IDE_PLUGIN" to IDE_PLUGIN,
- "UNSET" to UNSET
+ "UNSET" to UNSET,
+ "APP" to APP
)
fun valueOf(name: String): LibraryType {
val result = allTypes[name]
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/Camera2InteropIntegrationTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/Camera2InteropIntegrationTest.kt
index 0a60ca7..7a88d02 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/Camera2InteropIntegrationTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/Camera2InteropIntegrationTest.kt
@@ -156,9 +156,10 @@
when (state) {
// Filter out this state from the downstream flow
is SessionState.Unknown -> true
- is SessionState.Configured -> {
+ is SessionState.Configured -> true
+ is SessionState.Ready -> {
withContext(Dispatchers.Main) { processCameraProvider!!.unbindAll() }
- true // Filter out this state from the downstream flow
+ false
}
else -> false // Forward to the downstream flow
@@ -368,19 +369,19 @@
MutableStateFlow<SessionState>(SessionState.Unknown).apply {
val stateCallback = object : CameraCaptureSession.StateCallback() {
override fun onReady(session: CameraCaptureSession) {
- tryEmit(SessionState.Ready)
+ tryEmit(SessionState.Ready)
}
override fun onConfigured(session: CameraCaptureSession) {
- tryEmit(SessionState.Configured)
+ tryEmit(SessionState.Configured)
}
override fun onConfigureFailed(session: CameraCaptureSession) {
- tryEmit(SessionState.ConfigureFailed)
+ tryEmit(SessionState.ConfigureFailed)
}
override fun onClosed(session: CameraCaptureSession) {
- tryEmit(SessionState.Closed)
+ tryEmit(SessionState.Closed)
}
}
CameraPipeUtil.setSessionStateCallback(
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/OutlinedTextFieldTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/OutlinedTextFieldTest.kt
index d666189..1c4e841 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/OutlinedTextFieldTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/OutlinedTextFieldTest.kt
@@ -71,6 +71,7 @@
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.semantics.error
+import androidx.compose.ui.semantics.getOrNull
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assert
@@ -79,6 +80,7 @@
import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.click
import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onChildren
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
@@ -1080,19 +1082,32 @@
@Test
fun testErrorSemantics_messageOverridable() {
val errorMessage = "Special symbols not allowed"
+ lateinit var defaultErrorMessage: String
rule.setMaterialContent {
var isError = remember { mutableStateOf(true) }
OutlinedTextField(
value = "test",
onValueChange = {},
- modifier = Modifier.semantics { if (isError.value) error(errorMessage) },
+ modifier = Modifier
+ .testTag(TextfieldTag)
+ .semantics { if (isError.value) error(errorMessage) },
isError = isError.value
)
+ defaultErrorMessage = getString(Strings.DefaultErrorMessage)
}
- rule.onNodeWithText("test")
+ rule.onNodeWithTag(TextfieldTag)
.assert(SemanticsMatcher.keyIsDefined(SemanticsProperties.Error))
.assert(SemanticsMatcher.expectValue(SemanticsProperties.Error, errorMessage))
+
+ // Check that default error message is overwritten and not lingering in a child node
+ rule.onNodeWithTag(TextfieldTag, useUnmergedTree = true)
+ .onChildren()
+ .fetchSemanticsNodes()
+ .forEach { node ->
+ assertThat(node.config.getOrNull(SemanticsProperties.Error))
+ .isNotEqualTo(defaultErrorMessage)
+ }
}
@Test
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/TextFieldTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/TextFieldTest.kt
index e57bdd9..214cfb7 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/TextFieldTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/TextFieldTest.kt
@@ -83,6 +83,7 @@
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.semantics.error
+import androidx.compose.ui.semantics.getOrNull
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assert
@@ -91,6 +92,7 @@
import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.click
import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onChildren
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
@@ -1235,19 +1237,32 @@
@Test
fun testErrorSemantics_messageOverridable() {
val errorMessage = "Special symbols not allowed"
+ lateinit var defaultErrorMessage: String
rule.setMaterialContent {
- var isError = remember { mutableStateOf(true) }
+ val isError = remember { mutableStateOf(true) }
TextField(
value = "test",
onValueChange = {},
- modifier = Modifier.semantics { if (isError.value) error(errorMessage) },
+ modifier = Modifier
+ .testTag(TextfieldTag)
+ .semantics { if (isError.value) error(errorMessage) },
isError = isError.value
)
+ defaultErrorMessage = getString(DefaultErrorMessage)
}
- rule.onNodeWithText("test")
+ rule.onNodeWithTag(TextfieldTag)
.assert(SemanticsMatcher.keyIsDefined(SemanticsProperties.Error))
.assert(SemanticsMatcher.expectValue(SemanticsProperties.Error, errorMessage))
+
+ // Check that default error message is overwritten and not lingering in a child node
+ rule.onNodeWithTag(TextfieldTag, useUnmergedTree = true)
+ .onChildren()
+ .fetchSemanticsNodes()
+ .forEach { node ->
+ assertThat(node.config.getOrNull(SemanticsProperties.Error))
+ .isNotEqualTo(defaultErrorMessage)
+ }
}
@Test
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/OutlinedTextField.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/OutlinedTextField.kt
index 5f3120a..8e90c16 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/OutlinedTextField.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/OutlinedTextField.kt
@@ -172,6 +172,7 @@
modifier
}
.background(colors.backgroundColor(enabled).value, shape)
+ .defaultErrorSemantics(isError, getString(Strings.DefaultErrorMessage))
.defaultMinSize(
minWidth = TextFieldDefaults.MinWidth,
minHeight = TextFieldDefaults.MinHeight
@@ -373,6 +374,7 @@
modifier
}
.background(colors.backgroundColor(enabled).value, shape)
+ .defaultErrorSemantics(isError, getString(Strings.DefaultErrorMessage))
.defaultMinSize(
minWidth = TextFieldDefaults.MinWidth,
minHeight = TextFieldDefaults.MinHeight
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt
index dfbd2c8..984e496 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt
@@ -191,6 +191,7 @@
modifier = modifier
.background(colors.backgroundColor(enabled).value, shape)
.indicatorLine(enabled, isError, interactionSource, colors)
+ .defaultErrorSemantics(isError, getString(Strings.DefaultErrorMessage))
.defaultMinSize(
minWidth = TextFieldDefaults.MinWidth,
minHeight = TextFieldDefaults.MinHeight
@@ -379,6 +380,7 @@
modifier = modifier
.background(colors.backgroundColor(enabled).value, shape)
.indicatorLine(enabled, isError, interactionSource, colors)
+ .defaultErrorSemantics(isError, getString(Strings.DefaultErrorMessage))
.defaultMinSize(
minWidth = TextFieldDefaults.MinWidth,
minHeight = TextFieldDefaults.MinHeight
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextFieldImpl.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextFieldImpl.kt
index f46c5b1..c947d0a 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextFieldImpl.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextFieldImpl.kt
@@ -27,7 +27,6 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.defaultMinSize
-import androidx.compose.material.Strings.Companion.DefaultErrorMessage
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ComposableOpenTarget
import androidx.compose.runtime.CompositionLocalProvider
@@ -146,12 +145,6 @@
}
} else null
- // Developers need to handle invalid input manually. But since we don't provide error
- // message slot API, we can set the default error message in case developers forget about
- // it.
- val defaultErrorMessage = getString(DefaultErrorMessage)
- val decorationBoxModifier = Modifier.semantics { if (isError) error(defaultErrorMessage) }
-
val leadingIconColor = if (colors is TextFieldColorsWithIcons) {
colors.leadingIconColor(enabled, isError, interactionSource).value
} else {
@@ -177,7 +170,7 @@
when (type) {
TextFieldType.Filled -> {
TextFieldLayout(
- modifier = decorationBoxModifier,
+ modifier = Modifier,
textField = innerTextField,
placeholder = decoratedPlaceholder,
label = decoratedLabel,
@@ -201,7 +194,7 @@
}
OutlinedTextFieldLayout(
- modifier = decorationBoxModifier,
+ modifier = Modifier,
textField = innerTextField,
placeholder = decoratedPlaceholder,
label = decoratedLabel,
@@ -255,6 +248,13 @@
if (typography != null) ProvideTextStyle(typography, colorAndEmphasis) else colorAndEmphasis()
}
+// Developers need to handle invalid input manually. But since we don't provide an error message
+// slot API, we can set the default error message in case developers forget about it.
+internal fun Modifier.defaultErrorSemantics(
+ isError: Boolean,
+ defaultErrorMessage: String,
+): Modifier = if (isError) semantics { error(defaultErrorMessage) } else this
+
internal fun widthOrZero(placeable: Placeable?) = placeable?.width ?: 0
internal fun heightOrZero(placeable: Placeable?) = placeable?.height ?: 0
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/OutlinedTextFieldTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/OutlinedTextFieldTest.kt
index c8a4298..fddbdf2a 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/OutlinedTextFieldTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/OutlinedTextFieldTest.kt
@@ -52,6 +52,7 @@
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.semantics.error
+import androidx.compose.ui.semantics.getOrNull
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assert
@@ -60,6 +61,7 @@
import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.click
import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onChildren
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
@@ -1362,19 +1364,32 @@
@Test
fun testOutlinedTextField_errorSemantics_messageOverridable() {
val errorMessage = "Special symbols not allowed"
+ lateinit var defaultErrorMessage: String
rule.setMaterialContent(lightColorScheme()) {
val isError = remember { mutableStateOf(true) }
OutlinedTextField(
value = "test",
onValueChange = {},
- modifier = Modifier.semantics { if (isError.value) error(errorMessage) },
+ modifier = Modifier
+ .testTag(TextFieldTag)
+ .semantics { if (isError.value) error(errorMessage) },
isError = isError.value
)
+ defaultErrorMessage = getString(Strings.DefaultErrorMessage)
}
- rule.onNodeWithText("test")
+ rule.onNodeWithTag(TextFieldTag)
.assert(SemanticsMatcher.keyIsDefined(SemanticsProperties.Error))
.assert(SemanticsMatcher.expectValue(SemanticsProperties.Error, errorMessage))
+
+ // Check that default error message is overwritten and not lingering in a child node
+ rule.onNodeWithTag(TextFieldTag, useUnmergedTree = true)
+ .onChildren()
+ .fetchSemanticsNodes()
+ .forEach { node ->
+ assertThat(node.config.getOrNull(SemanticsProperties.Error))
+ .isNotEqualTo(defaultErrorMessage)
+ }
}
@Test
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextFieldTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextFieldTest.kt
index ef94485..e1b215d 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextFieldTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextFieldTest.kt
@@ -65,6 +65,7 @@
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.semantics.error
+import androidx.compose.ui.semantics.getOrNull
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assert
@@ -74,6 +75,7 @@
import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.click
import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onChildren
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
@@ -1466,19 +1468,32 @@
@Test
fun testTextField_errorSemantics_messageOverridable() {
val errorMessage = "Special symbols not allowed"
+ lateinit var defaultErrorMessage: String
rule.setMaterialContent(lightColorScheme()) {
val isError = remember { mutableStateOf(true) }
TextField(
value = "test",
onValueChange = {},
- modifier = Modifier.semantics { if (isError.value) error(errorMessage) },
+ modifier = Modifier
+ .testTag(TextFieldTag)
+ .semantics { if (isError.value) error(errorMessage) },
isError = isError.value
)
+ defaultErrorMessage = getString(DefaultErrorMessage)
}
- rule.onNodeWithText("test")
+ rule.onNodeWithTag(TextFieldTag)
.assert(SemanticsMatcher.keyIsDefined(SemanticsProperties.Error))
.assert(SemanticsMatcher.expectValue(SemanticsProperties.Error, errorMessage))
+
+ // Check that default error message is overwritten and not lingering in a child node
+ rule.onNodeWithTag(TextFieldTag, useUnmergedTree = true)
+ .onChildren()
+ .fetchSemanticsNodes()
+ .forEach { node ->
+ assertThat(node.config.getOrNull(SemanticsProperties.Error))
+ .isNotEqualTo(defaultErrorMessage)
+ }
}
@Test
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt
index 630f24b..3d47224 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt
@@ -180,6 +180,7 @@
} else {
modifier
}
+ .defaultErrorSemantics(isError, getString(Strings.DefaultErrorMessage))
.defaultMinSize(
minWidth = OutlinedTextFieldDefaults.MinWidth,
minHeight = OutlinedTextFieldDefaults.MinHeight
@@ -339,6 +340,7 @@
} else {
modifier
}
+ .defaultErrorSemantics(isError, getString(Strings.DefaultErrorMessage))
.defaultMinSize(
minWidth = OutlinedTextFieldDefaults.MinWidth,
minHeight = OutlinedTextFieldDefaults.MinHeight
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt
index cd786c6..039d06c 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt
@@ -198,6 +198,7 @@
BasicTextField(
value = value,
modifier = modifier
+ .defaultErrorSemantics(isError, getString(Strings.DefaultErrorMessage))
.defaultMinSize(
minWidth = TextFieldDefaults.MinWidth,
minHeight = TextFieldDefaults.MinHeight
@@ -343,6 +344,7 @@
BasicTextField(
value = value,
modifier = modifier
+ .defaultErrorSemantics(isError, getString(Strings.DefaultErrorMessage))
.defaultMinSize(
minWidth = TextFieldDefaults.MinWidth,
minHeight = TextFieldDefaults.MinHeight
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldImpl.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldImpl.kt
index 6cfb01f..2ee2a6c 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldImpl.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldImpl.kt
@@ -27,7 +27,6 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.defaultMinSize
-import androidx.compose.material3.Strings.Companion.DefaultErrorMessage
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
@@ -172,12 +171,6 @@
}
} else null
- // Developers need to handle invalid input manually. But since we don't provide error
- // message slot API, we can set the default error message in case developers forget about
- // it.
- val defaultErrorMessage = getString(DefaultErrorMessage)
- val decorationBoxModifier = Modifier.semantics { if (isError) error(defaultErrorMessage) }
-
val leadingIconColor = colors.leadingIconColor(enabled, isError, interactionSource).value
val decoratedLeading: @Composable (() -> Unit)? = leadingIcon?.let {
@Composable {
@@ -210,7 +203,7 @@
}
TextFieldLayout(
- modifier = decorationBoxModifier,
+ modifier = Modifier,
textField = innerTextField,
placeholder = decoratedPlaceholder,
label = decoratedLabel,
@@ -240,7 +233,7 @@
}
OutlinedTextFieldLayout(
- modifier = decorationBoxModifier,
+ modifier = Modifier,
textField = innerTextField,
placeholder = decoratedPlaceholder,
label = decoratedLabel,
@@ -286,6 +279,13 @@
if (typography != null) ProvideTextStyle(typography, contentWithColor) else contentWithColor()
}
+// Developers need to handle invalid input manually. But since we don't provide an error message
+// slot API, we can set the default error message in case developers forget about it.
+internal fun Modifier.defaultErrorSemantics(
+ isError: Boolean,
+ defaultErrorMessage: String,
+): Modifier = if (isError) semantics { error(defaultErrorMessage) } else this
+
internal fun widthOrZero(placeable: Placeable?) = placeable?.width ?: 0
internal fun heightOrZero(placeable: Placeable?) = placeable?.height ?: 0
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index 044964f..23a1cff 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -2363,22 +2363,9 @@
method @androidx.compose.runtime.Stable public static operator long times(long, long size);
}
- @androidx.compose.ui.ExperimentalComposeUiApi public sealed interface SubcomposeIntermediateMeasureScope extends androidx.compose.ui.layout.SubcomposeMeasureScope {
- method public long getLookaheadConstraints();
- method public kotlin.jvm.functions.Function2<androidx.compose.ui.layout.SubcomposeMeasureScope,androidx.compose.ui.unit.Constraints,androidx.compose.ui.layout.MeasureResult> getLookaheadMeasurePolicy();
- method public long getLookaheadSize();
- method public java.util.List<androidx.compose.ui.layout.Measurable> measurablesForSlot(Object? slotId);
- method public default java.util.List<androidx.compose.ui.layout.Measurable> subcompose(Object? slotId, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- property public abstract long lookaheadConstraints;
- property public abstract kotlin.jvm.functions.Function2<androidx.compose.ui.layout.SubcomposeMeasureScope,androidx.compose.ui.unit.Constraints,androidx.compose.ui.layout.MeasureResult> lookaheadMeasurePolicy;
- property public abstract long lookaheadSize;
- }
-
public final class SubcomposeLayoutKt {
method @androidx.compose.runtime.Composable public static void SubcomposeLayout(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
- method @androidx.compose.runtime.Composable @androidx.compose.ui.ExperimentalComposeUiApi public static void SubcomposeLayout(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeIntermediateMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> intermediateMeasurePolicy, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
method @androidx.compose.runtime.Composable @androidx.compose.ui.UiComposable public static void SubcomposeLayout(androidx.compose.ui.layout.SubcomposeLayoutState state, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
- method @androidx.compose.runtime.Composable @androidx.compose.ui.ExperimentalComposeUiApi @androidx.compose.ui.UiComposable public static void SubcomposeLayout(androidx.compose.ui.layout.SubcomposeLayoutState state, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeIntermediateMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> intermediateMeasurePolicy, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
method public static androidx.compose.ui.layout.SubcomposeSlotReusePolicy SubcomposeSlotReusePolicy(int maxSlotsToRetainForReuse);
}
@@ -2386,9 +2373,7 @@
ctor public SubcomposeLayoutState(androidx.compose.ui.layout.SubcomposeSlotReusePolicy slotReusePolicy);
ctor public SubcomposeLayoutState();
ctor @Deprecated public SubcomposeLayoutState(int maxSlotsToRetainForReuse);
- method @androidx.compose.ui.ExperimentalComposeUiApi public boolean isInLookaheadScope();
method public androidx.compose.ui.layout.SubcomposeLayoutState.PrecomposedSlotHandle precompose(Object? slotId, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- property @androidx.compose.ui.ExperimentalComposeUiApi public final boolean isInLookaheadScope;
}
public static interface SubcomposeLayoutState.PrecomposedSlotHandle {
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/SubcomposeLayoutSample.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/SubcomposeLayoutSample.kt
index 9c932f2..b116741 100644
--- a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/SubcomposeLayoutSample.kt
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/SubcomposeLayoutSample.kt
@@ -17,27 +17,9 @@
package androidx.compose.ui.samples
import androidx.annotation.Sampled
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.AnimationVector2D
-import androidx.compose.animation.core.VectorConverter
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.requiredSize
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.UiComposable
-import androidx.compose.ui.composed
-import androidx.compose.ui.layout.MeasurePolicy
import androidx.compose.ui.layout.SubcomposeLayout
-import androidx.compose.ui.layout.intermediateLayout
-import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.dp
-import kotlinx.coroutines.launch
@Sampled
@Composable
@@ -67,97 +49,4 @@
}
}
-enum class SlotsEnum { Main, Dependent }
-
-@OptIn(ExperimentalComposeUiApi::class)
-@Sampled
-fun SubcomposeLayoutWithIntermediateMeasurePolicySample() {
- // In this example, there is a custom modifier that animates the constraints and measures
- // child with the animated constraints, as defined below.
- // This modifier is built on top of `Modifier.intermediateLayout`, which
- // allows access to the lookahead size of the layout. A resize animation will be kicked off
- // whenever the lookahead size changes, to animate children from current size to lookahead size.
- // Fixed constraints created based on the animation value will be used to measure
- // child, so the child layout gradually changes its size and potentially its child's placement
- // to fit within the animated constraints.
- fun Modifier.animateConstraints() = composed {
- // Creates a size animation
- var sizeAnimation: Animatable<IntSize, AnimationVector2D>? by remember {
- mutableStateOf(null)
- }
-
- this.intermediateLayout { measurable, _ ->
- // When layout changes, the lookahead pass will calculate a new final size for the
- // child layout. This lookahead size can be used to animate the size
- // change, such that the animation starts from the current size and gradually
- // change towards `lookaheadSize`.
- if (lookaheadSize != sizeAnimation?.targetValue) {
- sizeAnimation?.run {
- launch { animateTo(lookaheadSize) }
- } ?: Animatable(lookaheadSize, IntSize.VectorConverter).let {
- sizeAnimation = it
- }
- }
- val (width, height) = sizeAnimation!!.value
- // Creates a fixed set of constraints using the animated size
- val animatedConstraints = Constraints.fixed(width, height)
- // Measure child with animated constraints.
- val placeable = measurable.measure(animatedConstraints)
- layout(placeable.width, placeable.height) {
- placeable.place(0, 0)
- }
- }
- }
-
- // In the example below, the SubcomposeLayout has a parent layout that animates its width
- // between two fixed sizes using the `animateConstraints` modifier we created above.
- @Composable
- fun SubcomposeLayoutWithAnimatingParentLayout(
- isWide: Boolean,
- modifier: Modifier = Modifier,
- content: @Composable @UiComposable () -> Unit
- ) {
- // Create a MeasurePolicy to measure all children with incoming constraints and return the
- // largest width & height.
- val myMeasurePolicy = MeasurePolicy { measurables, constraints ->
- val placeables = measurables.map { it.measure(constraints) }
- val maxWidth = placeables.maxOf { it.width }
- val maxHeight = placeables.maxOf { it.height }
- layout(maxWidth, maxHeight) {
- placeables.forEach { it.place(0, 0) }
- }
- }
- Box(
- Modifier
- .requiredSize(if (isWide) 400.dp else 200.dp)
- .animateConstraints()
- ) {
- // SubcomposeLayout's measurePolicy will only be invoked with lookahead constraints.
- // The parent layout in this example is animating between two fixed widths. The
- // [measurePolicy] parameter will only be called with lookahead constraints
- // (i.e. constraints for 400.dp x 400.dp or 200.dp x 200.dp depending on the state.)
- // This may cause content lambda to jump to its final size. To create a smooth
- // experience, we need to remeasure the content with the intermediate
- // constraints created by the `animateConstraints` that we built above. Therefore, we
- // need to provide a [intermediateMeasurePolicy] to define how to measure the
- // content (using the measureables of the content that was composed in [measurePolicy])
- // with intermediate constraints.
- SubcomposeLayout(
- modifier,
- intermediateMeasurePolicy = { intermediateConstraints ->
- // Retrieve the measureables for slotId = Unit, and measure them with
- // intermediate constraints using the measurePolicy we created above.
- with(myMeasurePolicy) {
- measure(
- measurablesForSlot(Unit),
- intermediateConstraints
- )
- }
- },
- measurePolicy = { constraints ->
- val measurables = subcompose(Unit) { content() }
- with(myMeasurePolicy) { measure(measurables, constraints) }
- })
- }
- }
-}
\ No newline at end of file
+enum class SlotsEnum { Main, Dependent }
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
index 378d5b7..942c141 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
@@ -196,7 +196,7 @@
Box(Modifier.fillMaxSize())
}[0].measure(constraints)
val size = placeable.run { IntSize(width, height) }
- if (this is SubcomposeIntermediateMeasureScope) {
+ if (!isLookingAhead) {
actualSize = size
} else {
actualTargetSize = size
@@ -589,30 +589,12 @@
@Test
fun defaultMeasurePolicyInSubcomposeLayout() {
- var actualLookaheadSize by mutableStateOf(IntSize.Zero)
var defaultIntermediateMeasureSize by mutableStateOf(IntSize.Zero)
rule.setContent {
CompositionLocalProvider(LocalDensity provides Density(1f)) {
LookaheadScope {
SubcomposeLayout(
Modifier
- .fillMaxSize()
- .requiredSize(200.dp),
- intermediateMeasurePolicy = { constraints ->
- measurablesForSlot(Unit)[0].measure(constraints)
- actualLookaheadSize = this.lookaheadSize
- layout(0, 0) {}
- }
- ) { constraints ->
- val placeable = subcompose(Unit) {
- Box(Modifier.requiredSize(400.dp, 600.dp))
- }[0].measure(constraints)
- layout(500, 300) {
- placeable.place(0, 0)
- }
- }
- SubcomposeLayout(
- Modifier
.size(150.dp)
.intermediateLayout { measurable, _ ->
measurable
@@ -636,7 +618,6 @@
}
}
rule.runOnIdle {
- assertEquals(IntSize(500, 300), actualLookaheadSize)
assertEquals(IntSize(500, 300), defaultIntermediateMeasureSize)
}
}
@@ -1719,12 +1700,18 @@
mutableStateListOf<Int>().apply { addAll(expectedPlacementOrder1) }
var iteration by mutableStateOf(0)
+ var lookaheadConstraints by mutableStateOf<Constraints?>(null)
// Expect the default placement to be the same as lookahead
rule.setContent {
LookaheadScope {
- SubcomposeLayout(
- intermediateMeasurePolicy = { lookaheadMeasurePolicy(lookaheadConstraints) }
- ) { constraints ->
+ SubcomposeLayout { incomingConstraints ->
+ val constraints = if (isLookingAhead) {
+ lookaheadConstraints = incomingConstraints
+ incomingConstraints
+ } else {
+ lookaheadConstraints!!
+ }
+
val placeables = mutableListOf<Placeable>()
repeat(3) { id ->
subcompose(id) {
@@ -1975,20 +1962,32 @@
}
SubcomposeLayout(
Modifier
- .fillMaxSize()
- .requiredSize(200.dp),
- intermediateMeasurePolicy = { constraints ->
- assertFalse(isLookingAhead)
- measurablesForSlot(Unit)[0].measure(constraints)
- layout(0, 0) {}
- }
+ .layout { measurable, constraints ->
+ measurable.measure(constraints).run {
+ if (isLookingAhead) {
+ assertEquals(500, width)
+ assertEquals(300, height)
+ } else {
+ assertEquals(100, width)
+ assertEquals(120, height)
+ }
+ layout(width, height) {
+ place(0, 0)
+ }
+ }
+ }
) { constraints ->
- assertTrue(isLookingAhead)
val placeable = subcompose(Unit) {
Box(Modifier.requiredSize(400.dp, 600.dp))
}[0].measure(constraints)
- layout(500, 300) {
- placeable.place(0, 0)
+ if (isLookingAhead) {
+ layout(500, 300) {
+ placeable.place(0, 0)
+ }
+ } else {
+ layout(100, 120) {
+ placeable.place(0, 0)
+ }
}
}
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
index d95d81b..619812e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
@@ -44,7 +44,6 @@
import androidx.compose.ui.node.requireOwner
import androidx.compose.ui.platform.createSubcomposition
import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
/**
@@ -83,155 +82,6 @@
* for example to use the values calculated during the measurement as params for the composition
* of the children.
*
- * When in a [LookaheadScope], [SubcomposeLayout] will be measured up to twice per frame.
- * The two measurements will be using different measure policies and potentially different
- * constraints.
- *
- * The first measurement happens in the lookahead pass, where new layout is calculated based on
- * the target constraints. Therefore, [measurePolicy] will receive the target constraints, and
- * subcompose its content based on the target constraints. Note: Target constraints refers to
- * the constraints that the [SubcomposeLayout] will receive once all the lookahead-based
- * animations on size/constraints in the ancestor layouts have finished.
- *
- * The second measurement is done in the intermediate measure pass after the lookahead pass.
- * The intermediate measure pass allows adjustments to the measurement/placement using the
- * pre-calculated layout information as animation targets to smooth over any
- * any layout changes. In this measurement, [intermediateMeasurePolicy] will be invoked with
- * the intermediate/animating constraints. By default, [measurePolicy] will be invoked in
- * [intermediateMeasurePolicy], and hence the same measure logic in [measurePolicy] with
- * intermediate constraints will be used to measure and layout children in the intermediate
- * pass.
- *
- * Note: When [measurePolicy] is invoked in the intermediate pass, `subcompose` will simply
- * return the measurables associated with the given slot id based on the subcomposition during
- * lookahead pass. This means if a given slot id has not been subcomposed in the lookahead pass,
- * invoking subcompose during intermediate pass will result in an empty list.
- *
- * Possible use cases:
- * * You need to know the constraints passed by the parent during the composition and can't solve
- * your use case with just custom [Layout] or [LayoutModifier].
- * See [androidx.compose.foundation.layout.BoxWithConstraints].
- * * You want to use the size of one child during the composition of the second child.
- * * You want to compose your items lazily based on the available size. For example you have a
- * list of 100 items and instead of composing all of them you only compose the ones which are
- * currently visible(say 5 of them) and compose next items when the component is scrolled.
- *
- * @sample androidx.compose.ui.samples.SubcomposeLayoutWithIntermediateMeasurePolicySample
- *
- * @param modifier [Modifier] to apply for the layout.
- * @param intermediateMeasurePolicy A measure policy that will be invoked during the intermediate
- * measure pass.
- * @param measurePolicy Measure policy which provides ability to subcompose during the measuring.
- */
-@ExperimentalComposeUiApi
-@Composable
-fun SubcomposeLayout(
- modifier: Modifier = Modifier,
- intermediateMeasurePolicy:
- (SubcomposeIntermediateMeasureScope.(Constraints) -> MeasureResult) = { constraints ->
- lookaheadMeasurePolicy(constraints)
- },
- measurePolicy: SubcomposeMeasureScope.(Constraints) -> MeasureResult
-) {
- SubcomposeLayout(
- state = remember { SubcomposeLayoutState() },
- modifier = modifier,
- intermediateMeasurePolicy = intermediateMeasurePolicy,
- measurePolicy = measurePolicy
- )
-}
-
-/**
- * [SubcomposeIntermediateMeasureScope] is the receiver scope for the intermediate measurer policy
- * that gets invoked during the intermediate measure pass.
- *
- * When in a [LookaheadScope], [SubcomposeLayout] will be measured up to twice per frame.
- * The two measurements will be using different measure policies and potentially different
- * constraints.
- *
- * The first measurement happens in the lookahead pass, where new layout is calculated based on
- * the target constraints. Therefore, measurePolicy will receive the target constraints, and
- * subcompose its content based on the target constraints. Note: Target constraints refers to
- * the constraints that the [SubcomposeLayout] will receive once all the lookahead-based
- * animations on size/constraints in the ancestor layouts have finished.
- *
- * The second measurement is done in the intermediate measure pass after the lookahead pass.
- * The intermediate measure pass allows adjustments to the measurement/placement using the
- * pre-calculated layout information as animation targets to smooth over any
- * any layout changes. In this measurement, intermediateMeasurePolicy will be invoked with
- * the intermediate/animating constraints. By default, measure policy will be invoked in
- * intermediateMeasurePolicy, and hence the same measure logic in measurePolicy with
- * intermediate constraints will be used to measure and layout children in the intermediate
- * pass.
- *
- * Note: When measurePolicy is invoked in [SubcomposeIntermediateMeasureScope], `subcompose` will
- * simply retrieve the measurables associated with the given slot id based on the subcomposition
- * during lookahead pass. This means if a given slot id has not been subcomposed in the lookahead
- * pass, invoking subcompose during intermediate pass will result in an empty list.
- *
- * @sample androidx.compose.ui.samples.SubcomposeLayoutWithIntermediateMeasurePolicySample
- */
-@ExperimentalComposeUiApi
-sealed interface SubcomposeIntermediateMeasureScope : SubcomposeMeasureScope {
- /**
- * Returns the list of measureables associated with [slotId] that was subcomposed in the
- * [SubcomposeLayout]'s measurePolicy block during the lookahead pass. If the given [slotId]
- * was not used in the subcomoposition, the returned list will be empty.
- */
- fun measurablesForSlot(slotId: Any?): List<Measurable>
-
- /**
- * The size returned in the [MeasureResult] by the measurePolicy invoked during lookahead pass.
- */
- val lookaheadSize: IntSize
-
- /**
- * This is the measure policy that is supplied to SubcomposeLayout in the measurePolicy
- * parameter. It is used in the lookahead pass, and it is also invoked by default in the
- * intermediateMeasurePolicy for the intermediate measure pass.
- *
- * During the intermediate pass, the [lookaheadMeasurePolicy] will receive potentially
- * different (i.e. animating) constraints, and will subsequently remeasure and replace
- * all children according to the new constraints.
- *
- * Note: Intermediate measure pass will NOT run **new** subcompositions. [subcompose]
- * calls in from the [lookaheadMeasurePolicy] in this pass will instead retrieve the measurables
- * for the given slot based on the subcomposition from lookahead pass. In the rare
- * case where slots are subcomposed conditionally dependent on constraints, it's recommended
- * to provide to [SubcomposeLayout] a custom intermediate measure policy. A less desirable
- * solution to this use case is to invoke [lookaheadMeasurePolicy] with [lookaheadConstraints]
- * as its parameter, which will skip any intermediate stages straight to the lookahead
- * sizes & positions.
- */
- val lookaheadMeasurePolicy: SubcomposeMeasureScope.(Constraints) -> MeasureResult
-
- /**
- * Returns the [Constraints] used in the lookahead pass.
- *
- * Note: Using this with [lookaheadMeasurePolicy] will effectively skip any intermediate stages
- * from lookahead-based layout animations. Therefore it is recommended to use [Constraints]
- * passed to intermediate measure policy to measure and layout children during intermediate
- * pass. The only exception to that should be when some of the subcompositions are conditional.
- * In that case, a custom intermediate measure policy should ideally be provided to
- * [SubcomposeLayout]. Using [lookaheadConstraints] with [lookaheadMeasurePolicy] should be
- * considered as the last resort.
- */
- val lookaheadConstraints: Constraints
-
- /**
- * This function retrieves [Measurable]s created for [slotId] based on
- * the subcomposition that happened in the lookahead pass. If [slotId] was not subcomposed
- * in the lookahead pass, [subcompose] will return an [emptyList].
- */
- override fun subcompose(slotId: Any?, content: @Composable () -> Unit): List<Measurable> =
- measurablesForSlot(slotId)
-}
-
-/**
- * Analogue of [Layout] which allows to subcompose the actual content during the measuring stage
- * for example to use the values calculated during the measurement as params for the composition
- * of the children.
- *
* Possible use cases:
* * You need to know the constraints passed by the parent during the composition and can't solve
* your use case with just custom [Layout] or [LayoutModifier].
@@ -254,66 +104,6 @@
modifier: Modifier = Modifier,
measurePolicy: SubcomposeMeasureScope.(Constraints) -> MeasureResult
) {
- @OptIn(ExperimentalComposeUiApi::class)
- SubcomposeLayout(state, modifier, { lookaheadMeasurePolicy(it) }, measurePolicy)
-}
-
-/**
- * Analogue of [Layout] which allows to subcompose the actual content during the measuring stage
- * for example to use the values calculated during the measurement as params for the composition
- * of the children.
- *
- * When in a [LookaheadScope], [SubcomposeLayout] will be measured up to twice per frame.
- * The two measurements will be using different measure policies and potentially different
- * constraints.
- *
- * The first measurement happens in the lookahead pass, where new layout is calculated based on
- * the target constraints. Therefore, [measurePolicy] will receive the target constraints, and
- * subcompose its content based on the target constraints. Note: Target constraints refers to
- * the constraints that the [SubcomposeLayout] will receive once all the lookahead-based
- * animations on size/constraints in the ancestor layouts have finished.
- *
- * The second measurement is done in the intermediate measure pass after the lookahead pass.
- * The intermediate measure pass allows adjustments to the measurement/placement using the
- * pre-calculated layout information as animation targets to smooth over any
- * any layout changes. In this measurement, [intermediateMeasurePolicy] will be invoked with
- * the intermediate/animating constraints. By default, [measurePolicy] will be invoked in
- * [intermediateMeasurePolicy], and hence the same measure logic in [measurePolicy] with
- * intermediate constraints will be used to measure and layout children in the intermediate
- * pass.
- *
- * Note: When [measurePolicy] is invoked in the intermediate pass, `subcompose` will simply
- * return the measurables associated with the given slot id based on the subcomposition during
- * lookahead pass. This means if a given slot id has not been subcomposed in the lookahead pass,
- * invoking subcompose during intermediate pass will result in an empty list.
- *
- * Possible use cases:
- * * You need to know the constraints passed by the parent during the composition and can't solve
- * your use case with just custom [Layout] or [LayoutModifier].
- * See [androidx.compose.foundation.layout.BoxWithConstraints].
- * * You want to use the size of one child during the composition of the second child.
- * * You want to compose your items lazily based on the available size. For example you have a
- * list of 100 items and instead of composing all of them you only compose the ones which are
- * currently visible(say 5 of them) and compose next items when the component is scrolled.
- *
- * @param state the state object to be used by the layout.
- * @param modifier [Modifier] to apply for the layout.
- * @param intermediateMeasurePolicy A measure policy that will be invoked during the intermediate
- * measure pass.
- * @param measurePolicy Measure policy which provides ability to subcompose during the measuring.
- */
-@Composable
-@UiComposable
-@ExperimentalComposeUiApi
-fun SubcomposeLayout(
- state: SubcomposeLayoutState,
- modifier: Modifier = Modifier,
- intermediateMeasurePolicy:
- (SubcomposeIntermediateMeasureScope.(Constraints) -> MeasureResult) = { constraints ->
- lookaheadMeasurePolicy(constraints)
- },
- measurePolicy: SubcomposeMeasureScope.(Constraints) -> MeasureResult
-) {
val compositionContext = rememberCompositionContext()
val materialized = currentComposer.materialize(modifier)
val localMap = currentComposer.currentCompositionLocalMap
@@ -323,10 +113,6 @@
set(state, state.setRoot)
set(compositionContext, state.setCompositionContext)
set(measurePolicy, state.setMeasurePolicy)
- set(
- intermediateMeasurePolicy,
- state.setIntermediateMeasurePolicy
- )
set(localMap, ComposeUiNode.SetResolvedCompositionLocals)
set(materialized, ComposeUiNode.SetModifier)
}
@@ -359,6 +145,11 @@
* used during the previous measuring.
* @param content the composable content which defines the slot. It could emit multiple
* layouts, in this case the returned list of [Measurable]s will have multiple elements.
+ * **Note:** When a [SubcomposeLayout] is in a [LookaheadScope], the subcomposition only
+ * happens during the lookahead pass. In the post-lookahead/main pass, [subcompose] will
+ * return the list of [Measurable]s that were subcomposed during the lookahead pass. If the
+ * structure of the subtree emitted from [content] is dependent on incoming constraints,
+ * consider using constraints received from the lookahead pass for both passes.
*/
fun subcompose(slotId: Any?, content: @Composable () -> Unit): List<Measurable>
}
@@ -368,7 +159,6 @@
*
* [slotReusePolicy] the policy defining what slots should be retained to be reused later.
*/
-@OptIn(ExperimentalComposeUiApi::class)
class SubcomposeLayoutState(
private val slotReusePolicy: SubcomposeSlotReusePolicy
) {
@@ -396,16 +186,6 @@
SubcomposeSlotReusePolicy(maxSlotsToRetainForReuse)
)
- /**
- * Returns whether the [SubcomposeLayout] is in a [LookaheadScope]. Intermediate measure policy
- * will be only invoked when in a [LookaheadScope].
- */
- @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
- @get:ExperimentalComposeUiApi
- @ExperimentalComposeUiApi
- val isInLookaheadScope: Boolean
- get() = state.isInLookaheadScope
-
private var _state: LayoutNodeSubcompositionsState? = null
private val state: LayoutNodeSubcompositionsState
get() = requireNotNull(_state) {
@@ -428,10 +208,6 @@
LayoutNode.((SubcomposeMeasureScope.(Constraints) -> MeasureResult)) -> Unit =
{ measurePolicy = state.createMeasurePolicy(it) }
- internal val setIntermediateMeasurePolicy:
- LayoutNode.(SubcomposeIntermediateMeasureScope.(Constraints) -> MeasureResult) -> Unit =
- { state.intermediateMeasurePolicy = it }
-
/**
* Composes the content for the given [slotId]. This makes the next scope.subcompose(slotId)
* call during the measure pass faster as the content is already composed.
@@ -601,18 +377,8 @@
// this map contains active slotIds (without precomposed or reusable nodes)
private val slotIdToNode = mutableMapOf<Any?, LayoutNode>()
private val scope = Scope()
- private val intermediateMeasureScope = IntermediateMeasureScopeImpl()
+ private val postLookaheadMeasureScope = PostLookaheadMeasureScopeImpl()
- /**
- * This is the intermediateMeasurePolicy that developers set in [SubcomposeLayout]. It defaults
- * to invoking [SubcomposeIntermediateMeasureScope.lookaheadMeasurePolicy].
- *
- * Note: This intermediate measure policy is only invoked when in a [LookaheadScope].
- */
- internal var intermediateMeasurePolicy:
- (SubcomposeIntermediateMeasureScope.(Constraints) -> MeasureResult) = {
- lookaheadMeasurePolicy(it)
- }
private val precomposeMap = mutableMapOf<Any?, LayoutNode>()
private val reusableSlotIdsSet = SubcomposeSlotReusePolicy.SlotIdsSet()
@@ -840,7 +606,6 @@
fun createMeasurePolicy(
block: SubcomposeMeasureScope.(Constraints) -> MeasureResult
): MeasurePolicy {
- intermediateMeasureScope.lookaheadMeasurePolicy = block
return object : LayoutNode.NoIntrinsicsMeasurePolicy(error = NoIntrinsicsMessage) {
override fun MeasureScope.measure(
measurables: List<Measurable>,
@@ -849,17 +614,14 @@
scope.layoutDirection = layoutDirection
scope.density = density
scope.fontScale = fontScale
- val isIntermediate =
- (root.layoutState == LayoutState.Measuring ||
- root.layoutState == LayoutState.LayingOut) && root.lookaheadRoot != null
- if (isIntermediate) {
- return intermediateMeasurePolicy.invoke(intermediateMeasureScope, constraints)
+ if (!isLookingAhead && root.lookaheadRoot != null) {
+ return with(postLookaheadMeasureScope) {
+ block(constraints)
+ }
} else {
currentIndex = 0
- intermediateMeasureScope.lookaheadConstraints = constraints
val result = scope.block(constraints)
val indexAfterMeasure = currentIndex
- intermediateMeasureScope.lookaheadSize = IntSize(result.width, result.height)
return object : MeasureResult {
override val width: Int
get() = result.width
@@ -1008,19 +770,15 @@
[email protected](slotId, content)
}
- private inner class IntermediateMeasureScopeImpl :
- SubcomposeIntermediateMeasureScope, MeasureScope by scope {
- override fun measurablesForSlot(slotId: Any?): List<Measurable> =
- slotIdToNode[slotId]?.childMeasurables ?: emptyList()
-
+ private inner class PostLookaheadMeasureScopeImpl :
+ SubcomposeMeasureScope, MeasureScope by scope {
/**
- * This is the size returned in the MeasureResult in the measure policy from the lookahead
- * pass.
+ * This function retrieves [Measurable]s created for [slotId] based on
+ * the subcomposition that happened in the lookahead pass. If [slotId] was not subcomposed
+ * in the lookahead pass, [subcompose] will return an [emptyList].
*/
- override var lookaheadSize: IntSize = IntSize.Zero
- override lateinit var lookaheadMeasurePolicy:
- SubcomposeMeasureScope.(Constraints) -> MeasureResult
- override var lookaheadConstraints: Constraints = Constraints()
+ override fun subcompose(slotId: Any?, content: @Composable () -> Unit): List<Measurable> =
+ slotIdToNode[slotId]?.childMeasurables ?: emptyList()
}
}
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index 8ce18e2..b2b8c41 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -214,29 +214,30 @@
docs("androidx.media2:media2-session:1.2.1")
docs("androidx.media2:media2-widget:1.2.1")
docs("androidx.media:media:1.6.0")
- docs("androidx.media3:media3-cast:1.0.1")
- docs("androidx.media3:media3-common:1.0.1")
- docs("androidx.media3:media3-database:1.0.1")
- docs("androidx.media3:media3-datasource:1.0.1")
- docs("androidx.media3:media3-datasource-cronet:1.0.1")
- docs("androidx.media3:media3-datasource-okhttp:1.0.1")
- docs("androidx.media3:media3-datasource-rtmp:1.0.1")
- docs("androidx.media3:media3-decoder:1.0.1")
- docs("androidx.media3:media3-effect:1.0.1")
- docs("androidx.media3:media3-exoplayer:1.0.1")
- docs("androidx.media3:media3-exoplayer-dash:1.0.1")
- docs("androidx.media3:media3-exoplayer-hls:1.0.1")
- docs("androidx.media3:media3-exoplayer-ima:1.0.1")
- docs("androidx.media3:media3-exoplayer-rtsp:1.0.1")
- docs("androidx.media3:media3-exoplayer-smoothstreaming:1.0.1")
- docs("androidx.media3:media3-exoplayer-workmanager:1.0.1")
- docs("androidx.media3:media3-extractor:1.0.1")
- docs("androidx.media3:media3-session:1.0.1")
- docs("androidx.media3:media3-test-utils:1.0.1")
- docs("androidx.media3:media3-test-utils-robolectric:1.0.1")
- docs("androidx.media3:media3-transformer:1.0.1")
- docs("androidx.media3:media3-ui:1.0.1")
- docs("androidx.media3:media3-ui-leanback:1.0.1")
+ docs("androidx.media3:media3-cast:1.1.0-alpha01")
+ docs("androidx.media3:media3-common:1.1.0-alpha01")
+ docs("androidx.media3:media3-database:1.1.0-alpha01")
+ docs("androidx.media3:media3-datasource:1.1.0-alpha01")
+ docs("androidx.media3:media3-datasource-cronet:1.1.0-alpha01")
+ docs("androidx.media3:media3-datasource-okhttp:1.1.0-alpha01")
+ docs("androidx.media3:media3-datasource-rtmp:1.1.0-alpha01")
+ docs("androidx.media3:media3-decoder:1.1.0-alpha01")
+ docs("androidx.media3:media3-effect:1.1.0-alpha01")
+ docs("androidx.media3:media3-exoplayer:1.1.0-alpha01")
+ docs("androidx.media3:media3-exoplayer-dash:1.1.0-alpha01")
+ docs("androidx.media3:media3-exoplayer-hls:1.1.0-alpha01")
+ docs("androidx.media3:media3-exoplayer-ima:1.1.0-alpha01")
+ docs("androidx.media3:media3-exoplayer-rtsp:1.1.0-alpha01")
+ docs("androidx.media3:media3-exoplayer-smoothstreaming:1.1.0-alpha01")
+ docs("androidx.media3:media3-exoplayer-workmanager:1.1.0-alpha01")
+ docs("androidx.media3:media3-extractor:1.1.0-alpha01")
+ docs("androidx.media3:media3-muxer:1.1.0-alpha01")
+ docs("androidx.media3:media3-session:1.1.0-alpha01")
+ docs("androidx.media3:media3-test-utils:1.1.0-alpha01")
+ docs("androidx.media3:media3-test-utils-robolectric:1.1.0-alpha01")
+ docs("androidx.media3:media3-transformer:1.1.0-alpha01")
+ docs("androidx.media3:media3-ui:1.1.0-alpha01")
+ docs("androidx.media3:media3-ui-leanback:1.1.0-alpha01")
docs("androidx.mediarouter:mediarouter:1.6.0-alpha03")
docs("androidx.mediarouter:mediarouter-testing:1.6.0-alpha03")
docs("androidx.metrics:metrics-performance:1.0.0-alpha04")
diff --git a/gradle/verification-keyring.keys b/gradle/verification-keyring.keys
index a595aaf..9d82f9b 100644
--- a/gradle/verification-keyring.keys
+++ b/gradle/verification-keyring.keys
@@ -3838,6 +3838,64 @@
=hALf
-----END PGP PUBLIC KEY BLOCK-----
+pub 3D12CA2AC19F3181
+uid Tatu Saloranta (cowtowncoder) <[email protected]>
+
+sub 575D6C921D84AC76
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: BCPG v1.68
+
+mQINBGL4BxIBEAC+lX44fd/zrVQPzdKygarBd/X0bBpGakT++Kfk4UBGl3q+wd2G
+R9puB9R377ds8hU7U3To8sHguUZo6DbD9Gb/is/WajSb9g92z+rMow3KbqfCYqWr
+kaIj27OJgbziFcnMAtvGoFRfaPI/7TOwEw3jT7B87RXeiATX4iL8fzMUmkfZm0Hk
+qjnepMQeaz3KzMY4DfBcI45kwzl3EIBFIlk428mhBU5iAAANoyPsimfqEPRCUDjx
+vT8g7PvpkBdNZgRS6R9vLxyzKi/f5KswZIMvop/pRXIhAKDhCCyr2GD+T3JoIKp9
+kvS1MQucWeX8+TFWh5qEA3e06Xu0JSdPCEej0BH06EiTMsAOU5bWqgLAO9DVpS32
+I092KAuMJlEPCnz7IGXVkeNY5KYrlsmoKrBO3GF/zsCyiZDvSULkVJcrtBCYOrgq
+HRIzvJWQaTJ5V15MD8CZIELyjCGZ8Jy8hdZpaTjYalw0bUq+yRAqMD5slp6A1tnv
+jyqVTgU+yRGq2HB90vJ0D3P1w4xRDuNF8c02futO415Yc/qkyh3/5AjGSoocrlfX
+cMreJXpQWVsvXn3NsitjsA6XOJpMOgipCDxfvn8SSLl9fWNJf55j7fCkBokF/lIi
+81RVQbyjVCOV0OEqHJLP9asPHyAFvUppNWtcvViPxVmb52djnw/x/61WVQARAQAB
+tDVUYXR1IFNhbG9yYW50YSAoY293dG93bmNvZGVyKSA8dGF0dS5zYWxvcmFudGFA
+aWtpLmZpPokCVAQTAQgAPhYhBCgRjAcMsioBdaLo1D0SyirBnzGBBQJi+AcSAhsD
+BQkJZgGABQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJED0SyirBnzGBnxsQALTJ
+d/chCW8zWgR6x9ZDfU2f5fgMhi8jjILCieiQdQ/vec3QqCVLpJmE+l6MrI75E7GY
+eL8Iu0DXO26nHHSEbDa9kGk+ohdODbOd+y6KE5t6qPFaHSG5Gj1iiQ7libmrO7EZ
+qPQzl4fwmzUvl/2x+kaL5WsC4SxbwTG2mGo8WRCz+hqM40yeBeKYxpTlN2VBrlU2
+xkJ1I0rQITtHwck1dClgf4up13uBna7TlCBJc7vio3OwAFbXTPHEL+efrn8zk0au
+J+cwocHmm4d9shGiwiSUAOEnQ8FZ08IwO5MoOUWKz/ARYLuR6FAd8HqFERbfUxU2
+AlcyLSAhSi0c+j2Gi9PFkNTABEDHpdTosaAgKpbYdGke/2sfA0BKmcNRIfGjs+W0
+GnX2c7tP6qc7iFCCP0TjXvR8jnCmH8BT1JiSN5C/JPH0hDE1Zvmvgs0hid48e6Zv
+k1yXvEdtd/rH0uM3yUs28EfdcqokhwKZi5duUeizgG1gGNCW8RGvhi5ks1bABK9i
+52JMZznINMb35mZ0bEK7YLi3K8DE2hfIU6yg8WnlXlx3eOK4e6jWxWbjuEmUJ7BE
+ZWvz5IJ9nFI1UAIq7Ug7ruM+CH8ntpHueuGp6goq39+0lTd/1Sc29kVadg/DXQtC
+mDDrw4Btpz8hXthmTnNVvP/GUu5K1fttW3gNU/ofuQINBGL4BxIBEACwpcarOeEN
+5TztQDTvemc+DXZuWirmHv1TyJmjOhU0hGGMzEnKKU6VZlUIg5YQNYknUOfOf/05
+pgQmsEhjjI8NBD3Cys6SVQ6wOlkA+KpDKs/dXwyJttYE+EG/IMzjwZW2DbF50Hkm
+T2VK/oFhRSf0Par+cbVQ/mNLAVC7ueZWBVXldezqVvk3tDYYZef9T8Qlf999LVXF
+giMzRFrzLLcd2KEHAX4se65FTxIfFYYCrshFIKDRi4IWNzQqtPV7mb94wXY0Vwse
+5mMEgjmieGPjBGYne2JU0xYNBxtly1y3aeDXcxNlNrcS/Ake9AqWAYU4agtocCef
+b1pt5Q3li0qg3PsVKDJ5qWDceb+kgcUuHgtwHFCVICoQUMsv9p5F/kWL38/OWcTR
+2lQ4tGerE2dmlyqFWu7mpELckAfXSpJobZltUbp8CO679g0lk+OJWSmxddlVybdX
+CFaAeOKQb0woQOkR1vo2tJHyGmGr20Eea+UX+kdLojVQwYCqICdvnK4YpHuhpT5c
+rzk6lohfZpBPMHdpR7FQQZeQEW5EbcFNfoUEwsgb2qkG+hQIL5Q7wRajHkGaG3Wu
+hY2xEyoHuLSb20hP5hI2uhtxswUl/+IOjjZtDCjzLz59Q+ADkDZYM+PN3eR0UJDq
+YKly49KTuz23zLluNjNwqo8K0y05XGCQMwARAQABiQI8BBgBCAAmFiEEKBGMBwyy
+KgF1oujUPRLKKsGfMYEFAmL4BxICGwwFCQlmAYAACgkQPRLKKsGfMYGQbQ//bxZq
+IUIrPa86oXELq19E+OFTvCKVQzgohiaKPS6Hx2rgtvPS2bJvO+rXlAvRAFyI8sX7
+Dq0deeZP0pXefidjpfjKz1bhW8Wf7RU6QOKc0Eyl8/YqCVZign1DzJlF1r2mrkZa
+0VRNLjh86P/Y1T7ZhrAiR0PNv++LEgNsPbL8Tu1ryWl8vWFdlos40W8xleP1nBU1
+9OWheOPU9VYN80eBD5ij2Cn0LCxJQdyNOpVD03P3Ycauk9OHOVCd+UVX2A7VwemX
+b4wRDL59gAfSGyYLHYqAF1XJbOXkheTPRsttxXRIkzvA/gjpmQmioNU3UhiMZ1EJ
+kbJ42loFPv7YplbmBXoMAKJF1402+sOVusC1FULMQQtpZvC/bgobqEqdTzhZa/Hr
+KA45BFpcaTO80jJto8kiZR7infwX1gBGrYgwXisxiiYPI5yVwAvYTcNnU1nD9Vk5
+iyTgx0BNBG1hLPaZBtkYarDmUo0KyYf0Y8mQZmd7U0nOHptdkR+5yND5yO29/wGf
+se1KeZcRNGgcYQFKGw72HDiYsOELiiwsFqyUMWPLEwQJave/tO4SYSY1wP4rA3IA
+IU24GMQFZ64lljAKQCRXLwDKK2tMyHz8I+GT+0+bbpz4ojkEmxCDxXfgUy1362tG
+FdEbaZ88HZxTa30iXtOXOLvWIYRp8hv3pSb8id8=
+=hALf
+-----END PGP PUBLIC KEY BLOCK-----
+
pub CC6346F2CE3872D9
uid Jisi Liu <[email protected]>
@@ -12161,6 +12219,41 @@
=S4Fa
-----END PGP PUBLIC KEY BLOCK-----
+pub 26E74B6874AEE127
+uid Sam Judd <[email protected]>
+
+sub B4E75C15C3C701AE
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: BCPG v1.68
+
+mQENBFPsGJIBCADOxQoTLxpZVRIbLaRfsHa2y/TEIGvxLP7TgqTwspZYnwBd0cOW
+OHAvF8yGfdk5gvkGTlQ/xchwu2Ix05FO2c+fBoOgIG1Gn2Q+PwheZklS7S+V+GFk
+t5S3m6b54Pl2lKbcasaM99r5g2+MhxwVnG66ldLS9fdPWqWrviOC+EipHfGyuhj0
+R0E2Oh+dNHrsZR0vtoJawA8xDP5D1Ii3jEVdi71PF8OlavG7saAZXtOW1kU77oPa
+SmGFTmcFLjDP1alo95TTfBihaYiwFFnW7NQEi5Bw1u3G0+Vaoo5eJBQwYsNxLs3h
+I1d7wsqFW5LK6LSxEjfvEhJypi0XzrFkFzMvABEBAAG0H1NhbSBKdWRkIDxzYW0u
+YS5qdWRkQGdtYWlsLmNvbT6JATgEEwECACIFAlPsGJICGwMGCwkIBwMCBhUIAgkK
+CwQWAgMBAh4BAheAAAoJECbnS2h0ruEn9DUIAItn8TzZh44JQ0aRO3dgmh4kUnnJ
+cJyw+0m5m4gGyjZ3vFzbCXgzCHAz2jN3bh6crnUoheIUCn206NRz+aG0JGXAkgV7
+9R7QAKY3K8QzW0QWqHOKv1n4FbfPKhaGMnXYUcQjV9pkVgDmqV4QzjmegATVLkOE
+VVNUjWIP6wXwj0I/UW4jO608tTi9juH+XkDkpf+qqix8FUn81tw7/weWuZXFTpAe
+qAYzjtzzDse8uI1tZWxc8H6AvKCuigoTsD6xkfuPfL4a3JepER0R3WlGhW9hDLBc
+/VXGULLl612XfeG+xREy/pjILzp4ajTC9FdAmdWF2wQWhM9ZXt38FSBHg4+5AQ0E
+U+wYkgEIALu35o9Q309zBBZD6WLTMvFt/X+N6S/cUrNLMv3YvR/Bo1mtDxsYwmzo
+dWOHo6TNthWm1FIkDhHUMB58q9oHzL2/JIL2k/jCULOhj+YAT2N5fQwvZ2WBtWtf
+suFYFO5y7UKKUqo+2Dhnk8YuorPbKjVXELrHFIq0IRlKEKnDfNrkxwHBpfeU4QaC
+UcCW+OjRZFsgZVVc8ZjhvvKd9/vX4Jt/c4/O52Ym+YEYtZaiawcNYFAdMhaiwSF+
+dof+v2e6BIYOJrBifZuILqA87XsHQyBCABO1zhspt5X2QGGqkjVHiFJnYiTwqYX7
+2n1OpoMOklJZ+ShbbGpCst81i7lyz28AEQEAAYkBHwQYAQIACQUCU+wYkgIbDAAK
+CRAm50todK7hJ8OJCACtdgGnPNmYILYJ3jyXh+WPDvxr3y3yMKmC0pRhgs/WxGK6
+cmRV/ej8OB5aIWCaoWeJ03twgqiaSMbn+7ONkr1DbPLzqQGY+QJBJEvLE9m9YcAd
+BMHCT0fn9Op0AYQhoInOPcKLD6NFO+c91uLBOg0q316Wlpl0p3ULH526a2YVPeKw
+mjCVlhrQSoHgxagNrol4gC8bfpypsjTwCccjesiYMOPPU6eyHelFAAGEuVDmkh9g
+Gb9wOqgum0vJEummqOStTopEGr63PoWVMXSQbkrTrayCcxjSiKRfK10myhIGFU2n
+pCL9QbIT7Aw2vZ5VjIm6fWIOPJJz4dsRuYL9Ar8c
+=es9L
+-----END PGP PUBLIC KEY BLOCK-----
+
pub 82B5574242C20D6F
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: BCPG v1.68
@@ -14226,6 +14319,64 @@
=TnZo
-----END PGP PUBLIC KEY BLOCK-----
+pub 5A68A2249128E2C6
+uid Tink Developers (Signing key for Maven artifacts) <[email protected]>
+
+sub 4E5C59DBFF7DACF9
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: BCPG v1.68
+
+mQINBGPcwwABEADTw/gqmHh4LTSDsBP0KMoXFtFQnv7xmVPPrPjt0NxGn3w2WIou
+7UaLUTViKkgm92h72gyM7N9JfNBLcYrqVf9ed75MPdGQgzIhkVg3SLWZGFoIQUJ4
+VznKuqJmd0dSRtApXL9ZoVXf1mLnbLkOvfLfw2hVIsMJcW9/G4It7pPY82IiwTLn
+XG/pw6+wLa5FGCM4mldPnyBDR935nSkgnZzQJyDESXZKS1uiU1rMcGWkVLJ1UYfg
+fT5c6jAk+75vhyQEvHReoa1T8fgBPD0jAlE7T80460x8dramshhAAIOZLnlAuiBN
+A7KPY7cUDxDyFNLdhj7lGjPP1UTv5mdcZc0H6tgaapOB8QzqnkAJN7GrPHjAWnu7
+ujdiT+lmng+waiBfoQN7HQyJXng8Skj1tVYjuAUNgUuA6p1hL30k9Ny9wO2BBg23
+OXYn8yLptZCUg4T31w2ko2PReSxMeEI6S2jWTALP9HH1Q1sinQnlJ8SfPAssG8wn
+qjaV4PtS8bO+Gy2NosG389dzibrmVJAHqymTLlaviWgeqAXEwZhbVcSOv+B3JgAX
+h1dI2zDJUMGV7jNbKa+UNGb+di8T3J5JEXCNM/Zvm3KNudfZFbcNS1pEzNRzm4gh
+kmNHQEtknSm6NHaWIP5eMIxsKGUA6qTR8XE9qrvpwV35rwbxmPHSowHzzwARAQAB
+tEdUaW5rIERldmVsb3BlcnMgKFNpZ25pbmcga2V5IGZvciBNYXZlbiBhcnRpZmFj
+dHMpIDx0aW5rLWRldkBnb29nbGUuY29tPokCTgQTAQoAOBYhBHOXbJw5wUebhOJk
+GlpooiSRKOLGBQJj3MMAAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEFpo
+oiSRKOLGX3YP/iAVKH2H5ZKxO78FTLS8Jf1bl48Z31kTnvZg/sMFA51D9p+kBxns
+dNe/4UAt7R7hchlJ6tah9+L5bBf3eU0r4vNwE7+d6WiYoKgGVaegRHfOJEqSlnXo
+a6b3J2ML2vgnRpWTFJPdtZXKekodf76We6YshbHfuix5B0Zfztn2UC54cuhCsi83
+FhEMW9WvMe4Men9u4MsiQU+wCYUtsRV6bZpLWbrYFJyB75/IwWiLZv4AiVIqUpB6
+yw/lTDsPTyrncEfE2+DYwyHduieY+gIR6SGkJh+7VF3ESj6UR1i7+sdcvWbLgJXe
+JoqQBROxnApKh/3LIDUGuONfiryEaC5Xy/l1YkEUsgp5RA5YrotFkTEZCvud6M9m
+iBrFOz0xAHwu84OfcSJsmzARh8lItd4wImykXnhyiTInVUrq9OeDN94RSTFQ7+Mh
+RT+CBnh6Z8y3jL9ekCpGZrxB0K4T4FBxQ0QNLnPZK2OfTEvmC2haP2sofbrYbo31
+TlafpzPfLXyp5sBGevT/vUd3YbXS69L3lDRKoOnJy3PK3UUpeSxTP0sN53mnembu
+8R6eHzuisrb1vl9wsIJ0EFn5F331xQagKYzDn0Vp63KltD3xOnB+itcsZETYamuL
+MHk3TXrNg47Iu6fbLEw9M2HHYDBm70AI1PpXtT4mtQ+TU18JtPM4ZpvHuQINBGPc
+wwABEADh/vvuWr2Pl5xD/gF1vKIdy+sNTTadx7EoAsdnrYShtP3jvUPL9VDvbpyH
+K9B6wFEulUM037L7BOl9khg9oO4G/NXlU3wiIJk4dI4tBrj1IAFD7z3qQ2Sgpy/b
+TsBRZZCwOiW28IxqQsx5DE4i5YYOEjjyqZiza4/I3TchKN0sEOwb77MEUrYS1CUe
+lpl/zFlYZNGXT/oDjJI2bVs1pvCMtb2iQW7m6JvDEY0xZ7zoRm2rJA567oV3WAO6
+u2T2tpAzfu1SYJRRPbUt45pdzWSZUzCQwcB0ZTAuQbK4nIsjPGv6oAPm3PgpEpW2
+PBHPux+UHN03k/vEpb5XLLTBuWIdgtXRzD2vSkEO7A6CBkYTY4TY5UodLgV+szYl
+G+N00m0h2SOf/9FEijRIA2XMfNYZ6E8x3I0ADXmOEE1MfdBGSEtk/Tzb/NX8Bb8/
+zk1tKRI00vEz1bjOTsiRZQ6Aod+hUcCPasTUAKIgTpe30zd1v1krF4leVRXHWJbU
+mpsr7CoJRIrKBghkP7K0vLUQzes0djxl/J9C8Ru7bjM6Ndjmy5+oSCMMPJyJ/Wpd
++fEWZxgIukAnjLtuZTUVeMESzP1CkUeNv+aGadPNGw97VRAFEfk6508ihg+TVEiy
+FORV/njYUB+4zm/+aczd6KKT24t6DIVdm1FkSds8SpcDT8Ei0wARAQABiQI2BBgB
+CgAgFiEEc5dsnDnBR5uE4mQaWmiiJJEo4sYFAmPcwwACGwwACgkQWmiiJJEo4sbA
+dRAAlzA7kLCzFnCSYr2TgCfQEoI8yslnPL0flq7ghw5yBK4OdUbYoUBYBroZMJLw
+hPvyaEdp3t63Sl/9GsYNfub+TAOJA64WuRtOT1QbOh6+U5T7X5yvPM3FAGUuYOlk
+1ABuTAtbOWW+iPOpE7sZaai0j9zH2vPyviBqZ6GtvsuVT7ynRbrYuWe9127ZkJet
+6zPzGXoyTE+FaGOdv/wd+9u1Qjk/lYowNoQ7xXWbnF4jD3o6lM56uOgvPUFoSnzb
+sd1fCXcfQ4wj+O3yEoMDVa1K9eIrSz7TrL5K4VzfOxaHxPh2orE8dFgjXy0Vm/KC
+XTOc9DcJXCjqJVh9RoDxTaNkjWfkN+1bq9NUaPauRduMwlkbk904ygXxMJ485hm5
+uSaCKM8eYBp4y/CdwOcnonBEg+lS3rVIcfDUByRrim5pOsIlSe3f2OX3txMYQvXJ
+ivYOA4phKSpntp4TDzGkZfLbCIpaFowR3px/c2LKuQmTmr+Vl8v5W4kBgQGEDdxY
+a7jhYx9HbLimtQG2XcCC+javwdubT/ItHrfcAB6B+dV0iRA5b4QDGtU0CsD9tY3N
+oaTSCeYc7Xae5YCXr3viH8vWPap984XZWLJZXM2s2Vm1XdeBTQxWCGVER9dnzGGE
+DsHNOZoy3UpsL+GQ+AORGsEAjOVJQnCjqUh7gtB8cwiPvps=
+=TnZo
+-----END PGP PUBLIC KEY BLOCK-----
+
pub 5E1F79A7C298661E
uid David P. Baker <[email protected]>
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 23ee8db..9a07f43 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -327,6 +327,7 @@
<trusted-key id="b46dc71e03feeb7f89d1f2491f7a8f87b9d8f501" group="org.jetbrains.trove4j"/>
<trusted-key id="b47034c19c9b1f3dc3702f8d476634a4694e716a" group="com.googlecode.java-diff-utils"/>
<trusted-key id="b4ac8cdc141af0ae468d16921da784ccb5c46dd5" group="net.bytebuddy"/>
+ <trusted-key id="b69a63a5ef7183dbc09199ac26e74b6874aee127" group="com.github.bumptech.glide"/>
<trusted-key id="b6e73d84ea4fcc47166087253faad2cd5ecbb314" group="org.apache.commons"/>
<trusted-key id="b801e2f8ef035068ec1139cc29579f18fa8fd93b" group="com.google.j2objc"/>
<trusted-key id="b9cca13c59f21c6ce841a8d1a4b1a03fb9c2ce23" group="com.squareup.leakcanary"/>
@@ -381,6 +382,7 @@
</trusted-key>
<trusted-key id="db0597e3144342256bc81e3ec727d053c4481cf5" group="org.tensorflow"/>
<trusted-key id="dbd744ace7ade6aa50dd591f66b50994442d2d40">
+ <trusting group="com.squareup.okhttp3"/>
<trusting group="com.squareup.okio"/>
<trusting group="com.squareup.wire"/>
</trusted-key>
@@ -477,6 +479,14 @@
<sha256 value="9fb18fd29b9dfe2e7ed5fe98a3be433a4c3cc4ea8f47f2b444155c39b4afddf5" origin="Generated by Gradle" reason="Unsigned. Used by media3."/>
</artifact>
</component>
+ <component group="com.google.ads.interactivemedia.v3" name="interactivemedia" version="3.30.1">
+ <artifact name="interactivemedia-3.30.1.aar">
+ <sha256 value="e2cc79031befb48c6212a0c71cf1535416a8903a9a373ba6c8b95dec95db83c0" origin="Generated by Gradle" reason="Artifact is not signed"/>
+ </artifact>
+ <artifact name="interactivemedia-3.30.1.pom">
+ <sha256 value="19f7d921a20d3be022620dd78bd708b2c16362cece59781c94b74a51f078243c" origin="Generated by Gradle" reason="Artifact is not signed"/>
+ </artifact>
+ </component>
<component group="com.google.android.apps.common.testing.accessibility.framework" name="accessibility-test-framework" version="2.1">
<artifact name="accessibility-test-framework-2.1.jar">
<sha256 value="7b0aa6ed7553597ce0610684a9f7eca8021eee218f2e2f427c04a7fbf5f920bd" origin="Generated by Gradle" reason="Artifact is not signed"/>
@@ -549,6 +559,14 @@
<sha256 value="c6898b1f71e69b15bf90c31fc3ef2de1cffbf454a770700f755b5a47ea48b540" origin="Generated by Gradle"/>
</artifact>
</component>
+ <component group="com.google.android.tv" name="tv-ads" version="1.0.0-alpha02">
+ <artifact name="tv-ads-1.0.0-alpha02.aar">
+ <sha256 value="d92a8eb07b926cdd199f3bdaf33478565cab125b92859e84f7abb5b7219bc5e4" origin="Generated by Gradle" reason="Artifact is not signed"/>
+ </artifact>
+ <artifact name="tv-ads-1.0.0-alpha02.pom">
+ <sha256 value="7b4315f0127628f2203ab66dfe490a9a07e06a38fb8e15ea2df1a35f1619308d" origin="Generated by Gradle" reason="Artifact is not signed"/>
+ </artifact>
+ </component>
<component group="com.google.camerax.effects" name="portrait" version="0.0.1">
<artifact name="portrait-0.0.1.aar">
<sha256 value="86d3e99a6123e3f830bf3f70392b30fc8d238374104ce00a02de20d8613af607" origin="Generated by Gradle"/>
diff --git a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/SdkCodeGenerator.kt b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/SdkCodeGenerator.kt
index c46fb02..d3332ac 100644
--- a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/SdkCodeGenerator.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/SdkCodeGenerator.kt
@@ -22,6 +22,7 @@
import androidx.privacysandbox.tools.core.generator.ClientProxyTypeGenerator
import androidx.privacysandbox.tools.core.generator.CoreLibInfoAndBinderWrapperConverterGenerator
import androidx.privacysandbox.tools.core.generator.GenerationTarget
+import androidx.privacysandbox.tools.core.generator.SdkActivityLauncherWrapperGenerator
import androidx.privacysandbox.tools.core.generator.ServerBinderCodeConverter
import androidx.privacysandbox.tools.core.generator.ServiceFactoryFileGenerator
import androidx.privacysandbox.tools.core.generator.StubDelegatesGenerator
@@ -29,6 +30,7 @@
import androidx.privacysandbox.tools.core.generator.TransportCancellationGenerator
import androidx.privacysandbox.tools.core.generator.ValueConverterFileGenerator
import androidx.privacysandbox.tools.core.model.ParsedApi
+import androidx.privacysandbox.tools.core.model.containsSdkActivityLauncher
import androidx.privacysandbox.tools.core.model.getOnlyService
import androidx.privacysandbox.tools.core.model.hasSuspendFunctions
import com.google.devtools.ksp.processing.CodeGenerator
@@ -68,6 +70,7 @@
generateCallbackProxies()
generateToolMetadata()
generateSuspendFunctionUtilities()
+ generateSdkActivityLauncherUtilities()
generateServiceFactoryFile()
}
@@ -151,6 +154,11 @@
.also(::write)
}
+ private fun generateSdkActivityLauncherUtilities() {
+ if (!api.containsSdkActivityLauncher()) return
+ SdkActivityLauncherWrapperGenerator(basePackageName()).generate().also(::write)
+ }
+
private fun write(spec: FileSpec) {
codeGenerator.createNewFile(Dependencies.ALL_FILES, spec.packageName, spec.name)
.bufferedWriter().use(spec::writeTo)
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/input/com/mysdk/MySdk.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/input/com/mysdk/MySdk.kt
index c096f86..c23eaad 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/input/com/mysdk/MySdk.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/input/com/mysdk/MySdk.kt
@@ -5,6 +5,7 @@
import androidx.privacysandbox.tools.PrivacySandboxService
import androidx.privacysandbox.tools.PrivacySandboxValue
import androidx.privacysandbox.ui.core.SandboxedUiAdapter
+import androidx.privacysandbox.ui.core.SdkActivityLauncher
@PrivacySandboxService
interface MySdk {
@@ -31,6 +32,8 @@
suspend fun returnUiInterface(): MyUiInterface
fun acceptUiInterfaceParam(input: MyUiInterface)
+
+ fun acceptSdkActivityLauncherParam(activityLauncher: SdkActivityLauncher)
}
@PrivacySandboxInterface
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MySdkStubDelegate.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MySdkStubDelegate.kt
index 0d5c413..79bd6e5 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MySdkStubDelegate.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MySdkStubDelegate.kt
@@ -1,6 +1,7 @@
package com.mysdk
import android.content.Context
+import android.os.Bundle
import androidx.privacysandbox.ui.provider.toCoreLibInfo
import com.mysdk.PrivacySandboxThrowableParcelConverter
import com.mysdk.PrivacySandboxThrowableParcelConverter.toThrowableParcel
@@ -172,4 +173,10 @@
delegate.acceptUiInterfaceParam((input.binder as MyUiInterfaceStubDelegate).delegate)
}
}
+
+ public override fun acceptSdkActivityLauncherParam(activityLauncher: Bundle): Unit {
+ coroutineScope.launch {
+ delegate.acceptSdkActivityLauncherParam(SdkActivityLauncherAndBinderWrapper(activityLauncher))
+ }
+ }
}
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/SdkActivityLauncherAndBinderWrapper.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/SdkActivityLauncherAndBinderWrapper.kt
new file mode 100644
index 0000000..8ef1af5
--- /dev/null
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/SdkActivityLauncherAndBinderWrapper.kt
@@ -0,0 +1,13 @@
+package com.mysdk
+
+import android.os.Bundle
+import androidx.privacysandbox.ui.core.SdkActivityLauncher
+import androidx.privacysandbox.ui.provider.SdkActivityLauncherFactory
+
+public class SdkActivityLauncherAndBinderWrapper private constructor(
+ private val `delegate`: SdkActivityLauncher,
+ public val launcherInfo: Bundle,
+) : SdkActivityLauncher by delegate {
+ public constructor(launcherInfo: Bundle) :
+ this(SdkActivityLauncherFactory.fromLauncherInfo(launcherInfo), launcherInfo)
+}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/parser/ApiStubParserTest.kt b/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/parser/ApiStubParserTest.kt
index 4c4c071..dae0d2ac 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/parser/ApiStubParserTest.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/parser/ApiStubParserTest.kt
@@ -26,6 +26,7 @@
import androidx.privacysandbox.tools.core.model.Types
import androidx.privacysandbox.tools.core.model.Types.asNullable
import androidx.privacysandbox.tools.core.model.ValueProperty
+import androidx.privacysandbox.tools.core.model.containsSdkActivityLauncher
import androidx.privacysandbox.tools.testing.CompilationTestHelper.assertCompiles
import androidx.room.compiler.processing.util.Source
import androidx.testutils.assertThrows
@@ -263,6 +264,59 @@
}
@Test
+ fun sandboxedUiAdapter_correctlyDetectedInService() {
+ val source = Source.kotlin(
+ "com/mysdk/MySdk.kt", """
+ import androidx.privacysandbox.tools.PrivacySandboxService
+ import androidx.privacysandbox.ui.core.SdkActivityLauncher
+ @PrivacySandboxService
+ interface MySdk {
+ fun useLauncher(launcher: SdkActivityLauncher)
+ }
+ """
+ )
+
+ assertThat(compileAndParseApi(source).containsSdkActivityLauncher()).isTrue()
+ }
+
+ @Test
+ fun sandboxedUiAdapter_correctlyDetectedInInterface() {
+ val source = Source.kotlin(
+ "com/mysdk/MySdk.kt", """
+ import androidx.privacysandbox.tools.PrivacySandboxService
+ import androidx.privacysandbox.tools.PrivacySandboxInterface
+ import androidx.privacysandbox.ui.core.SdkActivityLauncher
+ @PrivacySandboxService
+ interface MySdk
+
+ @PrivacySandboxInterface
+ interface MyInterface {
+ fun useLauncher(launcher: SdkActivityLauncher)
+ }
+ """
+ )
+
+ assertThat(compileAndParseApi(source).containsSdkActivityLauncher()).isTrue()
+ }
+
+ @Test
+ fun sandboxedUiAdapter_notDetectedWhenNotPresent() {
+ val source = Source.kotlin(
+ "com/mysdk/MySdk.kt", """
+ import androidx.privacysandbox.tools.PrivacySandboxService
+ // Deliberate unused import
+ import androidx.privacysandbox.ui.core.SdkActivityLauncher
+ @PrivacySandboxService
+ interface MySdk {
+ fun doStuff(input: String)
+ }
+ """
+ )
+
+ assertThat(compileAndParseApi(source).containsSdkActivityLauncher()).isFalse()
+ }
+
+ @Test
fun nonKotlinAnnotatedInterface_throws() {
val source = Source.java(
"com/mysdk/MySdk", """
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/input/com/sdk/MySdk.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/input/com/sdk/MySdk.kt
index 74b3e78..413a47a 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/input/com/sdk/MySdk.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/input/com/sdk/MySdk.kt
@@ -4,6 +4,7 @@
import androidx.privacysandbox.tools.PrivacySandboxInterface
import androidx.privacysandbox.tools.PrivacySandboxService
import androidx.privacysandbox.ui.core.SandboxedUiAdapter
+import androidx.privacysandbox.ui.core.SdkActivityLauncher
@PrivacySandboxService
interface MySdk {
@@ -21,6 +22,8 @@
fun doSomething(firstInterface: MyInterface, secondInterface: MySecondInterface)
fun doSomethingWithNullableInterface(maybeInterface: MySecondInterface?)
+
+ public fun doSomethingWithSdkActivityLauncher(launcher: SdkActivityLauncher)
}
@PrivacySandboxInterface
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MyInterface.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MyInterface.kt
index e04de66..cc0fb50 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MyInterface.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MyInterface.kt
@@ -1,9 +1,13 @@
package com.sdk
+import androidx.privacysandbox.ui.core.SdkActivityLauncher
+
public interface MyInterface {
public suspend fun add(x: Int, y: Int): Int
public fun doSomething(firstInterface: MyInterface, secondInterface: MySecondInterface): Unit
public fun doSomethingWithNullableInterface(maybeInterface: MySecondInterface?): Unit
+
+ public fun doSomethingWithSdkActivityLauncher(launcher: SdkActivityLauncher): Unit
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MyInterfaceClientProxy.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MyInterfaceClientProxy.kt
index 05069c3..1a874d7 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MyInterfaceClientProxy.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MyInterfaceClientProxy.kt
@@ -1,5 +1,7 @@
package com.sdk
+import androidx.privacysandbox.ui.client.toLauncherInfo
+import androidx.privacysandbox.ui.core.SdkActivityLauncher
import com.sdk.PrivacySandboxThrowableParcelConverter.fromThrowableParcel
import kotlin.coroutines.resumeWithException
import kotlinx.coroutines.suspendCancellableCoroutine
@@ -41,4 +43,8 @@
IMySecondInterfaceCoreLibInfoAndBinderWrapperConverter.toParcelable((notNullValue as
MySecondInterfaceClientProxy).coreLibInfo, notNullValue.remote) })
}
+
+ public override fun doSomethingWithSdkActivityLauncher(launcher: SdkActivityLauncher): Unit {
+ remote.doSomethingWithSdkActivityLauncher(launcher.toLauncherInfo())
+ }
}
diff --git a/privacysandbox/tools/tools-apipackager/build.gradle b/privacysandbox/tools/tools-apipackager/build.gradle
index 19089d3..006779b 100644
--- a/privacysandbox/tools/tools-apipackager/build.gradle
+++ b/privacysandbox/tools/tools-apipackager/build.gradle
@@ -15,6 +15,8 @@
*/
import androidx.build.LibraryType
+import androidx.build.SdkHelperKt
+import androidx.build.SupportConfig
plugins {
id("AndroidXPlugin")
@@ -35,6 +37,13 @@
testImplementation(project(":room:room-compiler-processing-testing"))
testImplementation(libs.junit)
testImplementation(libs.truth)
+
+ // TODO(b/281638337): Remove below dependency once SdkActivityLauncher stubs are removed
+ // Include android jar for compilation of generated sources.
+ testImplementation(fileTree(
+ dir: "${SdkHelperKt.getSdkPath(project)}/platforms/$SupportConfig.COMPILE_SDK_VERSION/",
+ include: "android.jar"
+ ))
}
androidx {
diff --git a/privacysandbox/tools/tools-apipackager/src/test/java/androidx/privacysandbox/tools/apipackager/PrivacySandboxApiPackagerTest.kt b/privacysandbox/tools/tools-apipackager/src/test/java/androidx/privacysandbox/tools/apipackager/PrivacySandboxApiPackagerTest.kt
index e537412..eb4b128 100644
--- a/privacysandbox/tools/tools-apipackager/src/test/java/androidx/privacysandbox/tools/apipackager/PrivacySandboxApiPackagerTest.kt
+++ b/privacysandbox/tools/tools-apipackager/src/test/java/androidx/privacysandbox/tools/apipackager/PrivacySandboxApiPackagerTest.kt
@@ -208,7 +208,7 @@
/** Compiles the given source file and returns a classpath with the results. */
private fun compileAndReturnUnzippedPackagedClasspath(source: Source): File {
- val result = compileAll(listOf(source))
+ val result = compileAll(listOf(source), includeLibraryStubs = false)
assertThat(result).succeeds()
assertThat(result.outputClasspath).hasSize(1)
@@ -221,7 +221,7 @@
generateSequence { input.nextEntry }
.forEach {
val file: File = outputDir.resolve(it.name)
- file.parentFile.mkdirs()
+ file.parentFile?.mkdirs()
file.createNewFile()
input.copyTo(file.outputStream())
}
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/AidlGenerator.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/AidlGenerator.kt
index 2cf4f2f..abb403b 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/AidlGenerator.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/AidlGenerator.kt
@@ -118,10 +118,7 @@
private fun uiAidlWrapper(annotatedInterface: AnnotatedInterface) =
aidlParcelable(annotatedInterface.uiAdapterAidlWrapper()) {
- addProperty(
- "coreLibInfo",
- AidlTypeSpec(bundleType(), kind = AidlTypeKind.PARCELABLE)
- )
+ addProperty("coreLibInfo", bundleAidlType)
addProperty("binder", annotatedInterface.aidlType())
}
@@ -246,7 +243,12 @@
private fun throwableParcelType() = Type(packageName(), throwableParcelName)
private fun parcelableStackFrameType() = Type(packageName(), parcelableStackFrameName)
- private fun bundleType() = Type("android.os", "Bundle")
+ private val bundleType = Type("android.os", "Bundle")
+ private val bundleAidlType =
+ AidlTypeSpec(
+ bundleType,
+ AidlTypeKind.PARCELABLE
+ )
private fun transactionCallback(type: Type) =
AidlTypeSpec(
@@ -276,6 +278,7 @@
Short::class.qualifiedName -> primitive("int")
Unit::class.qualifiedName -> primitive("void")
List::class.qualifiedName -> getAidlTypeDeclaration(type.typeParameters[0]).listSpec()
+ Types.sdkActivityLauncher.qualifiedName -> bundleAidlType
else -> throw IllegalArgumentException(
"Unsupported type conversion ${type.qualifiedName}"
)
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/BinderCodeConverter.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/BinderCodeConverter.kt
index 0ef84b3e..c029621 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/BinderCodeConverter.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/BinderCodeConverter.kt
@@ -16,6 +16,7 @@
package androidx.privacysandbox.tools.core.generator
+import androidx.privacysandbox.tools.core.generator.SpecNames.bundleClass
import androidx.privacysandbox.tools.core.generator.SpecNames.contextPropertyName
import androidx.privacysandbox.tools.core.model.AnnotatedInterface
import androidx.privacysandbox.tools.core.model.AnnotatedValue
@@ -25,6 +26,7 @@
import androidx.privacysandbox.tools.core.model.Types.asNonNull
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.MemberName
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.TypeName
@@ -80,6 +82,9 @@
CodeBlock.of(".map { %L }", convertToModelCodeBlock)
)
}
+ if (type.qualifiedName == Types.sdkActivityLauncher.qualifiedName) {
+ return CodeBlock.of("SdkActivityLauncherAndBinderWrapper(%L)", expression)
+ }
if (type == Types.short) {
return CodeBlock.of("%L.toShort()", expression)
}
@@ -140,6 +145,13 @@
toBinderList(type.typeParameters[0])
)
}
+ if (type.qualifiedName == Types.sdkActivityLauncher.qualifiedName) {
+ return CodeBlock.of(
+ "%L.%M()",
+ expression,
+ MemberName("androidx.privacysandbox.ui.client", "toLauncherInfo"),
+ )
+ }
if (type == Types.short) {
return CodeBlock.of("%L.toInt()", expression)
}
@@ -215,6 +227,8 @@
}
if (type.qualifiedName == List::class.qualifiedName)
return convertToBinderListType(type.typeParameters[0])
+ if (type.qualifiedName == Types.sdkActivityLauncher.qualifiedName)
+ return bundleClass
return type.poetTypeName()
}
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/SdkActivityLauncherWrapperGenerator.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/SdkActivityLauncherWrapperGenerator.kt
new file mode 100644
index 0000000..a90a3ef
--- /dev/null
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/SdkActivityLauncherWrapperGenerator.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2023 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.privacysandbox.tools.core.generator
+
+import androidx.privacysandbox.tools.core.model.Types
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.FunSpec
+import com.squareup.kotlinpoet.KModifier
+import com.squareup.kotlinpoet.PropertySpec
+import com.squareup.kotlinpoet.TypeSpec
+
+/**
+ * Used in server-side code generation to ensure that `SdkActivityLauncher`s are not repeatedly
+ * repackaged into binders.
+ */
+class SdkActivityLauncherWrapperGenerator(private val basePackageName: String) {
+ companion object {
+ const val className = "SdkActivityLauncherAndBinderWrapper"
+ }
+
+ fun generate(): FileSpec {
+ val classSpec = TypeSpec.classBuilder(className).build {
+ addSuperinterface(Types.sdkActivityLauncher.poetClassName(), CodeBlock.of("delegate"))
+ addModifiers(KModifier.PUBLIC)
+ primaryConstructor(
+ listOf(
+ PropertySpec.builder(
+ "delegate",
+ Types.sdkActivityLauncher.poetTypeName(),
+ ).addModifiers(KModifier.PRIVATE).build(),
+ PropertySpec.builder(
+ "launcherInfo",
+ SpecNames.bundleClass,
+ ).build(),
+ ),
+ KModifier.PRIVATE,
+ )
+ addFunction(
+ FunSpec.constructorBuilder()
+ .addParameter("launcherInfo", SpecNames.bundleClass)
+ .callThisConstructor(
+ CodeBlock.of(
+ "%T.fromLauncherInfo(launcherInfo)",
+ ClassName(
+ "androidx.privacysandbox.ui.provider",
+ "SdkActivityLauncherFactory"
+ ),
+ ),
+ CodeBlock.of("launcherInfo"),
+ ).build()
+ )
+ }
+
+ return FileSpec.builder(basePackageName, className).build {
+ addCommonSettings()
+ addType(classSpec)
+ }
+ }
+}
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/model/Models.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/model/Models.kt
index 88245e9..15d0e4b 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/model/Models.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/model/Models.kt
@@ -30,6 +30,26 @@
.any(Method::isSuspend)
}
+fun ParsedApi.containsSdkActivityLauncher(): Boolean {
+ return values.any { it.containsSdkActivityLauncher() } ||
+ interfaces.any { it.containsSdkActivityLauncher() } ||
+ callbacks.any { it.containsSdkActivityLauncher() } ||
+ services.any { it.containsSdkActivityLauncher() }
+}
+
+private fun AnnotatedInterface.containsSdkActivityLauncher(): Boolean {
+ val isInReturns = methods
+ .any { it.returnType.qualifiedName == Types.sdkActivityLauncher.qualifiedName }
+ val isInParams = methods
+ .flatMap { it.parameters }
+ .any { it.type.qualifiedName == Types.sdkActivityLauncher.qualifiedName }
+
+ return isInReturns || isInParams
+}
+
+private fun AnnotatedValue.containsSdkActivityLauncher(): Boolean =
+ properties.any { it.type.qualifiedName == Types.sdkActivityLauncher.qualifiedName }
+
object Types {
val unit = Type(packageName = "kotlin", simpleName = "Unit")
val boolean = Type(packageName = "kotlin", simpleName = "Boolean")
@@ -45,6 +65,8 @@
val any = Type("kotlin", simpleName = "Any")
val sandboxedUiAdapter =
Type(packageName = "androidx.privacysandbox.ui.core", simpleName = "SandboxedUiAdapter")
+ val sdkActivityLauncher =
+ Type(packageName = "androidx.privacysandbox.ui.core", simpleName = "SdkActivityLauncher")
fun list(elementType: Type) = Type(
packageName = "kotlin.collections",
@@ -57,6 +79,7 @@
return this
return copy(isNullable = true)
}
+
fun Type.asNonNull(): Type {
if (isNullable)
return copy(isNullable = false)
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/validator/ModelValidator.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/validator/ModelValidator.kt
index bc6aeb5..90603e5 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/validator/ModelValidator.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/validator/ModelValidator.kt
@@ -147,7 +147,9 @@
}
private fun isValidInterfaceParameterType(type: Type) =
- isValue(type) || isInterface(type) || isPrimitive(type) || isList(type) || isCallback(type)
+ isValue(type) || isInterface(type) || isPrimitive(type) || isList(type) ||
+ isCallback(type) || isBundledType(type)
+
private fun isValidInterfaceReturnType(type: Type) =
isValue(type) || isInterface(type) || isPrimitive(type) || isList(type)
private fun isValidValuePropertyType(type: Type) =
@@ -171,6 +173,8 @@
}
return false
}
+
+ private fun isBundledType(type: Type) = type == Types.sdkActivityLauncher
}
data class ValidationResult(val errors: List<String>) {
diff --git a/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/generator/AidlInterfaceGeneratorTest.kt b/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/generator/AidlInterfaceGeneratorTest.kt
index 780e27c..a536da8 100644
--- a/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/generator/AidlInterfaceGeneratorTest.kt
+++ b/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/generator/AidlInterfaceGeneratorTest.kt
@@ -72,7 +72,15 @@
parameters = listOf(),
returnType = Type("com.mysdk", "MyInterface"),
isSuspend = true,
- )
+ ),
+ Method(
+ name = "methodWithActivityLauncherParam",
+ parameters = listOf(
+ Parameter("activityLauncher", Types.sdkActivityLauncher)
+ ),
+ returnType = Types.unit,
+ isSuspend = false,
+ ),
)
)
)
diff --git a/privacysandbox/tools/tools-core/src/test/test-data/aidlinterfacegeneratortest/output/com/mysdk/IMyInterface.aidl b/privacysandbox/tools/tools-core/src/test/test-data/aidlinterfacegeneratortest/output/com/mysdk/IMyInterface.aidl
index 2a532b7..4815029 100644
--- a/privacysandbox/tools/tools-core/src/test/test-data/aidlinterfacegeneratortest/output/com/mysdk/IMyInterface.aidl
+++ b/privacysandbox/tools/tools-core/src/test/test-data/aidlinterfacegeneratortest/output/com/mysdk/IMyInterface.aidl
@@ -1,8 +1,10 @@
package com.mysdk;
+import android.os.Bundle;
import com.mysdk.IMyInterfaceTransactionCallback;
oneway interface IMyInterface {
+ void methodWithActivityLauncherParam(in Bundle activityLauncher) = 12556385;
void methodWithInterfaceParam(IMyInterface myInterface) = 5537946;
void suspendMethodWithInterfaceReturn(IMyInterfaceTransactionCallback transactionCallback) = 13841773;
}
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/CompilationTestHelper.kt b/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/CompilationTestHelper.kt
index 0a9c958..175800e 100644
--- a/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/CompilationTestHelper.kt
+++ b/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/CompilationTestHelper.kt
@@ -40,13 +40,17 @@
sources: List<Source>,
extraClasspath: List<File> = emptyList(),
symbolProcessorProviders: List<SymbolProcessorProvider> = emptyList(),
- processorOptions: Map<String, String> = emptyMap()
+ processorOptions: Map<String, String> = emptyMap(),
+ includeLibraryStubs: Boolean = true,
): TestCompilationResult {
val tempDir = Files.createTempDirectory("compile").toFile().also { it.deleteOnExit() }
+ // TODO(b/281638337): Remove library stubs once SdkActivityLauncher is upstreamed
+ val fullSources = sources +
+ if (includeLibraryStubs) syntheticUiLibraryStubs else emptyList()
return compile(
tempDir,
TestCompilationArguments(
- sources = sources,
+ sources = fullSources,
classpath = extraClasspath,
symbolProcessorProviders = symbolProcessorProviders,
processorOptions = processorOptions,
diff --git a/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/LibraryStubs.kt b/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/LibraryStubs.kt
new file mode 100644
index 0000000..95116e5
--- /dev/null
+++ b/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/LibraryStubs.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2023 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.privacysandbox.tools.testing
+
+import androidx.room.compiler.processing.util.Source
+
+val syntheticUiLibraryStubs = listOf(
+ Source.kotlin(
+ "androidx/privacysandbox/ui/core/SandboxedUiAdapter.kt", """
+ |package androidx.privacysandbox.ui.core
+ |
+ |import android.os.IBinder
+ |
+ |interface SdkActivityLauncher {
+ | suspend fun launchSdkActivity(sdkActivityHandlerToken: IBinder): Boolean
+ |}
+ |""".trimMargin()
+ ),
+ Source.kotlin(
+ "androidx/privacysandbox/ui/client/SdkActivityLaunchers.kt", """
+ |@file:JvmName("SdkActivityLaunchers")
+ |
+ |package androidx.privacysandbox.ui.client
+ |
+ |import android.os.Bundle
+ |import androidx.privacysandbox.ui.core.SdkActivityLauncher
+ |
+ |fun SdkActivityLauncher.toLauncherInfo(): Bundle {
+ | TODO("Stub!")
+ |}
+ |""".trimMargin()
+ ),
+ Source.kotlin(
+ "androidx/privacysandbox/ui/provider/SdkActivityLauncherFactory.kt", """
+ |package androidx.privacysandbox.ui.provider
+ |
+ |import android.os.Bundle
+ |import androidx.privacysandbox.ui.core.SdkActivityLauncher
+ |
+ |object SdkActivityLauncherFactory {
+ |
+ | @JvmStatic
+ | @Suppress("UNUSED_PARAMETER")
+ | fun fromLauncherInfo(launcherInfo: Bundle): SdkActivityLauncher {
+ | TODO("Stub!")
+ | }
+ |}""".trimMargin()
+ ),
+ Source.kotlin(
+ "androidx/core/os/BundleCompat.kt", """
+ |package androidx.core.os
+ |
+ |import android.os.IBinder
+ |import android.os.Bundle
+ |
+ |object BundleCompat {
+ | @Suppress("UNUSED_PARAMETER")
+ | fun getBinder(bundle: Bundle, key: String?): IBinder? {
+ | TODO("Stub!")
+ | }
+ |}
+ |""".trimMargin()
+ ),
+)
diff --git a/settings.gradle b/settings.gradle
index 0f11a3a..063496e 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1012,11 +1012,13 @@
includeProject(":wear:compose:compose-material3-benchmark", "wear/compose/compose-material3/benchmark", [BuildType.COMPOSE])
includeProject(":wear:compose:compose-material-core", [BuildType.COMPOSE])
includeProject(":wear:compose:compose-material-samples", "wear/compose/compose-material/samples", [BuildType.COMPOSE])
+includeProject(":wear:compose:compose-material3-integration-tests", "wear/compose/compose-material3/integration-tests", [BuildType.COMPOSE])
includeProject(":wear:compose:compose-material3-samples", "wear/compose/compose-material3/samples", [BuildType.COMPOSE])
includeProject(":wear:compose:compose-navigation", [BuildType.COMPOSE])
includeProject(":wear:compose:compose-navigation-samples", "wear/compose/compose-navigation/samples", [BuildType.COMPOSE])
includeProject(":wear:compose:compose-ui-tooling", [BuildType.COMPOSE])
includeProject(":wear:compose:integration-tests:demos", [BuildType.COMPOSE])
+includeProject(":wear:compose:integration-tests:demos:common", [BuildType.COMPOSE])
includeProject(":wear:compose:integration-tests:macrobenchmark", [BuildType.COMPOSE])
includeProject(":wear:compose:integration-tests:macrobenchmark-target", [BuildType.COMPOSE])
includeProject(":wear:compose:integration-tests:navigation", [BuildType.COMPOSE])
diff --git a/wear/compose/compose-material3/integration-tests/build.gradle b/wear/compose/compose-material3/integration-tests/build.gradle
new file mode 100644
index 0000000..b139b83
--- /dev/null
+++ b/wear/compose/compose-material3/integration-tests/build.gradle
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2023 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.Publish
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("AndroidXComposePlugin")
+ id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+ implementation(libs.kotlinStdlib)
+
+ implementation(project(":compose:animation:animation"))
+ implementation(project(":compose:foundation:foundation"))
+ implementation(project(":compose:foundation:foundation-layout"))
+ implementation(project(":compose:runtime:runtime"))
+ implementation(project(":compose:ui:ui"))
+ implementation(project(":compose:ui:ui-text"))
+
+ implementation(project(':wear:compose:compose-foundation'))
+ implementation(project(':wear:compose:compose-material3'))
+ implementation(project(':wear:compose:integration-tests:demos:common'))
+}
+
+androidx {
+ name = "AndroidX Wear Compose Material3 Components Demos"
+ publish = Publish.NONE
+ inceptionYear = "2023"
+ description = "Contains the demo code for the AndroidX Wear Compose Material 3 components."
+}
+
+android {
+ defaultConfig {
+ minSdkVersion 25
+ }
+ namespace "androidx.wear.compose.material3.demos"
+}
\ No newline at end of file
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
new file mode 100644
index 0000000..6492902
--- /dev/null
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2023 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.wear.compose.material3.demos
+
+import androidx.wear.compose.integration.demos.common.DemoCategory
+
+val WearMaterial3Demos = DemoCategory(
+ "Material3",
+ listOf(
+ DemoCategory(
+ "ScrollAway",
+ listOf(
+ // Add Material 3 demos here
+ )
+ ),
+ )
+)
\ No newline at end of file
diff --git a/wear/compose/compose-material3/src/commonMain/kotlin/androidx/wear/compose/material3/Button.kt b/wear/compose/compose-material3/src/commonMain/kotlin/androidx/wear/compose/material3/Button.kt
index 7503da4..4523dc5 100644
--- a/wear/compose/compose-material3/src/commonMain/kotlin/androidx/wear/compose/material3/Button.kt
+++ b/wear/compose/compose-material3/src/commonMain/kotlin/androidx/wear/compose/material3/Button.kt
@@ -803,8 +803,8 @@
fun outlinedButtonBorder(
enabled: Boolean,
borderColor: Color = MaterialTheme.colorScheme.outline,
- disabledBorderColor: Color = MaterialTheme.colorScheme.onSurface.copy(
- alpha = DisabledBorderAndContainerAlpha
+ disabledBorderColor: Color = MaterialTheme.colorScheme.onSurface.toDisabledColor(
+ disabledAlpha = DisabledBorderAndContainerAlpha
),
borderWidth: Dp = 1.dp
): BorderStroke {
@@ -853,18 +853,13 @@
contentColor: Color = contentColorFor(containerColor),
secondaryContentColor: Color = contentColor,
iconColor: Color = contentColor,
- disabledContainerColor: Color = MaterialTheme.colorScheme.onSurface.copy(
- alpha = DisabledBorderAndContainerAlpha
+ disabledContainerColor: Color = MaterialTheme.colorScheme.onSurface.toDisabledColor(
+ disabledAlpha = DisabledBorderAndContainerAlpha
),
- disabledContentColor: Color = MaterialTheme.colorScheme.onSurface.copy(
- alpha = ContentAlpha.disabled
- ),
- disabledSecondaryContentColor: Color = MaterialTheme.colorScheme.onSurface.copy(
- alpha = ContentAlpha.disabled
- ),
- disabledIconColor: Color = MaterialTheme.colorScheme.onSurface.copy(
- alpha = ContentAlpha.disabled
- ),
+ disabledContentColor: Color = MaterialTheme.colorScheme.onSurface.toDisabledColor(),
+ disabledSecondaryContentColor: Color =
+ MaterialTheme.colorScheme.onSurface.toDisabledColor(),
+ disabledIconColor: Color = MaterialTheme.colorScheme.onSurface.toDisabledColor()
): ButtonColors = ButtonColors(
containerColor = containerColor,
contentColor = contentColor,
diff --git a/wear/compose/compose-material3/src/commonMain/kotlin/androidx/wear/compose/material3/ColorScheme.kt b/wear/compose/compose-material3/src/commonMain/kotlin/androidx/wear/compose/material3/ColorScheme.kt
index 1216db9..1ee0789 100644
--- a/wear/compose/compose-material3/src/commonMain/kotlin/androidx/wear/compose/material3/ColorScheme.kt
+++ b/wear/compose/compose-material3/src/commonMain/kotlin/androidx/wear/compose/material3/ColorScheme.kt
@@ -454,3 +454,11 @@
}
internal val LocalColors = staticCompositionLocalOf<ColorScheme> { ColorScheme() }
+
+/**
+ * Convert given color to disabled color.
+ * @param disabledAlpha Alpha used to represent disabled colors.
+ */
+@Composable
+internal fun Color.toDisabledColor(disabledAlpha: Float = ContentAlpha.disabled) =
+ this.copy(alpha = this.alpha * disabledAlpha)
diff --git a/wear/compose/compose-material3/src/commonMain/kotlin/androidx/wear/compose/material3/IconButton.kt b/wear/compose/compose-material3/src/commonMain/kotlin/androidx/wear/compose/material3/IconButton.kt
index d4b2a43..75dd372 100644
--- a/wear/compose/compose-material3/src/commonMain/kotlin/androidx/wear/compose/material3/IconButton.kt
+++ b/wear/compose/compose-material3/src/commonMain/kotlin/androidx/wear/compose/material3/IconButton.kt
@@ -332,12 +332,10 @@
fun iconButtonColors(
containerColor: Color = Color.Transparent,
contentColor: Color = MaterialTheme.colorScheme.onBackground,
- disabledContainerColor: Color = MaterialTheme.colorScheme.onSurface.copy(
- alpha = DisabledBorderAndContainerAlpha
+ disabledContainerColor: Color = MaterialTheme.colorScheme.onSurface.toDisabledColor(
+ disabledAlpha = DisabledBorderAndContainerAlpha
),
- disabledContentColor: Color = MaterialTheme.colorScheme.onSurface.copy(
- alpha = ContentAlpha.disabled
- )
+ disabledContentColor: Color = MaterialTheme.colorScheme.onSurface.toDisabledColor()
): IconButtonColors = IconButtonColors(
containerColor = containerColor,
contentColor = contentColor,
@@ -359,8 +357,8 @@
fun outlinedIconButtonBorder(
enabled: Boolean,
borderColor: Color = MaterialTheme.colorScheme.outline,
- disabledBorderColor: Color = MaterialTheme.colorScheme.onSurface.copy(
- alpha = DisabledBorderAndContainerAlpha
+ disabledBorderColor: Color = MaterialTheme.colorScheme.onSurface.toDisabledColor(
+ disabledAlpha = DisabledBorderAndContainerAlpha
),
borderWidth: Dp = 1.dp
): BorderStroke {
diff --git a/wear/compose/integration-tests/demos/build.gradle b/wear/compose/integration-tests/demos/build.gradle
index 478b99e..0d46a1e 100644
--- a/wear/compose/integration-tests/demos/build.gradle
+++ b/wear/compose/integration-tests/demos/build.gradle
@@ -63,6 +63,8 @@
implementation(project(":wear:compose:compose-foundation-samples"))
implementation(project(':wear:compose:compose-material'))
implementation(project(":wear:compose:compose-material-samples"))
+ implementation(project(':wear:compose:integration-tests:demos:common'))
+ implementation(project(":wear:compose:compose-material3-integration-tests"))
androidTestImplementation(project(":compose:ui:ui-test-junit4"))
androidTestImplementation(project(":emoji2:emoji2"))
diff --git a/wear/compose/integration-tests/demos/common/build.gradle b/wear/compose/integration-tests/demos/common/build.gradle
new file mode 100644
index 0000000..9e6cd01e
--- /dev/null
+++ b/wear/compose/integration-tests/demos/common/build.gradle
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2023 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.
+ */
+
+plugins {
+ id("AndroidXPlugin")
+ id("AndroidXComposePlugin")
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+ implementation(libs.kotlinStdlib)
+
+ api("androidx.activity:activity:1.2.0")
+ implementation(project(':wear:compose:compose-material'))
+
+}
+
+android {
+ defaultConfig {
+ minSdkVersion 25
+ }
+ namespace "androidx.wear.compose.integration.demos.common"
+}
diff --git a/wear/compose/integration-tests/demos/common/src/main/java/androidx/wear/compose/integration/demos/common/Demo.kt b/wear/compose/integration-tests/demos/common/src/main/java/androidx/wear/compose/integration/demos/common/Demo.kt
new file mode 100644
index 0000000..52a987a
--- /dev/null
+++ b/wear/compose/integration-tests/demos/common/src/main/java/androidx/wear/compose/integration/demos/common/Demo.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2023 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.wear.compose.integration.demos.common
+
+import android.app.Activity
+import androidx.activity.ComponentActivity
+import androidx.compose.runtime.Composable
+import androidx.wear.compose.material.SwipeToDismissBoxState
+import kotlin.reflect.KClass
+
+/**
+ * Generic demo with a [title] that will be displayed in the list of demos.
+ */
+sealed class Demo(val title: String, val description: String? = null) {
+ override fun toString() = title
+}
+
+/**
+ * Demo that launches an [Activity] when selected.
+ *
+ * This should only be used for demos that need to customize the activity, the large majority of
+ * demos should just use [ComposableDemo] instead.
+ *
+ * @property activityClass the KClass (Foo::class) of the activity that will be launched when
+ * this demo is selected.
+ */
+class ActivityDemo<T : ComponentActivity>(title: String, val activityClass: KClass<T>) : Demo(title)
+
+/**
+ * A category of [Demo]s, that will display a list of [demos] when selected.
+ */
+class DemoCategory(
+ title: String,
+ val demos: List<Demo>
+) : Demo(title)
+
+/**
+ * Parameters which are used by [Demo] screens.
+ */
+class DemoParameters(
+ val navigateBack: () -> Unit,
+ val swipeToDismissBoxState: SwipeToDismissBoxState
+)
+
+/**
+ * Demo that displays [Composable] [content] when selected,
+ * with a method to navigate back to the parent.
+ */
+class ComposableDemo(
+ title: String,
+ description: String? = null,
+ val content: @Composable (params: DemoParameters) -> Unit,
+) : Demo(title, description)
diff --git a/wear/compose/integration-tests/demos/src/androidTest/java/androidx/wear/compose/integration/demos/test/DemoTest.kt b/wear/compose/integration-tests/demos/src/androidTest/java/androidx/wear/compose/integration/demos/test/DemoTest.kt
index 9fe82bca..646addf 100644
--- a/wear/compose/integration-tests/demos/src/androidTest/java/androidx/wear/compose/integration/demos/test/DemoTest.kt
+++ b/wear/compose/integration-tests/demos/src/androidTest/java/androidx/wear/compose/integration/demos/test/DemoTest.kt
@@ -28,10 +28,10 @@
import androidx.test.espresso.Espresso
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
-import androidx.wear.compose.integration.demos.Demo
import androidx.wear.compose.integration.demos.DemoActivity
-import androidx.wear.compose.integration.demos.DemoCategory
import androidx.wear.compose.integration.demos.WearComposeDemos
+import androidx.wear.compose.integration.demos.common.Demo
+import androidx.wear.compose.integration.demos.common.DemoCategory
import com.google.common.truth.Truth.assertThat
import org.junit.Ignore
import org.junit.Rule
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoActivity.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoActivity.kt
index 361fb0e..7de734f 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoActivity.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoActivity.kt
@@ -36,6 +36,9 @@
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalView
import androidx.core.app.ActivityCompat
+import androidx.wear.compose.integration.demos.common.ActivityDemo
+import androidx.wear.compose.integration.demos.common.Demo
+import androidx.wear.compose.integration.demos.common.DemoCategory
import androidx.wear.compose.material.MaterialTheme
/**
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt
index 108b9ad..7e6aa69 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt
@@ -32,7 +32,6 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
-import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.focus.FocusRequester
@@ -49,6 +48,11 @@
import androidx.wear.compose.foundation.lazy.ScalingLazyListState
import androidx.wear.compose.foundation.lazy.ScalingParams
import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
+import androidx.wear.compose.integration.demos.common.ActivityDemo
+import androidx.wear.compose.integration.demos.common.ComposableDemo
+import androidx.wear.compose.integration.demos.common.Demo
+import androidx.wear.compose.integration.demos.common.DemoCategory
+import androidx.wear.compose.integration.demos.common.DemoParameters
import androidx.wear.compose.material.Chip
import androidx.wear.compose.material.ChipDefaults
import androidx.wear.compose.material.ListHeader
@@ -154,13 +158,13 @@
modifier = Modifier.fillMaxWidth()
)
}
- if (demo.description != null) {
+ demo.description?.let { description ->
item {
CompositionLocalProvider(
LocalTextStyle provides MaterialTheme.typography.caption3
) {
Text(
- text = demo.description,
+ text = description,
modifier = Modifier.fillMaxWidth().align(Alignment.Center),
textAlign = TextAlign.Center
)
@@ -187,7 +191,6 @@
internal data class TimestampedDelta(val time: Long, val delta: Float)
-@OptIn(ExperimentalComposeUiApi::class)
@Suppress("ComposableModifierFactory")
@Composable
fun Modifier.rsbScroll(
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoComponents.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoComponents.kt
index ea5174c..4876340 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoComponents.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoComponents.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 The Android Open Source Project
+ * Copyright 2023 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.
@@ -16,8 +16,6 @@
package androidx.wear.compose.integration.demos
-import android.app.Activity
-import androidx.activity.ComponentActivity
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -43,53 +41,7 @@
import androidx.wear.compose.material.Icon
import androidx.wear.compose.material.LocalContentAlpha
import androidx.wear.compose.material.MaterialTheme
-import androidx.wear.compose.material.SwipeToDismissBoxState
import androidx.wear.compose.material.Text
-import kotlin.reflect.KClass
-
-/**
- * Generic demo with a [title] that will be displayed in the list of demos.
- */
-sealed class Demo(val title: String, val description: String? = null) {
- override fun toString() = title
-}
-
-/**
- * Demo that launches an [Activity] when selected.
- *
- * This should only be used for demos that need to customize the activity, the large majority of
- * demos should just use [ComposableDemo] instead.
- *
- * @property activityClass the KClass (Foo::class) of the activity that will be launched when
- * this demo is selected.
- */
-class ActivityDemo<T : ComponentActivity>(title: String, val activityClass: KClass<T>) : Demo(title)
-
-/**
- * A category of [Demo]s, that will display a list of [demos] when selected.
- */
-class DemoCategory(
- title: String,
- val demos: List<Demo>
-) : Demo(title)
-
-/**
- * Parameters which are used by [Demo] screens.
- */
-class DemoParameters(
- val navigateBack: () -> Unit,
- val swipeToDismissBoxState: SwipeToDismissBoxState
-)
-
-/**
- * Demo that displays [Composable] [content] when selected,
- * with a method to navigate back to the parent.
- */
-class ComposableDemo(
- title: String,
- description: String? = null,
- val content: @Composable (params: DemoParameters) -> Unit,
-) : Demo(title, description)
/**
* A simple [Icon] with default size
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/Demos.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/Demos.kt
index 6fa889e..8efc582 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/Demos.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/Demos.kt
@@ -23,7 +23,10 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
+import androidx.wear.compose.integration.demos.common.ComposableDemo
+import androidx.wear.compose.integration.demos.common.DemoCategory
import androidx.wear.compose.material.Text
+import androidx.wear.compose.material3.demos.WearMaterial3Demos
val Info = DemoCategory(
"App Info",
@@ -56,6 +59,7 @@
listOf(
WearFoundationDemos,
WearMaterialDemos,
+ WearMaterial3Demos,
Info
)
)
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/FoundationDemos.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/FoundationDemos.kt
index c54950a..730a272 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/FoundationDemos.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/FoundationDemos.kt
@@ -34,6 +34,8 @@
import androidx.wear.compose.foundation.samples.SimpleScalingLazyColumnWithContentPadding
import androidx.wear.compose.foundation.samples.SimpleScalingLazyColumnWithSnap
import androidx.wear.compose.foundation.samples.SwipeToRevealWithExpandables
+import androidx.wear.compose.integration.demos.common.ComposableDemo
+import androidx.wear.compose.integration.demos.common.DemoCategory
val WearFoundationDemos = DemoCategory(
"Foundation",
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt
index 205da0d..82d84b9 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt
@@ -27,6 +27,8 @@
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
+import androidx.wear.compose.integration.demos.common.ComposableDemo
+import androidx.wear.compose.integration.demos.common.DemoCategory
import androidx.wear.compose.material.samples.AlertDialogSample
import androidx.wear.compose.material.samples.AlertWithButtons
import androidx.wear.compose.material.samples.AlertWithChips
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ScrollingWithRotaryInputDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ScrollingWithRotaryInputDemo.kt
index 7153372..33c1b57 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ScrollingWithRotaryInputDemo.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ScrollingWithRotaryInputDemo.kt
@@ -26,6 +26,7 @@
import androidx.compose.ui.samples.PreRotaryEventSample
import androidx.compose.ui.samples.RotaryEventSample
import androidx.compose.ui.unit.dp
+import androidx.wear.compose.integration.demos.common.ComposableDemo
import androidx.wear.compose.material.Text
internal val RotaryInputDemos = listOf(
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/Int32Nodes.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/Int32Nodes.java
index 5893701..1d69a87 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/Int32Nodes.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/Int32Nodes.java
@@ -26,8 +26,8 @@
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32;
import androidx.wear.protolayout.expression.DynamicDataKey;
import androidx.wear.protolayout.expression.PlatformHealthSources;
-import androidx.wear.protolayout.expression.pipeline.sensor.SensorGateway;
import androidx.wear.protolayout.expression.proto.AnimationParameterProto.AnimationSpec;
+import androidx.wear.protolayout.expression.proto.DynamicDataProto.DynamicDataValue;
import androidx.wear.protolayout.expression.proto.DynamicProto.AnimatableFixedInt32;
import androidx.wear.protolayout.expression.proto.DynamicProto.ArithmeticInt32Op;
import androidx.wear.protolayout.expression.proto.DynamicProto.DurationPartType;
@@ -39,6 +39,7 @@
import androidx.wear.protolayout.expression.proto.FixedProto.FixedInt32;
import java.time.Duration;
+import java.util.function.Function;
/** Dynamic data nodes which yield integers. */
class Int32Nodes {
@@ -83,7 +84,7 @@
super(
stateStore,
getDataKey(protoNode.getSourceType()),
- se -> se.getInt32Val().getValue(),
+ getStateExtractor(protoNode.getSourceType()),
downstream);
}
@@ -100,6 +101,21 @@
throw new IllegalArgumentException(
"Unknown DynamicInt32 platform source type: " + type);
}
+
+ @NonNull
+ private static Function<DynamicDataValue, Integer> getStateExtractor(
+ PlatformInt32SourceType type) {
+ if (type == PlatformInt32SourceType.PLATFORM_INT32_SOURCE_TYPE_CURRENT_HEART_RATE) {
+ return se -> (int) se.getFloatVal().getValue();
+ }
+
+ if (type == PlatformInt32SourceType.PLATFORM_INT32_SOURCE_TYPE_DAILY_STEP_COUNT) {
+ return se -> se.getInt32Val().getValue();
+ }
+
+ throw new IllegalArgumentException(
+ "Unknown DynamicInt32 platform source type: " + type);
+ }
}
/** Dynamic integer node that supports arithmetic operations. */
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/SensorGatewaySingleDataProvider.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/SensorGatewaySingleDataProvider.java
index ee1671e..564bab1 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/SensorGatewaySingleDataProvider.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/SensorGatewaySingleDataProvider.java
@@ -22,11 +22,13 @@
import androidx.annotation.RestrictTo.Scope;
import androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue;
import androidx.wear.protolayout.expression.PlatformDataKey;
+import androidx.wear.protolayout.expression.PlatformHealthSources;
import androidx.wear.protolayout.expression.pipeline.sensor.SensorGateway;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.Executor;
+import java.util.function.Function;
/** This provider provides sensor data as state value. */
@RestrictTo(Scope.LIBRARY_GROUP_PREFIX)
@@ -35,12 +37,20 @@
@NonNull final PlatformDataKey<?> mSupportedKey;
@Nullable private SensorGateway.Consumer mSensorGatewayConsumer = null;
+ @NonNull Function<Double, DynamicDataValue> mConvertFunc;
+
public SensorGatewaySingleDataProvider(
@NonNull SensorGateway sensorGateway,
@NonNull PlatformDataKey<?> supportedKey
) {
this.mSensorGateway = sensorGateway;
this.mSupportedKey = supportedKey;
+
+ if (mSupportedKey.equals(PlatformHealthSources.HEART_RATE_BPM)) {
+ mConvertFunc = value -> DynamicDataValue.fromFloat(value.floatValue());
+ } else { // mSupportedKey.equals(PlatformHealthSources.DAILY_STEPS)
+ mConvertFunc = value -> DynamicDataValue.fromInt(value.intValue());
+ }
}
@Override
@@ -52,7 +62,7 @@
@Override
public void onData(double value) {
executor.execute(() -> callback.onData(
- Map.of(mSupportedKey, DynamicDataValue.fromFloat((float) value)))
+ Map.of(mSupportedKey, mConvertFunc.apply(value)))
);
}
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/StateSourceNode.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/StateSourceNode.java
index 1cd0634..ef45da0 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/StateSourceNode.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/StateSourceNode.java
@@ -29,6 +29,7 @@
class StateSourceNode<T>
implements DynamicDataSourceNode<T>,
DynamicTypeValueReceiverWithPreUpdate<DynamicDataValue> {
+ @NonNull private static final String RESERVED_NAMESPACE = "protolayout";
private final StateStore mStateStore;
private final DynamicDataKey<?> mKey;
private final Function<DynamicDataValue, T> mStateExtractor;
@@ -92,6 +93,11 @@
if (namespace.isEmpty()) {
return new AppDataKey<T>(key);
}
+
+ if (RESERVED_NAMESPACE.equalsIgnoreCase(namespace)) {
+ return new PlatformDataKey<T>(key);
+ }
+
return new PlatformDataKey<T>(namespace, key);
}
}
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/FakeSensorGateway.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/FakeSensorGateway.java
new file mode 100644
index 0000000..c661e3f
--- /dev/null
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/FakeSensorGateway.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2023 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.wear.protolayout.expression.pipeline;
+
+import androidx.annotation.NonNull;
+import androidx.wear.protolayout.expression.PlatformDataKey;
+import androidx.wear.protolayout.expression.pipeline.sensor.SensorGateway;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+class FakeSensorGateway implements SensorGateway {
+ final List<Consumer> registeredConsumers = new ArrayList<>();
+
+ @Override
+ public void enableUpdates() {
+ }
+
+ @Override
+ public void disableUpdates() {
+ }
+
+ @Override
+ public void registerSensorGatewayConsumer(
+ @NonNull PlatformDataKey<?> key, @NonNull Consumer consumer) {
+ registeredConsumers.add(consumer);
+ }
+
+ @Override
+ public void registerSensorGatewayConsumer(
+ @NonNull PlatformDataKey<?> key,
+ @NonNull Executor executor,
+ @NonNull Consumer consumer) {
+ registerSensorGatewayConsumer(key, consumer);
+ }
+
+ @Override
+ public void unregisterSensorGatewayConsumer(
+ @NonNull PlatformDataKey<?> key, @NonNull Consumer consumer) {
+ registeredConsumers.remove(consumer);
+ }
+}
\ No newline at end of file
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/FloatNodeTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/FloatNodeTest.java
index a1acb98..4f2b87a 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/FloatNodeTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/FloatNodeTest.java
@@ -24,9 +24,11 @@
import android.os.Looper;
+import androidx.collection.ArrayMap;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat;
import androidx.wear.protolayout.expression.AppDataKey;
+import androidx.wear.protolayout.expression.PlatformHealthSources;
import androidx.wear.protolayout.expression.pipeline.FloatNodes.AnimatableFixedFloatNode;
import androidx.wear.protolayout.expression.pipeline.FloatNodes.ArithmeticFloatNode;
import androidx.wear.protolayout.expression.pipeline.FloatNodes.DynamicAnimatedFloatNode;
@@ -51,6 +53,7 @@
import org.junit.runner.RunWith;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
@RunWith(AndroidJUnit4.class)
@@ -127,6 +130,40 @@
}
@Test
+ public void stateFloatSource_canSubscribeToHeartRateUpdates() {
+ FakeSensorGateway fakeSensorGateway = new FakeSensorGateway();
+ StateStore stateStore = new StateStore(new ArrayMap<>());
+ stateStore.putAllPlatformProviders(
+ Collections.singletonMap(
+ PlatformHealthSources.HEART_RATE_BPM,
+ new SensorGatewaySingleDataProvider(
+ fakeSensorGateway, PlatformHealthSources.HEART_RATE_BPM)));
+ StateFloatSource dailyStepsSource =
+ StateFloatSource.newBuilder()
+ .setSourceKey(PlatformHealthSources.HEART_RATE_BPM.getKey())
+ .setSourceNamespace(PlatformHealthSources.HEART_RATE_BPM.getNamespace())
+ .build();
+ List<Float> results = new ArrayList<>();
+ StateFloatSourceNode dailyStepsSourceNode =
+ new StateFloatSourceNode(
+ stateStore,
+ dailyStepsSource,
+ new AddToListCallback<>(results));
+
+ dailyStepsSourceNode.preInit();
+ dailyStepsSourceNode.init();
+ assertThat(fakeSensorGateway.registeredConsumers).hasSize(1);
+
+ fakeSensorGateway.registeredConsumers.get(0).onData(70.0);
+ assertThat(results).hasSize(1);
+ assertThat(results).containsExactly(70.0f);
+
+ fakeSensorGateway.registeredConsumers.get(0).onData(80.0);
+ assertThat(results).hasSize(2);
+ assertThat(results).containsExactly(70.0f, 80.0f);
+ }
+
+ @Test
public void stateFloatSourceNode_noUpdatesAfterDestroy() {
List<Float> results = new ArrayList<>();
float oldValue = 6.5f;
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/Int32NodesTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/Int32NodesTest.java
index 2738f50..043eab7 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/Int32NodesTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/Int32NodesTest.java
@@ -25,12 +25,10 @@
import android.os.Looper;
-import androidx.annotation.NonNull;
import androidx.collection.ArrayMap;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.wear.protolayout.expression.AppDataKey;
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32;
-import androidx.wear.protolayout.expression.PlatformDataKey;
import androidx.wear.protolayout.expression.PlatformHealthSources;
import androidx.wear.protolayout.expression.pipeline.Int32Nodes.AnimatableFixedInt32Node;
import androidx.wear.protolayout.expression.pipeline.Int32Nodes.DynamicAnimatedInt32Node;
@@ -38,7 +36,6 @@
import androidx.wear.protolayout.expression.pipeline.Int32Nodes.GetDurationPartOpNode;
import androidx.wear.protolayout.expression.pipeline.Int32Nodes.LegacyPlatformInt32SourceNode;
import androidx.wear.protolayout.expression.pipeline.Int32Nodes.StateInt32SourceNode;
-import androidx.wear.protolayout.expression.pipeline.sensor.SensorGateway;
import androidx.wear.protolayout.expression.proto.AnimationParameterProto.AnimationSpec;
import androidx.wear.protolayout.expression.proto.DynamicProto.AnimatableFixedInt32;
import androidx.wear.protolayout.expression.proto.DynamicProto.DurationPartType;
@@ -230,6 +227,40 @@
}
@Test
+ public void stateInt32Source_canSubscribeToDailyStepsUpdates() {
+ FakeSensorGateway fakeSensorGateway = new FakeSensorGateway();
+ StateStore stateStore = new StateStore(new ArrayMap<>());
+ stateStore.putAllPlatformProviders(
+ Collections.singletonMap(
+ PlatformHealthSources.DAILY_STEPS,
+ new SensorGatewaySingleDataProvider(
+ fakeSensorGateway, PlatformHealthSources.DAILY_STEPS)));
+ StateInt32Source dailyStepsSource =
+ StateInt32Source.newBuilder()
+ .setSourceKey(PlatformHealthSources.DAILY_STEPS.getKey())
+ .setSourceNamespace(PlatformHealthSources.DAILY_STEPS.getNamespace())
+ .build();
+ List<Integer> results = new ArrayList<>();
+ StateInt32SourceNode dailyStepsSourceNode =
+ new StateInt32SourceNode(
+ stateStore,
+ dailyStepsSource,
+ new AddToListCallback<>(results));
+
+ dailyStepsSourceNode.preInit();
+ dailyStepsSourceNode.init();
+ assertThat(fakeSensorGateway.registeredConsumers).hasSize(1);
+
+ fakeSensorGateway.registeredConsumers.get(0).onData(70);
+ assertThat(results).hasSize(1);
+ assertThat(results).containsExactly(70);
+
+ fakeSensorGateway.registeredConsumers.get(0).onData(80);
+ assertThat(results).hasSize(2);
+ assertThat(results).containsExactly(70, 80);
+ }
+
+ @Test
public void animatableFixedInt32_animates() {
int startValue = 3;
int endValue = 33;
@@ -363,6 +394,76 @@
}
@Test
+ public void platformInt32Source_canSubscribeToHeartRateUpdates() {
+ FakeSensorGateway fakeSensorGateway = new FakeSensorGateway();
+ StateStore stateStore = new StateStore(new ArrayMap<>());
+ stateStore.putAllPlatformProviders(
+ Collections.singletonMap(
+ PlatformHealthSources.HEART_RATE_BPM,
+ new SensorGatewaySingleDataProvider(
+ fakeSensorGateway, PlatformHealthSources.HEART_RATE_BPM)));
+ PlatformInt32Source platformSource =
+ PlatformInt32Source.newBuilder()
+ .setSourceType(
+ PlatformInt32SourceType
+ .PLATFORM_INT32_SOURCE_TYPE_CURRENT_HEART_RATE)
+ .build();
+ List<Integer> results = new ArrayList<>();
+ LegacyPlatformInt32SourceNode platformSourceNode =
+ new LegacyPlatformInt32SourceNode(
+ stateStore,
+ platformSource,
+ new AddToListCallback<>(results));
+
+ platformSourceNode.preInit();
+ platformSourceNode.init();
+ assertThat(fakeSensorGateway.registeredConsumers).hasSize(1);
+
+ fakeSensorGateway.registeredConsumers.get(0).onData(70);
+ assertThat(results).hasSize(1);
+ assertThat(results).containsExactly(70);
+
+ fakeSensorGateway.registeredConsumers.get(0).onData(80);
+ assertThat(results).hasSize(2);
+ assertThat(results).containsExactly(70, 80);
+ }
+
+ @Test
+ public void platformInt32Source_canSubscribeToDailyStepsUpdates() {
+ FakeSensorGateway fakeSensorGateway = new FakeSensorGateway();
+ StateStore stateStore = new StateStore(new ArrayMap<>());
+ stateStore.putAllPlatformProviders(
+ Collections.singletonMap(
+ PlatformHealthSources.DAILY_STEPS,
+ new SensorGatewaySingleDataProvider(
+ fakeSensorGateway, PlatformHealthSources.DAILY_STEPS)));
+ PlatformInt32Source platformSource =
+ PlatformInt32Source.newBuilder()
+ .setSourceType(
+ PlatformInt32SourceType
+ .PLATFORM_INT32_SOURCE_TYPE_DAILY_STEP_COUNT)
+ .build();
+ List<Integer> results = new ArrayList<>();
+ LegacyPlatformInt32SourceNode platformSourceNode =
+ new LegacyPlatformInt32SourceNode(
+ stateStore,
+ platformSource,
+ new AddToListCallback<>(results));
+
+ platformSourceNode.preInit();
+ platformSourceNode.init();
+ assertThat(fakeSensorGateway.registeredConsumers).hasSize(1);
+
+ fakeSensorGateway.registeredConsumers.get(0).onData(70.0);
+ assertThat(results).hasSize(1);
+ assertThat(results).containsExactly(70);
+
+ fakeSensorGateway.registeredConsumers.get(0).onData(80.0);
+ assertThat(results).hasSize(2);
+ assertThat(results).containsExactly(70, 80);
+ }
+
+ @Test
public void platformInt32Source_propagatesInvalidatedSignal() {
FakeSensorGateway fakeSensorGateway = new FakeSensorGateway();
StateStore stateStore = new StateStore(new ArrayMap<>());
@@ -392,36 +493,4 @@
fakeSensorGateway.registeredConsumers.get(0).onInvalidated();
verify(mMockValueReceiver).onInvalidated();
}
-
- private static class FakeSensorGateway implements SensorGateway {
- final List<Consumer> registeredConsumers = new ArrayList<>();
-
- @Override
- public void enableUpdates() {
- }
-
- @Override
- public void disableUpdates() {
- }
-
- @Override
- public void registerSensorGatewayConsumer(
- @NonNull PlatformDataKey<?> key, @NonNull Consumer consumer) {
- registeredConsumers.add(consumer);
- }
-
- @Override
- public void registerSensorGatewayConsumer(
- @NonNull PlatformDataKey<?> key,
- @NonNull Executor executor,
- @NonNull Consumer consumer) {
- registerSensorGatewayConsumer(key, consumer);
- }
-
- @Override
- public void unregisterSensorGatewayConsumer(
- @NonNull PlatformDataKey<?> key, @NonNull Consumer consumer) {
- registeredConsumers.remove(consumer);
- }
- }
}
diff --git a/wear/protolayout/protolayout-expression/api/current.txt b/wear/protolayout/protolayout-expression/api/current.txt
index 7f2e9cb..1ca79e0 100644
--- a/wear/protolayout/protolayout-expression/api/current.txt
+++ b/wear/protolayout/protolayout-expression/api/current.txt
@@ -104,10 +104,13 @@
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool eq(androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool from(androidx.wear.protolayout.expression.DynamicDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool!>);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool fromByteArray(byte[]);
+ method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool fromByteArray(byte[], int, int);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool ne(androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool negate();
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool or(androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool);
method public default byte[] toDynamicBoolByteArray();
+ method public default int toDynamicBoolByteArray(byte[]);
+ method public default int toDynamicBoolByteArray(byte[], int, int);
}
public static interface DynamicBuilders.DynamicColor extends androidx.wear.protolayout.expression.DynamicBuilders.DynamicType {
@@ -120,18 +123,24 @@
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor constant(@ColorInt int);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor from(androidx.wear.protolayout.expression.DynamicDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor!>);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor fromByteArray(byte[]);
+ method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor fromByteArray(byte[], int, int);
method public static androidx.wear.protolayout.expression.ConditionScopes.ConditionScope<androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor!,java.lang.Integer!> onCondition(androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool);
method public default byte[] toDynamicColorByteArray();
+ method public default int toDynamicColorByteArray(byte[]);
+ method public default int toDynamicColorByteArray(byte[], int, int);
}
public static interface DynamicBuilders.DynamicDuration extends androidx.wear.protolayout.expression.DynamicBuilders.DynamicType {
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicDuration fromByteArray(byte[]);
+ method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicDuration fromByteArray(byte[], int, int);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 getHoursPart();
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 getIntDaysPart();
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 getMinutesPart();
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 getSecondsPart();
method public static androidx.wear.protolayout.expression.ConditionScopes.ConditionScope<androidx.wear.protolayout.expression.DynamicBuilders.DynamicDuration!,java.time.Duration!> onCondition(androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool);
method public default byte[] toDynamicDurationByteArray();
+ method public default int toDynamicDurationByteArray(byte[]);
+ method public default int toDynamicDurationByteArray(byte[], int, int);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 toIntDays();
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 toIntHours();
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 toIntMinutes();
@@ -157,6 +166,7 @@
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicString format(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat.FloatFormatter);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat from(androidx.wear.protolayout.expression.DynamicDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat!>);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat fromByteArray(byte[]);
+ method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat fromByteArray(byte[], int, int);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool gt(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool gt(float);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool gte(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat);
@@ -181,6 +191,8 @@
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat times(float);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat times(androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32);
method public default byte[] toDynamicFloatByteArray();
+ method public default int toDynamicFloatByteArray(byte[]);
+ method public default int toDynamicFloatByteArray(byte[], int, int);
}
public static class DynamicBuilders.DynamicFloat.FloatFormatter {
@@ -202,9 +214,12 @@
public static interface DynamicBuilders.DynamicInstant extends androidx.wear.protolayout.expression.DynamicBuilders.DynamicType {
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicDuration durationUntil(androidx.wear.protolayout.expression.DynamicBuilders.DynamicInstant);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicInstant fromByteArray(byte[]);
+ method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicInstant fromByteArray(byte[], int, int);
method public static androidx.wear.protolayout.expression.ConditionScopes.ConditionScope<androidx.wear.protolayout.expression.DynamicBuilders.DynamicInstant!,java.time.Instant!> onCondition(androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicInstant platformTimeWithSecondsPrecision();
method public default byte[] toDynamicInstantByteArray();
+ method public default int toDynamicInstantByteArray(byte[]);
+ method public default int toDynamicInstantByteArray(byte[], int, int);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicInstant withSecondsPrecision(java.time.Instant);
}
@@ -227,6 +242,7 @@
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicString format(androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32.IntFormatter);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 from(androidx.wear.protolayout.expression.DynamicDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32!>);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 fromByteArray(byte[]);
+ method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 fromByteArray(byte[], int, int);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool gt(androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool gt(int);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool gte(androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32);
@@ -255,6 +271,8 @@
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 times(int);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat times(float);
method public default byte[] toDynamicInt32ByteArray();
+ method public default int toDynamicInt32ByteArray(byte[]);
+ method public default int toDynamicInt32ByteArray(byte[], int, int);
}
public static class DynamicBuilders.DynamicInt32.IntFormatter {
@@ -274,8 +292,11 @@
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicString constant(String);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicString from(androidx.wear.protolayout.expression.DynamicDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicString!>);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicString fromByteArray(byte[]);
+ method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicString fromByteArray(byte[], int, int);
method public static androidx.wear.protolayout.expression.ConditionScopes.ConditionScope<androidx.wear.protolayout.expression.DynamicBuilders.DynamicString!,java.lang.String!> onCondition(androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool);
method public default byte[] toDynamicStringByteArray();
+ method public default int toDynamicStringByteArray(byte[]);
+ method public default int toDynamicStringByteArray(byte[], int, int);
}
public static interface DynamicBuilders.DynamicType {
@@ -286,10 +307,15 @@
public static interface DynamicDataBuilders.DynamicDataValue {
method public static androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue fromBool(boolean);
+ method public static androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue fromByteArray(byte[]);
+ method public static androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue fromByteArray(byte[], int, int);
method public static androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue fromColor(@ColorInt int);
method public static androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue fromFloat(float);
method public static androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue fromInt(int);
method public static androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue fromString(String);
+ method public default byte[] toDynamicDataValueByteArray();
+ method public default int toDynamicDataValueByteArray(byte[]);
+ method public default int toDynamicDataValueByteArray(byte[], int, int);
}
public abstract class DynamicDataKey<T extends androidx.wear.protolayout.expression.DynamicBuilders.DynamicType> {
diff --git a/wear/protolayout/protolayout-expression/api/public_plus_experimental_current.txt b/wear/protolayout/protolayout-expression/api/public_plus_experimental_current.txt
index ace5549..ba0d81d 100644
--- a/wear/protolayout/protolayout-expression/api/public_plus_experimental_current.txt
+++ b/wear/protolayout/protolayout-expression/api/public_plus_experimental_current.txt
@@ -104,10 +104,13 @@
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool eq(androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool from(androidx.wear.protolayout.expression.DynamicDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool!>);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool fromByteArray(byte[]);
+ method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool fromByteArray(byte[], int, int);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool ne(androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool negate();
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool or(androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool);
method public default byte[] toDynamicBoolByteArray();
+ method public default int toDynamicBoolByteArray(byte[]);
+ method public default int toDynamicBoolByteArray(byte[], int, int);
}
public static interface DynamicBuilders.DynamicColor extends androidx.wear.protolayout.expression.DynamicBuilders.DynamicType {
@@ -120,18 +123,24 @@
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor constant(@ColorInt int);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor from(androidx.wear.protolayout.expression.DynamicDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor!>);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor fromByteArray(byte[]);
+ method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor fromByteArray(byte[], int, int);
method public static androidx.wear.protolayout.expression.ConditionScopes.ConditionScope<androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor!,java.lang.Integer!> onCondition(androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool);
method public default byte[] toDynamicColorByteArray();
+ method public default int toDynamicColorByteArray(byte[]);
+ method public default int toDynamicColorByteArray(byte[], int, int);
}
public static interface DynamicBuilders.DynamicDuration extends androidx.wear.protolayout.expression.DynamicBuilders.DynamicType {
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicDuration fromByteArray(byte[]);
+ method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicDuration fromByteArray(byte[], int, int);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 getHoursPart();
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 getIntDaysPart();
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 getMinutesPart();
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 getSecondsPart();
method public static androidx.wear.protolayout.expression.ConditionScopes.ConditionScope<androidx.wear.protolayout.expression.DynamicBuilders.DynamicDuration!,java.time.Duration!> onCondition(androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool);
method public default byte[] toDynamicDurationByteArray();
+ method public default int toDynamicDurationByteArray(byte[]);
+ method public default int toDynamicDurationByteArray(byte[], int, int);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 toIntDays();
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 toIntHours();
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 toIntMinutes();
@@ -157,6 +166,7 @@
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicString format(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat.FloatFormatter);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat from(androidx.wear.protolayout.expression.DynamicDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat!>);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat fromByteArray(byte[]);
+ method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat fromByteArray(byte[], int, int);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool gt(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool gt(float);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool gte(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat);
@@ -181,6 +191,8 @@
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat times(float);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat times(androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32);
method public default byte[] toDynamicFloatByteArray();
+ method public default int toDynamicFloatByteArray(byte[]);
+ method public default int toDynamicFloatByteArray(byte[], int, int);
}
public static class DynamicBuilders.DynamicFloat.FloatFormatter {
@@ -202,9 +214,12 @@
public static interface DynamicBuilders.DynamicInstant extends androidx.wear.protolayout.expression.DynamicBuilders.DynamicType {
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicDuration durationUntil(androidx.wear.protolayout.expression.DynamicBuilders.DynamicInstant);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicInstant fromByteArray(byte[]);
+ method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicInstant fromByteArray(byte[], int, int);
method public static androidx.wear.protolayout.expression.ConditionScopes.ConditionScope<androidx.wear.protolayout.expression.DynamicBuilders.DynamicInstant!,java.time.Instant!> onCondition(androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicInstant platformTimeWithSecondsPrecision();
method public default byte[] toDynamicInstantByteArray();
+ method public default int toDynamicInstantByteArray(byte[]);
+ method public default int toDynamicInstantByteArray(byte[], int, int);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicInstant withSecondsPrecision(java.time.Instant);
}
@@ -227,6 +242,7 @@
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicString format(androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32.IntFormatter);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 from(androidx.wear.protolayout.expression.DynamicDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32!>);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 fromByteArray(byte[]);
+ method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 fromByteArray(byte[], int, int);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool gt(androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool gt(int);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool gte(androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32);
@@ -255,6 +271,8 @@
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 times(int);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat times(float);
method public default byte[] toDynamicInt32ByteArray();
+ method public default int toDynamicInt32ByteArray(byte[]);
+ method public default int toDynamicInt32ByteArray(byte[], int, int);
}
public static class DynamicBuilders.DynamicInt32.IntFormatter {
@@ -274,8 +292,11 @@
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicString constant(String);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicString from(androidx.wear.protolayout.expression.DynamicDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicString!>);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicString fromByteArray(byte[]);
+ method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicString fromByteArray(byte[], int, int);
method public static androidx.wear.protolayout.expression.ConditionScopes.ConditionScope<androidx.wear.protolayout.expression.DynamicBuilders.DynamicString!,java.lang.String!> onCondition(androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool);
method public default byte[] toDynamicStringByteArray();
+ method public default int toDynamicStringByteArray(byte[]);
+ method public default int toDynamicStringByteArray(byte[], int, int);
}
public static interface DynamicBuilders.DynamicType {
@@ -286,10 +307,15 @@
public static interface DynamicDataBuilders.DynamicDataValue {
method public static androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue fromBool(boolean);
+ method public static androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue fromByteArray(byte[]);
+ method public static androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue fromByteArray(byte[], int, int);
method public static androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue fromColor(@ColorInt int);
method public static androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue fromFloat(float);
method public static androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue fromInt(int);
method public static androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue fromString(String);
+ method public default byte[] toDynamicDataValueByteArray();
+ method public default int toDynamicDataValueByteArray(byte[]);
+ method public default int toDynamicDataValueByteArray(byte[], int, int);
}
public abstract class DynamicDataKey<T extends androidx.wear.protolayout.expression.DynamicBuilders.DynamicType> {
diff --git a/wear/protolayout/protolayout-expression/api/restricted_current.txt b/wear/protolayout/protolayout-expression/api/restricted_current.txt
index 7f2e9cb..1ca79e0 100644
--- a/wear/protolayout/protolayout-expression/api/restricted_current.txt
+++ b/wear/protolayout/protolayout-expression/api/restricted_current.txt
@@ -104,10 +104,13 @@
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool eq(androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool from(androidx.wear.protolayout.expression.DynamicDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool!>);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool fromByteArray(byte[]);
+ method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool fromByteArray(byte[], int, int);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool ne(androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool negate();
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool or(androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool);
method public default byte[] toDynamicBoolByteArray();
+ method public default int toDynamicBoolByteArray(byte[]);
+ method public default int toDynamicBoolByteArray(byte[], int, int);
}
public static interface DynamicBuilders.DynamicColor extends androidx.wear.protolayout.expression.DynamicBuilders.DynamicType {
@@ -120,18 +123,24 @@
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor constant(@ColorInt int);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor from(androidx.wear.protolayout.expression.DynamicDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor!>);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor fromByteArray(byte[]);
+ method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor fromByteArray(byte[], int, int);
method public static androidx.wear.protolayout.expression.ConditionScopes.ConditionScope<androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor!,java.lang.Integer!> onCondition(androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool);
method public default byte[] toDynamicColorByteArray();
+ method public default int toDynamicColorByteArray(byte[]);
+ method public default int toDynamicColorByteArray(byte[], int, int);
}
public static interface DynamicBuilders.DynamicDuration extends androidx.wear.protolayout.expression.DynamicBuilders.DynamicType {
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicDuration fromByteArray(byte[]);
+ method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicDuration fromByteArray(byte[], int, int);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 getHoursPart();
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 getIntDaysPart();
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 getMinutesPart();
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 getSecondsPart();
method public static androidx.wear.protolayout.expression.ConditionScopes.ConditionScope<androidx.wear.protolayout.expression.DynamicBuilders.DynamicDuration!,java.time.Duration!> onCondition(androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool);
method public default byte[] toDynamicDurationByteArray();
+ method public default int toDynamicDurationByteArray(byte[]);
+ method public default int toDynamicDurationByteArray(byte[], int, int);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 toIntDays();
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 toIntHours();
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 toIntMinutes();
@@ -157,6 +166,7 @@
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicString format(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat.FloatFormatter);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat from(androidx.wear.protolayout.expression.DynamicDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat!>);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat fromByteArray(byte[]);
+ method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat fromByteArray(byte[], int, int);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool gt(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool gt(float);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool gte(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat);
@@ -181,6 +191,8 @@
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat times(float);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat times(androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32);
method public default byte[] toDynamicFloatByteArray();
+ method public default int toDynamicFloatByteArray(byte[]);
+ method public default int toDynamicFloatByteArray(byte[], int, int);
}
public static class DynamicBuilders.DynamicFloat.FloatFormatter {
@@ -202,9 +214,12 @@
public static interface DynamicBuilders.DynamicInstant extends androidx.wear.protolayout.expression.DynamicBuilders.DynamicType {
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicDuration durationUntil(androidx.wear.protolayout.expression.DynamicBuilders.DynamicInstant);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicInstant fromByteArray(byte[]);
+ method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicInstant fromByteArray(byte[], int, int);
method public static androidx.wear.protolayout.expression.ConditionScopes.ConditionScope<androidx.wear.protolayout.expression.DynamicBuilders.DynamicInstant!,java.time.Instant!> onCondition(androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicInstant platformTimeWithSecondsPrecision();
method public default byte[] toDynamicInstantByteArray();
+ method public default int toDynamicInstantByteArray(byte[]);
+ method public default int toDynamicInstantByteArray(byte[], int, int);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicInstant withSecondsPrecision(java.time.Instant);
}
@@ -227,6 +242,7 @@
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicString format(androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32.IntFormatter);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 from(androidx.wear.protolayout.expression.DynamicDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32!>);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 fromByteArray(byte[]);
+ method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 fromByteArray(byte[], int, int);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool gt(androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool gt(int);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool gte(androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32);
@@ -255,6 +271,8 @@
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 times(int);
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat times(float);
method public default byte[] toDynamicInt32ByteArray();
+ method public default int toDynamicInt32ByteArray(byte[]);
+ method public default int toDynamicInt32ByteArray(byte[], int, int);
}
public static class DynamicBuilders.DynamicInt32.IntFormatter {
@@ -274,8 +292,11 @@
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicString constant(String);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicString from(androidx.wear.protolayout.expression.DynamicDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicString!>);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicString fromByteArray(byte[]);
+ method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicString fromByteArray(byte[], int, int);
method public static androidx.wear.protolayout.expression.ConditionScopes.ConditionScope<androidx.wear.protolayout.expression.DynamicBuilders.DynamicString!,java.lang.String!> onCondition(androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool);
method public default byte[] toDynamicStringByteArray();
+ method public default int toDynamicStringByteArray(byte[]);
+ method public default int toDynamicStringByteArray(byte[], int, int);
}
public static interface DynamicBuilders.DynamicType {
@@ -286,10 +307,15 @@
public static interface DynamicDataBuilders.DynamicDataValue {
method public static androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue fromBool(boolean);
+ method public static androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue fromByteArray(byte[]);
+ method public static androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue fromByteArray(byte[], int, int);
method public static androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue fromColor(@ColorInt int);
method public static androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue fromFloat(float);
method public static androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue fromInt(int);
method public static androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue fromString(String);
+ method public default byte[] toDynamicDataValueByteArray();
+ method public default int toDynamicDataValueByteArray(byte[]);
+ method public default int toDynamicDataValueByteArray(byte[], int, int);
}
public abstract class DynamicDataKey<T extends androidx.wear.protolayout.expression.DynamicBuilders.DynamicType> {
diff --git a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicBuilders.java b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicBuilders.java
index 162b840..284104c 100644
--- a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicBuilders.java
+++ b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicBuilders.java
@@ -38,9 +38,11 @@
import androidx.wear.protolayout.expression.FixedValueBuilders.FixedString;
import androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue;
import androidx.wear.protolayout.expression.proto.DynamicProto;
+import androidx.wear.protolayout.protobuf.CodedInputStream;
+import androidx.wear.protolayout.protobuf.CodedOutputStream;
import androidx.wear.protolayout.protobuf.ExtensionRegistryLite;
-import androidx.wear.protolayout.protobuf.InvalidProtocolBufferException;
+import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.Duration;
@@ -1506,25 +1508,72 @@
/**
* Creates a {@link DynamicInt32} from a byte array generated by {@link
* #toDynamicInt32ByteArray()}.
+ *
+ * @throws IllegalArgumentException if the byte array does not contain a valid serialization
*/
@NonNull
static DynamicInt32 fromByteArray(@NonNull byte[] byteArray) {
+ return fromByteArray(byteArray, 0, byteArray.length);
+ }
+
+ /**
+ * Creates a {@link DynamicInt32} from the provided byte array at the provided offset and
+ * length, that was generated by one of the {@link #toDynamicInt32ByteArray} overloads.
+ *
+ * @throws IllegalArgumentException if the byte array does not contain a valid serialization
+ * in the provided offset and length
+ */
+ @NonNull
+ static DynamicInt32 fromByteArray(@NonNull byte[] byteArray, int offset, int length) {
try {
return dynamicInt32FromProto(
DynamicProto.DynamicInt32.parseFrom(
- byteArray, ExtensionRegistryLite.getEmptyRegistry()));
- } catch (InvalidProtocolBufferException e) {
+ CodedInputStream.newInstance(byteArray, offset, length),
+ ExtensionRegistryLite.getEmptyRegistry()));
+ } catch (IOException e) {
throw new IllegalArgumentException(
"Byte array could not be parsed into DynamicInt32", e);
}
}
- /** Creates a byte array that can later be used with {@link #fromByteArray(byte[])}. */
+ /**
+ * Serializes the {@link DynamicInt32} into a new byte array that can later be used with
+ * {@link #fromByteArray(byte[])}.
+ */
@NonNull
default byte[] toDynamicInt32ByteArray() {
return toDynamicInt32Proto().toByteArray();
}
+ /**
+ * Serializes the {@link DynamicInt32} into the provided byte array, returning the amount of
+ * bytes written, that can later be used with {@code DynamicInt32.fromByteArray(byteArray,
+ * 0, bytesWritten)}.
+ *
+ * @throws IllegalArgumentException if the byte array is too small
+ */
+ default int toDynamicInt32ByteArray(@NonNull byte[] byteArray) {
+ return toDynamicInt32ByteArray(byteArray, 0, byteArray.length);
+ }
+
+ /**
+ * Serializes the {@link DynamicInt32} into the provided byte array, returning the amount of
+ * bytes written, limited by the provided offset and length, that can later be used with
+ * {@code DynamicInt32.fromByteArray(byteArray, offset, bytesWritten)}.
+ *
+ * @throws IllegalArgumentException if the byte array is too small
+ */
+ default int toDynamicInt32ByteArray(@NonNull byte[] byteArray, int offset, int length) {
+ CodedOutputStream stream = CodedOutputStream.newInstance(byteArray, offset, length);
+ try {
+ toDynamicInt32Proto().writeTo(stream);
+ } catch (IOException e) {
+ throw new IllegalArgumentException(
+ "Provided byte array not large enough to contain this DynamicInt32", e);
+ }
+ return stream.getTotalBytesWritten();
+ }
+
/** Creates a constant-valued {@link DynamicInt32}. */
@NonNull
static DynamicInt32 constant(int constant) {
@@ -3283,26 +3332,73 @@
/**
* Creates a {@link DynamicString} from a byte array generated by {@link
* #toDynamicStringByteArray()}.
+ *
+ * @throws IllegalArgumentException if the byte array does not contain a valid serialization
*/
@NonNull
static DynamicString fromByteArray(@NonNull byte[] byteArray) {
+ return fromByteArray(byteArray, 0, byteArray.length);
+ }
+
+ /**
+ * Creates a {@link DynamicString} from the provided byte array at the provided offset and
+ * length, that was generated by one of the {@link #toDynamicStringByteArray} overloads.
+ *
+ * @throws IllegalArgumentException if the byte array does not contain a valid serialization
+ * in the provided offset and length
+ */
+ @NonNull
+ static DynamicString fromByteArray(@NonNull byte[] byteArray, int offset, int length) {
try {
return dynamicStringFromProto(
DynamicProto.DynamicString.parseFrom(
- byteArray, ExtensionRegistryLite.getEmptyRegistry()));
- } catch (InvalidProtocolBufferException e) {
+ CodedInputStream.newInstance(byteArray, offset, length),
+ ExtensionRegistryLite.getEmptyRegistry()));
+ } catch (IOException e) {
throw new IllegalArgumentException(
"Byte array could not be parsed into DynamicString", e);
}
}
- /** Creates a byte array that can later be used with {@link #fromByteArray(byte[])}. */
+ /**
+ * Serializes the {@link DynamicString} into a new byte array that can later be used with
+ * {@link #fromByteArray(byte[])}.
+ */
@NonNull
default byte[] toDynamicStringByteArray() {
return toDynamicStringProto().toByteArray();
}
/**
+ * Serializes the {@link DynamicString} into the provided byte array, returning the amount
+ * of bytes written, that can later be used with {@code DynamicString.fromByteArray(
+ * byteArray, 0, bytesWritten)}.
+ *
+ * @throws IllegalArgumentException if the byte array is too small
+ */
+ default int toDynamicStringByteArray(@NonNull byte[] byteArray) {
+ return toDynamicStringByteArray(byteArray, 0, byteArray.length);
+ }
+
+ /**
+ * Serializes the {@link DynamicString} into the provided byte array, returning the amount
+ * of bytes written, limited by the provided offset and length, that can later be used with
+ * {@code DynamicString.fromByteArray(byteArray, offset, bytesWritten)}.
+ *
+ * @throws IllegalArgumentException if the byte array is too small
+ */
+ default int toDynamicStringByteArray(@NonNull byte[] byteArray, int offset, int length) {
+ CodedOutputStream stream = CodedOutputStream.newInstance(byteArray, offset, length);
+ try {
+ toDynamicStringProto().writeTo(stream);
+ } catch (IOException e) {
+ throw new IllegalArgumentException(
+ "Provided byte array not large enough to contain this DynamicString", e);
+ }
+ return stream.getTotalBytesWritten();
+ }
+
+ /**
* Creates a constant-valued {@link DynamicString}. The resulted {@link DynamicString} is
* subject to being truncated if it's too long.
*/
@@ -4081,26 +4177,73 @@
/**
* Creates a {@link DynamicFloat} from a byte array generated by {@link
* #toDynamicFloatByteArray()}.
+ *
+ * @throws IllegalArgumentException if the byte array does not contain a valid serialization
*/
@NonNull
static DynamicFloat fromByteArray(@NonNull byte[] byteArray) {
+ return fromByteArray(byteArray, 0, byteArray.length);
+ }
+
+ /**
+ * Creates a {@link DynamicFloat} from the provided byte array at the provided offset and
+ * length, that was generated by one of the {@link #toDynamicFloatByteArray} overloads.
+ *
+ * @throws IllegalArgumentException if the byte array does not contain a valid serialization
+ * in the provided offset and length
+ */
+ @NonNull
+ static DynamicFloat fromByteArray(@NonNull byte[] byteArray, int offset, int length) {
try {
return dynamicFloatFromProto(
DynamicProto.DynamicFloat.parseFrom(
- byteArray, ExtensionRegistryLite.getEmptyRegistry()));
- } catch (InvalidProtocolBufferException e) {
+ CodedInputStream.newInstance(byteArray, offset, length),
+ ExtensionRegistryLite.getEmptyRegistry()));
+ } catch (IOException e) {
throw new IllegalArgumentException(
"Byte array could not be parsed into DynamicFloat", e);
}
}
- /** Creates a byte array that can later be used with {@link #fromByteArray(byte[])}. */
+ /**
+ * Serializes the {@link DynamicFloat} into a new byte array that can later be used with
+ * {@link #fromByteArray(byte[])}.
+ */
@NonNull
default byte[] toDynamicFloatByteArray() {
return toDynamicFloatProto().toByteArray();
}
/**
+ * Serializes the {@link DynamicFloat} into the provided byte array, returning the amount of
+ * bytes written, that can later be used with {@code DynamicFloat.fromByteArray(byteArray,
+ * 0, bytesWritten)}.
+ *
+ * @throws IllegalArgumentException if the byte array is too small
+ */
+ default int toDynamicFloatByteArray(@NonNull byte[] byteArray) {
+ return toDynamicFloatByteArray(byteArray, 0, byteArray.length);
+ }
+
+ /**
+ * Serializes the {@link DynamicFloat} into the provided byte array, returning the amount of
+ * bytes written, limited by the provided offset and length, that can later be used with
+ * {@code DynamicFloat.fromByteArray(byteArray, offset, bytesWritten)}.
+ *
+ * @throws IllegalArgumentException if the byte array is too small
+ */
+ default int toDynamicFloatByteArray(@NonNull byte[] byteArray, int offset, int length) {
+ CodedOutputStream stream = CodedOutputStream.newInstance(byteArray, offset, length);
+ try {
+ toDynamicFloatProto().writeTo(stream);
+ } catch (IOException e) {
+ throw new IllegalArgumentException(
+ "Provided byte array not large enough to contain this DynamicFloat", e);
+ }
+ return stream.getTotalBytesWritten();
+ }
+
+ /**
* Creates a constant-valued {@link DynamicFloat}.
*
* <p>If {@code Float.isNan(constant)} is true, the value will be invalid. And any
@@ -5668,25 +5811,72 @@
/**
* Creates a {@link DynamicBool} from a byte array generated by {@link
* #toDynamicBoolByteArray()}.
+ *
+ * @throws IllegalArgumentException if the byte array does not contain a valid serialization
*/
@NonNull
static DynamicBool fromByteArray(@NonNull byte[] byteArray) {
+ return fromByteArray(byteArray, 0, byteArray.length);
+ }
+
+ /**
+ * Creates a {@link DynamicBool} from the provided byte array at the provided offset and
+ * length, that was generated by one of the {@link #toDynamicBoolByteArray} overloads.
+ *
+ * @throws IllegalArgumentException if the byte array does not contain a valid serialization
+ * in the provided offset and length
+ */
+ @NonNull
+ static DynamicBool fromByteArray(@NonNull byte[] byteArray, int offset, int length) {
try {
return dynamicBoolFromProto(
DynamicProto.DynamicBool.parseFrom(
- byteArray, ExtensionRegistryLite.getEmptyRegistry()));
- } catch (InvalidProtocolBufferException e) {
+ CodedInputStream.newInstance(byteArray, offset, length),
+ ExtensionRegistryLite.getEmptyRegistry()));
+ } catch (IOException e) {
throw new IllegalArgumentException(
"Byte array could not be parsed into DynamicBool", e);
}
}
- /** Creates a byte array that can later be used with {@link #fromByteArray(byte[])}. */
+ /**
+ * Serializes the {@link DynamicBool} into a new byte array that can later be used with
+ * {@link #fromByteArray(byte[])}.
+ */
@NonNull
default byte[] toDynamicBoolByteArray() {
return toDynamicBoolProto().toByteArray();
}
+ /**
+ * Serializes the {@link DynamicBool} into the provided byte array, returning the amount of
+ * bytes written, that can later be used with {@code DynamicBool.fromByteArray(byteArray, 0,
+ * bytesWritten)}.
+ *
+ * @throws IllegalArgumentException if the byte array is too small
+ */
+ default int toDynamicBoolByteArray(@NonNull byte[] byteArray) {
+ return toDynamicBoolByteArray(byteArray, 0, byteArray.length);
+ }
+
+ /**
+ * Serializes the {@link DynamicBool} into the provided byte array, returning the amount of
+ * bytes written, limited by the provided offset and length, that can later be used with
+ * {@code DynamicBool.fromByteArray(byteArray, offset, bytesWritten)}.
+ *
+ * @throws IllegalArgumentException if the byte array is too small
+ */
+ default int toDynamicBoolByteArray(@NonNull byte[] byteArray, int offset, int length) {
+ CodedOutputStream stream = CodedOutputStream.newInstance(byteArray, offset, length);
+ try {
+ toDynamicBoolProto().writeTo(stream);
+ } catch (IOException e) {
+ throw new IllegalArgumentException(
+ "Provided byte array not large enough to contain this DynamicBool", e);
+ }
+ return stream.getTotalBytesWritten();
+ }
+
/** Creates a constant-valued {@link DynamicBool}. */
@NonNull
static DynamicBool constant(boolean constant) {
@@ -6401,25 +6591,72 @@
/**
* Creates a {@link DynamicColor} from a byte array generated by {@link
* #toDynamicColorByteArray()}.
+ *
+ * @throws IllegalArgumentException if the byte array does not contain a valid serialization
*/
@NonNull
static DynamicColor fromByteArray(@NonNull byte[] byteArray) {
+ return fromByteArray(byteArray, 0, byteArray.length);
+ }
+
+ /**
+ * Creates a {@link DynamicColor} from the provided byte array at the provided offset and
+ * length, that was generated by one of the {@link #toDynamicColorByteArray} overloads.
+ *
+ * @throws IllegalArgumentException if the byte array does not contain a valid serialization
+ * in the provided offset and length
+ */
+ @NonNull
+ static DynamicColor fromByteArray(@NonNull byte[] byteArray, int offset, int length) {
try {
return dynamicColorFromProto(
DynamicProto.DynamicColor.parseFrom(
- byteArray, ExtensionRegistryLite.getEmptyRegistry()));
- } catch (InvalidProtocolBufferException e) {
+ CodedInputStream.newInstance(byteArray, offset, length),
+ ExtensionRegistryLite.getEmptyRegistry()));
+ } catch (IOException e) {
throw new IllegalArgumentException(
"Byte array could not be parsed into DynamicColor", e);
}
}
- /** Creates a byte array that can later be used with {@link #fromByteArray(byte[])}. */
+ /**
+ * Serializes the {@link DynamicColor} into a new byte array that can later be used with
+ * {@link #fromByteArray(byte[])}.
+ */
@NonNull
default byte[] toDynamicColorByteArray() {
return toDynamicColorProto().toByteArray();
}
+ /**
+ * Serializes the {@link DynamicColor} into the provided byte array, returning the amount of
+ * bytes written, that can later be used with {@code DynamicColor.fromByteArray(byteArray,
+ * 0, bytesWritten)}.
+ *
+ * @throws IllegalArgumentException if the byte array is too small
+ */
+ default int toDynamicColorByteArray(@NonNull byte[] byteArray) {
+ return toDynamicColorByteArray(byteArray, 0, byteArray.length);
+ }
+
+ /**
+ * Serializes the {@link DynamicColor} into the provided byte array, returning the amount of
+ * bytes written, limited by the provided offset and length, that can later be used with
+ * {@code DynamicColor.fromByteArray(byteArray, offset, bytesWritten)}.
+ *
+ * @throws IllegalArgumentException if the byte array is too small
+ */
+ default int toDynamicColorByteArray(@NonNull byte[] byteArray, int offset, int length) {
+ CodedOutputStream stream = CodedOutputStream.newInstance(byteArray, offset, length);
+ try {
+ toDynamicColorProto().writeTo(stream);
+ } catch (IOException e) {
+ throw new IllegalArgumentException(
+ "Provided byte array not large enough to contain this DynamicColor", e);
+ }
+ return stream.getTotalBytesWritten();
+ }
+
/** Creates a constant-valued {@link DynamicColor}. */
@NonNull
static DynamicColor constant(@ColorInt int constant) {
@@ -6840,26 +7077,73 @@
/**
* Creates a {@link DynamicInstant} from a byte array generated by {@link
* #toDynamicInstantByteArray()}.
+ *
+ * @throws IllegalArgumentException if the byte array does not contain a valid serialization
*/
@NonNull
static DynamicInstant fromByteArray(@NonNull byte[] byteArray) {
+ return fromByteArray(byteArray, 0, byteArray.length);
+ }
+
+ /**
+ * Creates a {@link DynamicInstant} from the provided byte array at the provided offset and
+ * length, that was generated by one of the {@link #toDynamicInstantByteArray} overloads.
+ *
+ * @throws IllegalArgumentException if the byte array does not contain a valid serialization
+ * in the provided offset and length
+ */
+ @NonNull
+ static DynamicInstant fromByteArray(@NonNull byte[] byteArray, int offset, int length) {
try {
return dynamicInstantFromProto(
DynamicProto.DynamicInstant.parseFrom(
- byteArray, ExtensionRegistryLite.getEmptyRegistry()));
- } catch (InvalidProtocolBufferException e) {
+ CodedInputStream.newInstance(byteArray, offset, length),
+ ExtensionRegistryLite.getEmptyRegistry()));
+ } catch (IOException e) {
throw new IllegalArgumentException(
"Byte array could not be parsed into DynamicInstant", e);
}
}
- /** Creates a byte array that can later be used with {@link #fromByteArray(byte[])}. */
+ /**
+ * Serializes the {@link DynamicInstant} into a new byte array that can later be used with
+ * {@link #fromByteArray(byte[])}.
+ */
@NonNull
default byte[] toDynamicInstantByteArray() {
return toDynamicInstantProto().toByteArray();
}
/**
+ * Serializes the {@link DynamicInstant} into the provided byte array, returning the amount
+ * of bytes written, that can later be used with {@code DynamicInstant.fromByteArray(
+ * byteArray, 0, bytesWritten)}.
+ *
+ * @throws IllegalArgumentException if the byte array is too small
+ */
+ default int toDynamicInstantByteArray(@NonNull byte[] byteArray) {
+ return toDynamicInstantByteArray(byteArray, 0, byteArray.length);
+ }
+
+ /**
+ * Serializes the {@link DynamicInstant} into the provided byte array, returning the amount
+ * of bytes written, limited by the provided offset and length, that can later be used with
+ * {@code DynamicInstant.fromByteArray(byteArray, offset, bytesWritten)}.
+ *
+ * @throws IllegalArgumentException if the byte array is too small
+ */
+ default int toDynamicInstantByteArray(@NonNull byte[] byteArray, int offset, int length) {
+ CodedOutputStream stream = CodedOutputStream.newInstance(byteArray, offset, length);
+ try {
+ toDynamicInstantProto().writeTo(stream);
+ } catch (IOException e) {
+ throw new IllegalArgumentException(
+ "Provided byte array not large enough to contain this DynamicInstant", e);
+ }
+ return stream.getTotalBytesWritten();
+ }
+
+ /**
* Creates a constant-valued {@link DynamicInstant} from an {@link Instant}. If {@link
* Instant} precision is greater than seconds, then any excess precision information will be
* dropped.
@@ -7266,26 +7550,73 @@
/**
* Creates a {@link DynamicDuration} from a byte array generated by {@link
* #toDynamicDurationByteArray()}.
+ *
+ * @throws IllegalArgumentException if the byte array does not contain a valid serialization
*/
@NonNull
static DynamicDuration fromByteArray(@NonNull byte[] byteArray) {
+ return fromByteArray(byteArray, 0, byteArray.length);
+ }
+
+ /**
+ * Creates a {@link DynamicDuration} from the provided byte array at the provided offset and
+ * length, that was generated by one of the {@link #toDynamicDurationByteArray} overloads.
+ *
+ * @throws IllegalArgumentException if the byte array does not contain a valid serialization
+ * in the provided offset and length
+ */
+ @NonNull
+ static DynamicDuration fromByteArray(@NonNull byte[] byteArray, int offset, int length) {
try {
return dynamicDurationFromProto(
DynamicProto.DynamicDuration.parseFrom(
- byteArray, ExtensionRegistryLite.getEmptyRegistry()));
- } catch (InvalidProtocolBufferException e) {
+ CodedInputStream.newInstance(byteArray, offset, length),
+ ExtensionRegistryLite.getEmptyRegistry()));
+ } catch (IOException e) {
throw new IllegalArgumentException(
"Byte array could not be parsed into DynamicDuration", e);
}
}
- /** Creates a byte array that can later be used with {@link #fromByteArray(byte[])}. */
+ /**
+ * Serializes the {@link DynamicDuration} into a new byte array that can later be used with
+ * {@link #fromByteArray(byte[])}.
+ */
@NonNull
default byte[] toDynamicDurationByteArray() {
return toDynamicDurationProto().toByteArray();
}
/**
+ * Serializes the {@link DynamicDuration} into the provided byte array, returning the amount
+ * of bytes written, that can later be used with {@code DynamicDuration.fromByteArray(
+ * byteArray, 0, bytesWritten)}.
+ *
+ * @throws IllegalArgumentException if the byte array is too small
+ */
+ default int toDynamicDurationByteArray(@NonNull byte[] byteArray) {
+ return toDynamicDurationByteArray(byteArray, 0, byteArray.length);
+ }
+
+ /**
+ * Serializes the {@link DynamicDuration} into the provided byte array, returning the amount
+ * of bytes written, limited by the provided offset and length, that can later be used with
+ * {@code DynamicDuration.fromByteArray(byteArray, offset, bytesWritten)}.
+ *
+ * @throws IllegalArgumentException if the byte array is too small
+ */
+ default int toDynamicDurationByteArray(@NonNull byte[] byteArray, int offset, int length) {
+ CodedOutputStream stream = CodedOutputStream.newInstance(byteArray, offset, length);
+ try {
+ toDynamicDurationProto().writeTo(stream);
+ } catch (IOException e) {
+ throw new IllegalArgumentException(
+ "Provided byte array not large enough to contain this DynamicDuration", e);
+ }
+ return stream.getTotalBytesWritten();
+ }
+
+ /**
* Creates a constant-valued {@link DynamicDuration} from a {@link Duration}. If {@link
* Duration} precision is greater than seconds, then any excess precision information will
* be dropped.
diff --git a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicDataBuilders.java b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicDataBuilders.java
index 7f4eddd..8f91189 100644
--- a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicDataBuilders.java
+++ b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicDataBuilders.java
@@ -27,6 +27,11 @@
import androidx.wear.protolayout.expression.FixedValueBuilders.FixedInt32;
import androidx.wear.protolayout.expression.FixedValueBuilders.FixedString;
import androidx.wear.protolayout.expression.proto.DynamicDataProto;
+import androidx.wear.protolayout.protobuf.CodedInputStream;
+import androidx.wear.protolayout.protobuf.CodedOutputStream;
+import androidx.wear.protolayout.protobuf.ExtensionRegistryLite;
+
+import java.io.IOException;
/** Builders for dynamic data value of a provider. */
public final class DynamicDataBuilders {
@@ -46,6 +51,75 @@
@NonNull
DynamicDataProto.DynamicDataValue toDynamicDataValueProto();
+ /**
+ * Creates a {@link DynamicDataValue} from a byte array generated by {@link
+ * #toDynamicDataValueByteArray()}.
+ *
+ * @throws IllegalArgumentException if the byte array does not contain a valid serialization
+ */
+ @NonNull
+ static DynamicDataValue fromByteArray(@NonNull byte[] byteArray) {
+ return fromByteArray(byteArray, 0, byteArray.length);
+ }
+
+ /**
+ * Creates a {@link DynamicDataValue} from the provided byte array at the provided offset and
+ * length, that was generated by one of the {@link #toDynamicDataValueByteArray} overloads.
+ *
+ * @throws IllegalArgumentException if the byte array does not contain a valid serialization in
+ * the provided offset and length
+ */
+ @NonNull
+ static DynamicDataValue fromByteArray(@NonNull byte[] byteArray, int offset, int length) {
+ try {
+ return dynamicDataValueFromProto(
+ DynamicDataProto.DynamicDataValue.parseFrom(
+ CodedInputStream.newInstance(byteArray, offset, length),
+ ExtensionRegistryLite.getEmptyRegistry()));
+ } catch (IOException e) {
+ throw new IllegalArgumentException(
+ "Byte array could not be parsed into DynamicDataValue", e);
+ }
+ }
+
+ /**
+ * Serializes the {@link DynamicDataValue} into a new byte array that can later be used with
+ * {@link #fromByteArray(byte[])}.
+ */
+ @NonNull
+ default byte[] toDynamicDataValueByteArray() {
+ return toDynamicDataValueProto().toByteArray();
+ }
+
+ /**
+ * Serializes the {@link DynamicDataValue} into the provided byte array, returning the amount of
+ * bytes written, that can later be used with {@code DynamicDataValue.fromByteArray(byteArray,
+ * 0, bytesWritten)}.
+ *
+ * @throws IllegalArgumentException if the byte array is too small
+ */
+ default int toDynamicDataValueByteArray(@NonNull byte[] byteArray) {
+ return toDynamicDataValueByteArray(byteArray, 0, byteArray.length);
+ }
+
+ /**
+ * Serializes the {@link DynamicDataValue} into the provided byte array, returning the amount of
+ * bytes written, limited by the provided offset and length, that can later be used with {@code
+ * DynamicDataValue.fromByteArray(byteArray, offset, bytesWritten)}.
+ *
+ * @throws IllegalArgumentException if the byte array is too small
+ */
+ default int toDynamicDataValueByteArray(@NonNull byte[] byteArray, int offset, int length) {
+ CodedOutputStream stream = CodedOutputStream.newInstance(byteArray, offset, length);
+ try {
+ toDynamicDataValueProto().writeTo(stream);
+ } catch (IOException e) {
+ throw new IllegalArgumentException(
+ "Provided byte array not large enough to contain this DynamicDataValue", e);
+ }
+ return stream.getTotalBytesWritten();
+ }
+
/** Creates a boolean {@link DynamicDataValue}. */
@NonNull
static DynamicDataValue fromBool(boolean constant) {
diff --git a/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/DynamicBoolTest.java b/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/DynamicBoolTest.java
index c7263ec..3c0bc87 100644
--- a/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/DynamicBoolTest.java
+++ b/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/DynamicBoolTest.java
@@ -144,7 +144,7 @@
}
@Test
- public void validProto() {
+ public void fromByteArray_validProto() {
DynamicBool from = DynamicBool.constant(true);
DynamicBool to = DynamicBool.fromByteArray(from.toDynamicBoolByteArray());
@@ -152,8 +152,56 @@
}
@Test
- public void invalidProto() {
+ public void fromByteArray_invalidProto() {
assertThrows(
IllegalArgumentException.class, () -> DynamicBool.fromByteArray(new byte[] {1}));
}
+
+ @Test
+ public void fromByteArray_existingByteArray() {
+ DynamicBool from = DynamicBool.constant(true);
+ byte[] buffer = new byte[100];
+ int written = from.toDynamicBoolByteArray(buffer, 10, 50);
+
+ DynamicBool to = DynamicBool.fromByteArray(buffer, 10, written);
+
+ assertThat(to.toDynamicBoolProto().getFixed().getValue()).isTrue();
+ }
+
+ @Test
+ public void fromByteArray_existingByteArrayTooSmall() {
+ DynamicBool from = DynamicBool.constant(true);
+ byte[] buffer = new byte[100];
+ int written = from.toDynamicBoolByteArray(buffer);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DynamicBool.fromByteArray(buffer, 0, written - 1));
+ }
+
+ @Test
+ public void fromByteArray_existingByteArrayTooLarge() {
+ DynamicBool from = DynamicBool.constant(true);
+ byte[] buffer = new byte[100];
+ int written = from.toDynamicBoolByteArray(buffer);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DynamicBool.fromByteArray(buffer, 0, written + 1));
+ }
+
+ @Test
+ public void toByteArray_existingByteArrayTooSmall() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DynamicBool.constant(true).toDynamicBoolByteArray(new byte[1]));
+ }
+
+ @Test
+ public void toByteArray_existingByteArraySameSize() {
+ DynamicBool from = DynamicBool.constant(true);
+
+ assertThat(from.toDynamicBoolByteArray(new byte[100]))
+ .isEqualTo(from.toDynamicBoolByteArray().length);
+ }
}
diff --git a/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/DynamicColorTest.java b/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/DynamicColorTest.java
index 2751e61..d879ce5f 100644
--- a/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/DynamicColorTest.java
+++ b/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/DynamicColorTest.java
@@ -140,7 +140,7 @@
}
@Test
- public void validProto() {
+ public void fromByteArray_validProto() {
DynamicColor from = DynamicColor.constant(CONSTANT_VALUE);
DynamicColor to = DynamicColor.fromByteArray(from.toDynamicColorByteArray());
@@ -148,7 +148,55 @@
}
@Test
- public void invalidProto() {
+ public void fromByteArray_invalidProto() {
assertThrows(IllegalArgumentException.class, () -> DynamicColor.fromByteArray(new byte[] {1}));
}
+
+ @Test
+ public void fromByteArray_existingByteArray() {
+ DynamicColor from = DynamicColor.constant(CONSTANT_VALUE);
+ byte[] buffer = new byte[100];
+ int written = from.toDynamicColorByteArray(buffer, 10, 50);
+
+ DynamicColor to = DynamicColor.fromByteArray(buffer, 10, written);
+
+ assertThat(to.toDynamicColorProto().getFixed().getArgb()).isEqualTo(CONSTANT_VALUE);
+ }
+
+ @Test
+ public void fromByteArray_existingByteArrayTooSmall() {
+ DynamicColor from = DynamicColor.constant(CONSTANT_VALUE);
+ byte[] buffer = new byte[100];
+ int written = from.toDynamicColorByteArray(buffer);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DynamicColor.fromByteArray(buffer, 0, written - 1));
+ }
+
+ @Test
+ public void fromByteArray_existingByteArrayTooLarge() {
+ DynamicColor from = DynamicColor.constant(CONSTANT_VALUE);
+ byte[] buffer = new byte[100];
+ int written = from.toDynamicColorByteArray(buffer);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DynamicColor.fromByteArray(buffer, 0, written + 1));
+ }
+
+ @Test
+ public void toByteArray_existingByteArrayTooSmall() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DynamicColor.constant(CONSTANT_VALUE).toDynamicColorByteArray(new byte[1]));
+ }
+
+ @Test
+ public void toByteArray_existingByteArraySameSize() {
+ DynamicColor from = DynamicColor.constant(CONSTANT_VALUE);
+
+ assertThat(from.toDynamicColorByteArray(new byte[100]))
+ .isEqualTo(from.toDynamicColorByteArray().length);
+ }
}
diff --git a/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/DynamicFloatTest.java b/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/DynamicFloatTest.java
index 3036fbed..c7b9e7e 100644
--- a/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/DynamicFloatTest.java
+++ b/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/DynamicFloatTest.java
@@ -238,7 +238,7 @@
}
@Test
- public void validProto() {
+ public void fromByteArray_validProto() {
DynamicFloat from = DynamicFloat.constant(CONSTANT_VALUE);
DynamicFloat to = DynamicFloat.fromByteArray(from.toDynamicFloatByteArray());
@@ -246,8 +246,56 @@
}
@Test
- public void invalidProto() {
+ public void fromByteArray_invalidProto() {
assertThrows(
IllegalArgumentException.class, () -> DynamicFloat.fromByteArray(new byte[] {1}));
}
+
+ @Test
+ public void fromByteArray_existingByteArray() {
+ DynamicFloat from = DynamicFloat.constant(CONSTANT_VALUE);
+ byte[] buffer = new byte[100];
+ int written = from.toDynamicFloatByteArray(buffer, 10, 50);
+
+ DynamicFloat to = DynamicFloat.fromByteArray(buffer, 10, written);
+
+ assertThat(to.toDynamicFloatProto().getFixed().getValue()).isEqualTo(CONSTANT_VALUE);
+ }
+
+ @Test
+ public void fromByteArray_existingByteArrayTooSmall() {
+ DynamicFloat from = DynamicFloat.constant(CONSTANT_VALUE);
+ byte[] buffer = new byte[100];
+ int written = from.toDynamicFloatByteArray(buffer);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DynamicFloat.fromByteArray(buffer, 0, written - 1));
+ }
+
+ @Test
+ public void fromByteArray_existingByteArrayTooLarge() {
+ DynamicFloat from = DynamicFloat.constant(CONSTANT_VALUE);
+ byte[] buffer = new byte[100];
+ int written = from.toDynamicFloatByteArray(buffer);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DynamicFloat.fromByteArray(buffer, 0, written + 1));
+ }
+
+ @Test
+ public void toByteArray_existingByteArrayTooSmall() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DynamicFloat.constant(CONSTANT_VALUE).toDynamicFloatByteArray(new byte[1]));
+ }
+
+ @Test
+ public void toByteArray_existingByteArraySameSize() {
+ DynamicFloat from = DynamicFloat.constant(CONSTANT_VALUE);
+
+ assertThat(from.toDynamicFloatByteArray(new byte[100]))
+ .isEqualTo(from.toDynamicFloatByteArray().length);
+ }
}
diff --git a/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/DynamicInt32Test.java b/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/DynamicInt32Test.java
index a130199..ff1bc0f 100644
--- a/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/DynamicInt32Test.java
+++ b/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/DynamicInt32Test.java
@@ -131,7 +131,7 @@
}
@Test
- public void validProto() {
+ public void fromByteArray_validProto() {
DynamicInt32 from = DynamicInt32.constant(CONSTANT_VALUE);
DynamicInt32 to = DynamicInt32.fromByteArray(from.toDynamicInt32ByteArray());
@@ -139,8 +139,56 @@
}
@Test
- public void invalidProto() {
+ public void fromByteArray_invalidProto() {
assertThrows(
IllegalArgumentException.class, () -> DynamicInt32.fromByteArray(new byte[] {1}));
}
+
+ @Test
+ public void fromByteArray_existingByteArray() {
+ DynamicInt32 from = DynamicInt32.constant(CONSTANT_VALUE);
+ byte[] buffer = new byte[100];
+ int written = from.toDynamicInt32ByteArray(buffer, 10, 50);
+
+ DynamicInt32 to = DynamicInt32.fromByteArray(buffer, 10, written);
+
+ assertThat(to.toDynamicInt32Proto().getFixed().getValue()).isEqualTo(CONSTANT_VALUE);
+ }
+
+ @Test
+ public void fromByteArray_existingByteArrayTooSmall() {
+ DynamicInt32 from = DynamicInt32.constant(CONSTANT_VALUE);
+ byte[] buffer = new byte[100];
+ int written = from.toDynamicInt32ByteArray(buffer);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DynamicInt32.fromByteArray(buffer, 0, written - 1));
+ }
+
+ @Test
+ public void fromByteArray_existingByteArrayTooLarge() {
+ DynamicInt32 from = DynamicInt32.constant(CONSTANT_VALUE);
+ byte[] buffer = new byte[100];
+ int written = from.toDynamicInt32ByteArray(buffer);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DynamicInt32.fromByteArray(buffer, 0, written + 1));
+ }
+
+ @Test
+ public void toByteArray_existingByteArrayTooSmall() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DynamicInt32.constant(CONSTANT_VALUE).toDynamicInt32ByteArray(new byte[1]));
+ }
+
+ @Test
+ public void toByteArray_existingByteArraySameSize() {
+ DynamicInt32 from = DynamicInt32.constant(CONSTANT_VALUE);
+
+ assertThat(from.toDynamicInt32ByteArray(new byte[100]))
+ .isEqualTo(from.toDynamicInt32ByteArray().length);
+ }
}
diff --git a/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/DynamicStringTest.java b/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/DynamicStringTest.java
index b33df22..658664f 100644
--- a/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/DynamicStringTest.java
+++ b/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/DynamicStringTest.java
@@ -120,7 +120,7 @@
}
@Test
- public void validProto() {
+ public void fromByteArray_validProto() {
DynamicString from = DynamicString.constant(CONSTANT_VALUE);
DynamicString to = DynamicString.fromByteArray(from.toDynamicStringByteArray());
@@ -128,7 +128,55 @@
}
@Test
- public void invalidProto() {
+ public void fromByteArray_invalidProto() {
assertThrows(IllegalArgumentException.class, () -> DynamicString.fromByteArray(new byte[] {1}));
}
+
+ @Test
+ public void fromByteArray_existingByteArray() {
+ DynamicString from = DynamicString.constant(CONSTANT_VALUE);
+ byte[] buffer = new byte[100];
+ int written = from.toDynamicStringByteArray(buffer, 10, 50);
+
+ DynamicString to = DynamicString.fromByteArray(buffer, 10, written);
+
+ assertThat(to.toDynamicStringProto().getFixed().getValue()).isEqualTo(CONSTANT_VALUE);
+ }
+
+ @Test
+ public void fromByteArray_existingByteArrayTooSmall() {
+ DynamicString from = DynamicString.constant(CONSTANT_VALUE);
+ byte[] buffer = new byte[100];
+ int written = from.toDynamicStringByteArray(buffer);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DynamicString.fromByteArray(buffer, 0, written - 1));
+ }
+
+ @Test
+ public void fromByteArray_existingByteArrayTooLarge() {
+ DynamicString from = DynamicString.constant(CONSTANT_VALUE);
+ byte[] buffer = new byte[100];
+ int written = from.toDynamicStringByteArray(buffer);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DynamicString.fromByteArray(buffer, 0, written + 1));
+ }
+
+ @Test
+ public void toByteArray_existingByteArrayTooSmall() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DynamicString.constant(CONSTANT_VALUE).toDynamicStringByteArray(new byte[1]));
+ }
+
+ @Test
+ public void toByteArray_existingByteArraySameSize() {
+ DynamicString from = DynamicString.constant(CONSTANT_VALUE);
+
+ assertThat(from.toDynamicStringByteArray(new byte[100]))
+ .isEqualTo(from.toDynamicStringByteArray().length);
+ }
}