Merge "Show SelectionHandle when text aligned to End" into androidx-main
diff --git a/activity/activity-compose/build.gradle b/activity/activity-compose/build.gradle
index b7f49ecc..4dd01f4 100644
--- a/activity/activity-compose/build.gradle
+++ b/activity/activity-compose/build.gradle
@@ -54,7 +54,7 @@
androidx {
name = "Activity Compose"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2020"
description = "Compose integration with Activity"
metalavaK2UastEnabled = true
diff --git a/activity/activity-ktx/build.gradle b/activity/activity-ktx/build.gradle
index 2e41cd9..6cc0206 100644
--- a/activity/activity-ktx/build.gradle
+++ b/activity/activity-ktx/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
+import androidx.build.LibraryType
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
@@ -59,7 +59,7 @@
androidx {
name = "Activity Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2018"
description = "Kotlin extensions for 'activity' artifact"
metalavaK2UastEnabled = true
diff --git a/appsearch/appsearch-ktx/build.gradle b/appsearch/appsearch-ktx/build.gradle
index afd8740..e771a6d 100644
--- a/appsearch/appsearch-ktx/build.gradle
+++ b/appsearch/appsearch-ktx/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
+import androidx.build.LibraryType
plugins {
id('AndroidXPlugin')
@@ -45,7 +45,7 @@
androidx {
name = "AppSearch - Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = '2021'
description = 'AndroidX AppSearch - Kotlin Extensions'
metalavaK2UastEnabled = true
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPlugin.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPlugin.kt
index 3138193..24946de 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPlugin.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPlugin.kt
@@ -362,6 +362,7 @@
library = isLibraryModule(),
sourceDir = mergeTaskProvider.flatMap { it.baselineProfileDir },
outputDir = project.provider { srcOutputDir },
+ hasDependencies = baselineProfileConfiguration.allDependencies.isNotEmpty(),
isLastTask = true,
warnings = baselineProfileExtension.warnings
)
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt
index 4179410..c886ed9 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt
@@ -76,7 +76,7 @@
project: Project,
variantName: String,
mergeAwareTaskName: String,
- hasDependencies: Boolean = false,
+ hasDependencies: Boolean,
library: Boolean,
sourceProfilesFileCollection: FileCollection,
outputDir: Provider<Directory>,
@@ -123,6 +123,8 @@
.set(warnings.noBaselineProfileRulesGenerated)
task.printWarningNoStartupProfileRulesGenerated
.set(warnings.noStartupProfileRulesGenerated)
+ task.printWarningVariantHasNoBaselineProfileDependency
+ .set(warnings.variantHasNoBaselineProfileDependency)
}
}
@@ -134,6 +136,7 @@
sourceDir: Provider<Directory>,
outputDir: Provider<Directory>,
isLastTask: Boolean,
+ hasDependencies: Boolean,
warnings: Warnings
): TaskProvider<MergeBaselineProfileTask> {
return project
@@ -150,10 +153,13 @@
task.library.set(library)
task.variantName.set(variantName)
task.lastTask.set(isLastTask)
+ task.hasDependencies.set(hasDependencies)
task.printWarningNoBaselineProfileRulesGenerated
.set(warnings.noBaselineProfileRulesGenerated)
task.printWarningNoStartupProfileRulesGenerated
.set(warnings.noStartupProfileRulesGenerated)
+ task.printWarningVariantHasNoBaselineProfileDependency
+ .set(warnings.variantHasNoBaselineProfileDependency)
}
}
}
@@ -187,19 +193,46 @@
@get:Input
abstract val printWarningNoStartupProfileRulesGenerated: Property<Boolean>
+ @get:Input
+ abstract val printWarningVariantHasNoBaselineProfileDependency: Property<Boolean>
+
private val logger by lazy { BaselineProfilePluginLogger(this.getLogger()) }
+ private val variantHasDependencies by lazy {
+ hasDependencies.isPresent && hasDependencies.get()
+ }
+
@TaskAction
fun exec() {
- if (hasDependencies.isPresent && !hasDependencies.get()) {
- throw GradleException(
- """
+ // This warning should be printed only if no dependency has been set for the processed
+ // variant.
+ if (lastTask.get() && !variantHasDependencies) {
+ logger.warn(
+ property = { printWarningVariantHasNoBaselineProfileDependency.get() },
+ propertyName = "variantHasNoBaselineProfileDependency",
+ message = """
The baseline profile consumer plugin is applied to this module but no dependency
- has been set. Please review the configuration of build.gradle for this module
- making sure that a `baselineProfile` dependency exists and points to a valid
- `com.android.test` module that has the `androidx.baselineprofile` or
- `androidx.baselineprofile.producer` plugin applied.
+ has been set for variant `${variantName.get()}`, so no baseline profile will be
+ generated for it.
+
+ A dependency for all the variants can be added in the dependency block using
+ `baselineProfile` configuration:
+
+ dependencies {
+ ...
+ baselineProfile(project(":baselineprofile"))
+ }
+
+ Or for a specific variant in the baseline profile block:
+
+ baselineProfile {
+ variants {
+ freeRelease {
+ from(project(":baselineprofile"))
+ }
+ }
+ }
""".trimIndent()
)
}
@@ -217,13 +250,15 @@
// Read the profile rules from the file collection that contains the profile artifacts from
// all the configurations for this variant and merge them in a single list.
- val profileRules = baselineProfileFileCollection.files
+ val baselineProfileRules = baselineProfileFileCollection.files
.readLines {
FILENAME_MATCHER_BASELINE_PROFILE in it.name ||
FILENAME_MATCHER_STARTUP_PROFILE in it.name
}
- if (variantName.isPresent && profileRules.isEmpty()) {
+ // This warning should be printed only if the variant has dependencies but there are no
+ // baseline profile rules.
+ if (lastTask.get() && variantHasDependencies && baselineProfileRules.isEmpty()) {
logger.warn(
property = { printWarningNoBaselineProfileRulesGenerated.get() },
propertyName = "noBaselineProfileRulesGenerated",
@@ -242,7 +277,7 @@
// - group by class and method (ignoring flag) and for each group keep only the first value
// - apply the filters
// - sort with comparator
- val filteredBaselineProfileRules = profileRules
+ val filteredBaselineProfileRules = baselineProfileRules
.sorted()
.asSequence()
.mapNotNull { ProfileRule.parse(it) }
@@ -267,9 +302,10 @@
.sortedWith(ProfileRule.comparator)
// Check if the filters filtered out all the rules.
- if (profileRules.isNotEmpty() &&
+ if (baselineProfileRules.isNotEmpty() &&
filteredBaselineProfileRules.isEmpty() &&
- rules.isNotEmpty()) {
+ rules.isNotEmpty()
+ ) {
throw GradleException(
"""
The baseline profile consumer plugin is configured with filters that exclude all
@@ -294,7 +330,9 @@
val startupRules = baselineProfileFileCollection.files
.readLines { FILENAME_MATCHER_STARTUP_PROFILE in it.name }
- if (variantName.isPresent && startupRules.isEmpty()) {
+ // This warning should be printed only if the variant has dependencies but there are no
+ // startup profile rules.
+ if (lastTask.get() && variantHasDependencies && startupRules.isEmpty()) {
logger.warn(
property = { printWarningNoStartupProfileRulesGenerated.get() },
propertyName = "noBaselineProfileRulesGenerated",
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/WarningsExtension.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/WarningsExtension.kt
index 6caa86a..d9cd3d2 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/WarningsExtension.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/WarningsExtension.kt
@@ -74,4 +74,10 @@
* the generate baseline profile command.
*/
var noStartupProfileRulesGenerated = true
+
+ /**
+ * Controls the warning printed when a variant has no baseline profile dependency set,
+ * either globally or a specific one.
+ */
+ var variantHasNoBaselineProfileDependency = true
}
diff --git a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
index 589cad5d..aefa4be 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
@@ -386,18 +386,12 @@
flavors = false,
dependencyOnProducerProject = false
)
-
- gradleRunner
- .withArguments("generateReleaseBaselineProfile", "--stacktrace")
- .buildAndFail()
- .output
- .replace("\n", " ")
- .also {
- assertThat(it).contains(
- "The baseline profile consumer plugin is applied to " +
- "this module but no dependency has been set"
- )
- }
+ gradleRunner.build("generateReleaseBaselineProfile", "--stacktrace") {
+ assertThat(it.replace("\n", " ")).contains(
+ "The baseline profile consumer plugin is applied to this module but no " +
+ "dependency has been set for variant `release`"
+ )
+ }
}
@Test
diff --git a/benchmark/integration-tests/baselineprofile-consumer/build.gradle b/benchmark/integration-tests/baselineprofile-consumer/build.gradle
index c98970f..e8760c8 100644
--- a/benchmark/integration-tests/baselineprofile-consumer/build.gradle
+++ b/benchmark/integration-tests/baselineprofile-consumer/build.gradle
@@ -33,12 +33,22 @@
}
android {
+ sourceSets {
+ // This is required because of release variant specific code used by hilt.
+ releaseLibrariesOnly {
+ java.srcDirs += "src/release/"
+ }
+ }
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt")
}
+ releaseLibrariesOnly {
+ initWith(release)
+ matchingFallbacks += "release"
+ }
}
namespace "androidx.benchmark.integration.baselineprofile.consumer"
}
@@ -49,7 +59,6 @@
implementation(libs.hiltAndroid)
kapt(libs.hiltCompiler)
implementation(project(":profileinstaller:profileinstaller"))
- baselineProfile(project(":benchmark:integration-tests:baselineprofile-producer"))
}
baselineProfile {
@@ -58,6 +67,15 @@
// trigger baseline profile generation and integration tests on device.
saveInSrc = true
automaticGenerationDuringBuild = false
+
+ variants {
+ release {
+ from(project(":benchmark:integration-tests:baselineprofile-producer"))
+ }
+ releaseLibrariesOnly {
+ // No dependency set
+ }
+ }
}
apply(from: "../baselineprofile-test-utils/utils.gradle")
diff --git a/benchmark/integration-tests/baselineprofile-producer/build.gradle b/benchmark/integration-tests/baselineprofile-producer/build.gradle
index 0f1243c..3a320b1 100644
--- a/benchmark/integration-tests/baselineprofile-producer/build.gradle
+++ b/benchmark/integration-tests/baselineprofile-producer/build.gradle
@@ -32,7 +32,7 @@
android {
defaultConfig {
- minSdkVersion 23
+ minSdkVersion 24
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
testOptions.managedDevices.devices {
@@ -42,6 +42,10 @@
systemImageSource = "aosp"
}
}
+ buildTypes {
+ release { }
+ releaseLibrariesOnly { }
+ }
targetProjectPath = ":benchmark:integration-tests:baselineprofile-consumer"
namespace "androidx.benchmark.integration.baselineprofile.producer"
}
diff --git a/benchmark/integration-tests/baselineprofile-producer/src/main/java/androidx/benchmark/integration/baselineprofile/producer/StartupBenchmarks.kt b/benchmark/integration-tests/baselineprofile-producer/src/main/java/androidx/benchmark/integration/baselineprofile/producer/StartupBenchmarks.kt
new file mode 100644
index 0000000..a1e44eb
--- /dev/null
+++ b/benchmark/integration-tests/baselineprofile-producer/src/main/java/androidx/benchmark/integration/baselineprofile/producer/StartupBenchmarks.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark.integration.baselineprofile.producer
+
+import android.content.Intent
+import androidx.benchmark.macro.BaselineProfileMode
+import androidx.benchmark.macro.CompilationMode
+import androidx.benchmark.macro.StartupMode
+import androidx.benchmark.macro.StartupTimingMetric
+import androidx.benchmark.macro.junit4.MacrobenchmarkRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class StartupBenchmarks {
+
+ @get:Rule
+ val rule = MacrobenchmarkRule()
+
+ @Test
+ fun startupCompilationBaselineProfiles() =
+ benchmark(CompilationMode.Partial(BaselineProfileMode.Require))
+
+ private fun benchmark(compilationMode: CompilationMode) = rule.measureRepeated(
+ packageName = PACKAGE_NAME,
+ metrics = listOf(StartupTimingMetric()),
+ compilationMode = compilationMode,
+ startupMode = StartupMode.COLD,
+ iterations = 10,
+ setupBlock = { pressHome() },
+ measureBlock = { startActivityAndWait(Intent(ACTION)) }
+ )
+
+ companion object {
+ private const val PACKAGE_NAME =
+ "androidx.benchmark.integration.baselineprofile.consumer"
+ private const val ACTION =
+ "androidx.benchmark.integration.baselineprofile.consumer.EMPTY_ACTIVITY"
+ }
+}
diff --git a/biometric/biometric-ktx/build.gradle b/biometric/biometric-ktx/build.gradle
index 7ce4886..0a615c2 100644
--- a/biometric/biometric-ktx/build.gradle
+++ b/biometric/biometric-ktx/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.LibraryType
import androidx.build.Publish
plugins {
@@ -37,7 +39,7 @@
androidx {
name = "Biometric Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2020"
description = "Kotlin extensions for the Biometric Library."
metalavaK2UastEnabled = true
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index c8a7158..8d77fac 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -507,8 +507,7 @@
if (!project.name.contains("camera-camera2-pipe")) {
kotlinCompilerArgs += "-Xjvm-default=all"
}
- if (androidXExtension.type == LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY ||
- androidXExtension.type == LibraryType.PUBLISHED_KOTLIN_ONLY_TEST_LIBRARY) {
+ if (androidXExtension.type.targetsKotlinConsumersOnly) {
// The Kotlin Compiler adds intrinsic assertions which are only relevant
// when the code is consumed by Java users. Therefore we can turn this off
// when code is being consumed by Kotlin users.
@@ -998,7 +997,8 @@
val mavenGroup = androidXExtension.mavenGroup
val isProbablyPublished =
androidXExtension.type == LibraryType.PUBLISHED_LIBRARY ||
- androidXExtension.type == LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY ||
+ androidXExtension.type ==
+ LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS ||
androidXExtension.type == LibraryType.UNSET
if (mavenGroup != null && isProbablyPublished && androidXExtension.shouldPublish()) {
validateProjectMavenGroup(mavenGroup.group)
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/dackka/DackkaTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/dackka/DackkaTask.kt
index ac7e956..becd6c4 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/dackka/DackkaTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/dackka/DackkaTask.kt
@@ -34,6 +34,7 @@
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
@@ -115,9 +116,20 @@
@get:Input abstract val nullabilityAnnotations: ListProperty<String>
- @get:[InputFiles PathSensitive(PathSensitivity.NONE)]
+ // Version metadata for apiSince, only marked as @InputFiles if includeVersionMetadata is true
+ @get:Internal
abstract val versionMetadataFiles: ConfigurableFileCollection
+ @InputFiles
+ @PathSensitive(PathSensitivity.NONE)
+ fun getOptionalVersionMetadataFiles(): ConfigurableFileCollection {
+ return if (includeVersionMetadata) {
+ versionMetadataFiles
+ } else {
+ objects.fileCollection()
+ }
+ }
+
// Maps to the system variable LIBRARY_METADATA_FILE containing artifactID and other metadata
@get:[InputFile PathSensitive(PathSensitivity.NONE)]
abstract val libraryMetadataFile: RegularFileProperty
@@ -131,7 +143,7 @@
/**
* Option for whether to include apiSince metadata in the docs. Defaults to including metadata.
- * Run with `--no-version-metadata -x generateApi` to avoid running `generateApi` before `docs`.
+ * Run with `--no-version-metadata` to avoid running `generateApi` before `docs`.
*/
@get:Input
@set:Option(
@@ -283,8 +295,8 @@
* an exact match of the version metadata attributes to be selected as version metadata.
*/
private fun getVersionMetadataFiles(): List<File> {
- if (!includeVersionMetadata) return emptyList()
- val (json, nonJson) = versionMetadataFiles.files.partition { it.extension == "json" }
+ val (json, nonJson) = getOptionalVersionMetadataFiles().files
+ .partition { it.extension == "json" }
if (nonJson.isNotEmpty()) {
logger.error(
"The following were resolved as version metadata files but are not JSON files. " +
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 abfbe4d9..d311b35 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,6 @@
package androidx.build.metalava
import androidx.build.AndroidXExtension
-import androidx.build.LibraryType
import androidx.build.addFilterableTasks
import androidx.build.addToBuildOnServer
import androidx.build.addToCheckTask
@@ -74,9 +73,7 @@
// implemented by excluding APIs with this annotation from the restricted API file.
val generateRestrictToLibraryGroupAPIs = !extension.mavenGroup!!.requireSameVersion
val kotlinSourceLevel: Provider<KotlinVersion> = extension.kotlinApiVersion
- val targetsJavaConsumers = (extension.type != LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY &&
- extension.type != LibraryType.PUBLISHED_KOTLIN_ONLY_TEST_LIBRARY
- )
+ val targetsJavaConsumers = !extension.type.targetsKotlinConsumersOnly
val generateApi =
project.tasks.register("generateApi", GenerateApiTask::class.java) { task ->
task.group = "API"
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/LibraryType.kt b/buildSrc/public/src/main/kotlin/androidx/build/LibraryType.kt
index 2ed87dc..ebdf2b8 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/LibraryType.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/LibraryType.kt
@@ -56,25 +56,25 @@
* 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. INTERNAL_TEST DEMO IDE_PLUGIN
*/
sealed class LibraryType(
val publish: Publish = Publish.NONE,
val sourceJars: Boolean = false,
val checkApi: RunApiTasks = RunApiTasks.No("Unknown Library Type"),
val compilationTarget: CompilationTarget = CompilationTarget.DEVICE,
- val allowCallingVisibleForTestsApis: Boolean = false
+ val allowCallingVisibleForTestsApis: Boolean = false,
+ val targetsKotlinConsumersOnly: Boolean = false
) {
val name: String
get() = javaClass.simpleName
companion object {
val PUBLISHED_LIBRARY = PublishedLibrary()
- val PUBLISHED_KOTLIN_ONLY_LIBRARY = PublishedKotlinOnlyLibrary()
+ val PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS =
+ PublishedLibrary(targetsKotlinConsumersOnly = true)
val PUBLISHED_TEST_LIBRARY = PublishedTestLibrary()
- val PUBLISHED_KOTLIN_ONLY_TEST_LIBRARY = PublishedKotlinOnlyTestLibrary()
+ val PUBLISHED_KOTLIN_ONLY_TEST_LIBRARY =
+ PublishedTestLibrary(targetsKotlinConsumersOnly = true)
val INTERNAL_TEST_LIBRARY = InternalTestLibrary()
val INTERNAL_HOST_TEST_LIBRARY = InternalHostTestLibrary()
val SAMPLES = Samples()
@@ -95,7 +95,8 @@
private val allTypes =
mapOf(
"PUBLISHED_LIBRARY" to PUBLISHED_LIBRARY,
- "PUBLISHED_KOTLIN_ONLY_LIBRARY" to PUBLISHED_KOTLIN_ONLY_LIBRARY,
+ "PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS"
+ to PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS,
"PUBLISHED_TEST_LIBRARY" to PUBLISHED_TEST_LIBRARY,
"PUBLISHED_KOTLIN_ONLY_TEST_LIBRARY" to PUBLISHED_KOTLIN_ONLY_TEST_LIBRARY,
"INTERNAL_TEST_LIBRARY" to INTERNAL_TEST_LIBRARY,
@@ -121,20 +122,16 @@
}
}
- open class PublishedLibrary(allowCallingVisibleForTestsApis: Boolean = false) :
+ open class PublishedLibrary(
+ allowCallingVisibleForTestsApis: Boolean = false,
+ targetsKotlinConsumersOnly: Boolean = false
+ ) :
LibraryType(
publish = Publish.SNAPSHOT_AND_RELEASE,
sourceJars = true,
checkApi = RunApiTasks.Yes(),
- allowCallingVisibleForTestsApis = allowCallingVisibleForTestsApis
- )
-
- open class PublishedKotlinOnlyLibrary(allowCallingVisibleForTestsApis: Boolean = false) :
- LibraryType(
- publish = Publish.SNAPSHOT_AND_RELEASE,
- sourceJars = true,
- checkApi = RunApiTasks.Yes(),
- allowCallingVisibleForTestsApis = allowCallingVisibleForTestsApis
+ allowCallingVisibleForTestsApis = allowCallingVisibleForTestsApis,
+ targetsKotlinConsumersOnly = targetsKotlinConsumersOnly
)
open class InternalLibrary(
@@ -147,10 +144,10 @@
allowCallingVisibleForTestsApis = allowCallingVisibleForTestsApis
)
- class PublishedTestLibrary() : PublishedLibrary(allowCallingVisibleForTestsApis = true)
-
- class PublishedKotlinOnlyTestLibrary() : PublishedKotlinOnlyLibrary(
- allowCallingVisibleForTestsApis = true)
+ class PublishedTestLibrary(targetsKotlinConsumersOnly: Boolean = false) : PublishedLibrary(
+ allowCallingVisibleForTestsApis = true,
+ targetsKotlinConsumersOnly = targetsKotlinConsumersOnly
+ )
class InternalTestLibrary() : InternalLibrary(allowCallingVisibleForTestsApis = true)
diff --git a/buildSrc/settingsScripts/project-dependency-graph.groovy b/buildSrc/settingsScripts/project-dependency-graph.groovy
index 1590a62..5fccfd5 100644
--- a/buildSrc/settingsScripts/project-dependency-graph.groovy
+++ b/buildSrc/settingsScripts/project-dependency-graph.groovy
@@ -295,7 +295,7 @@
private static Pattern composePlugin = Pattern.compile("id\\(\"AndroidXComposePlugin\"\\)")
private static Pattern iconGenerator = Pattern.compile("IconGenerationTask\\.register")
private static Pattern publishedLibrary = Pattern.compile(
- "(type = LibraryType\\.(PUBLISHED_LIBRARY|GRADLE_PLUGIN|ANNOTATION_PROCESSOR|PUBLISHED_KOTLIN_ONLY_LIBRARY)|" +
+ "(type = LibraryType\\.(PUBLISHED_LIBRARY|GRADLE_PLUGIN|ANNOTATION_PROCESSOR|PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS)|" +
"publish = Publish\\.SNAPSHOT_AND_RELEASE)"
)
}
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/EmptyFragment.kt b/buildSrc/stableAidlImports/android/graphics/Bitmap.aidl
similarity index 71%
rename from privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/EmptyFragment.kt
rename to buildSrc/stableAidlImports/android/graphics/Bitmap.aidl
index 3a27fde..5c5744a 100644
--- a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/EmptyFragment.kt
+++ b/buildSrc/stableAidlImports/android/graphics/Bitmap.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-package androidx.privacysandbox.ui.integration.testapp
+package android.graphics;
-class EmptyFragment : BaseFragment() {
- override fun handleDrawerStateChange(isDrawerOpen: Boolean) {
- }
-}
+@JavaOnlyStableParcelable parcelable Bitmap;
diff --git a/camera/camera-camera2-pipe-integration/build.gradle b/camera/camera-camera2-pipe-integration/build.gradle
index d9da277..74db51a 100644
--- a/camera/camera-camera2-pipe-integration/build.gradle
+++ b/camera/camera-camera2-pipe-integration/build.gradle
@@ -27,7 +27,7 @@
id("AndroidXPlugin")
id("com.android.library")
id("kotlin-android")
- id("kotlin-kapt")
+ id("com.google.devtools.ksp")
}
dependencies {
@@ -46,7 +46,7 @@
implementation(project(":camera:camera-camera2-pipe"))
implementation(project(":concurrent:concurrent-futures-ktx"))
- kapt(libs.daggerCompiler)
+ ksp(libs.daggerCompiler)
testImplementation(libs.testCore)
testImplementation(libs.testRunner)
@@ -99,11 +99,9 @@
test.maxParallelForks(2)
}
-kapt {
- javacOptions {
- option("-Adagger.fastInit=enabled")
- option("-Adagger.fullBindingGraphValidation=ERROR")
- }
+ksp {
+ arg("dagger.fastInit", "enabled")
+ arg("dagger.fullBindingGraphValidation", "ERROR")
}
androidx {
diff --git a/camera/camera-camera2-pipe/build.gradle b/camera/camera-camera2-pipe/build.gradle
index 2afb6a4..5949205 100644
--- a/camera/camera-camera2-pipe/build.gradle
+++ b/camera/camera-camera2-pipe/build.gradle
@@ -27,7 +27,7 @@
id("AndroidXPlugin")
id("com.android.library")
id("kotlin-android")
- id("kotlin-kapt")
+ id("com.google.devtools.ksp")
}
dependencies {
@@ -38,7 +38,7 @@
implementation(libs.atomicFu)
implementation(libs.dagger)
- kapt(libs.daggerCompiler)
+ ksp(libs.daggerCompiler)
testImplementation(libs.testCore)
testImplementation(libs.testRunner)
@@ -52,7 +52,7 @@
testImplementation(project(":camera:camera-camera2-pipe-testing"))
testImplementation(project(":internal-testutils-truth"))
- kaptTest(libs.daggerCompiler)
+ kspTest(libs.daggerCompiler)
androidTestImplementation(libs.testExtJunit)
androidTestImplementation(libs.testRunner)
@@ -68,11 +68,9 @@
namespace "androidx.camera.camera2.pipe"
}
-kapt {
- javacOptions {
- option("-Adagger.fastInit=enabled")
- option("-Adagger.fullBindingGraphValidation=ERROR")
- }
+ksp {
+ arg("dagger.fastInit", "enabled")
+ arg("dagger.fullBindingGraphValidation", "ERROR")
}
androidx {
diff --git a/camera/camera-core/src/main/cpp/CMakeLists.txt b/camera/camera-core/src/main/cpp/CMakeLists.txt
index 8293c0f..0e8862c 100644
--- a/camera/camera-core/src/main/cpp/CMakeLists.txt
+++ b/camera/camera-core/src/main/cpp/CMakeLists.txt
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations under
# the License.
#
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.22.1)
project(camera_core_jni)
diff --git a/camera/camera-testing/src/main/cpp/CMakeLists.txt b/camera/camera-testing/src/main/cpp/CMakeLists.txt
index af959ea..1023b50 100644
--- a/camera/camera-testing/src/main/cpp/CMakeLists.txt
+++ b/camera/camera-testing/src/main/cpp/CMakeLists.txt
@@ -14,7 +14,7 @@
# the License.
#
-cmake_minimum_required(VERSION 3.4.1)
+cmake_minimum_required(VERSION 3.22.1)
project(camera_testing_jni)
diff --git a/camera/camera-viewfinder-compose/api/current.txt b/camera/camera-viewfinder-compose/api/current.txt
index ad010a8..518164d 100644
--- a/camera/camera-viewfinder-compose/api/current.txt
+++ b/camera/camera-viewfinder-compose/api/current.txt
@@ -1,8 +1,28 @@
// Signature format: 4.0
package androidx.camera.viewfinder.compose {
+ @androidx.compose.runtime.Stable public interface CoordinateTransformer {
+ method public float[] getTransformMatrix();
+ method public long transform(long);
+ property public float[] transformMatrix;
+ }
+
+ public final class CoordinateTransformerKt {
+ method public static androidx.camera.viewfinder.compose.MutableCoordinateTransformer MutableCoordinateTransformer(optional float[] matrix);
+ }
+
+ public final class IdentityCoordinateTransformer implements androidx.camera.viewfinder.compose.CoordinateTransformer {
+ property public float[] transformMatrix;
+ field public static final androidx.camera.viewfinder.compose.IdentityCoordinateTransformer INSTANCE;
+ }
+
+ public interface MutableCoordinateTransformer extends androidx.camera.viewfinder.compose.CoordinateTransformer {
+ method public void setTransformMatrix(float[]);
+ property public float[] transformMatrix;
+ }
+
public final class ViewfinderKt {
- method @androidx.compose.runtime.Composable public static void Viewfinder(androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest surfaceRequest, androidx.camera.viewfinder.surface.ImplementationMode implementationMode, androidx.camera.viewfinder.surface.TransformationInfo transformationInfo, optional androidx.compose.ui.Modifier modifier);
+ method @androidx.compose.runtime.Composable public static void Viewfinder(androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest surfaceRequest, androidx.camera.viewfinder.surface.ImplementationMode implementationMode, androidx.camera.viewfinder.surface.TransformationInfo transformationInfo, optional androidx.compose.ui.Modifier modifier, optional androidx.camera.viewfinder.compose.MutableCoordinateTransformer? coordinateTransformer);
}
}
diff --git a/camera/camera-viewfinder-compose/api/restricted_current.txt b/camera/camera-viewfinder-compose/api/restricted_current.txt
index ad010a8..518164d 100644
--- a/camera/camera-viewfinder-compose/api/restricted_current.txt
+++ b/camera/camera-viewfinder-compose/api/restricted_current.txt
@@ -1,8 +1,28 @@
// Signature format: 4.0
package androidx.camera.viewfinder.compose {
+ @androidx.compose.runtime.Stable public interface CoordinateTransformer {
+ method public float[] getTransformMatrix();
+ method public long transform(long);
+ property public float[] transformMatrix;
+ }
+
+ public final class CoordinateTransformerKt {
+ method public static androidx.camera.viewfinder.compose.MutableCoordinateTransformer MutableCoordinateTransformer(optional float[] matrix);
+ }
+
+ public final class IdentityCoordinateTransformer implements androidx.camera.viewfinder.compose.CoordinateTransformer {
+ property public float[] transformMatrix;
+ field public static final androidx.camera.viewfinder.compose.IdentityCoordinateTransformer INSTANCE;
+ }
+
+ public interface MutableCoordinateTransformer extends androidx.camera.viewfinder.compose.CoordinateTransformer {
+ method public void setTransformMatrix(float[]);
+ property public float[] transformMatrix;
+ }
+
public final class ViewfinderKt {
- method @androidx.compose.runtime.Composable public static void Viewfinder(androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest surfaceRequest, androidx.camera.viewfinder.surface.ImplementationMode implementationMode, androidx.camera.viewfinder.surface.TransformationInfo transformationInfo, optional androidx.compose.ui.Modifier modifier);
+ method @androidx.compose.runtime.Composable public static void Viewfinder(androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest surfaceRequest, androidx.camera.viewfinder.surface.ImplementationMode implementationMode, androidx.camera.viewfinder.surface.TransformationInfo transformationInfo, optional androidx.compose.ui.Modifier modifier, optional androidx.camera.viewfinder.compose.MutableCoordinateTransformer? coordinateTransformer);
}
}
diff --git a/camera/camera-viewfinder-compose/build.gradle b/camera/camera-viewfinder-compose/build.gradle
index 91f4586..18fc9d7 100644
--- a/camera/camera-viewfinder-compose/build.gradle
+++ b/camera/camera-viewfinder-compose/build.gradle
@@ -51,7 +51,7 @@
androidx {
name = "androidx.camera:camera-viewfinder-compose"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2023"
description = "Composable ViewFinder implementation for CameraX"
metalavaK2UastEnabled = true
diff --git a/camera/camera-viewfinder-compose/src/androidTest/kotlin/androidx/camera/viewfinder/compose/CoordinateTransformerTest.kt b/camera/camera-viewfinder-compose/src/androidTest/kotlin/androidx/camera/viewfinder/compose/CoordinateTransformerTest.kt
new file mode 100644
index 0000000..68a9878
--- /dev/null
+++ b/camera/camera-viewfinder-compose/src/androidTest/kotlin/androidx/camera/viewfinder/compose/CoordinateTransformerTest.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package androidx.camera.viewfinder.compose
+
+import androidx.compose.ui.geometry.Offset
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class CoordinateTransformerTest {
+
+ @Test
+ fun transform_withoutChangingMatrix_shouldReturnSameOffset() {
+ val transformer = MutableCoordinateTransformer()
+
+ val offset = Offset(10f, 10f)
+
+ with(transformer) {
+ val cameraSpaceOffset = offset.transform()
+ assertThat(cameraSpaceOffset).isEqualTo(offset)
+ }
+ }
+
+ @Test
+ fun transform_withMatrix() {
+ val transformer = MutableCoordinateTransformer()
+
+ val offset = Offset(10f, 10f)
+ transformer.transformMatrix.scale(2.0f, 2.0f)
+
+ with(transformer) {
+ val cameraSpaceOffset = offset.transform()
+ assertThat(cameraSpaceOffset).isEqualTo(Offset(20f, 20f))
+ }
+ }
+}
diff --git a/camera/camera-viewfinder-compose/src/androidTest/kotlin/androidx/camera/viewfinder/compose/ViewfinderTest.kt b/camera/camera-viewfinder-compose/src/androidTest/kotlin/androidx/camera/viewfinder/compose/ViewfinderTest.kt
index c799604..4e41191 100644
--- a/camera/camera-viewfinder-compose/src/androidTest/kotlin/androidx/camera/viewfinder/compose/ViewfinderTest.kt
+++ b/camera/camera-viewfinder-compose/src/androidTest/kotlin/androidx/camera/viewfinder/compose/ViewfinderTest.kt
@@ -26,6 +26,8 @@
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
@@ -57,6 +59,80 @@
assertCanRetrieveSurface(implementationMode = ImplementationMode.COMPATIBLE)
}
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
+ @Test
+ fun coordinatesTransformationSameSizeNoRotation(): Unit = runBlocking {
+
+ val coordinateTransformer = MutableCoordinateTransformer()
+
+ val surfaceRequest = ViewfinderSurfaceRequest.Builder(TEST_RESOLUTION).build()
+ rule.setContent {
+ with(LocalDensity.current) {
+ Viewfinder(
+ modifier = Modifier.size(540.toDp(), 960.toDp()),
+ surfaceRequest = surfaceRequest,
+ transformationInfo = TEST_TRANSFORMATION_INFO,
+ implementationMode = ImplementationMode.PERFORMANCE,
+ coordinateTransformer = coordinateTransformer
+ )
+ }
+ }
+
+ val expectedMatrix = Matrix(
+ values = floatArrayOf(
+ 1f, 0f, 0f, 0f,
+ 0f, 1f, 0f, 0f,
+ 0f, 0f, 1f, 0f,
+ 0f, 0f, 0f, 1f
+ )
+ )
+
+ assertThat(coordinateTransformer.transformMatrix.values)
+ .isEqualTo(expectedMatrix.values)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
+ @Test
+ fun coordinatesTransformationSameSizeWithHalfCrop(): Unit = runBlocking {
+ // Viewfinder size: 1080x1920
+ // Surface size: 1080x1920
+ // Crop rect size: 540x960
+
+ val coordinateTransformer = MutableCoordinateTransformer()
+
+ val surfaceRequest = ViewfinderSurfaceRequest.Builder(TEST_RESOLUTION).build()
+ rule.setContent {
+ with(LocalDensity.current) {
+ Viewfinder(
+ modifier = Modifier.size(540.toDp(), 960.toDp()),
+ surfaceRequest = surfaceRequest,
+ transformationInfo = TransformationInfo(
+ sourceRotation = 0,
+ cropRectLeft = 0,
+ cropRectRight = 270,
+ cropRectTop = 0,
+ cropRectBottom = 480,
+ shouldMirror = false
+ ),
+ implementationMode = ImplementationMode.PERFORMANCE,
+ coordinateTransformer = coordinateTransformer
+ )
+ }
+ }
+
+ val expectedMatrix = Matrix(
+ values = floatArrayOf(
+ 0.5f, 0f, 0f, 0f,
+ 0f, 0.5f, 0f, 0f,
+ 0f, 0f, 1f, 0f,
+ 0f, 0f, 0f, 1f
+ )
+ )
+
+ assertThat(coordinateTransformer.transformMatrix.values)
+ .isEqualTo(expectedMatrix.values)
+ }
+
@RequiresApi(Build.VERSION_CODES.M) // Needed for Surface.lockHardwareCanvas()
private suspend fun assertCanRetrieveSurface(implementationMode: ImplementationMode) {
val surfaceDeferred = CompletableDeferred<Surface>()
@@ -87,7 +163,7 @@
companion object {
val TEST_VIEWFINDER_SIZE = DpSize(360.dp, 640.dp)
- val TEST_RESOLUTION = Size(1080, 1920)
+ val TEST_RESOLUTION = Size(540, 960)
val TEST_TRANSFORMATION_INFO = TransformationInfo(
sourceRotation = 0,
cropRectLeft = 0,
diff --git a/camera/camera-viewfinder-compose/src/main/java/androidx/camera/viewfinder/compose/CoordinateTransformer.kt b/camera/camera-viewfinder-compose/src/main/java/androidx/camera/viewfinder/compose/CoordinateTransformer.kt
new file mode 100644
index 0000000..93d589d
--- /dev/null
+++ b/camera/camera-viewfinder-compose/src/main/java/androidx/camera/viewfinder/compose/CoordinateTransformer.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.viewfinder.compose
+
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Matrix
+
+/**
+ * Coordinate transformer that's used to convert coordinates from one space to another.
+ *
+ * [transformMatrix] must be set by whoever manipulates the coordinate space, otherwise an identity
+ * will be used for the coordinate transformations. When used in a [Viewfinder], the viewfinder
+ * will set this transform matrix.
+ *
+ */
+@Stable
+interface CoordinateTransformer {
+ /**
+ * Matrix that's used for coordinate transformations.
+ */
+ val transformMatrix: Matrix
+
+ /**
+ * Returns the [Offset] in the transformed space.
+ */
+ fun Offset.transform() = transformMatrix.map(this)
+}
+
+/**
+ * CoordinateTransformer where the transformMatrix is mutable.
+ */
+interface MutableCoordinateTransformer : CoordinateTransformer {
+ override var transformMatrix: Matrix
+}
+
+/**
+ * Creates a [MutableCoordinateTransformer] with the given matrix as the transformMatrix.
+ */
+fun MutableCoordinateTransformer(matrix: Matrix = Matrix()): MutableCoordinateTransformer =
+ MutableCoordinateTransformerImpl(matrix)
+
+private class MutableCoordinateTransformerImpl(
+ initialMatrix: Matrix
+) : MutableCoordinateTransformer {
+ override var transformMatrix: Matrix by mutableStateOf(initialMatrix)
+}
+
+/**
+ * [CoordinateTransformer] where the transformMatrix is the identity matrix.
+ */
+object IdentityCoordinateTransformer : CoordinateTransformer {
+ override val transformMatrix = Matrix()
+
+ override fun Offset.transform() = this
+}
diff --git a/camera/camera-viewfinder-compose/src/main/java/androidx/camera/viewfinder/compose/Viewfinder.kt b/camera/camera-viewfinder-compose/src/main/java/androidx/camera/viewfinder/compose/Viewfinder.kt
index 266c716a..0d055c2 100644
--- a/camera/camera-viewfinder-compose/src/main/java/androidx/camera/viewfinder/compose/Viewfinder.kt
+++ b/camera/camera-viewfinder-compose/src/main/java/androidx/camera/viewfinder/compose/Viewfinder.kt
@@ -17,6 +17,7 @@
package androidx.camera.viewfinder.compose
import android.annotation.SuppressLint
+import android.graphics.RectF
import android.util.Size
import android.view.Surface
import androidx.camera.viewfinder.compose.internal.SurfaceTransformationUtil
@@ -55,6 +56,10 @@
* @param implementationMode Determines the underlying implementation of the [Surface].
* @param transformationInfo Specifies the required transformations for the media being displayed.
* @param modifier Modifier to be applied to the [Viewfinder]
+ * @param coordinateTransformer Coordinate transformer that can be used to convert Compose space
+ * coordinates such as touch coordinates to surface space coordinates.
+ * When the Viewfinder is displaying content from the camera, this transformer can be used to
+ * translate touch events into camera sensor coordinates for focus and metering actions.
*
* TODO(b/322420487): Add a sample with `@sample`
*/
@@ -63,7 +68,8 @@
surfaceRequest: ViewfinderSurfaceRequest,
implementationMode: ImplementationMode,
transformationInfo: TransformationInfo,
- modifier: Modifier = Modifier
+ modifier: Modifier = Modifier,
+ coordinateTransformer: MutableCoordinateTransformer? = null,
) {
val resolution = surfaceRequest.resolution
@@ -85,6 +91,7 @@
// TODO(b/322420176): Properly handle onSurfaceChanged()
},
+ coordinateTransformer,
)
}
}
@@ -107,6 +114,7 @@
transformationInfo: TransformationInfo,
implementationMode: ImplementationMode,
onInit: AndroidExternalSurfaceScope.() -> Unit,
+ coordinateTransformer: MutableCoordinateTransformer?,
) {
// For TextureView, correct the orientation to match the target rotation.
val correctionMatrix = Matrix()
@@ -134,13 +142,25 @@
val heightOffset = 0.coerceAtLeast((placeable.height - constraints.maxHeight) / 2)
layout(placeable.width, placeable.height) {
placeable.placeWithLayer(widthOffset, heightOffset) {
- val surfaceRectInViewfinder =
- SurfaceTransformationUtil.getTransformedSurfaceRect(
- resolution,
+ val surfaceToViewFinderMatrix =
+ SurfaceTransformationUtil.getTransformedSurfaceMatrix(
transformationInfo,
Size(constraints.maxWidth, constraints.maxHeight)
)
+ coordinateTransformer?.transformMatrix = Matrix().apply {
+ setFrom(surfaceToViewFinderMatrix)
+ invert()
+ }
+
+ val surfaceRectInViewfinder = RectF(
+ 0f,
+ 0f,
+ resolution.width.toFloat(),
+ resolution.height.toFloat()
+ )
+ surfaceToViewFinderMatrix.mapRect(surfaceRectInViewfinder)
+
transformOrigin = TransformOrigin(0f, 0f)
scaleX = surfaceRectInViewfinder.width() / resolution.width
scaleY = surfaceRectInViewfinder.height() / resolution.height
diff --git a/camera/camera-viewfinder-compose/src/main/java/androidx/camera/viewfinder/compose/internal/SurfaceTransformationUtil.kt b/camera/camera-viewfinder-compose/src/main/java/androidx/camera/viewfinder/compose/internal/SurfaceTransformationUtil.kt
index f2a118d..41134e2 100644
--- a/camera/camera-viewfinder-compose/src/main/java/androidx/camera/viewfinder/compose/internal/SurfaceTransformationUtil.kt
+++ b/camera/camera-viewfinder-compose/src/main/java/androidx/camera/viewfinder/compose/internal/SurfaceTransformationUtil.kt
@@ -174,18 +174,16 @@
return matrix
}
- fun getTransformedSurfaceRect(
- resolution: Size,
+ fun getTransformedSurfaceMatrix(
transformationInfo: TransformationInfo,
viewfinderSize: Size,
- ): RectF {
+ ): Matrix {
val surfaceToViewFinder: Matrix =
getSurfaceToViewFinderMatrix(
viewfinderSize,
transformationInfo,
)
- val rect = RectF(0f, 0f, resolution.width.toFloat(), resolution.height.toFloat())
- surfaceToViewFinder.mapRect(rect)
- return rect
+
+ return surfaceToViewFinder
}
}
diff --git a/camera/integration-tests/coretestapp/src/main/cpp/CMakeLists.txt b/camera/integration-tests/coretestapp/src/main/cpp/CMakeLists.txt
index 1f9ba23..680736f 100644
--- a/camera/integration-tests/coretestapp/src/main/cpp/CMakeLists.txt
+++ b/camera/integration-tests/coretestapp/src/main/cpp/CMakeLists.txt
@@ -14,7 +14,7 @@
# the License.
#
-cmake_minimum_required(VERSION 3.4.1)
+cmake_minimum_required(VERSION 3.22.1)
project(camera_test_app_jni)
diff --git a/collection/collection-ktx/build.gradle b/collection/collection-ktx/build.gradle
index 1cdeaee..7bdd853 100644
--- a/collection/collection-ktx/build.gradle
+++ b/collection/collection-ktx/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -34,7 +34,7 @@
androidx {
name = "Collections Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2018"
description = "Kotlin extensions for 'collection' artifact"
metalavaK2UastEnabled = true
diff --git a/compose/animation/animation-core/build.gradle b/compose/animation/animation-core/build.gradle
index d13c916..1258389 100644
--- a/compose/animation/animation-core/build.gradle
+++ b/compose/animation/animation-core/build.gradle
@@ -122,7 +122,7 @@
androidx {
name = "Compose Animation Core"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2019"
description = "Animation engine and animation primitives that are the building blocks of the Compose animation library"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/animation/animation-core/src/androidInstrumentedTest/kotlin/androidx/compose/animation/core/SeekableTransitionStateTest.kt b/compose/animation/animation-core/src/androidInstrumentedTest/kotlin/androidx/compose/animation/core/SeekableTransitionStateTest.kt
index f2101f2..d5992f9 100644
--- a/compose/animation/animation-core/src/androidInstrumentedTest/kotlin/androidx/compose/animation/core/SeekableTransitionStateTest.kt
+++ b/compose/animation/animation-core/src/androidInstrumentedTest/kotlin/androidx/compose/animation/core/SeekableTransitionStateTest.kt
@@ -51,6 +51,7 @@
import androidx.test.filters.SdkSuppress
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.android.awaitFrame
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
@@ -2390,4 +2391,81 @@
}
}
}
+
+ @Test
+ fun isRunningDuringAnimateTo() {
+ val seekableTransitionState = SeekableTransitionState(AnimStates.From)
+ lateinit var transition: Transition<AnimStates>
+ var animatedValue by mutableIntStateOf(-1)
+
+ rule.mainClock.autoAdvance = false
+
+ rule.setContent {
+ LaunchedEffect(seekableTransitionState) {
+ seekableTransitionState.animateTo(AnimStates.To)
+ }
+ transition = rememberTransition(seekableTransitionState, label = "Test")
+ animatedValue = transition.animateInt(
+ label = "Value",
+ transitionSpec = { tween(easing = LinearEasing) }
+ ) { state ->
+ when (state) {
+ AnimStates.From -> 0
+ else -> 1000
+ }
+ }.value
+ }
+ rule.runOnIdle {
+ assertEquals(0, animatedValue)
+ assertFalse(transition.isRunning)
+ }
+ rule.mainClock.advanceTimeByFrame() // wait for composition after animateTo()
+ rule.mainClock.advanceTimeByFrame() // one frame to set the start time
+ rule.runOnIdle {
+ assertTrue(animatedValue > 0)
+ assertTrue(transition.isRunning)
+ }
+ rule.mainClock.advanceTimeBy(5000)
+ rule.runOnIdle {
+ assertEquals(1000, animatedValue)
+ assertFalse(transition.isRunning)
+ }
+ }
+
+ @Test
+ fun isRunningFalseAfterSnapTo() {
+ val seekableTransitionState = SeekableTransitionState(AnimStates.From)
+ lateinit var transition: Transition<AnimStates>
+ var animatedValue by mutableIntStateOf(-1)
+
+ rule.mainClock.autoAdvance = false
+
+ rule.setContent {
+ LaunchedEffect(seekableTransitionState) {
+ awaitFrame() // Not sure why this is needed. Animated val doesn't change without it.
+ seekableTransitionState.snapTo(AnimStates.To)
+ }
+ transition = rememberTransition(seekableTransitionState, label = "Test")
+ animatedValue = transition.animateInt(
+ label = "Value",
+ transitionSpec = { tween(easing = LinearEasing) }
+ ) { state ->
+ when (state) {
+ AnimStates.From -> 0
+ else -> 1000
+ }
+ }.value
+ }
+ rule.runOnIdle {
+ assertEquals(0, animatedValue)
+ assertFalse(transition.isRunning)
+ }
+ rule.mainClock.advanceTimeByFrame() // wait for composition after animateTo()
+ rule.mainClock.advanceTimeByFrame() // one frame to snap
+ rule.mainClock.advanceTimeByFrame() // one frame for LaunchedEffect's awaitFrame()
+ rule.runOnIdle {
+ assertEquals(1000, animatedValue)
+ assertFalse(transition.isRunning)
+ }
+ }
}
diff --git a/compose/animation/animation-core/src/androidUnitTest/kotlin/androidx/compose/animation/core/AnimatableTest.kt b/compose/animation/animation-core/src/androidUnitTest/kotlin/androidx/compose/animation/core/AnimatableTest.kt
index 051d544..39555aa 100644
--- a/compose/animation/animation-core/src/androidUnitTest/kotlin/androidx/compose/animation/core/AnimatableTest.kt
+++ b/compose/animation/animation-core/src/androidUnitTest/kotlin/androidx/compose/animation/core/AnimatableTest.kt
@@ -21,6 +21,7 @@
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.IntSize
import com.google.common.truth.Truth.assertThat
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertFalse
@@ -309,6 +310,48 @@
}
@Test
+ fun testIntSize_alwaysWithinValidBounds() {
+ val animatable = Animatable(
+ initialValue = IntSize(10, 10),
+ typeConverter = IntSize.VectorConverter,
+ visibilityThreshold = IntSize.VisibilityThreshold
+ )
+
+ val values = mutableListOf<IntSize>()
+
+ runBlocking {
+ val clock = SuspendAnimationTest.TestFrameClock()
+
+ // Add frames to evaluate at
+ clock.frame(0L)
+ clock.frame(25L * 1_000_000L)
+ clock.frame(75L * 1_000_000L)
+ clock.frame(100L * 1_000_000L)
+
+ withContext(clock) {
+ // Animate linearly from -100 to 100
+ animatable.animateTo(
+ IntSize(100, 100),
+ keyframes {
+ durationMillis = 100
+ IntSize(-100, -100) at 0 using LinearEasing
+ }
+ ) {
+ values.add(value)
+ }
+ }
+ }
+
+ // The internal animation is expected to be: -100, -50, 50, 100. But for IntSize, we don't
+ // support negative values, so it's clamped to Zero
+ assertEquals(4, values.size)
+ assertEquals(IntSize.Zero, values[0])
+ assertEquals(IntSize.Zero, values[1])
+ assertEquals(IntSize(50, 50), values[2])
+ assertEquals(IntSize(100, 100), values[3])
+ }
+
+ @Test
fun animationResult_toString() {
val animatable = AnimationResult(
endReason = AnimationEndReason.Finished,
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
index fc75648..ba2fc7d 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
@@ -475,6 +475,7 @@
// the correct animation values
waitForCompositionAfterTargetStateChange()
}
+ transition.onTransitionEnd()
}
}
@@ -689,6 +690,7 @@
currentState = targetState
waitForComposition()
fraction = 0f
+ transition.onTransitionEnd()
}
}
}
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorConverters.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorConverters.kt
index 6455afd..d197dc0 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorConverters.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorConverters.kt
@@ -124,6 +124,8 @@
/**
* A type converter that converts a [IntSize] to a [AnimationVector2D], and vice versa.
+ *
+ * Clamps negative values to zero when converting back to [IntSize].
*/
val IntSize.Companion.VectorConverter: TwoWayConverter<IntSize, AnimationVector2D>
get() = IntSizeToVector
@@ -174,11 +176,18 @@
/**
* A type converter that converts a [IntSize] to a [AnimationVector2D], and vice versa.
+ *
+ * Clamps negative values to zero when converting back to [IntSize].
*/
private val IntSizeToVector: TwoWayConverter<IntSize, AnimationVector2D> =
TwoWayConverter(
{ AnimationVector2D(it.width.toFloat(), it.height.toFloat()) },
- { IntSize(it.v1.fastRoundToInt(), it.v2.fastRoundToInt()) }
+ {
+ IntSize(
+ width = it.v1.fastRoundToInt().coerceAtLeast(0),
+ height = it.v2.fastRoundToInt().coerceAtLeast(0)
+ )
+ }
)
/**
diff --git a/compose/animation/animation-graphics/build.gradle b/compose/animation/animation-graphics/build.gradle
index 15c9724..7a464f2 100644
--- a/compose/animation/animation-graphics/build.gradle
+++ b/compose/animation/animation-graphics/build.gradle
@@ -119,7 +119,7 @@
androidx {
name = "Compose Animation Graphics"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2021"
description = "Compose Animation Graphics Library for using animated-vector resources in Compose"
samples(project(":compose:animation:animation-graphics:animation-graphics-samples"))
diff --git a/compose/animation/animation-tooling-internal/build.gradle b/compose/animation/animation-tooling-internal/build.gradle
index dc22417..a5f56b2 100644
--- a/compose/animation/animation-tooling-internal/build.gradle
+++ b/compose/animation/animation-tooling-internal/build.gradle
@@ -21,9 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
-import androidx.build.RunApiTasks
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -37,8 +35,7 @@
androidx {
name = "Compose Animation Tooling"
description = "Compose Animation APIs for tooling support. Internal use only."
- publish = Publish.SNAPSHOT_AND_RELEASE
- runApiTasks = new RunApiTasks.Yes()
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
metalavaK2UastEnabled = true
doNotDocumentReason = "Only used externally by Android Studio"
}
diff --git a/compose/animation/animation/build.gradle b/compose/animation/animation/build.gradle
index cff8085..c4a6998 100644
--- a/compose/animation/animation/build.gradle
+++ b/compose/animation/animation/build.gradle
@@ -122,7 +122,7 @@
androidx {
name = "Compose Animation"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2019"
description = "Compose animation library"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/sharedelement/ContainerTransformDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/sharedelement/ContainerTransformDemo.kt
index a0f83f8..9f41de50 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/sharedelement/ContainerTransformDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/sharedelement/ContainerTransformDemo.kt
@@ -18,6 +18,7 @@
package androidx.compose.animation.demos.sharedelement
+import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.ExperimentalSharedTransitionApi
@@ -34,6 +35,8 @@
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
@@ -55,7 +58,6 @@
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Favorite
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -70,22 +72,14 @@
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import kotlinx.coroutines.delay
@Preview
@Composable
fun ContainerTransformDemo(model: MyModel = remember { MyModel().apply { selected = items[1] } }) {
+ BackHandler {
+ model.selected = null
+ }
SharedTransitionLayout {
- LaunchedEffect(key1 = Unit) {
- while (true) {
- delay(2500)
- if (model.selected == null) {
- model.selected = model.items[1]
- } else {
- model.selected = null
- }
- }
- }
AnimatedContent(
model.selected,
transitionSpec = {
@@ -213,14 +207,22 @@
) {
Column(
Modifier
+ .clickable(
+ interactionSource = remember {
+ MutableInteractionSource()
+ },
+ indication = null
+ ) {
+ model.selected = null
+ }
.sharedBounds(
rememberSharedContentState(
key = "container + ${selected.id}"
),
this@AnimatedVisibilityScope,
- scaleInSharedContentToBounds(contentScale = ContentScale.Crop),
- scaleOutSharedContentToBounds(contentScale = ContentScale.Crop),
- clipInOverlayDuringTransition = OverlayClip(RoundedCornerShape(20.dp))
+ scaleInSharedContentToBounds(contentScale = ContentScale.Crop) + fadeIn(),
+ scaleOutSharedContentToBounds(contentScale = ContentScale.Crop) + fadeOut(),
+ clipInOverlayDuringTransition = OverlayClip(RoundedCornerShape(20.dp)),
)
) {
Row(Modifier.fillMaxHeight(0.5f)) {
@@ -274,7 +276,11 @@
contentPadding = PaddingValues(top = 90.dp)
) {
items(6) {
- KittyItem(model.items[it])
+ Box(modifier = Modifier.clickable {
+ model.selected = model.items[it]
+ }) {
+ KittyItem(model.items[it])
+ }
}
}
}
@@ -286,8 +292,8 @@
Kitty("油条", R.drawable.yt_profile, "Tabby", 1),
Kitty("Cowboy", R.drawable.cowboy, "American Short Hair", 2),
Kitty("Pepper", R.drawable.pepper, "Tabby", 3),
- Kitty("Unknown", R.drawable.question_mark, "Unknown", 4),
- Kitty("Unknown", R.drawable.question_mark, "Unknown", 5),
+ Kitty("Unknown", R.drawable.question_mark, "Unknown Breed", 4),
+ Kitty("Unknown", R.drawable.question_mark, "Unknown Breed", 5),
Kitty("YT", R.drawable.yt_profile2, "Tabby", 6),
)
var selected: Kitty? by mutableStateOf(null)
@@ -301,7 +307,7 @@
.padding(start = 10.dp, end = 10.dp, bottom = 10.dp)
.sharedBounds(
rememberSharedContentState(key = "container + ${kitty.id}"),
- this@AnimatedVisibilityScope
+ this@AnimatedVisibilityScope,
)
.background(Color.White, RoundedCornerShape(20.dp))
) {
@@ -317,7 +323,6 @@
)
.aspectRatio(1f)
.clip(RoundedCornerShape(20.dp))
- .background(Color(0xffaaaaaa))
)
Spacer(Modifier.size(10.dp))
Text(
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/res/drawable/question_mark.xml b/compose/animation/animation/integration-tests/animation-demos/src/main/res/drawable/question_mark.xml
index 929ae8a..f87589f 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/res/drawable/question_mark.xml
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/res/drawable/question_mark.xml
@@ -17,5 +17,6 @@
<vector android:height="12dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="12dp" xmlns:android="http://schemas.android.com/apk/res/android">
+<path android:pathData="M0,0h24v24H0V0z" android:fillColor="#aaaaaa" />
<path android:fillColor="#CCCCCC" android:pathData="M11.07,12.85c0.77,-1.39 2.25,-2.21 3.11,-3.44c0.91,-1.29 0.4,-3.7 -2.18,-3.7c-1.69,0 -2.52,1.28 -2.87,2.34L6.54,6.96C7.25,4.83 9.18,3 11.99,3c2.35,0 3.96,1.07 4.78,2.41c0.7,1.15 1.11,3.3 0.03,4.9c-1.2,1.77 -2.35,2.31 -2.97,3.45c-0.25,0.46 -0.35,0.76 -0.35,2.24h-2.89C10.58,15.22 10.46,13.95 11.07,12.85zM14,20c0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2c0,-1.1 0.9,-2 2,-2S14,18.9 14,20z"/>
</vector>
\ No newline at end of file
diff --git a/compose/desktop/desktop/build.gradle b/compose/desktop/desktop/build.gradle
index 0dc6488..996864d 100644
--- a/compose/desktop/desktop/build.gradle
+++ b/compose/desktop/desktop/build.gradle
@@ -97,7 +97,7 @@
androidx {
name = "Compose Desktop"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
publish = Publish.SNAPSHOT_AND_RELEASE
inceptionYear = "2020"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/foundation/foundation-layout/build.gradle b/compose/foundation/foundation-layout/build.gradle
index 33f9f46..6bbd065 100644
--- a/compose/foundation/foundation-layout/build.gradle
+++ b/compose/foundation/foundation-layout/build.gradle
@@ -115,7 +115,7 @@
androidx {
name = "Compose Layouts"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2019"
description = "Compose layout implementations"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/BasicTextField2ToggleTextBenchmark.kt b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/BasicTextField2ToggleTextBenchmark.kt
new file mode 100644
index 0000000..bb2d93c
--- /dev/null
+++ b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/BasicTextField2ToggleTextBenchmark.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.benchmark.text
+
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.benchmark.benchmarkFirstCompose
+import androidx.compose.testutils.benchmark.benchmarkFirstDraw
+import androidx.compose.testutils.benchmark.benchmarkFirstLayout
+import androidx.compose.testutils.benchmark.benchmarkFirstMeasure
+import androidx.compose.testutils.benchmark.benchmarkLayoutPerf
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkDraw
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkLayout
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkMeasure
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkRecompose
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.text.benchmark.TextBenchmarkTestRule
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.test.filters.SmallTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@SmallTest
+@RunWith(Parameterized::class)
+class BasicTextField2ToggleTextBenchmark(
+ private val textLength: Int
+) {
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "length={0}")
+ fun initParameters(): Array<Any> = arrayOf(32, 512).filterForCi()
+ }
+
+ private val textBenchmarkRule = TextBenchmarkTestRule()
+ private val benchmarkRule = ComposeBenchmarkRule()
+
+ @get:Rule
+ val testRule = RuleChain
+ .outerRule(textBenchmarkRule)
+ .around(benchmarkRule)
+
+ private val width = textBenchmarkRule.widthDp.dp
+ private val fontSize = textBenchmarkRule.fontSizeSp.sp
+
+ private val caseFactory = {
+ textBenchmarkRule.generator { generator ->
+ BasicTextField2ToggleTextTestCase(
+ textGenerator = generator,
+ textLength = textLength,
+ textNumber = textBenchmarkRule.repeatTimes,
+ width = width,
+ fontSize = fontSize
+ )
+ }
+ }
+
+ /**
+ * Measure the time taken to compose a [BasicTextField] composable from scratch with the
+ * given input. This is the time taken to call the [BasicTextField] composable function.
+ */
+ @Test
+ fun first_compose() {
+ benchmarkRule.benchmarkFirstCompose(caseFactory)
+ }
+
+ /**
+ * Measure the time taken by the first time measure the [BasicTextField] composable with the
+ * given input. This is mainly the time used to measure all the [Measurable]s in the
+ * [BasicTextField] composable.
+ */
+ @Test
+ fun first_measure() {
+ benchmarkRule.benchmarkFirstMeasure(caseFactory)
+ }
+
+ /**
+ * Measure the time taken by the first time layout the [BasicTextField] composable with the
+ * given input.
+ */
+ @Test
+ fun first_layout() {
+ benchmarkRule.benchmarkFirstLayout(caseFactory)
+ }
+
+ /**
+ * Measure the time taken by first time draw the [BasicTextField] composable with the given
+ * input.
+ */
+ @Test
+ fun first_draw() {
+ benchmarkRule.benchmarkFirstDraw(caseFactory)
+ }
+
+ /**
+ * Measure the time taken by layout the [BasicTextField] composable after the layout
+ * constrains changed. This is mainly the time used to re-measure and re-layout the composable.
+ */
+ @Test
+ fun layout() {
+ benchmarkRule.benchmarkLayoutPerf(caseFactory)
+ }
+
+ /**
+ * Measure the time taken to recompose the [BasicTextField] composable when text gets toggled.
+ */
+ @Test
+ fun toggleText_recompose() {
+ benchmarkRule.toggleStateBenchmarkRecompose(caseFactory, requireRecomposition = false)
+ }
+
+ /**
+ * Measure the time taken to measure the [BasicTextField] composable when text gets toggled.
+ */
+ @Test
+ fun toggleText_measure() {
+ benchmarkRule.toggleStateBenchmarkMeasure(
+ caseFactory = caseFactory,
+ toggleCausesRecompose = false,
+ assertOneRecomposition = false
+ )
+ }
+
+ /**
+ * Measure the time taken to layout the [BasicTextField] composable when text gets toggled.
+ */
+ @Test
+ fun toggleText_layout() {
+ benchmarkRule.toggleStateBenchmarkLayout(
+ caseFactory = caseFactory,
+ assertOneRecomposition = false,
+ toggleCausesRecompose = false
+ )
+ }
+
+ /**
+ * Measure the time taken to draw the [BasicTextField] composable when text gets toggled.
+ */
+ @Test
+ fun toggleText_draw() {
+ benchmarkRule.toggleStateBenchmarkDraw(
+ caseFactory = caseFactory,
+ toggleCausesRecompose = false,
+ assertOneRecomposition = false
+ )
+ }
+}
diff --git a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/BasicTextField2ToggleTextTestCase.kt b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/BasicTextField2ToggleTextTestCase.kt
new file mode 100644
index 0000000..c22686e
--- /dev/null
+++ b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/BasicTextField2ToggleTextTestCase.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("DEPRECATION")
+
+package androidx.compose.foundation.benchmark.text
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.requiredWidth
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.testutils.LayeredComposeTestCase
+import androidx.compose.testutils.ToggleableTestCase
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.InterceptPlatformTextInput
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.benchmark.RandomTextGenerator
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.TextUnit
+import kotlinx.coroutines.awaitCancellation
+
+@OptIn(ExperimentalComposeUiApi::class)
+class BasicTextField2ToggleTextTestCase(
+ private val textGenerator: RandomTextGenerator,
+ private val textLength: Int,
+ private val textNumber: Int,
+ private val width: Dp,
+ private val fontSize: TextUnit
+) : LayeredComposeTestCase(), ToggleableTestCase {
+
+ private val states = List(textNumber) {
+ TextFieldState(textGenerator.nextParagraph(length = textLength))
+ }
+
+ @Composable
+ override fun MeasuredContent() {
+ for (state in states) {
+ BasicTextField(
+ state = state,
+ textStyle = TextStyle(color = Color.Black, fontSize = fontSize),
+ modifier = Modifier
+ .background(color = Color.Cyan)
+ .requiredWidth(width)
+ )
+ }
+ }
+
+ @Composable
+ override fun ContentWrappers(content: @Composable () -> Unit) {
+ Column(
+ modifier = Modifier
+ .width(width)
+ .verticalScroll(rememberScrollState())
+ ) {
+ InterceptPlatformTextInput(
+ interceptor = { _, _ -> awaitCancellation() },
+ content = content
+ )
+ }
+ }
+
+ override fun toggleState() {
+ states.forEach {
+ it.setTextAndPlaceCursorAtEnd(textGenerator.nextParagraph(length = textLength))
+ }
+ }
+}
diff --git a/compose/foundation/foundation/build.gradle b/compose/foundation/foundation/build.gradle
index cd6706e..0d7637f 100644
--- a/compose/foundation/foundation/build.gradle
+++ b/compose/foundation/foundation/build.gradle
@@ -70,7 +70,7 @@
api("androidx.annotation:annotation:1.1.0")
api("androidx.annotation:annotation-experimental:1.4.0")
implementation("androidx.emoji2:emoji2:1.3.0")
- implementation("androidx.core:core:1.12.0")
+ implementation("androidx.core:core:1.13.1")
}
}
@@ -155,7 +155,7 @@
androidx {
name = "Compose Foundation"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2018"
description = "Higher level abstractions of the Compose UI primitives. This library is design system agnostic, providing the high-level building blocks for both application and design-system developers"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldOutputTransformationDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldOutputTransformationDemos.kt
index 9b4690a..a476523 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldOutputTransformationDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldOutputTransformationDemos.kt
@@ -195,7 +195,9 @@
}
if (middleWedge) {
val index = asCharSequence().indexOf("ghi")
- insert(index, middle.text.toString())
+ if (index > 0) {
+ insert(index, middle.text.toString())
+ }
}
if (suffixEnabled) {
append(suffix.text)
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemPlacementAnimationTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemPlacementAnimationTest.kt
index b90192b..237e51f 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemPlacementAnimationTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemPlacementAnimationTest.kt
@@ -596,6 +596,48 @@
}
@Test
+ fun moveItemToTheTop_itemWithMoreChildren_outsideBounds_shouldNotCrash() {
+ var list by mutableStateOf(listOf(0, 1, 2, 3, 4, 5))
+ val listSize = itemSize * 3
+ val listSizeDp = with(rule.density) { listSize.toDp() }
+ rule.setContent {
+ LazyList(
+ maxSize = listSizeDp,
+ startIndex = 3
+ ) {
+ items(list, key = { it }) {
+ Item(it)
+ if (it != list.last()) {
+ Box(modifier = Modifier)
+ }
+ }
+ }
+ }
+
+ assertPositions(
+ 3 to 0f,
+ 4 to itemSize,
+ 5 to itemSize * 2
+ )
+
+ // move item 5 out of bounds
+ rule.runOnUiThread {
+ list = listOf(5, 0, 1, 2, 3, 4)
+ }
+
+ // should not crash
+ onAnimationFrame { fraction ->
+ if (fraction == 1.0f) {
+ assertPositions(
+ 2 to 0f,
+ 3 to itemSize,
+ 4 to itemSize * 2
+ )
+ }
+ }
+ }
+
+ @Test
fun moveItemToTheBottomOutsideOfBounds_withSpacing() {
var list by mutableStateOf(listOf(0, 1, 2, 3, 4, 5))
val listSize = itemSize * 3 + spacing * 2
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt
index 3b003f7..d57ab48 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt
@@ -96,7 +96,10 @@
stickyHeader {
Text(
"Section $section",
- Modifier.fillMaxWidth().background(Color.LightGray).padding(8.dp)
+ Modifier
+ .fillMaxWidth()
+ .background(Color.LightGray)
+ .padding(8.dp)
)
}
items(10) {
@@ -109,9 +112,9 @@
@Sampled
@Composable
fun AnimateItemSample() {
- var list by remember { mutableStateOf(listOf("A", "B", "C")) }
+ var list by remember { mutableStateOf(listOf("1", "2", "3")) }
Column {
- Button(onClick = { list = list + "D" }) {
+ Button(onClick = { list = list + "${list.count() + 1}" }) {
Text("Add new item")
}
Button(onClick = { list = list.shuffled() }) {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/MagnifierTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/MagnifierTest.kt
index c1da808..c49618d5 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/MagnifierTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/MagnifierTest.kt
@@ -409,6 +409,10 @@
fun platformMagnifierModifier_updatesProperties_whenZoomChanged() {
var zoom by mutableStateOf(1f)
val platformMagnifier = CountingPlatformMagnifier()
+ val factory = PlatformMagnifierFactory(
+ platformMagnifier,
+ canUpdateZoom = true
+ )
rule.setContent {
Box(
Modifier.magnifier(
@@ -416,10 +420,7 @@
magnifierCenter = { Offset.Unspecified },
zoom = zoom,
onSizeChanged = null,
- platformMagnifierFactory = PlatformMagnifierFactory(
- platformMagnifier,
- canUpdateZoom = true
- )
+ platformMagnifierFactory = factory
)
)
}
@@ -556,7 +557,7 @@
@SdkSuppress(minSdkVersion = 28)
@Test
- fun platformMagnifierModifier_firesOnSizeChanged_initially_whenSourceCenterUnspecified() {
+ fun platformMagnifierModifier_doesNotFireOnSizeChanged_initially_whenSourceCenterUnspecified() {
val magnifierSize = IntSize(10, 11)
val sizeEvents = mutableListOf<DpSize>()
val platformMagnifier = CountingPlatformMagnifier().apply {
@@ -574,6 +575,34 @@
)
}
+ rule.runOnIdle { assertThat(sizeEvents).isEmpty() }
+ }
+
+ @SdkSuppress(minSdkVersion = 28)
+ @Test
+ fun platformMagnifierModifier_firesOnSizeChanged_afterSourceCenterIsSpecified() {
+ val magnifierSize = IntSize(10, 11)
+ val sizeEvents = mutableListOf<DpSize>()
+ val platformMagnifier = CountingPlatformMagnifier().apply {
+ size = magnifierSize
+ }
+ var sourceCenter by mutableStateOf(Offset.Unspecified)
+ rule.setContent {
+ Box(
+ Modifier.magnifier(
+ sourceCenter = { sourceCenter },
+ magnifierCenter = { Offset.Unspecified },
+ zoom = Float.NaN,
+ onSizeChanged = { sizeEvents += it },
+ platformMagnifierFactory = PlatformMagnifierFactory(platformMagnifier)
+ )
+ )
+ }
+
+ rule.runOnIdle { assertThat(sizeEvents).isEmpty() }
+
+ sourceCenter = Offset(1f, 1f)
+
rule.runOnIdle {
assertThat(sizeEvents).containsExactly(
with(rule.density) {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldHandwritingGestureTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldHandwritingGestureTest.kt
index 5b1a62f..37c4219 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldHandwritingGestureTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldHandwritingGestureTest.kt
@@ -23,22 +23,31 @@
import android.view.inputmethod.InputConnection
import android.view.inputmethod.InsertGesture
import android.view.inputmethod.JoinOrSplitGesture
+import android.view.inputmethod.PreviewableHandwritingGesture
import android.view.inputmethod.RemoveSpaceGesture
import android.view.inputmethod.SelectGesture
import android.view.inputmethod.SelectRangeGesture
import androidx.annotation.RequiresApi
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.setFocusableContent
import androidx.compose.foundation.text.input.InputMethodInterceptor
+import androidx.compose.foundation.text.selection.LocalTextSelectionColors
+import androidx.compose.foundation.text.selection.TextSelectionColors
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertPixelColor
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.toAndroidRectF
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.graphics.toPixelMap
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalTextToolbar
@@ -47,12 +56,15 @@
import androidx.compose.ui.platform.TextToolbarStatus
import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.requestFocus
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.TextFieldValue
+import androidx.core.graphics.ColorUtils
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.filters.SdkSuppress
@@ -72,6 +84,10 @@
private val Tag = "CoreTextField"
+ private val backgroundColor = Color.Red
+ private val textColor = Color.Green
+ private val selectionColor = Color.Blue
+
private val lineMargin = 16f
@Test
@@ -96,6 +112,31 @@
}
@Test
+ fun textField_selectGesture_preview_wordLevel() {
+ val text = "abc def ghi"
+ val initialCursor = 3
+ testHandwritingGesture(
+ text = text,
+ initialSelection = TextRange(initialCursor),
+ gestureFactory = { textLayoutResult ->
+ val localBoundingBox = textLayoutResult.boundingBoxOf("b")
+ val screenBoundingBox = localToScreen(localBoundingBox).toAndroidRectF()
+ SelectGesture.Builder()
+ .setSelectionArea(screenBoundingBox)
+ .setGranularity(HandwritingGesture.GRANULARITY_WORD)
+ .build()
+ },
+ preview = true,
+ imageAssertion = { imageBitmap, textLayoutResult ->
+ imageBitmap.assertSelectionPreviewHighlight(textLayoutResult, text.rangeOf("abc"))
+ }
+ ) { textFieldValue, _, _ ->
+ assertThat(textFieldValue.text).isEqualTo(text)
+ assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+ }
+ }
+
+ @Test
fun textField_selectGesture_characterLevel() {
val text = "abcdef"
testHandwritingGesture(
@@ -117,6 +158,31 @@
}
@Test
+ fun textField_selectGesture_preview_characterLevel() {
+ val text = "abc def ghi"
+ val initialCursor = 3
+ testHandwritingGesture(
+ text = text,
+ initialSelection = TextRange(initialCursor),
+ gestureFactory = { textLayoutResult ->
+ val localBoundingBox = textLayoutResult.boundingBoxOf("bc")
+ val screenBoundingBox = localToScreen(localBoundingBox).toAndroidRectF()
+ SelectGesture.Builder()
+ .setSelectionArea(screenBoundingBox)
+ .setGranularity(HandwritingGesture.GRANULARITY_CHARACTER)
+ .build()
+ },
+ preview = true,
+ imageAssertion = { imageBitmap, textLayoutResult ->
+ imageBitmap.assertSelectionPreviewHighlight(textLayoutResult, text.rangeOf("bc"))
+ }
+ ) { textFieldValue, _, _ ->
+ assertThat(textFieldValue.text).isEqualTo(text)
+ assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+ }
+ }
+
+ @Test
fun textField_selectGesture_characterLevel_noSelection_insertFallbackText() {
val text = "abcdef"
val fallback = "fallbackText"
@@ -161,6 +227,29 @@
}
@Test
+ fun textField_selectGesture_preview_characterLevel_noSelection() {
+ val text = "abcdef"
+ val initialCursor = 3
+ testHandwritingGesture(
+ text = text,
+ initialSelection = TextRange(initialCursor),
+ gestureFactory = { _ ->
+ SelectGesture.Builder()
+ .setSelectionArea(RectF(0f, 0f, 1f, 1f))
+ .setGranularity(HandwritingGesture.GRANULARITY_CHARACTER)
+ .build()
+ },
+ preview = true,
+ imageAssertion = { imageBitmap, textLayoutResult ->
+ imageBitmap.assertNoHighlight(textLayoutResult)
+ }
+ ) { textFieldValue, _, _ ->
+ assertThat(textFieldValue.text).isEqualTo(text)
+ assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+ }
+ }
+
+ @Test
fun textField_selectGesture_wordLevel_noSelection_insertFallbackText() {
val text = "abc def ghi"
val fallback = "fallback"
@@ -211,6 +300,31 @@
}
@Test
+ fun textField_selectGesture_preview_wordLevel_noSelection() {
+ val text = "abc def ghi"
+ val initialCursor = 3
+ testHandwritingGesture(
+ text = text,
+ initialSelection = TextRange(initialCursor),
+ gestureFactory = { textLayoutResult ->
+ val localBoundingBox = textLayoutResult.boundingBoxOf("a")
+ val screenBoundingBox = localToScreen(localBoundingBox).toAndroidRectF()
+ SelectGesture.Builder()
+ .setSelectionArea(screenBoundingBox)
+ .setGranularity(HandwritingGesture.GRANULARITY_WORD)
+ .build()
+ },
+ preview = true,
+ imageAssertion = { imageBitmap, textLayoutResult ->
+ imageBitmap.assertNoHighlight(textLayoutResult)
+ }
+ ) { textFieldValue, _, _ ->
+ assertThat(textFieldValue.text).isEqualTo(text)
+ assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+ }
+ }
+
+ @Test
fun textField_deleteGesture_wordLevel_removeSpaceBeforeDeletion() {
val text = "abc def ghi"
testHandwritingGesture(
@@ -234,6 +348,31 @@
}
@Test
+ fun textField_deleteGesture_preview_wordLevel() {
+ val text = "abc def ghi"
+ val initialCursor = 3
+ testHandwritingGesture(
+ text = text,
+ initialSelection = TextRange(initialCursor),
+ gestureFactory = { textLayoutResult ->
+ val localBoundingBox = textLayoutResult.boundingBoxOf("h")
+ val screenBoundingBox = localToScreen(localBoundingBox).toAndroidRectF()
+ DeleteGesture.Builder()
+ .setDeletionArea(screenBoundingBox)
+ .setGranularity(HandwritingGesture.GRANULARITY_WORD)
+ .build()
+ },
+ preview = true,
+ imageAssertion = { imageBitmap, textLayoutResult ->
+ imageBitmap.assertDeletionPreviewHighlight(textLayoutResult, text.rangeOf("ghi"))
+ }
+ ) { textFieldValue, _, _ ->
+ assertThat(textFieldValue.text).isEqualTo(text)
+ assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+ }
+ }
+
+ @Test
fun textField_deleteGesture_wordLevel_onlyRemoveSpaceBeforeDeletion() {
val text = "abc\n def ghi"
testHandwritingGesture(
@@ -327,6 +466,31 @@
}
@Test
+ fun textField_deleteGesture_preview_characterLevel() {
+ val text = "abcdefghi"
+ val initialCursor = 3
+ testHandwritingGesture(
+ text = text,
+ initialSelection = TextRange(initialCursor),
+ gestureFactory = { textLayoutResult ->
+ val localBoundingBox = textLayoutResult.boundingBoxOf("def")
+ val screenBoundingBox = localToScreen(localBoundingBox).toAndroidRectF()
+ DeleteGesture.Builder()
+ .setDeletionArea(screenBoundingBox)
+ .setGranularity(HandwritingGesture.GRANULARITY_CHARACTER)
+ .build()
+ },
+ preview = true,
+ imageAssertion = { imageBitmap, textLayoutResult ->
+ imageBitmap.assertDeletionPreviewHighlight(textLayoutResult, text.rangeOf("def"))
+ }
+ ) { textFieldValue, _, _ ->
+ assertThat(textFieldValue.text).isEqualTo(text)
+ assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+ }
+ }
+
+ @Test
fun textField_deleteGesture_characterLevel_notRemoveSpaces() {
val text = "abcdef ghi"
testHandwritingGesture(
@@ -397,6 +561,29 @@
}
}
+ @Test
+ fun textField_deleteGesture_preview_noDeletion() {
+ val text = "abc def ghi"
+ val initialCursor = 3
+ testHandwritingGesture(
+ text = text,
+ initialSelection = TextRange(initialCursor),
+ gestureFactory = { _ ->
+ DeleteGesture.Builder()
+ .setDeletionArea(RectF(-1f, -1f, 0f, 0f))
+ .setGranularity(HandwritingGesture.GRANULARITY_WORD)
+ .build()
+ },
+ preview = true,
+ imageAssertion = { imageBitmap, textLayoutResult ->
+ imageBitmap.assertNoHighlight(textLayoutResult)
+ }
+ ) { textFieldValue, _, _ ->
+ assertThat(textFieldValue.text).isEqualTo(text)
+ assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+ }
+ }
+
fun textField_selectRangeGesture_characterLevel() {
val text = "abc\ndef"
testHandwritingGesture(
@@ -425,6 +612,38 @@
}
@Test
+ fun textField_selectRangeGesture_preview_characterLevel() {
+ val text = "abc\ndef"
+ val initialCursor = 3
+ testHandwritingGesture(
+ text = text,
+ initialSelection = TextRange(initialCursor),
+ gestureFactory = { textLayoutResult ->
+ val startArea = textLayoutResult.boundingBoxOf("c").let {
+ localToScreen(it).toAndroidRectF()
+ }
+
+ val endArea = textLayoutResult.boundingBoxOf("d").let {
+ localToScreen(it).toAndroidRectF()
+ }
+
+ SelectRangeGesture.Builder()
+ .setSelectionStartArea(startArea)
+ .setSelectionEndArea(endArea)
+ .setGranularity(HandwritingGesture.GRANULARITY_CHARACTER)
+ .build()
+ },
+ preview = true,
+ imageAssertion = { imageBitmap, textLayoutResult ->
+ imageBitmap.assertSelectionPreviewHighlight(textLayoutResult, text.rangeOf("c\nd"))
+ }
+ ) { textFieldValue, _, _ ->
+ assertThat(textFieldValue.text).isEqualTo(text)
+ assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+ }
+ }
+
+ @Test
fun textField_selectRangeGesture_wordLevel() {
val text = "abc\ndef jhi"
testHandwritingGesture(
@@ -453,6 +672,41 @@
}
@Test
+ fun textField_selectRangeGesture_preview_wordLevel() {
+ val text = "abc\ndef jhi"
+ val initialCursor = 3
+ testHandwritingGesture(
+ text = text,
+ initialSelection = TextRange(initialCursor),
+ gestureFactory = { textLayoutResult ->
+ val startArea = textLayoutResult.boundingBoxOf("b").let {
+ localToScreen(it).toAndroidRectF()
+ }
+
+ val endArea = textLayoutResult.boundingBoxOf("e").let {
+ localToScreen(it).toAndroidRectF()
+ }
+
+ SelectRangeGesture.Builder()
+ .setSelectionStartArea(startArea)
+ .setSelectionEndArea(endArea)
+ .setGranularity(HandwritingGesture.GRANULARITY_WORD)
+ .build()
+ },
+ preview = true,
+ imageAssertion = { imageBitmap, textLayoutResult ->
+ imageBitmap.assertSelectionPreviewHighlight(
+ textLayoutResult,
+ text.rangeOf("abc\ndef")
+ )
+ }
+ ) { textFieldValue, _, _ ->
+ assertThat(textFieldValue.text).isEqualTo(text)
+ assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+ }
+ }
+
+ @Test
fun textField_selectRangeGesture_nothingSelectedInStartArea_insertFallbackText() {
val text = "abc\ndef"
val fallback = "fallbackText"
@@ -515,6 +769,35 @@
}
@Test
+ fun textField_selectRangeGesture_preview_nothingSelectedInStartArea() {
+ val text = "abc\ndef"
+ val initialCursor = 3
+ testHandwritingGesture(
+ text = text,
+ initialSelection = TextRange(initialCursor),
+ gestureFactory = { textLayoutResult ->
+ val endArea = textLayoutResult.boundingBoxOf("d").let {
+ localToScreen(it).toAndroidRectF()
+ }
+ // The startArea selects nothing, but the endArea contains one character, it
+ // should still fallback.
+ SelectRangeGesture.Builder()
+ .setSelectionStartArea(RectF(0f, 0f, 1f, 1f))
+ .setSelectionEndArea(endArea)
+ .setGranularity(HandwritingGesture.GRANULARITY_CHARACTER)
+ .build()
+ },
+ preview = true,
+ imageAssertion = { imageBitmap, textLayoutResult ->
+ imageBitmap.assertNoHighlight(textLayoutResult)
+ }
+ ) { textFieldValue, _, _ ->
+ assertThat(textFieldValue.text).isEqualTo(text)
+ assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+ }
+ }
+
+ @Test
fun textField_selectRangeGesture_noSelection_fail() {
val text = "abcdef"
testHandwritingGesture(
@@ -564,6 +847,37 @@
}
@Test
+ fun textField_deleteRangeGesture_preview_characterLevel() {
+ val text = "abc\ndef"
+ val initialCursor = 3
+ testHandwritingGesture(
+ text = text,
+ initialSelection = TextRange(initialCursor),
+ gestureFactory = { textLayoutResult ->
+ val startArea = textLayoutResult.boundingBoxOf("c").let {
+ localToScreen(it).toAndroidRectF()
+ }
+ val endArea = textLayoutResult.boundingBoxOf("d").let {
+ localToScreen(it).toAndroidRectF()
+ }
+
+ DeleteRangeGesture.Builder()
+ .setDeletionStartArea(startArea)
+ .setDeletionEndArea(endArea)
+ .setGranularity(HandwritingGesture.GRANULARITY_CHARACTER)
+ .build()
+ },
+ preview = true,
+ imageAssertion = { imageBitmap, textLayoutResult ->
+ imageBitmap.assertDeletionPreviewHighlight(textLayoutResult, text.rangeOf("c\nd"))
+ }
+ ) { textFieldValue, _, _ ->
+ assertThat(textFieldValue.text).isEqualTo(text)
+ assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+ }
+ }
+
+ @Test
fun textField_deleteRangeGesture_wordLevel() {
val text = "abc def\n jhi lmn"
testHandwritingGesture(
@@ -592,6 +906,39 @@
}
@Test
+ fun textField_deleteRangeGesture_preview_wordLevel() {
+ val text = "abc def\n jhi lmn"
+ val initialCursor = 3
+ testHandwritingGesture(
+ text = text,
+ initialSelection = TextRange(initialCursor),
+ gestureFactory = { textLayoutResult ->
+ val startArea = textLayoutResult.boundingBoxOf("e").let {
+ localToScreen(it).toAndroidRectF()
+ }
+ val endArea = textLayoutResult.boundingBoxOf("h").let {
+ localToScreen(it).toAndroidRectF()
+ }
+
+ DeleteRangeGesture.Builder()
+ .setDeletionStartArea(startArea)
+ .setDeletionEndArea(endArea)
+ .setGranularity(HandwritingGesture.GRANULARITY_WORD)
+ .build()
+ },
+ preview = true,
+ imageAssertion = { imageBitmap, textLayoutResult ->
+ imageBitmap.assertDeletionPreviewHighlight(
+ textLayoutResult, text.rangeOf("def\n jhi")
+ )
+ }
+ ) { textFieldValue, _, _ ->
+ assertThat(textFieldValue.text).isEqualTo(text)
+ assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+ }
+ }
+
+ @Test
fun textField_deleteRangeGesture_nothingDeletedInStartArea_insertFallbackText() {
val text = "abc\ndef"
val fallback = "fallbackText"
@@ -654,6 +1001,35 @@
}
@Test
+ fun textField_deleteRangeGesture_preview_nothingDeletedInStartArea() {
+ val text = "abc def\n jhi lmn"
+ val initialCursor = 3
+ testHandwritingGesture(
+ text = text,
+ initialSelection = TextRange(initialCursor),
+ gestureFactory = { textLayoutResult ->
+ val endArea = textLayoutResult.boundingBoxOf("d").let {
+ localToScreen(it).toAndroidRectF()
+ }
+ // The startArea selects nothing, but the endArea contains one character, it
+ // should still fallback.
+ DeleteRangeGesture.Builder()
+ .setDeletionStartArea(RectF(0f, 0f, 1f, 1f))
+ .setDeletionEndArea(endArea)
+ .setGranularity(HandwritingGesture.GRANULARITY_CHARACTER)
+ .build()
+ },
+ preview = true,
+ imageAssertion = { imageBitmap, textLayoutResult ->
+ imageBitmap.assertNoHighlight(textLayoutResult)
+ }
+ ) { textFieldValue, _, _ ->
+ assertThat(textFieldValue.text).isEqualTo(text)
+ assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+ }
+ }
+
+ @Test
fun textField_deleteRangeGesture_noDeletion_fail() {
val text = "abcdef"
testHandwritingGesture(
@@ -1268,6 +1644,8 @@
text: String,
initialSelection: TextRange = TextRange(text.length),
gestureFactory: LayoutCoordinates.(TextLayoutResult) -> HandwritingGesture,
+ preview: Boolean = false,
+ imageAssertion: ((ImageBitmap, TextLayoutResult) -> Unit)? = null,
assertion: (TextFieldValue, resultCode: Int, TextToolbar) -> Unit
) {
var textFieldValue by mutableStateOf(TextFieldValue(text, initialSelection))
@@ -1280,14 +1658,20 @@
override val handwritingGestureLineMargin: Float = lineMargin
}
CompositionLocalProvider(
+ LocalTextSelectionColors provides TextSelectionColors(
+ selectionColor,
+ selectionColor
+ ),
LocalTextToolbar provides textToolbar,
LocalViewConfiguration provides viewConfiguration
) {
CoreTextField(
value = textFieldValue,
onValueChange = { textFieldValue = it },
+ textStyle = TextStyle(color = textColor),
modifier = Modifier
.fillMaxSize()
+ .background(backgroundColor)
.testTag(Tag)
.onGloballyPositioned { layoutCoordinates = it },
onTextLayout = {
@@ -1303,12 +1687,20 @@
var resultCode = InputConnection.HANDWRITING_GESTURE_RESULT_UNKNOWN
inputMethodInterceptor.withInputConnection {
- performHandwritingGesture(gesture, /* executor= */null) { resultCode = it }
+ if (preview) {
+ previewHandwritingGesture(gesture as PreviewableHandwritingGesture, null)
+ } else {
+ performHandwritingGesture(gesture, /* executor= */ null) { resultCode = it }
+ }
}
rule.runOnIdle {
assertion.invoke(textFieldValue, resultCode, textToolbar)
}
+
+ if (imageAssertion != null) {
+ imageAssertion(rule.onNodeWithTag(Tag).captureToImage(), textLayoutResult!!)
+ }
}
private fun setContent(
@@ -1327,6 +1719,56 @@
return rect.translate(localOriginInScreen)
}
+ private fun ImageBitmap.assertSelectionPreviewHighlight(
+ textLayoutResult: TextLayoutResult,
+ range: TextRange
+ ) {
+ assertHighlight(textLayoutResult, range, selectionColor)
+ }
+
+ private fun ImageBitmap.assertDeletionPreviewHighlight(
+ textLayoutResult: TextLayoutResult,
+ range: TextRange
+ ) {
+ val deletionPreviewColor = textColor.copy(alpha = textColor.alpha * 0.2f)
+ val compositeColor = Color(
+ ColorUtils.compositeColors(
+ deletionPreviewColor.toArgb(),
+ backgroundColor.toArgb()
+ )
+ )
+ assertHighlight(textLayoutResult, range, compositeColor)
+ }
+
+ private fun ImageBitmap.assertNoHighlight(textLayoutResult: TextLayoutResult) {
+ assertHighlight(textLayoutResult, TextRange.Zero, Color.Unspecified)
+ }
+
+ private fun ImageBitmap.assertHighlight(
+ textLayoutResult: TextLayoutResult,
+ range: TextRange,
+ highlightColor: Color
+ ) {
+ val pixelMap =
+ toPixelMap(width = textLayoutResult.size.width, height = textLayoutResult.size.height)
+ for (offset in 0 until textLayoutResult.layoutInput.text.length) {
+ if (textLayoutResult.layoutInput.text[offset] == '\n') {
+ continue
+ }
+ // Check the top left pixel of each character (assumes LTR). This pixel is always part
+ // of the background (not part of the text foreground).
+ val line = textLayoutResult.multiParagraph.getLineForOffset(offset)
+ val lineTop = textLayoutResult.multiParagraph.getLineTop(line).ceilToIntPx()
+ val horizontal =
+ textLayoutResult.multiParagraph.getHorizontalPosition(offset, true).ceilToIntPx()
+ if (offset in range) {
+ pixelMap.assertPixelColor(highlightColor, horizontal, lineTop)
+ } else {
+ pixelMap.assertPixelColor(backgroundColor, horizontal, lineTop)
+ }
+ }
+ }
+
private fun FakeTextToolbar(): TextToolbar {
return object : TextToolbar {
private var _status: TextToolbarStatus = TextToolbarStatus.Hidden
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldHandwritingTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldHandwritingTest.kt
index 3dc1a55..4931a0a 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldHandwritingTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldHandwritingTest.kt
@@ -39,6 +39,8 @@
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.text.input.ImeOptions
+import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
@@ -55,6 +57,7 @@
@get:Rule
val rule = createComposeRule()
private val inputMethodInterceptor = InputMethodInterceptor(rule)
+ private val keyboardHelper = KeyboardHelper(rule)
private val Tag = "CoreTextField"
@@ -240,6 +243,45 @@
performHandwritingAndExpect(stylusHandwritingStarted = true)
}
+ @Test
+ fun coreTextField_passwordField_notStartStylusHandwriting() {
+ inputMethodManagerFactory = { fakeImm }
+
+ setContent {
+ val value = remember { TextFieldValue() }
+ CoreTextField(
+ value = value,
+ onValueChange = { },
+ imeOptions = ImeOptions(keyboardType = KeyboardType.Password),
+ modifier = Modifier
+ .fillMaxSize()
+ .testTag(Tag),
+ )
+ }
+
+ performHandwritingAndExpect(stylusHandwritingStarted = false)
+ }
+
+ @Test
+ fun coreTextField_passwordField_attemptStylusHandwritingShowSoftInput() {
+ rule.setContent {
+ keyboardHelper.initialize()
+ val value = remember { TextFieldValue() }
+ CoreTextField(
+ value = value,
+ onValueChange = { },
+ imeOptions = ImeOptions(keyboardType = KeyboardType.Password),
+ modifier = Modifier
+ .fillMaxSize()
+ .testTag(Tag),
+ )
+ }
+
+ rule.onNodeWithTag(Tag).performStylusHandwriting()
+ keyboardHelper.waitForKeyboardVisibility(true)
+ assertThat(keyboardHelper.isSoftwareKeyboardShown()).isTrue()
+ }
+
private fun testStylusHandwriting(
stylusHandwritingStarted: Boolean,
interaction: SemanticsNodeInteraction.() -> Unit
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/TextFieldDelegateIntegrationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/TextFieldDelegateIntegrationTest.kt
index 938bbf7..c5dd825 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/TextFieldDelegateIntegrationTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/TextFieldDelegateIntegrationTest.kt
@@ -70,15 +70,52 @@
TextFieldDelegate.draw(
canvas = actualCanvas,
value = TextFieldValue(text = "Hello, World", selection = selection),
- selectionPaint = Paint().apply { color = selectionColor },
+ selectionPreviewHighlightRange = TextRange.Zero,
+ deletionPreviewHighlightRange = TextRange.Zero,
offsetMapping = OffsetMapping.Identity,
- textLayoutResult = layoutResult
+ textLayoutResult = layoutResult,
+ highlightPaint = Paint(),
+ selectionBackgroundColor = selectionColor
)
assertThat(actualBitmap.sameAs(expectedBitmap)).isTrue()
}
@Test
+ fun draw_highlight_test() {
+ val textDelegate = TextDelegate(
+ text = AnnotatedString("Hello, World"),
+ style = TextStyle.Default,
+ maxLines = 2,
+ density = density,
+ fontFamilyResolver = fontFamilyResolver
+ )
+ val layoutResult = textDelegate.layout(Constraints.fixedWidth(1024), layoutDirection)
+ val deletionPreviewHighlightRange = TextRange(3, 5)
+
+ val actualBitmap = layoutResult.toBitmap()
+ val actualCanvas = Canvas(android.graphics.Canvas(actualBitmap))
+ TextFieldDelegate.draw(
+ canvas = actualCanvas,
+ value = TextFieldValue(text = "Hello, World", selection = TextRange.Zero),
+ selectionPreviewHighlightRange = TextRange.Zero,
+ deletionPreviewHighlightRange = deletionPreviewHighlightRange,
+ offsetMapping = OffsetMapping.Identity,
+ textLayoutResult = layoutResult,
+ highlightPaint = Paint(),
+ selectionBackgroundColor = Color.Blue
+ )
+
+ val expectedBitmap = layoutResult.toBitmap()
+ val expectedCanvas = Canvas(android.graphics.Canvas(expectedBitmap))
+ val selectionPath = layoutResult.multiParagraph.getPathForRange(3, 5)
+ // Default text color is black, so deletion preview highlight is black with 20% alpha.
+ expectedCanvas.drawPath(selectionPath, Paint().apply { color = Color(0f, 0f, 0f, 0.2f) })
+ TextPainter.paint(expectedCanvas, layoutResult)
+ assertThat(actualBitmap.sameAs(expectedBitmap)).isTrue()
+ }
+
+ @Test
fun layout_height_constraint_max_height() {
val textDelegate = TextDelegate(
text = AnnotatedString("Hello, World"),
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldHandwritingTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldHandwritingTest.kt
index 66cd515..5cdef70 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldHandwritingTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldHandwritingTest.kt
@@ -18,6 +18,8 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.foundation.text.KeyboardHelper
+import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.handwriting.isStylusHandwritingSupported
import androidx.compose.foundation.text.performStylusClick
import androidx.compose.foundation.text.performStylusHandwriting
@@ -33,8 +35,10 @@
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.text.input.KeyboardType
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Rule
@@ -56,6 +60,8 @@
private val imm = FakeInputMethodManager()
+ private val keyboardHelper = KeyboardHelper(rule)
+
@Before
fun setup() {
// Test is only meaningful when stylus handwriting is supported.
@@ -188,6 +194,38 @@
performHandwritingAndExpect(stylusHandwritingStarted = true)
}
+ @Test
+ fun textField_passwordField_notStartStylusHandwriting() {
+ immRule.setFactory { imm }
+ inputMethodInterceptor.setTextFieldTestContent {
+ val state = remember { TextFieldState() }
+ BasicTextField(
+ state = state,
+ modifier = Modifier.fillMaxSize().testTag(Tag),
+ keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
+ )
+ }
+
+ performHandwritingAndExpect(stylusHandwritingStarted = false)
+ }
+
+ @Test
+ fun coreTextField_passwordField_attemptStylusHandwritingShowSoftInput() {
+ rule.setContent {
+ keyboardHelper.initialize()
+ val state = remember { TextFieldState() }
+ BasicTextField(
+ state = state,
+ modifier = Modifier.fillMaxSize().testTag(Tag),
+ keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
+ )
+ }
+
+ rule.onNodeWithTag(Tag).performStylusHandwriting()
+ keyboardHelper.waitForKeyboardVisibility(true)
+ Truth.assertThat(keyboardHelper.isSoftwareKeyboardShown()).isTrue()
+ }
+
private fun testStylusHandwriting(
stylusHandwritingStarted: Boolean,
interaction: SemanticsNodeInteraction.() -> Unit
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/EditorInfoTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/EditorInfoTest.kt
index 683499e..2a4c26e4 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/EditorInfoTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/EditorInfoTest.kt
@@ -18,8 +18,13 @@
import android.text.InputType
import android.view.inputmethod.DeleteGesture
+import android.view.inputmethod.DeleteRangeGesture
import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.InsertGesture
+import android.view.inputmethod.JoinOrSplitGesture
+import android.view.inputmethod.RemoveSpaceGesture
import android.view.inputmethod.SelectGesture
+import android.view.inputmethod.SelectRangeGesture
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.ImeOptions
@@ -599,8 +604,29 @@
val info = EditorInfo()
info.update(ImeOptions.Default)
- assertThat(info.supportedHandwritingGestures).contains(SelectGesture::class.java)
- assertThat(info.supportedHandwritingGestures).contains(DeleteGesture::class.java)
+ assertThat(info.supportedHandwritingGestures).containsExactly(
+ SelectGesture::class.java,
+ DeleteGesture::class.java,
+ SelectRangeGesture::class.java,
+ DeleteRangeGesture::class.java,
+ JoinOrSplitGesture::class.java,
+ InsertGesture::class.java,
+ RemoveSpaceGesture::class.java,
+ )
+ }
+
+ @SdkSuppress(minSdkVersion = 34)
+ @Test
+ fun supportedStylusHandwritingGesturePreviews() {
+ val info = EditorInfo()
+ info.update(ImeOptions.Default)
+
+ assertThat(info.supportedHandwritingGesturePreviews).containsExactly(
+ SelectGesture::class.java,
+ DeleteGesture::class.java,
+ SelectRangeGesture::class.java,
+ DeleteRangeGesture::class.java,
+ )
}
private fun EditorInfo.update(
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.android.kt
index 186bdd2..7d2e1e9 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.android.kt
@@ -20,8 +20,11 @@
import android.view.View
import android.widget.Magnifier
import androidx.annotation.ChecksSdkIntAtLeast
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.neverEqualPolicy
import androidx.compose.runtime.setValue
import androidx.compose.runtime.withFrameMillis
import androidx.compose.ui.Modifier
@@ -39,7 +42,6 @@
import androidx.compose.ui.node.requireDensity
import androidx.compose.ui.node.requireView
import androidx.compose.ui.platform.InspectorInfo
-import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.SemanticsPropertyKey
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
import androidx.compose.ui.unit.Density
@@ -48,6 +50,7 @@
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.toSize
+import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
/**
@@ -263,7 +266,7 @@
private var view: View? = null
/**
- * Current density provided by [LocalDensity]. Used as a receiver to callback functions that
+ * Current density provided by [requireDensity]. Used as a receiver to callback functions that
* are expected return pixel targeted offsets.
*/
private var density: Density? = null
@@ -274,9 +277,28 @@
private var magnifier: PlatformMagnifier? = null
/**
- * Anchor Composable's position in root layout.
+ * The latest [LayoutCoordinates] that is reported by [onGloballyPositioned] callback. Using
+ * [neverEqualPolicy] guarantees that every update to this value restarts snapshots aware
+ * listeners since the [LayoutCoordinates] instance itself does not change.
*/
- private var anchorPositionInRoot: Offset by mutableStateOf(Offset.Unspecified)
+ private var layoutCoordinates: LayoutCoordinates? by mutableStateOf(null, neverEqualPolicy())
+
+ /**
+ * Lazily initialized state that keeps track of anchor Composable's position in root layout.
+ * This state should be derived from [layoutCoordinates]. This variable shouldn't be used
+ * directly from the code, only [anchorPositionInRoot] should initialize and read from this.
+ */
+ private var anchorPositionInRootState: State<Offset>? = null
+
+ private val anchorPositionInRoot: Offset
+ get() {
+ if (anchorPositionInRootState == null) {
+ anchorPositionInRootState = derivedStateOf {
+ layoutCoordinates?.positionInRoot() ?: Offset.Unspecified
+ }
+ }
+ return anchorPositionInRootState?.value ?: Offset.Unspecified
+ }
/**
* Position where [sourceCenter] is mapped on root layout. This is passed to platform magnifier
@@ -290,6 +312,8 @@
*/
private var previousSize: IntSize? = null
+ private var drawSignalChannel: Channel<Unit>? = null
+
fun update(
sourceCenter: Density.() -> Offset,
magnifierCenter: (Density.() -> Offset)?,
@@ -305,9 +329,12 @@
val previousZoom = this.zoom
val previousSize = this.size
val previousCornerRadius = this.cornerRadius
+ val previousUseTextDefault = this.useTextDefault
val previousElevation = this.elevation
val previousClippingEnabled = this.clippingEnabled
val previousPlatformMagnifierFactory = this.platformMagnifierFactory
+ val previousView = this.view
+ val previousDensity = this.density
this.sourceCenter = sourceCenter
this.magnifierCenter = magnifierCenter
@@ -320,26 +347,45 @@
this.onSizeChanged = onSizeChanged
this.platformMagnifierFactory = platformMagnifierFactory
- // On platforms >=Q, the zoom level can be updated dynamically on an existing magnifier, so
- // if the zoom changes between recompositions we don't need to recreate the magnifier. On
- // older platforms, the zoom can only be set initially, so we use the zoom itself as a key
- // so the magnifier gets recreated if it changes.
- if (
- magnifier == null ||
- (zoom != previousZoom && !platformMagnifierFactory.canUpdateZoom) ||
- size != previousSize ||
- cornerRadius != previousCornerRadius ||
- elevation != previousElevation ||
- clippingEnabled != previousClippingEnabled ||
- platformMagnifierFactory != previousPlatformMagnifierFactory
- ) {
+ val view = requireView()
+ val density = requireDensity()
+
+ val shouldRecreate = magnifier != null && // only recreate if it was already created
+ // On platforms >=Q, the zoom level can be updated dynamically on an existing magnifier,
+ // so if the zoom changes between recompositions we don't need to recreate the
+ // magnifier. On older platforms, the zoom can only be set initially, so we use the
+ // zoom itself as a key so the magnifier gets recreated if it changes.
+ ((!zoom.equalsIncludingNaN(previousZoom) && !platformMagnifierFactory.canUpdateZoom) ||
+ size != previousSize ||
+ cornerRadius != previousCornerRadius ||
+ elevation != previousElevation ||
+ useTextDefault != previousUseTextDefault ||
+ clippingEnabled != previousClippingEnabled ||
+ platformMagnifierFactory != previousPlatformMagnifierFactory ||
+ view != previousView ||
+ density != previousDensity)
+
+ if (shouldRecreate) {
recreateMagnifier()
}
+
updateMagnifier()
}
override fun onAttach() {
onObservedReadsChanged()
+ drawSignalChannel = Channel()
+ coroutineScope.launch {
+ while (true) {
+ drawSignalChannel?.receive()
+ // don't update the magnifier immediately, actual frame draw happens right after
+ // all draw commands are recorded. Magnifier update should happen in the next frame.
+ if (magnifier != null) {
+ withFrameMillis { }
+ magnifier?.updateContent()
+ }
+ }
+ }
}
override fun onDetach() {
@@ -349,23 +395,14 @@
override fun onObservedReadsChanged() {
observeReads {
- val previousView = view
- val view = requireView().also { this.view = it }
- val previousDensity = density
- val density = requireDensity().also { this.density = it }
-
- if (magnifier == null || view != previousView || density != previousDensity) {
- recreateMagnifier()
- }
-
updateMagnifier()
}
}
private fun recreateMagnifier() {
magnifier?.dismiss()
- val view = view ?: return
- val density = density ?: return
+ val view = (view ?: requireView()).also { view = it }
+ val density = (density ?: requireDensity()).also { density = it }
magnifier = platformMagnifierFactory.create(
view = view,
useTextDefault = useTextDefault,
@@ -380,37 +417,38 @@
}
private fun updateMagnifier() {
- val magnifier = magnifier ?: return
- val density = density ?: return
+ val density = density ?: requireDensity().also { density = it }
val sourceCenterOffset = sourceCenter(density)
- sourceCenterInRoot =
- if (anchorPositionInRoot.isSpecified && sourceCenterOffset.isSpecified) {
- anchorPositionInRoot + sourceCenterOffset
- } else {
- Offset.Unspecified
- }
- // Once the position is set, it's never null again, so we don't need to worry
- // about dismissing the magnifier if this expression changes value.
- if (sourceCenterInRoot.isSpecified) {
- // Calculate magnifier center if it's provided. Only accept if the returned value is
- // specified. Then add [anchorPositionInRoot] for relative positioning.
+ // the order of these checks are important since we don't want to query
+ // `anchorPositionInRoot` if `sourceCenterOffset` is unspecified.
+ if (sourceCenterOffset.isSpecified && anchorPositionInRoot.isSpecified) {
+ sourceCenterInRoot = anchorPositionInRoot + sourceCenterOffset
+ // Calculate magnifier center if it's provided. Only accept if the returned
+ // value is specified. Then add [anchorPositionInRoot] for relative positioning.
val magnifierCenter = magnifierCenter?.invoke(density)
?.takeIf { it.isSpecified }
?.let { anchorPositionInRoot + it }
?: Offset.Unspecified
- magnifier.update(
+ if (magnifier == null) {
+ recreateMagnifier()
+ }
+
+ magnifier?.update(
sourceCenter = sourceCenterInRoot,
magnifierCenter = magnifierCenter,
zoom = zoom
)
updateSizeIfNecessary()
- } else {
- // Can't place the magnifier at an unspecified location, so just hide it.
- magnifier.dismiss()
+ return
}
+
+ // If the flow reaches here, it means that the magnifier could not be placed at a specified
+ // position. We now need to hide it so it doesn't show up at an invalid location.
+ sourceCenterInRoot = Offset.Unspecified
+ magnifier?.dismiss()
}
private fun updateSizeIfNecessary() {
@@ -425,19 +463,14 @@
override fun ContentDrawScope.draw() {
drawContent()
- // don't update the magnifier immediately, actual frame draw happens right after all draw
- // commands are recorded. Magnifier update should happen in the next frame.
- coroutineScope.launch {
- withFrameMillis { }
- magnifier?.updateContent()
- }
+ drawSignalChannel?.trySend(Unit)
}
override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
// The mutable state must store the Offset, not the LocalCoordinates, because the same
// LocalCoordinates instance may be sent to this callback multiple times, not implement
// equals, or be stable, and so won't invalidate the snapshotFlow.
- anchorPositionInRoot = coordinates.positionInRoot()
+ layoutCoordinates = coordinates
}
override fun SemanticsPropertyReceiver.applySemantics() {
@@ -448,3 +481,13 @@
@ChecksSdkIntAtLeast(api = 28)
internal fun isPlatformMagnifierSupported(sdkVersion: Int = Build.VERSION.SDK_INT) =
sdkVersion >= 28
+
+/**
+ * Normally `Float.NaN == Float.NaN` returns false but we use [Float.NaN] to mean Unspecified.
+ * The comparison between two unspecified values should return _equal_ if we are only interested
+ * in state changes.
+ */
+internal fun Float.equalsIncludingNaN(other: Float): Boolean {
+ if (this.isNaN() && other.isNaN()) return true
+ return this == other
+}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/EditorInfo.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/EditorInfo.android.kt
index 1bdf683..a53b03d 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/EditorInfo.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/EditorInfo.android.kt
@@ -203,5 +203,11 @@
InsertGesture::class.java,
RemoveSpaceGesture::class.java
)
+ editorInfo.supportedHandwritingGesturePreviews = setOf(
+ SelectGesture::class.java,
+ DeleteGesture::class.java,
+ SelectRangeGesture::class.java,
+ DeleteRangeGesture::class.java
+ )
}
}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/HandwritingGesture.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/HandwritingGesture.android.kt
index d47c722..ba0422e 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/HandwritingGesture.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/HandwritingGesture.android.kt
@@ -421,6 +421,30 @@
}
@DoNotInline
+ internal fun LegacyTextFieldState.previewHandwritingGesture(
+ gesture: PreviewableHandwritingGesture,
+ textFieldSelectionManager: TextFieldSelectionManager?,
+ cancellationSignal: CancellationSignal?
+ ): Boolean {
+ val text = untransformedText ?: return false
+ if (text != layoutResult?.value?.layoutInput?.text) {
+ // The text is transformed or layout is null, handwriting gesture failed.
+ return false
+ }
+ when (gesture) {
+ is SelectGesture -> previewSelectGesture(gesture, textFieldSelectionManager)
+ is DeleteGesture -> previewDeleteGesture(gesture, textFieldSelectionManager)
+ is SelectRangeGesture -> previewSelectRangeGesture(gesture, textFieldSelectionManager)
+ is DeleteRangeGesture -> previewDeleteRangeGesture(gesture, textFieldSelectionManager)
+ else -> return false
+ }
+ cancellationSignal?.setOnCancelListener {
+ textFieldSelectionManager?.clearPreviewHighlight()
+ }
+ return true
+ }
+
+ @DoNotInline
private fun LegacyTextFieldState.performSelectGesture(
gesture: SelectGesture,
textSelectionManager: TextFieldSelectionManager?,
@@ -439,6 +463,20 @@
}
@DoNotInline
+ private fun LegacyTextFieldState.previewSelectGesture(
+ gesture: SelectGesture,
+ textFieldSelectionManager: TextFieldSelectionManager?
+ ) {
+ textFieldSelectionManager?.setSelectionPreviewHighlight(
+ getRangeForScreenRect(
+ gesture.selectionArea.toComposeRect(),
+ gesture.granularity.toTextGranularity(),
+ TextInclusionStrategy.ContainsCenter
+ )
+ )
+ }
+
+ @DoNotInline
private fun LegacyTextFieldState.performDeleteGesture(
gesture: DeleteGesture,
text: AnnotatedString,
@@ -463,6 +501,20 @@
}
@DoNotInline
+ private fun LegacyTextFieldState.previewDeleteGesture(
+ gesture: DeleteGesture,
+ textFieldSelectionManager: TextFieldSelectionManager?
+ ) {
+ textFieldSelectionManager?.setDeletionPreviewHighlight(
+ getRangeForScreenRect(
+ gesture.deletionArea.toComposeRect(),
+ gesture.granularity.toTextGranularity(),
+ TextInclusionStrategy.ContainsCenter
+ )
+ )
+ }
+
+ @DoNotInline
private fun LegacyTextFieldState.performSelectRangeGesture(
gesture: SelectRangeGesture,
textSelectionManager: TextFieldSelectionManager?,
@@ -486,6 +538,21 @@
}
@DoNotInline
+ private fun LegacyTextFieldState.previewSelectRangeGesture(
+ gesture: SelectRangeGesture,
+ textFieldSelectionManager: TextFieldSelectionManager?
+ ) {
+ textFieldSelectionManager?.setSelectionPreviewHighlight(
+ getRangeForScreenRects(
+ gesture.selectionStartArea.toComposeRect(),
+ gesture.selectionEndArea.toComposeRect(),
+ gesture.granularity.toTextGranularity(),
+ TextInclusionStrategy.ContainsCenter
+ )
+ )
+ }
+
+ @DoNotInline
private fun LegacyTextFieldState.performDeleteRangeGesture(
gesture: DeleteRangeGesture,
text: AnnotatedString,
@@ -510,6 +577,21 @@
}
@DoNotInline
+ private fun LegacyTextFieldState.previewDeleteRangeGesture(
+ gesture: DeleteRangeGesture,
+ textFieldSelectionManager: TextFieldSelectionManager?
+ ) {
+ textFieldSelectionManager?.setDeletionPreviewHighlight(
+ getRangeForScreenRects(
+ gesture.deletionStartArea.toComposeRect(),
+ gesture.deletionEndArea.toComposeRect(),
+ gesture.granularity.toTextGranularity(),
+ TextInclusionStrategy.ContainsCenter
+ )
+ )
+ }
+
+ @DoNotInline
private fun LegacyTextFieldState.performJoinOrSplitGesture(
gesture: JoinOrSplitGesture,
text: AnnotatedString,
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/RecordingInputConnection.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/RecordingInputConnection.android.kt
index 4d897e0..34e71cb 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/RecordingInputConnection.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/RecordingInputConnection.android.kt
@@ -18,6 +18,7 @@
import android.os.Build
import android.os.Bundle
+import android.os.CancellationSignal
import android.os.Handler
import android.text.TextUtils
import android.util.Log
@@ -30,10 +31,12 @@
import android.view.inputmethod.HandwritingGesture
import android.view.inputmethod.InputConnection
import android.view.inputmethod.InputContentInfo
+import android.view.inputmethod.PreviewableHandwritingGesture
import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
import androidx.compose.foundation.text.LegacyTextFieldState
import androidx.compose.foundation.text.input.internal.HandwritingGestureApi34.performHandwritingGesture
+import androidx.compose.foundation.text.input.internal.HandwritingGestureApi34.previewHandwritingGesture
import androidx.compose.foundation.text.selection.TextFieldSelectionManager
import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.text.input.CommitTextCommand
@@ -426,6 +429,22 @@
}
}
+ override fun previewHandwritingGesture(
+ gesture: PreviewableHandwritingGesture,
+ cancellationSignal: CancellationSignal?
+ ): Boolean {
+ if (DEBUG) { logDebug("previewHandwritingGesture($gesture, $cancellationSignal)") }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ return Api34LegacyPerformHandwritingGestureImpl.previewHandwritingGesture(
+ legacyTextFieldState,
+ textFieldSelectionManager,
+ gesture,
+ cancellationSignal
+ )
+ }
+ return false
+ }
+
// endregion
// region Unsupported callbacks
@@ -533,4 +552,18 @@
consumer.accept(result)
}
}
+
+ @DoNotInline
+ fun previewHandwritingGesture(
+ legacyTextFieldState: LegacyTextFieldState?,
+ textFieldSelectionManager: TextFieldSelectionManager?,
+ gesture: PreviewableHandwritingGesture,
+ cancellationSignal: CancellationSignal?
+ ): Boolean {
+ return legacyTextFieldState?.previewHandwritingGesture(
+ gesture,
+ textFieldSelectionManager,
+ cancellationSignal
+ ) ?: false
+ }
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemAnimator.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemAnimator.kt
index 612ba62..0d1bb80 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemAnimator.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemAnimator.kt
@@ -241,6 +241,7 @@
lane = info.lane,
span = info.span
)
+
item.nonScrollableItem = true
// check if we have any active placement animation on the item
val inProgress =
@@ -248,6 +249,14 @@
if ((!inProgress && newIndex == previousKeyToIndexMap?.getIndex(key))) {
removeInfoForKey(key)
} else {
+ // anytime we compose a new item, and we use it,
+ // we need to update our item info mapping
+ info.updateAnimation(
+ item,
+ coroutineScope,
+ graphicsContext,
+ crossAxisOffset = info.crossAxisOffset
+ )
if (newIndex < firstVisibleIndex) {
movingAwayToStartBound.add(item)
} else {
@@ -444,7 +453,8 @@
fun updateAnimation(
positionedItem: T,
coroutineScope: CoroutineScope,
- graphicsContext: GraphicsContext
+ graphicsContext: GraphicsContext,
+ crossAxisOffset: Int = positionedItem.crossAxisOffset
) {
for (i in positionedItem.placeablesCount until animations.size) {
animations[i]?.release()
@@ -453,7 +463,7 @@
animations = animations.copyOf(positionedItem.placeablesCount)
}
constraints = positionedItem.constraints
- crossAxisOffset = positionedItem.crossAxisOffset
+ this.crossAxisOffset = crossAxisOffset
lane = positionedItem.lane
span = positionedItem.span
repeat(positionedItem.placeablesCount) { index ->
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField.kt
index c163320..00dd988 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField.kt
@@ -325,11 +325,9 @@
.scrollable(
state = scrollState,
orientation = orientation,
- // Disable scrolling when textField is disabled, there is no where to scroll, and
- // another dragging gesture is taking place
- enabled = enabled &&
- scrollState.maxValue > 0 &&
- textFieldSelectionState.draggingHandle == null,
+ // Disable scrolling when textField is disabled or another dragging gesture is taking
+ // place
+ enabled = enabled && textFieldSelectionState.draggingHandle == null,
reverseDirection = ScrollableDefaults.reverseDirection(
layoutDirection = layoutDirection,
orientation = orientation,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
index 1338218..50eccc5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
@@ -119,6 +119,7 @@
import androidx.compose.ui.text.input.FinishComposingTextCommand
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.ImeOptions
+import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.OffsetMapping
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.TextFieldValue
@@ -405,13 +406,18 @@
)
.pointerHoverIcon(textPointerIcon)
.then(
- if (isStylusHandwritingSupported) {
- Modifier.pointerInput(writeable) {
- if (writeable) {
- detectStylusHandwriting {
- if (!state.hasFocus) {
- focusRequester.requestFocus()
- }
+ if (isStylusHandwritingSupported && writeable) {
+ Modifier.pointerInput(Unit) {
+ detectStylusHandwriting {
+ if (!state.hasFocus) {
+ focusRequester.requestFocus()
+ }
+ // If this is a password field, we can't trigger handwriting.
+ // The expected behavior is 1) request focus 2) show software keyboard.
+ // Note: TextField will show software keyboard automatically when it
+ // gain focus. 3) show a toast message telling that handwriting is not
+ // supported for password fields. TODO(b/335294152)
+ if (imeOptions.keyboardType != KeyboardType.Password) {
// TextInputService is calling LegacyTextInputServiceAdapter under the
// hood. And because it's a public API, startStylusHandwriting is added
// to legacyTextInputServiceAdapter instead.
@@ -419,8 +425,8 @@
// session starts when the editor is not focused, this is handled
// internally by the LegacyTextInputServiceAdapter.
legacyTextInputServiceAdapter.startStylusHandwriting()
- true
}
+ true
}
}
} else {
@@ -434,9 +440,12 @@
TextFieldDelegate.draw(
canvas,
value,
+ state.selectionPreviewHighlightRange,
+ state.deletionPreviewHighlightRange,
offsetMapping,
layoutResult.value,
- state.selectionPaint
+ state.highlightPaint,
+ state.selectionBackgroundColor
)
}
}
@@ -614,7 +623,7 @@
}
}
- val showCursor = enabled && !readOnly && windowInfo.isWindowFocused
+ val showCursor = enabled && !readOnly && windowInfo.isWindowFocused && !state.hasHighlight()
val cursorModifier = Modifier.cursor(state, value, offsetMapping, cursorBrush, showCursor)
DisposableEffect(manager) {
@@ -976,6 +985,8 @@
// Text has been changed, enter the HandleState.None and hide the cursor handle.
handleState = HandleState.None
}
+ selectionPreviewHighlightRange = TextRange.Zero
+ deletionPreviewHighlightRange = TextRange.Zero
onValueChangeOriginal(it)
recomposeScope.invalidate()
}
@@ -984,8 +995,16 @@
keyboardActionRunner.runAction(imeAction)
}
- /** The paint used to draw highlight background for selected text. */
- val selectionPaint: Paint = Paint()
+ /** The paint used to draw highlight backgrounds. */
+ val highlightPaint: Paint = Paint()
+ var selectionBackgroundColor = Color.Unspecified
+
+ /** Range of text to be highlighted to display handwriting gesture previews from the IME. */
+ var selectionPreviewHighlightRange: TextRange by mutableStateOf(TextRange.Zero)
+ var deletionPreviewHighlightRange: TextRange by mutableStateOf(TextRange.Zero)
+
+ fun hasHighlight() =
+ !selectionPreviewHighlightRange.collapsed || !deletionPreviewHighlightRange.collapsed
fun update(
untransformedText: AnnotatedString,
@@ -1000,7 +1019,7 @@
selectionBackgroundColor: Color
) {
this.onValueChangeOriginal = onValueChange
- this.selectionPaint.color = selectionBackgroundColor
+ this.selectionBackgroundColor = selectionBackgroundColor
this.keyboardActionRunner.apply {
this.keyboardActions = keyboardActions
this.focusManager = focusManager
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldDelegate.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldDelegate.kt
index b6567d5..eabeb8e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldDelegate.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldDelegate.kt
@@ -23,7 +23,9 @@
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Canvas
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Paint
+import androidx.compose.ui.graphics.isUnspecified
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.findRootCoordinates
import androidx.compose.ui.text.AnnotatedString
@@ -112,28 +114,75 @@
*
* @param canvas The target canvas.
* @param value The editor state
+ * @param selectionPreviewHighlightRange Range to be highlighted to preview a handwriting
+ * selection gesture
+ * @param deletionPreviewHighlightRange Range to be highlighted to preview a handwriting
+ * deletion gesture
* @param offsetMapping The offset map
- * @param selectionPaint The selection paint
+ * @param textLayoutResult The text layout result
+ * @param highlightPaint Paint used to draw highlight backgrounds
+ * @param selectionBackgroundColor The selection highlight background color
*/
@JvmStatic
internal fun draw(
canvas: Canvas,
value: TextFieldValue,
+ selectionPreviewHighlightRange: TextRange,
+ deletionPreviewHighlightRange: TextRange,
offsetMapping: OffsetMapping,
textLayoutResult: TextLayoutResult,
- selectionPaint: Paint
+ highlightPaint: Paint,
+ selectionBackgroundColor: Color
) {
- if (!value.selection.collapsed) {
- val start = offsetMapping.originalToTransformed(value.selection.min)
- val end = offsetMapping.originalToTransformed(value.selection.max)
- if (start != end) {
- val selectionPath = textLayoutResult.getPathForRange(start, end)
- canvas.drawPath(selectionPath, selectionPaint)
- }
+ if (!selectionPreviewHighlightRange.collapsed) {
+ highlightPaint.color = selectionBackgroundColor
+ drawHighlight(
+ canvas,
+ selectionPreviewHighlightRange,
+ offsetMapping,
+ textLayoutResult,
+ highlightPaint
+ )
+ } else if (!deletionPreviewHighlightRange.collapsed) {
+ val textColor =
+ textLayoutResult.layoutInput.style.color.takeUnless { it.isUnspecified }
+ ?: Color.Black
+ highlightPaint.color = textColor.copy(alpha = textColor.alpha * 0.2f)
+ drawHighlight(
+ canvas,
+ deletionPreviewHighlightRange,
+ offsetMapping,
+ textLayoutResult,
+ highlightPaint
+ )
+ } else if (!value.selection.collapsed) {
+ highlightPaint.color = selectionBackgroundColor
+ drawHighlight(
+ canvas,
+ value.selection,
+ offsetMapping,
+ textLayoutResult,
+ highlightPaint
+ )
}
TextPainter.paint(canvas, textLayoutResult)
}
+ private fun drawHighlight(
+ canvas: Canvas,
+ range: TextRange,
+ offsetMapping: OffsetMapping,
+ textLayoutResult: TextLayoutResult,
+ paint: Paint
+ ) {
+ val start = offsetMapping.originalToTransformed(range.min)
+ val end = offsetMapping.originalToTransformed(range.max)
+ if (start != end) {
+ val selectionPath = textLayoutResult.getPathForRange(start, end)
+ canvas.drawPath(selectionPath, paint)
+ }
+ }
+
/**
* Notify system that focused input area.
*
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDecoratorModifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDecoratorModifier.kt
index 48e06ad..eeddf40 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDecoratorModifier.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDecoratorModifier.kt
@@ -89,6 +89,7 @@
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.ImeOptions
+import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.IntSize
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -224,28 +225,34 @@
detectTextFieldLongPressAndAfterDrag(requestFocus)
}
}
- // Note: when editable changes (enabled or readOnly changes), this pointerInputModifier
- // is reset. And we don't need to worry about cancel or launch the stylus handwriting
- // detecting job.
+ // Note: when editable changes (enabled or readOnly changes) or keyboard type changes,
+ // this pointerInputModifier is reset. And we don't need to worry about cancel or launch
+ // the stylus handwriting detecting job.
if (isStylusHandwritingSupported && editable) {
launch(start = CoroutineStart.UNDISPATCHED) {
detectStylusHandwriting {
if (!isFocused) {
requestFocus()
}
-
- // Send the handwriting start signal to platform.
- // The editor should send the signal when it is focused or is about
- // to gain focus, Here are more details:
- // 1) if the editor already has an active input session, the
- // platform handwriting service should already listen to this flow
- // and it'll start handwriting right away.
- //
- // 2) if the editor is not focused, but it'll be focused and
- // create a new input session, one handwriting signal will be
- // replayed when the platform collect this flow. And the platform
- // should trigger handwriting accordingly.
- stylusHandwritingTrigger?.tryEmit(Unit)
+ // If this is a password field, we can't trigger handwriting.
+ // The expected behavior is 1) request focus 2) show software keyboard.
+ // Note: TextField will show software keyboard automatically when it
+ // gain focus. 3) show a toast message telling that handwriting is not
+ // supported for password fields. TODO(b/335294152)
+ if (keyboardOptions.keyboardType != KeyboardType.Password) {
+ // Send the handwriting start signal to platform.
+ // The editor should send the signal when it is focused or is about
+ // to gain focus, Here are more details:
+ // 1) if the editor already has an active input session, the
+ // platform handwriting service should already listen to this flow
+ // and it'll start handwriting right away.
+ //
+ // 2) if the editor is not focused, but it'll be focused and
+ // create a new input session, one handwriting signal will be
+ // replayed when the platform collect this flow. And the platform
+ // should trigger handwriting accordingly.
+ stylusHandwritingTrigger?.tryEmit(Unit)
+ }
return@detectStylusHandwriting true
}
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifier.kt
index 7cf3de1..fe8166b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifier.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifier.kt
@@ -98,7 +98,14 @@
// Hide the magnifier when dragged too far (outside the horizontal bounds of how big the
// magnifier actually is). See
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/widget/Editor.java;l=5228-5231;drc=2fdb6bd709be078b72f011334362456bb758922c
- if ((dragX - centerX).absoluteValue > magnifierSize.width / 2) {
+ // Also check whether magnifierSize is calculated. A platform magnifier instance is not
+ // created until it's requested for the first time. So the size will only be calculated after we
+ // return a specified offset from this function.
+ // It is very unlikely that this behavior would cause a flicker since magnifier immediately
+ // shows up where the pointer is being dragged. The pointer needs to drag further than the half
+ // of magnifier's width to hide by the following logic.
+ if (magnifierSize != IntSize.Zero &&
+ (dragX - centerX).absoluteValue > magnifierSize.width / 2) {
return Offset.Unspecified
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
index 4cb66ac..465fc46 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
@@ -1034,7 +1034,14 @@
// Hide the magnifier when dragged too far (outside the horizontal bounds of how big the
// magnifier actually is). See
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/widget/Editor.java;l=5228-5231;drc=2fdb6bd709be078b72f011334362456bb758922c
- if ((dragX - textConstrainedX).absoluteValue > magnifierSize.width / 2) {
+ // Also check whether magnifierSize is calculated. A platform magnifier instance is not
+ // created until it's requested for the first time. So the size will only be calculated after we
+ // return a specified offset from this function.
+ // It is very unlikely that this behavior would cause a flicker since magnifier immediately
+ // shows up where the pointer is being dragged. The pointer needs to drag further than the half
+ // of magnifier's width to hide by the following logic.
+ if (magnifierSize != IntSize.Zero &&
+ (dragX - textConstrainedX).absoluteValue > magnifierSize.width / 2) {
return Offset.Unspecified
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt
index bb5b445..a32cf68 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt
@@ -576,6 +576,23 @@
updateFloatingToolbar(show = false)
}
+ internal fun setSelectionPreviewHighlight(range: TextRange) {
+ state?.selectionPreviewHighlightRange = range
+ state?.deletionPreviewHighlightRange = TextRange.Zero
+ if (!range.collapsed) exitSelectionMode()
+ }
+
+ internal fun setDeletionPreviewHighlight(range: TextRange) {
+ state?.deletionPreviewHighlightRange = range
+ state?.selectionPreviewHighlightRange = TextRange.Zero
+ if (!range.collapsed) exitSelectionMode()
+ }
+
+ internal fun clearPreviewHighlight() {
+ state?.deletionPreviewHighlightRange = TextRange.Zero
+ state?.selectionPreviewHighlightRange = TextRange.Zero
+ }
+
/**
* The method for copying text.
*
@@ -1036,7 +1053,14 @@
// Hide the magnifier when dragged too far (outside the horizontal bounds of how big the
// magnifier actually is). See
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/widget/Editor.java;l=5228-5231;drc=2fdb6bd709be078b72f011334362456bb758922c
- if ((dragX - centerX).absoluteValue > magnifierSize.width / 2) {
+ // Also check whether magnifierSize is calculated. A platform magnifier instance is not
+ // created until it's requested for the first time. So the size will only be calculated after we
+ // return a specified offset from this function.
+ // It is very unlikely that this behavior would cause a flicker since magnifier immediately
+ // shows up where the pointer is being dragged. The pointer needs to drag further than the half
+ // of magnifier's width to hide by the following logic.
+ if (magnifierSize != IntSize.Zero &&
+ (dragX - centerX).absoluteValue > magnifierSize.width / 2) {
return Offset.Unspecified
}
diff --git a/compose/material/material-icons-core/build.gradle b/compose/material/material-icons-core/build.gradle
index 8931a2a..f720873 100644
--- a/compose/material/material-icons-core/build.gradle
+++ b/compose/material/material-icons-core/build.gradle
@@ -110,7 +110,7 @@
androidx {
name = "Compose Material Icons Core"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
mavenVersion = LibraryVersions.COMPOSE
inceptionYear = "2020"
description = "Compose Material Design core icons. This module contains the most commonly used set of Material icons."
diff --git a/compose/material/material-icons-extended/build.gradle b/compose/material/material-icons-extended/build.gradle
index 568f7f0..324023b 100644
--- a/compose/material/material-icons-extended/build.gradle
+++ b/compose/material/material-icons-extended/build.gradle
@@ -124,7 +124,7 @@
androidx {
name = "Compose Material Icons Extended"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
mavenVersion = LibraryVersions.COMPOSE
// This module has a large number (5000+) of generated source files and so doc generation /
// API tracking will simply take too long
diff --git a/compose/material/material-navigation/build.gradle b/compose/material/material-navigation/build.gradle
index 179c44c..1a55ed7 100644
--- a/compose/material/material-navigation/build.gradle
+++ b/compose/material/material-navigation/build.gradle
@@ -13,9 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-import androidx.build.Publish
-import androidx.build.RunApiTasks
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -41,7 +39,7 @@
androidx {
name = "Compose Material Navigation"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
mavenVersion = LibraryVersions.COMPOSE
inceptionYear = "2024"
description = "Compose Material integration with Navigation"
diff --git a/compose/material/material-ripple/build.gradle b/compose/material/material-ripple/build.gradle
index 7cbf897..8ca7ece 100644
--- a/compose/material/material-ripple/build.gradle
+++ b/compose/material/material-ripple/build.gradle
@@ -117,7 +117,7 @@
androidx {
name = "Compose Material Ripple"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
mavenVersion = LibraryVersions.COMPOSE
inceptionYear = "2020"
description = "Material ripple used to build interactive components"
diff --git a/compose/material/material/api/current.txt b/compose/material/material/api/current.txt
index 69623fb..004b759 100644
--- a/compose/material/material/api/current.txt
+++ b/compose/material/material/api/current.txt
@@ -910,8 +910,10 @@
@androidx.compose.runtime.Immutable public final class TextFieldDefaults {
method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public void BorderBox(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, androidx.compose.material.TextFieldColors colors, optional androidx.compose.ui.graphics.Shape shape, optional float focusedBorderThickness, optional float unfocusedBorderThickness);
- method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public void OutlinedTextFieldDecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.material.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> border);
- method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public void TextFieldDecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.material.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding);
+ method @Deprecated @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public void OutlinedTextFieldDecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.material.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> border);
+ method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public void OutlinedTextFieldDecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> border);
+ method @Deprecated @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public void TextFieldDecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.material.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding);
+ method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public void TextFieldDecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding);
method public float getFocusedBorderThickness();
method public float getMinHeight();
method public float getMinWidth();
diff --git a/compose/material/material/api/restricted_current.txt b/compose/material/material/api/restricted_current.txt
index 69623fb..004b759 100644
--- a/compose/material/material/api/restricted_current.txt
+++ b/compose/material/material/api/restricted_current.txt
@@ -910,8 +910,10 @@
@androidx.compose.runtime.Immutable public final class TextFieldDefaults {
method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public void BorderBox(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, androidx.compose.material.TextFieldColors colors, optional androidx.compose.ui.graphics.Shape shape, optional float focusedBorderThickness, optional float unfocusedBorderThickness);
- method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public void OutlinedTextFieldDecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.material.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> border);
- method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public void TextFieldDecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.material.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding);
+ method @Deprecated @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public void OutlinedTextFieldDecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.material.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> border);
+ method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public void OutlinedTextFieldDecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> border);
+ method @Deprecated @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public void TextFieldDecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.material.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding);
+ method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public void TextFieldDecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding);
method public float getFocusedBorderThickness();
method public float getMinHeight();
method public float getMinWidth();
diff --git a/compose/material/material/build.gradle b/compose/material/material/build.gradle
index 97ec8c8..31c1ece 100644
--- a/compose/material/material/build.gradle
+++ b/compose/material/material/build.gradle
@@ -148,7 +148,7 @@
androidx {
name = "Compose Material Components"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
mavenVersion = LibraryVersions.COMPOSE
inceptionYear = "2018"
description = "Compose Material Design Components library"
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/TextFieldDecorationBoxDemos.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/TextFieldDecorationBoxDemos.kt
index b6d93db..1144cc1 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/TextFieldDecorationBoxDemos.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/TextFieldDecorationBoxDemos.kt
@@ -23,7 +23,6 @@
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
-import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
@@ -131,10 +130,6 @@
onValueChange = { text = it },
modifier = Modifier
.indicatorLine(enabled, false, interactionSource, TextFieldDefaults.textFieldColors())
- .background(
- TextFieldDefaults.textFieldColors().backgroundColor(enabled).value,
- TextFieldDefaults.TextFieldShape
- )
.width(TextFieldDefaults.MinWidth),
singleLine = singleLine,
interactionSource = interactionSource
@@ -174,9 +169,7 @@
BasicTextField(
value = text,
onValueChange = { text = it },
- modifier = indicator
- .background(colors.backgroundColor(enabled).value, TextFieldDefaults.TextFieldShape)
- .width(TextFieldDefaults.MinWidth),
+ modifier = indicator.width(TextFieldDefaults.MinWidth),
singleLine = singleLine,
interactionSource = interactionSource,
enabled = enabled
diff --git a/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/textfield/TextFieldDecorationBoxTest.kt b/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/textfield/TextFieldDecorationBoxTest.kt
index c322df8..61d68c1 100644
--- a/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/textfield/TextFieldDecorationBoxTest.kt
+++ b/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/textfield/TextFieldDecorationBoxTest.kt
@@ -451,6 +451,124 @@
}
}
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun outlinedTextFieldBox_appliesBackgroundColor() {
+ val textFieldWidth = 300
+ val textFieldHeight = 150
+ val borderWidth = 2
+ val value = ""
+
+ rule.setMaterialContent {
+ CompositionLocalProvider(LocalDensity provides Density) {
+ val interactionSource = remember { MutableInteractionSource() }
+ val singleLine = true
+ val colors = TextFieldDefaults.outlinedTextFieldColors(
+ backgroundColor = Color.Red
+ )
+ BasicTextField(
+ value = value,
+ onValueChange = {},
+ modifier = Modifier.size(
+ with(Density) { textFieldWidth.toDp() },
+ with(Density) { textFieldHeight.toDp() }
+ ),
+ singleLine = singleLine,
+ interactionSource = interactionSource
+ ) {
+ OutlinedTextFieldDecorationBox(
+ value = value,
+ innerTextField = it,
+ enabled = true,
+ visualTransformation = VisualTransformation.None,
+ interactionSource = interactionSource,
+ singleLine = singleLine,
+ border = {
+ TextFieldDefaults.BorderBox(
+ enabled = true,
+ isError = false,
+ colors = colors,
+ interactionSource = interactionSource,
+ shape = RectangleShape,
+ unfocusedBorderThickness = with(Density) { borderWidth.toDp() }
+ )
+ },
+ colors = colors,
+ contentPadding = PaddingValues(0.dp)
+ )
+ }
+ }
+ }
+
+ rule.onNodeWithText(value)
+ .captureToImage()
+ .assertPixels(IntSize(textFieldWidth, textFieldHeight)) {
+ // to account for border + edge pixels
+ if (it.x in (borderWidth + 2)..(textFieldWidth - borderWidth - 2) &&
+ it.y in (borderWidth + 2)..(textFieldHeight - borderWidth - 2)) {
+ Color.Red
+ } else null
+ }
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun textFieldBox_defaultIndicatorLineColor_appliesBackgroundColor() {
+ val textFieldWidth = 300
+ val textFieldHeight = 150
+ val borderWidth = 2
+ val value = ""
+
+ rule.setMaterialContent {
+ CompositionLocalProvider(LocalDensity provides Density) {
+ val interactionSource = remember { MutableInteractionSource() }
+ val singleLine = true
+ val colors = TextFieldDefaults.textFieldColors(
+ backgroundColor = Color.Red
+ )
+ BasicTextField(
+ value = value,
+ onValueChange = {},
+ modifier = Modifier
+ .indicatorLine(
+ enabled = true,
+ isError = false,
+ colors = colors,
+ interactionSource = interactionSource,
+ unfocusedIndicatorLineThickness = with(Density) { borderWidth.toDp() }
+ )
+ .size(
+ with(Density) { textFieldWidth.toDp() },
+ with(Density) { textFieldHeight.toDp() }
+ ),
+ singleLine = singleLine,
+ interactionSource = interactionSource
+ ) {
+ TextFieldDecorationBox(
+ value = value,
+ innerTextField = it,
+ enabled = true,
+ visualTransformation = VisualTransformation.None,
+ interactionSource = interactionSource,
+ singleLine = singleLine,
+ colors = colors,
+ contentPadding = PaddingValues(0.dp)
+ )
+ }
+ }
+ }
+
+ rule.onNodeWithText(value)
+ .captureToImage()
+ .assertPixels(IntSize(textFieldWidth, textFieldHeight)) {
+ // to account for border + edge pixels
+ if (it.x in 2..(textFieldWidth - 2) &&
+ it.y in 2..(textFieldHeight - borderWidth - 2)) {
+ Color.Red
+ } else null
+ }
+ }
+
@Test
fun outlinedTextFieldBox_innerTextLocation_withMultilineLabel() {
val labelHeight = 60.dp
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 66c7527..6cf332c 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
@@ -16,7 +16,6 @@
package androidx.compose.material
-import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.Interaction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
@@ -154,7 +153,7 @@
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
minLines: Int = 1,
interactionSource: MutableInteractionSource? = null,
- shape: Shape = MaterialTheme.shapes.small,
+ shape: Shape = TextFieldDefaults.OutlinedTextFieldShape,
colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors()
) {
@Suppress("NAME_SHADOWING")
@@ -182,7 +181,6 @@
Modifier
}
)
- .background(colors.backgroundColor(enabled).value, shape)
.defaultErrorSemantics(isError, getString(Strings.DefaultErrorMessage))
.defaultMinSize(
minWidth = TextFieldDefaults.MinWidth,
@@ -213,6 +211,7 @@
enabled = enabled,
isError = isError,
interactionSource = interactionSource,
+ shape = shape,
colors = colors,
border = {
TextFieldDefaults.BorderBox(
@@ -251,7 +250,7 @@
singleLine: Boolean = false,
maxLines: Int = Int.MAX_VALUE,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
- shape: Shape = MaterialTheme.shapes.small,
+ shape: Shape = TextFieldDefaults.OutlinedTextFieldShape,
colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors()
) {
OutlinedTextField(
@@ -391,7 +390,6 @@
Modifier
}
)
- .background(colors.backgroundColor(enabled).value, shape)
.defaultErrorSemantics(isError, getString(Strings.DefaultErrorMessage))
.defaultMinSize(
minWidth = TextFieldDefaults.MinWidth,
@@ -422,6 +420,7 @@
enabled = enabled,
isError = isError,
interactionSource = interactionSource,
+ shape = shape,
colors = colors,
border = {
TextFieldDefaults.BorderBox(
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 d0ca8cf..b85ec36 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
@@ -17,7 +17,6 @@
package androidx.compose.material
import androidx.compose.foundation.BorderStroke
-import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.Interaction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
@@ -177,8 +176,7 @@
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
minLines: Int = 1,
interactionSource: MutableInteractionSource? = null,
- shape: Shape =
- MaterialTheme.shapes.small.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize),
+ shape: Shape = TextFieldDefaults.TextFieldShape,
colors: TextFieldColors = TextFieldDefaults.textFieldColors()
) {
@Suppress("NAME_SHADOWING")
@@ -193,7 +191,6 @@
BasicTextField(
value = value,
modifier = modifier
- .background(colors.backgroundColor(enabled).value, shape)
.indicatorLine(enabled, isError, interactionSource, colors)
.defaultErrorSemantics(isError, getString(Strings.DefaultErrorMessage))
.defaultMinSize(
@@ -226,7 +223,8 @@
enabled = enabled,
isError = isError,
interactionSource = interactionSource,
- colors = colors
+ shape = shape,
+ colors = colors,
)
}
)
@@ -384,7 +382,6 @@
BasicTextField(
value = value,
modifier = modifier
- .background(colors.backgroundColor(enabled).value, shape)
.indicatorLine(enabled, isError, interactionSource, colors)
.defaultErrorSemantics(isError, getString(Strings.DefaultErrorMessage))
.defaultMinSize(
@@ -417,7 +414,8 @@
enabled = enabled,
isError = isError,
interactionSource = interactionSource,
- colors = colors
+ shape = shape,
+ colors = colors,
)
}
)
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextFieldDefaults.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextFieldDefaults.kt
index 37904b7..8f09e82 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextFieldDefaults.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextFieldDefaults.kt
@@ -544,6 +544,7 @@
* container
* @param trailingIcon the optional trailing icon to be displayed at the end of the text field
* container
+ * @param shape the shape of the text field's container
* @param colors [TextFieldColors] that will be used to resolve color of the text and content
* (including label, placeholder, leading and trailing icons, bottom indicator) for this text field in
* different states. See [TextFieldDefaults.textFieldColors]
@@ -570,6 +571,7 @@
placeholder: @Composable (() -> Unit)? = null,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
+ shape: Shape = TextFieldShape,
colors: TextFieldColors = textFieldColors(),
contentPadding: PaddingValues =
if (label == null) {
@@ -591,8 +593,10 @@
enabled = enabled,
isError = isError,
interactionSource = interactionSource,
+ shape = shape,
colors = colors,
- contentPadding = contentPadding
+ contentPadding = contentPadding,
+ border = null,
)
}
@@ -638,6 +642,7 @@
* container
* @param trailingIcon the optional trailing icon to be displayed at the end of the text field
* container
+ * @param shape the shape of the text field's container and border
* @param colors [TextFieldColors] that will be used to resolve color of the text and content
* (including label, placeholder, leading and trailing icons, border) for this text field in
* different states. See [TextFieldDefaults.outlinedTextFieldColors]
@@ -662,10 +667,11 @@
placeholder: @Composable (() -> Unit)? = null,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
+ shape: Shape = OutlinedTextFieldShape,
colors: TextFieldColors = outlinedTextFieldColors(),
contentPadding: PaddingValues = outlinedTextFieldPadding(),
border: @Composable () -> Unit = {
- BorderBox(enabled, isError, interactionSource, colors)
+ BorderBox(enabled, isError, interactionSource, colors, shape)
}
) {
CommonDecorationBox(
@@ -681,11 +687,95 @@
enabled = enabled,
isError = isError,
interactionSource = interactionSource,
+ shape = shape,
colors = colors,
contentPadding = contentPadding,
- border = border
+ border = border,
)
}
+
+ @Deprecated(
+ level = DeprecationLevel.HIDDEN,
+ message = "Maintained for binary compatibility. Use overload with `shape` parameter."
+ )
+ @Composable
+ @ExperimentalMaterialApi
+ fun TextFieldDecorationBox(
+ value: String,
+ innerTextField: @Composable () -> Unit,
+ enabled: Boolean,
+ singleLine: Boolean,
+ visualTransformation: VisualTransformation,
+ interactionSource: InteractionSource,
+ isError: Boolean = false,
+ label: @Composable (() -> Unit)? = null,
+ placeholder: @Composable (() -> Unit)? = null,
+ leadingIcon: @Composable (() -> Unit)? = null,
+ trailingIcon: @Composable (() -> Unit)? = null,
+ colors: TextFieldColors = textFieldColors(),
+ contentPadding: PaddingValues =
+ if (label == null) {
+ textFieldWithoutLabelPadding()
+ } else {
+ textFieldWithLabelPadding()
+ }
+ ) = TextFieldDecorationBox(
+ value = value,
+ innerTextField = innerTextField,
+ enabled = enabled,
+ singleLine = singleLine,
+ visualTransformation = visualTransformation,
+ interactionSource = interactionSource,
+ isError = isError,
+ label = label,
+ placeholder = placeholder,
+ leadingIcon = leadingIcon,
+ trailingIcon = trailingIcon,
+ shape = TextFieldShape,
+ colors = colors,
+ contentPadding = contentPadding,
+ )
+
+ @Deprecated(
+ level = DeprecationLevel.HIDDEN,
+ message = "Maintained for binary compatibility. Use overload with `shape` parameter."
+ )
+ @Composable
+ @ExperimentalMaterialApi
+ fun OutlinedTextFieldDecorationBox(
+ value: String,
+ innerTextField: @Composable () -> Unit,
+ enabled: Boolean,
+ singleLine: Boolean,
+ visualTransformation: VisualTransformation,
+ interactionSource: InteractionSource,
+ isError: Boolean = false,
+ label: @Composable (() -> Unit)? = null,
+ placeholder: @Composable (() -> Unit)? = null,
+ leadingIcon: @Composable (() -> Unit)? = null,
+ trailingIcon: @Composable (() -> Unit)? = null,
+ colors: TextFieldColors = outlinedTextFieldColors(),
+ contentPadding: PaddingValues = outlinedTextFieldPadding(),
+ border: @Composable () -> Unit = {
+ BorderBox(enabled, isError, interactionSource, colors)
+ }
+ ) = OutlinedTextFieldDecorationBox(
+ value = value,
+ innerTextField = innerTextField,
+ enabled = enabled,
+ singleLine = singleLine,
+ visualTransformation = visualTransformation,
+ interactionSource = interactionSource,
+ isError = isError,
+ label = label,
+ placeholder = placeholder,
+ leadingIcon = leadingIcon,
+ trailingIcon = trailingIcon,
+ shape = OutlinedTextFieldShape,
+ colors = colors,
+ contentPadding = contentPadding,
+ border = border,
+ )
}
@Immutable
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 5c493f87..85a7722 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
@@ -22,6 +22,7 @@
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
+import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.layout.Box
@@ -37,6 +38,7 @@
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.layout.IntrinsicMeasurable
import androidx.compose.ui.layout.LayoutIdParentData
@@ -66,16 +68,17 @@
innerTextField: @Composable () -> Unit,
visualTransformation: VisualTransformation,
label: @Composable (() -> Unit)?,
- placeholder: @Composable (() -> Unit)? = null,
- leadingIcon: @Composable (() -> Unit)? = null,
- trailingIcon: @Composable (() -> Unit)? = null,
- singleLine: Boolean = false,
- enabled: Boolean = true,
- isError: Boolean = false,
+ placeholder: @Composable (() -> Unit)?,
+ leadingIcon: @Composable (() -> Unit)?,
+ trailingIcon: @Composable (() -> Unit)?,
+ singleLine: Boolean,
+ enabled: Boolean,
+ isError: Boolean,
interactionSource: InteractionSource,
contentPadding: PaddingValues,
+ shape: Shape,
colors: TextFieldColors,
- border: @Composable (() -> Unit)? = null
+ border: @Composable (() -> Unit)?,
) {
val transformedText = remember(value, visualTransformation) {
visualTransformation.filter(AnnotatedString(value))
@@ -159,10 +162,13 @@
}
}
+ val backgroundModifier =
+ Modifier.background(colors.backgroundColor(enabled).value, shape)
+
when (type) {
TextFieldType.Filled -> {
TextFieldLayout(
- modifier = Modifier,
+ modifier = backgroundModifier,
textField = innerTextField,
placeholder = decoratedPlaceholder,
label = decoratedLabel,
@@ -186,7 +192,7 @@
}
OutlinedTextFieldLayout(
- modifier = Modifier,
+ modifier = backgroundModifier,
textField = innerTextField,
placeholder = decoratedPlaceholder,
label = decoratedLabel,
diff --git a/compose/material3/adaptive/adaptive-layout/build.gradle b/compose/material3/adaptive/adaptive-layout/build.gradle
index 7e7f7c9..bc2d364 100644
--- a/compose/material3/adaptive/adaptive-layout/build.gradle
+++ b/compose/material3/adaptive/adaptive-layout/build.gradle
@@ -44,6 +44,7 @@
api(project(":compose:material3:adaptive:adaptive"))
api(project(":compose:animation:animation-core"))
api(project(":compose:ui:ui"))
+ implementation(project(":compose:animation:animation"))
implementation("androidx.compose.foundation:foundation:1.6.5")
implementation("androidx.compose.foundation:foundation-layout:1.6.5")
implementation("androidx.compose.ui:ui-geometry:1.6.5")
@@ -116,7 +117,7 @@
androidx {
name = "Material Adaptive"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2023"
description = "Compose Material Design Adaptive Library"
samples(project(":compose:material3:adaptive:adaptive-samples"))
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/AnimateBoundsModifier.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/AnimateBoundsModifier.kt
index b504210..b3234de 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/AnimateBoundsModifier.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/AnimateBoundsModifier.kt
@@ -20,7 +20,6 @@
import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.animation.core.TargetBasedAnimation
import androidx.compose.animation.core.VectorConverter
-import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.ApproachLayoutModifierNode
@@ -120,7 +119,6 @@
lookaheadCoordinates: LayoutCoordinates
) = animateFraction() != 1f
- @OptIn(ExperimentalComposeUiApi::class)
override fun ApproachMeasureScope.approachMeasure(
measurable: Measurable,
constraints: Constraints
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/Pane.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/Pane.kt
index 2d255ce..9a5aa23 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/Pane.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/Pane.kt
@@ -18,7 +18,6 @@
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.AnimatedVisibilityScope
-import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@@ -37,8 +36,6 @@
* @sample androidx.compose.material3.adaptive.samples.ListDetailPaneScaffoldSample
* @sample androidx.compose.material3.adaptive.samples.ListDetailPaneScaffoldSampleWithExtraPane
*/
-@Suppress("IllegalExperimentalApiUsage") // TODO: address before moving to beta
-@OptIn(ExperimentalAnimationApi::class)
@ExperimentalMaterial3AdaptiveApi
@Composable
fun ThreePaneScaffoldScope.AnimatedPane(
diff --git a/compose/material3/adaptive/adaptive-navigation/build.gradle b/compose/material3/adaptive/adaptive-navigation/build.gradle
index 5297890..9087365 100644
--- a/compose/material3/adaptive/adaptive-navigation/build.gradle
+++ b/compose/material3/adaptive/adaptive-navigation/build.gradle
@@ -112,7 +112,7 @@
androidx {
name = "Material Adaptive"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2023"
description = "Compose Material Design Adaptive Library"
}
diff --git a/compose/material3/adaptive/adaptive/build.gradle b/compose/material3/adaptive/adaptive/build.gradle
index 42422b8..7f83a8b 100644
--- a/compose/material3/adaptive/adaptive/build.gradle
+++ b/compose/material3/adaptive/adaptive/build.gradle
@@ -112,7 +112,7 @@
androidx {
name = "Material Adaptive"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2023"
description = "Compose Material Design Adaptive Library"
}
diff --git a/compose/material3/material3-adaptive-navigation-suite/build.gradle b/compose/material3/material3-adaptive-navigation-suite/build.gradle
index de32cd7..bac0af8 100644
--- a/compose/material3/material3-adaptive-navigation-suite/build.gradle
+++ b/compose/material3/material3-adaptive-navigation-suite/build.gradle
@@ -112,7 +112,7 @@
androidx {
name = "Material Adaptive Navigation Suite"
mavenVersion = LibraryVersions.COMPOSE_MATERIAL3_ADAPTIVE_NAVIGATION_SUITE
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2023"
description = "Compose Material Design Adaptive Navigation Suite Library"
samples(project(":compose:material3:material3-adaptive-navigation-suite:material3-adaptive-navigation-suite-samples"))
diff --git a/compose/material3/material3-common/build.gradle b/compose/material3/material3-common/build.gradle
index f41127b..b55c721 100644
--- a/compose/material3/material3-common/build.gradle
+++ b/compose/material3/material3-common/build.gradle
@@ -111,7 +111,7 @@
androidx {
name = "Compose Material 3 Common"
mavenVersion = LibraryVersions.COMPOSE_MATERIAL3_COMMON
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2023"
description = "Compose Material 3 Common Library. This library contains foundational, themeless " +
"components that can be shared between different Material libraries or used by app" +
diff --git a/compose/material3/material3-window-size-class/build.gradle b/compose/material3/material3-window-size-class/build.gradle
index 28239af..638ecf4 100644
--- a/compose/material3/material3-window-size-class/build.gradle
+++ b/compose/material3/material3-window-size-class/build.gradle
@@ -119,7 +119,7 @@
androidx {
name = "Compose Material 3 Window Size Class"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2022"
description = "Provides window size classes for building responsive UIs"
samples(project(":compose:material3:material3-window-size-class:material3-window-size-class-samples"))
diff --git a/compose/material3/material3/build.gradle b/compose/material3/material3/build.gradle
index cadd1ea..36f7bb2c 100644
--- a/compose/material3/material3/build.gradle
+++ b/compose/material3/material3/build.gradle
@@ -143,7 +143,7 @@
androidx {
name = "Compose Material3 Components"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2021"
description = "Compose Material You Design Components library"
samples(project(":compose:material3:material3:material3-samples"))
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SwitchSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SwitchSamples.kt
index 9fbb12a..a427429 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SwitchSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SwitchSamples.kt
@@ -49,23 +49,20 @@
@Composable
fun SwitchWithThumbIconSample() {
var checked by remember { mutableStateOf(true) }
- // Icon isn't focusable, no need for content description
- val icon: (@Composable () -> Unit)? = if (checked) {
- {
- Icon(
- imageVector = Icons.Filled.Check,
- contentDescription = null,
- modifier = Modifier.size(SwitchDefaults.IconSize),
- )
- }
- } else {
- null
- }
Switch(
modifier = Modifier.semantics { contentDescription = "Demo with icon" },
checked = checked,
onCheckedChange = { checked = it },
- thumbContent = icon
+ thumbContent = {
+ if (checked) {
+ // Icon isn't focusable, no need for content description
+ Icon(
+ imageVector = Icons.Filled.Check,
+ contentDescription = null,
+ modifier = Modifier.size(SwitchDefaults.IconSize),
+ )
+ }
+ }
)
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Switch.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Switch.kt
index 7e17cb0..5681ab7 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Switch.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Switch.kt
@@ -17,6 +17,8 @@
package androidx.compose.material3
import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.SnapSpec
import androidx.compose.animation.core.TweenSpec
import androidx.compose.foundation.background
import androidx.compose.foundation.border
@@ -24,38 +26,34 @@
import androidx.compose.foundation.interaction.Interaction
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.interaction.collectIsPressedAsState
+import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.BoxScope
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.requiredSize
-import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.selection.toggleable
import androidx.compose.material3.tokens.SwitchTokens
+import androidx.compose.material3.tokens.SwitchTokens.TrackOutlineWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.Immutable
-import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.Stable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.graphics.takeOrElse
-import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.node.LayoutModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.invalidateMeasurement
+import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.semantics.Role
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.dp
-import kotlin.math.roundToInt
import kotlinx.coroutines.launch
/**
@@ -98,43 +96,14 @@
colors: SwitchColors = SwitchDefaults.colors(),
interactionSource: MutableInteractionSource? = null,
) {
- @Suppress("NAME_SHADOWING")
- val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
- val uncheckedThumbDiameter = if (thumbContent == null) {
- UncheckedThumbDiameter
- } else {
- ThumbDiameter
- }
-
- val thumbPaddingStart = (SwitchHeight - uncheckedThumbDiameter) / 2
- val minBound = with(LocalDensity.current) { thumbPaddingStart.toPx() }
- val maxBound = with(LocalDensity.current) { ThumbPathLength.toPx() }
- val valueToOffset = remember<(Boolean) -> Float>(minBound, maxBound) {
- { value -> if (value) maxBound else minBound }
- }
-
- val targetValue = valueToOffset(checked)
- val offset = remember { Animatable(targetValue) }
- val scope = rememberCoroutineScope()
-
- SideEffect {
- // min bound might have changed if the icon is only rendered in checked state.
- offset.updateBounds(lowerBound = minBound)
- }
-
- DisposableEffect(checked) {
- if (offset.targetValue != targetValue) {
- scope.launch {
- offset.animateTo(targetValue, AnimationSpec)
- }
- }
- onDispose { }
- }
+ @Suppress("NAME_SHADOWING") val interactionSource =
+ interactionSource ?: remember { MutableInteractionSource() }
// TODO: Add Swipeable modifier b/223797571
- val toggleableModifier =
- if (onCheckedChange != null) {
- Modifier.toggleable(
+ val toggleableModifier = if (onCheckedChange != null) {
+ Modifier
+ .minimumInteractiveComponentSize()
+ .toggleable(
value = checked,
onValueChange = onCheckedChange,
enabled = enabled,
@@ -142,101 +111,59 @@
interactionSource = interactionSource,
indication = null
)
- } else {
- Modifier
- }
+ } else {
+ Modifier
+ }
- Box(
- modifier
- .then(
- if (onCheckedChange != null) {
- Modifier.minimumInteractiveComponentSize()
- } else {
- Modifier
- }
- )
+ SwitchImpl(
+ modifier = modifier
.then(toggleableModifier)
.wrapContentSize(Alignment.Center)
- .requiredSize(SwitchWidth, SwitchHeight)
- ) {
- SwitchImpl(
- checked = checked,
- enabled = enabled,
- colors = colors,
- thumbValue = offset.asState(),
- interactionSource = interactionSource,
- thumbShape = SwitchTokens.HandleShape.value,
- uncheckedThumbDiameter = uncheckedThumbDiameter,
- minBound = thumbPaddingStart,
- maxBound = ThumbPathLength,
- thumbContent = thumbContent,
- )
- }
+ .requiredSize(SwitchWidth, SwitchHeight),
+ checked = checked,
+ enabled = enabled,
+ colors = colors,
+ interactionSource = interactionSource,
+ thumbShape = SwitchTokens.HandleShape.value,
+ thumbContent = thumbContent,
+ )
}
@Composable
@Suppress("ComposableLambdaParameterNaming", "ComposableLambdaParameterPosition")
-private fun BoxScope.SwitchImpl(
+private fun SwitchImpl(
+ modifier: Modifier,
checked: Boolean,
enabled: Boolean,
colors: SwitchColors,
- thumbValue: State<Float>,
thumbContent: (@Composable () -> Unit)?,
interactionSource: InteractionSource,
thumbShape: Shape,
- uncheckedThumbDiameter: Dp,
- minBound: Dp,
- maxBound: Dp,
) {
val trackColor = colors.trackColor(enabled, checked)
- val isPressed by interactionSource.collectIsPressedAsState()
-
- val thumbValueDp = with(LocalDensity.current) { thumbValue.value.toDp() }
- val thumbSizeDp = if (isPressed) {
- SwitchTokens.PressedHandleWidth
- } else {
- uncheckedThumbDiameter + (ThumbDiameter - uncheckedThumbDiameter) *
- ((thumbValueDp - minBound) / (maxBound - minBound))
- }
-
- val thumbOffset = if (isPressed) {
- with(LocalDensity.current) {
- if (checked) {
- ThumbPathLength - SwitchTokens.TrackOutlineWidth
- } else {
- SwitchTokens.TrackOutlineWidth
- }.toPx()
- }
- } else {
- thumbValue.value
- }
-
+ val resolvedThumbColor = colors.thumbColor(enabled, checked)
val trackShape = SwitchTokens.TrackShape.value
- val modifier = Modifier
- .align(Alignment.Center)
- .width(SwitchWidth)
- .height(SwitchHeight)
- .border(
- SwitchTokens.TrackOutlineWidth,
- colors.borderColor(enabled, checked),
- trackShape
- )
- .background(trackColor, trackShape)
- Box(modifier) {
- val resolvedThumbColor = colors.thumbColor(enabled, checked)
+ Box(
+ modifier
+ .border(
+ TrackOutlineWidth,
+ colors.borderColor(enabled, checked),
+ trackShape
+ )
+ .background(trackColor, trackShape)
+ ) {
Box(
modifier = Modifier
.align(Alignment.CenterStart)
- .offset { IntOffset(thumbOffset.roundToInt(), 0) }
+ .then(ThumbElement(interactionSource, checked))
.indication(
interactionSource = interactionSource,
indication = rippleOrFallbackImplementation(
bounded = false,
- SwitchTokens.StateLayerSize / 2
+ radius = SwitchTokens.StateLayerSize / 2
)
)
- .requiredSize(thumbSizeDp)
.background(resolvedThumbColor, thumbShape),
contentAlignment = Alignment.Center
) {
@@ -251,14 +178,124 @@
}
}
-internal val ThumbDiameter = SwitchTokens.SelectedHandleWidth
-internal val UncheckedThumbDiameter = SwitchTokens.UnselectedHandleWidth
-private val SwitchWidth = SwitchTokens.TrackWidth
-private val SwitchHeight = SwitchTokens.TrackHeight
-private val ThumbPadding = (SwitchHeight - ThumbDiameter) / 2
-private val ThumbPathLength = (SwitchWidth - ThumbDiameter) - ThumbPadding
+private data class ThumbElement(
+ val interactionSource: InteractionSource,
+ val checked: Boolean,
+) : ModifierNodeElement<ThumbNode>() {
+ override fun create() = ThumbNode(interactionSource, checked)
-private val AnimationSpec = TweenSpec<Float>(durationMillis = 100)
+ override fun update(node: ThumbNode) {
+ node.interactionSource = interactionSource
+ if (node.checked != checked) {
+ node.invalidateMeasurement()
+ }
+ node.checked = checked
+ node.update()
+ }
+
+ override fun InspectorInfo.inspectableProperties() {
+ name = "switchThumb"
+ properties["interactionSource"] = interactionSource
+ properties["checked"] = checked
+ }
+}
+
+private class ThumbNode(
+ var interactionSource: InteractionSource,
+ var checked: Boolean,
+) : Modifier.Node(), LayoutModifierNode {
+
+ override val shouldAutoInvalidate: Boolean
+ get() = false
+
+ private var isPressed = false
+ private var offsetAnim: Animatable<Float, AnimationVector1D>? = null
+ private var sizeAnim: Animatable<Float, AnimationVector1D>? = null
+ private var initialOffset: Float = Float.NaN
+ private var initialSize: Float = Float.NaN
+
+ override fun onAttach() {
+ coroutineScope.launch {
+ var pressCount = 0
+ interactionSource.interactions.collect { interaction ->
+ when (interaction) {
+ is PressInteraction.Press -> pressCount++
+ is PressInteraction.Release -> pressCount--
+ is PressInteraction.Cancel -> pressCount--
+ }
+ val pressed = pressCount > 0
+ if (isPressed != pressed) {
+ isPressed = pressed
+ invalidateMeasurement()
+ }
+ }
+ }
+ }
+
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints
+ ): MeasureResult {
+ val hasContent = measurable.maxIntrinsicHeight(constraints.maxWidth) != 0 &&
+ measurable.maxIntrinsicWidth(constraints.maxHeight) != 0
+ val size = when {
+ isPressed -> SwitchTokens.PressedHandleWidth
+ hasContent || checked -> ThumbDiameter
+ else -> UncheckedThumbDiameter
+ }.toPx()
+
+ val actualSize = (sizeAnim?.value ?: size).toInt()
+ val placeable = measurable.measure(
+ Constraints.fixed(actualSize, actualSize)
+ )
+ val thumbPaddingStart = (SwitchHeight - size.toDp()) / 2f
+ val minBound = thumbPaddingStart.toPx()
+ val thumbPathLength = (SwitchWidth - ThumbDiameter) - ThumbPadding
+ val maxBound = thumbPathLength.toPx()
+ val offset = when {
+ isPressed && checked -> maxBound - TrackOutlineWidth.toPx()
+ isPressed && !checked -> TrackOutlineWidth.toPx()
+ checked -> maxBound
+ else -> minBound
+ }
+
+ if (sizeAnim?.targetValue != size) {
+ coroutineScope.launch {
+ sizeAnim?.animateTo(
+ size,
+ if (isPressed) SnapSpec else AnimationSpec
+ )
+ }
+ }
+
+ if (offsetAnim?.targetValue != offset) {
+ coroutineScope.launch {
+ offsetAnim?.animateTo(
+ offset,
+ if (isPressed) SnapSpec else AnimationSpec
+ )
+ }
+ }
+
+ if (initialSize.isNaN() && initialOffset.isNaN()) {
+ initialSize = size
+ initialOffset = offset
+ }
+
+ return layout(actualSize, actualSize) {
+ placeable.placeRelative(offsetAnim?.value?.toInt() ?: offset.toInt(), 0)
+ }
+ }
+
+ fun update() {
+ if (sizeAnim == null && !initialSize.isNaN()) {
+ sizeAnim = Animatable(initialSize)
+ }
+
+ if (offsetAnim == null && !initialOffset.isNaN())
+ offsetAnim = Animatable(initialOffset)
+ }
+}
/**
* Contains the default values used by [Switch]
@@ -572,3 +609,13 @@
return result
}
}
+
+/* @VisibleForTesting */
+internal val ThumbDiameter = SwitchTokens.SelectedHandleWidth
+internal val UncheckedThumbDiameter = SwitchTokens.UnselectedHandleWidth
+
+private val SwitchWidth = SwitchTokens.TrackWidth
+private val SwitchHeight = SwitchTokens.TrackHeight
+private val ThumbPadding = (SwitchHeight - ThumbDiameter) / 2
+private val SnapSpec = SnapSpec<Float>()
+private val AnimationSpec = TweenSpec<Float>(durationMillis = 100)
diff --git a/compose/runtime/runtime-livedata/build.gradle b/compose/runtime/runtime-livedata/build.gradle
index 0a09b7d..fdcb555 100644
--- a/compose/runtime/runtime-livedata/build.gradle
+++ b/compose/runtime/runtime-livedata/build.gradle
@@ -49,7 +49,7 @@
androidx {
name = "Compose LiveData integration"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2020"
description = "Compose integration with LiveData"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/runtime/runtime-rxjava2/build.gradle b/compose/runtime/runtime-rxjava2/build.gradle
index 2f58ae2..682c0a4 100644
--- a/compose/runtime/runtime-rxjava2/build.gradle
+++ b/compose/runtime/runtime-rxjava2/build.gradle
@@ -47,7 +47,7 @@
androidx {
name = "Compose RxJava 2 integration"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2020"
description = "Compose integration with RxJava 2"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/runtime/runtime-rxjava3/build.gradle b/compose/runtime/runtime-rxjava3/build.gradle
index f90ceba..490e4e0 100644
--- a/compose/runtime/runtime-rxjava3/build.gradle
+++ b/compose/runtime/runtime-rxjava3/build.gradle
@@ -47,7 +47,7 @@
androidx {
name = "Compose RxJava 3 integration"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2020"
description = "Compose integration with RxJava 3"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/runtime/runtime-saveable/build.gradle b/compose/runtime/runtime-saveable/build.gradle
index e73d562..5612159 100644
--- a/compose/runtime/runtime-saveable/build.gradle
+++ b/compose/runtime/runtime-saveable/build.gradle
@@ -116,7 +116,7 @@
androidx {
name = "Compose Saveable"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2020"
description = "Compose components that allow saving and restoring the local ui state"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/runtime/runtime-tracing/build.gradle b/compose/runtime/runtime-tracing/build.gradle
index 4f60cda..a6b744f 100644
--- a/compose/runtime/runtime-tracing/build.gradle
+++ b/compose/runtime/runtime-tracing/build.gradle
@@ -21,9 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
-import androidx.build.RunApiTasks
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -51,7 +49,7 @@
androidx {
name = "Compose Runtime: Tracing"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2022"
description = "Additional tracing in Compose"
metalavaK2UastEnabled = true
diff --git a/compose/runtime/runtime/build.gradle b/compose/runtime/runtime/build.gradle
index 063fec6..60a8ba3 100644
--- a/compose/runtime/runtime/build.gradle
+++ b/compose/runtime/runtime/build.gradle
@@ -132,7 +132,7 @@
androidx {
name = "Compose Runtime"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2019"
description = "Tree composition support for code generated by the Compose compiler plugin and corresponding public API"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/ui/ui-android-stubs/build.gradle b/compose/ui/ui-android-stubs/build.gradle
index 161d6f7..168677e 100644
--- a/compose/ui/ui-android-stubs/build.gradle
+++ b/compose/ui/ui-android-stubs/build.gradle
@@ -34,7 +34,7 @@
androidx {
name = "Compose Android Stubs"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2020"
description = "Stubs for classes in older Android APIs"
metalavaK2UastEnabled = true
diff --git a/compose/ui/ui-geometry/build.gradle b/compose/ui/ui-geometry/build.gradle
index e731a2f..7f7f827 100644
--- a/compose/ui/ui-geometry/build.gradle
+++ b/compose/ui/ui-geometry/build.gradle
@@ -95,7 +95,7 @@
androidx {
name = "Compose Geometry"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2020"
description = "Compose classes related to dimensions without units"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/ui/ui-geometry/src/androidUnitTest/kotlin/androidx/compose/ui/geometry/OffsetTest.kt b/compose/ui/ui-geometry/src/androidUnitTest/kotlin/androidx/compose/ui/geometry/OffsetTest.kt
index 88c4553..35deeb6 100644
--- a/compose/ui/ui-geometry/src/androidUnitTest/kotlin/androidx/compose/ui/geometry/OffsetTest.kt
+++ b/compose/ui/ui-geometry/src/androidUnitTest/kotlin/androidx/compose/ui/geometry/OffsetTest.kt
@@ -16,7 +16,6 @@
package androidx.compose.ui.geometry
-import kotlin.test.assertFails
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@@ -137,9 +136,14 @@
Offset(-10.0f, Float.POSITIVE_INFINITY),
-Offset(10.0f, Float.NEGATIVE_INFINITY)
)
- assertFails {
- -Offset(Float.NaN, Float.NaN)
- }
+
+ // behavior for -Unspecified
+ val minusUnspecified = -Offset(Float.NaN, Float.NaN)
+ assertTrue(minusUnspecified.x.isNaN())
+ assertTrue(minusUnspecified.y.isNaN())
+ assertTrue(minusUnspecified.isUnspecified)
+ assertFalse(minusUnspecified.isSpecified)
+ assertFalse(minusUnspecified.isFinite)
}
@Test
@@ -154,8 +158,7 @@
assertFalse(Offset(Float.NEGATIVE_INFINITY, 20.0f).isFinite)
assertFalse(Offset(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY).isFinite)
- assertFails {
- Offset(Float.NaN, Float.NaN).isFinite
- }
+ // isFinite should return false for unspecified/NaN values
+ assertFalse(Offset(Float.NaN, Float.NaN).isFinite)
}
}
diff --git a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Offset.kt b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Offset.kt
index 459e711..33f57b5 100644
--- a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Offset.kt
+++ b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Offset.kt
@@ -61,24 +61,10 @@
@kotlin.jvm.JvmInline
value class Offset internal constructor(internal val packedValue: Long) {
@Stable
- val x: Float
- get() {
- // Explicitly compare against packed values to avoid auto-boxing of Size.Unspecified
- checkPrecondition(packedValue != UnspecifiedPackedFloats) {
- "Offset is unspecified"
- }
- return unpackFloat1(packedValue)
- }
+ val x: Float get() = unpackFloat1(packedValue)
@Stable
- val y: Float
- get() {
- // Explicitly compare against packed values to avoid auto-boxing of Size.Unspecified
- checkPrecondition(packedValue != UnspecifiedPackedFloats) {
- "Offset is unspecified"
- }
- return unpackFloat2(packedValue)
- }
+ val y: Float get() = unpackFloat2(packedValue)
@Stable
inline operator fun component1(): Float = x
@@ -135,9 +121,6 @@
*/
@Stable
fun getDistance(): Float {
- checkPrecondition(packedValue != UnspecifiedPackedFloats) {
- "Offset is unspecified"
- }
val x = unpackFloat1(packedValue)
val y = unpackFloat2(packedValue)
return sqrt(x * x + y * y)
@@ -150,9 +133,6 @@
*/
@Stable
fun getDistanceSquared(): Float {
- checkPrecondition(packedValue != UnspecifiedPackedFloats) {
- "Offset is unspecified"
- }
val x = unpackFloat1(packedValue)
val y = unpackFloat2(packedValue)
return x * x + y * y
@@ -168,9 +148,6 @@
*/
@Stable
operator fun unaryMinus(): Offset {
- checkPrecondition(packedValue != UnspecifiedPackedFloats) {
- "Offset is unspecified"
- }
return Offset(packedValue xor DualFloatSignBit)
}
@@ -183,12 +160,6 @@
*/
@Stable
operator fun minus(other: Offset): Offset {
- checkPrecondition(
- packedValue != UnspecifiedPackedFloats &&
- other.packedValue != UnspecifiedPackedFloats
- ) {
- "Offset is unspecified"
- }
return Offset(
packFloats(
unpackFloat1(packedValue) - unpackFloat1(other.packedValue),
@@ -206,12 +177,6 @@
*/
@Stable
operator fun plus(other: Offset): Offset {
- checkPrecondition(
- packedValue != UnspecifiedPackedFloats &&
- other.packedValue != UnspecifiedPackedFloats
- ) {
- "Offset is unspecified"
- }
return Offset(
packFloats(
unpackFloat1(packedValue) + unpackFloat1(other.packedValue),
@@ -229,9 +194,6 @@
*/
@Stable
operator fun times(operand: Float): Offset {
- checkPrecondition(packedValue != UnspecifiedPackedFloats) {
- "Offset is unspecified"
- }
return Offset(
packFloats(
unpackFloat1(packedValue) * operand,
@@ -249,9 +211,6 @@
*/
@Stable
operator fun div(operand: Float): Offset {
- checkPrecondition(packedValue != UnspecifiedPackedFloats) {
- "Offset is unspecified"
- }
return Offset(
packFloats(
unpackFloat1(packedValue) / operand,
@@ -269,9 +228,6 @@
*/
@Stable
operator fun rem(operand: Float): Offset {
- checkPrecondition(packedValue != UnspecifiedPackedFloats) {
- "Offset is unspecified"
- }
return Offset(
packFloats(
unpackFloat1(packedValue) % operand,
@@ -306,12 +262,6 @@
*/
@Stable
fun lerp(start: Offset, stop: Offset, fraction: Float): Offset {
- checkPrecondition(
- start.packedValue != UnspecifiedPackedFloats &&
- stop.packedValue != UnspecifiedPackedFloats
- ) {
- "Offset is unspecified"
- }
return Offset(
packFloats(
lerp(unpackFloat1(start.packedValue), unpackFloat1(stop.packedValue), fraction),
@@ -325,13 +275,10 @@
*/
@Stable
val Offset.isFinite: Boolean get() {
- checkPrecondition(packedValue != UnspecifiedPackedFloats) {
- "Offset is unspecified"
- }
// Mask out the sign bit and do an equality check in each 32-bit lane
// against the "infinity base" mask (to check whether each packed float
// is infinite or not).
- val v = (packedValue and DualUnsignedFloatMask) xor DualFloatInfinityBase
+ val v = (packedValue and DualFloatInfinityBase) xor DualFloatInfinityBase
return (((v shr 1) or Uint64High32) - v) and Uint64High32 == 0L
}
@@ -339,13 +286,15 @@
* `false` when this is [Offset.Unspecified].
*/
@Stable
-val Offset.isSpecified: Boolean get() = packedValue != UnspecifiedPackedFloats
+val Offset.isSpecified: Boolean
+ get() = packedValue and DualUnsignedFloatMask != UnspecifiedPackedFloats
/**
* `true` when this is [Offset.Unspecified].
*/
@Stable
-val Offset.isUnspecified: Boolean get() = packedValue == UnspecifiedPackedFloats
+val Offset.isUnspecified: Boolean
+ get() = packedValue and DualUnsignedFloatMask == UnspecifiedPackedFloats
/**
* If this [Offset] [isSpecified] then this is returned, otherwise [block] is executed
diff --git a/compose/ui/ui-graphics/api/current.txt b/compose/ui/ui-graphics/api/current.txt
index 515e4f5..41516b7 100644
--- a/compose/ui/ui-graphics/api/current.txt
+++ b/compose/ui/ui-graphics/api/current.txt
@@ -918,7 +918,7 @@
method @androidx.compose.runtime.Stable public static androidx.compose.ui.graphics.Shadow lerp(androidx.compose.ui.graphics.Shadow start, androidx.compose.ui.graphics.Shadow stop, float fraction);
}
- @androidx.compose.runtime.Immutable public interface Shape {
+ @androidx.compose.runtime.Stable public interface Shape {
method public androidx.compose.ui.graphics.Outline createOutline(long size, androidx.compose.ui.unit.LayoutDirection layoutDirection, androidx.compose.ui.unit.Density density);
}
diff --git a/compose/ui/ui-graphics/api/restricted_current.txt b/compose/ui/ui-graphics/api/restricted_current.txt
index 68a8faa..9da68f8 100644
--- a/compose/ui/ui-graphics/api/restricted_current.txt
+++ b/compose/ui/ui-graphics/api/restricted_current.txt
@@ -990,7 +990,7 @@
method @androidx.compose.runtime.Stable public static androidx.compose.ui.graphics.Shadow lerp(androidx.compose.ui.graphics.Shadow start, androidx.compose.ui.graphics.Shadow stop, float fraction);
}
- @androidx.compose.runtime.Immutable public interface Shape {
+ @androidx.compose.runtime.Stable public interface Shape {
method public androidx.compose.ui.graphics.Outline createOutline(long size, androidx.compose.ui.unit.LayoutDirection layoutDirection, androidx.compose.ui.unit.Density density);
}
diff --git a/compose/ui/ui-graphics/build.gradle b/compose/ui/ui-graphics/build.gradle
index e95cbdd..8046e187 100644
--- a/compose/ui/ui-graphics/build.gradle
+++ b/compose/ui/ui-graphics/build.gradle
@@ -76,7 +76,9 @@
// This has stub APIs for access to legacy Android APIs, so we don't want
// any dependency on this module.
compileOnly(project(":compose:ui:ui-android-stubs"))
- implementation("androidx.graphics:graphics-path:1.0.0-beta02")
+ // TODO: Re-pin when 1.0.1 is released
+ //implementation("androidx.graphics:graphics-path:1.0.1")
+ implementation(project(":graphics:graphics-path"))
implementation libs.androidx.core
api("androidx.annotation:annotation-experimental:1.4.0")
}
@@ -139,7 +141,7 @@
androidx {
name = "Compose Graphics"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2020"
description = "Compose graphics"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/OutlineTest.kt b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/OutlineTest.kt
index 776c8d5..c106240 100644
--- a/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/OutlineTest.kt
+++ b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/OutlineTest.kt
@@ -22,6 +22,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
import org.junit.Test
import org.junit.runner.RunWith
@@ -55,4 +56,44 @@
)
assertEquals(Rect(0f, 15f, 100f, 200f), pathOutline.bounds)
}
+
+ @Test
+ fun testRectOutlineEquality() {
+ val outlineRect = Outline.Rectangle(Rect(1f, 2f, 3f, 4f))
+ val equalOutlineRect = Outline.Rectangle(Rect(1f, 2f, 3f, 4f))
+ val differentOutlineRect = Outline.Rectangle(Rect(4f, 3f, 2f, 1f))
+ assertEquals(outlineRect, equalOutlineRect)
+ assertNotEquals(outlineRect, differentOutlineRect)
+ }
+
+ @Test
+ fun testRoundRectOutlineEquality() {
+ val roundRectOutline = Outline.Rounded(
+ RoundRect(5f, 10f, 15f, 20f, CornerRadius(7f))
+ )
+ val equalRoundRectOutline = Outline.Rounded(
+ RoundRect(5f, 10f, 15f, 20f, CornerRadius(7f))
+ )
+ val differentRoundRectOutline = Outline.Rounded(
+ RoundRect(20f, 15f, 10f, 5f, CornerRadius(3f))
+ )
+ assertEquals(roundRectOutline, equalRoundRectOutline)
+ assertNotEquals(roundRectOutline, differentRoundRectOutline)
+ }
+
+ @Test
+ fun testPathOutlineEquality() {
+ val path = Path().apply {
+ moveTo(5f, 15f)
+ lineTo(100f, 200f)
+ lineTo(0f, 200f)
+ close()
+ }
+ val pathOutline = Outline.Generic(path)
+ val pathOutline2 = Outline.Generic(path)
+
+ // Generic outlines should only be referentially equal, as the path can change over time
+ assertEquals(pathOutline, pathOutline)
+ assertNotEquals(pathOutline, pathOutline2)
+ }
}
diff --git a/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayerTest.kt b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayerTest.kt
index bf3b965..6f14deb 100644
--- a/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayerTest.kt
+++ b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayerTest.kt
@@ -21,9 +21,6 @@
import android.graphics.PixelFormat
import android.graphics.drawable.Drawable
import android.os.Build
-import android.os.Handler
-import android.os.HandlerThread
-import android.os.Looper
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
@@ -51,7 +48,6 @@
import androidx.compose.ui.graphics.drawscope.inset
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.graphics.nativeCanvas
-import androidx.compose.ui.graphics.throwIllegalArgumentException
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.graphics.toPixelMap
import androidx.compose.ui.unit.Density
@@ -71,10 +67,7 @@
import java.util.concurrent.TimeUnit
import kotlin.math.roundToInt
import kotlin.test.assertNotNull
-import kotlinx.coroutines.android.asCoroutineDispatcher
import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.withContext
-import org.junit.After
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
@@ -92,61 +85,12 @@
val TEST_SIZE = IntSize(TEST_WIDTH, TEST_HEIGHT)
}
- private var waitThread: HandlerThread? = null
- private var waitHandler: Handler? = null
-
- private fun obtainWaitHandler(): Handler {
- synchronized(this) {
- var thread = waitThread
- if (thread == null) {
- thread = HandlerThread("waitThread").also {
- it.start()
- waitThread = it
- }
- }
-
- var handler = waitHandler
- if (handler == null) {
- handler = Handler(thread.looper)
- }
- return handler
- }
- }
-
- /**
- * Helper method used to synchronously obtain an [ImageBitmap] from a [GraphicsLayer]
- */
- private fun GraphicsLayer.toImageBitmap(handler: Handler): ImageBitmap {
- if (Looper.myLooper() === handler.looper) {
- throwIllegalArgumentException("Handler looper cannot be the same as the current " +
- "looper: ${Looper.myLooper()?.thread?.name} ${handler.looper.thread.name}")
- }
- val latch = CountDownLatch(1)
- var bitmap: ImageBitmap?
- runBlocking {
- withContext(handler.asCoroutineDispatcher().immediate) {
- bitmap = toImageBitmap()
- latch.countDown()
- }
- latch.await()
- }
- return bitmap!!
- }
-
- @After
- fun teardown() {
- synchronized(this) {
- waitThread?.quit()
- waitThread = null
- }
- }
-
@Test
fun testGraphicsLayerBitmap() {
- var bitmap: ImageBitmap? = null
+ lateinit var layer: GraphicsLayer
graphicsLayerTest(
block = { graphicsContext ->
- graphicsContext.createGraphicsLayer().apply {
+ layer = graphicsContext.createGraphicsLayer().apply {
assertEquals(IntSize.Zero, this.size)
record {
drawRect(
@@ -169,13 +113,13 @@
size = size / 2f
)
}
- bitmap = toImageBitmap(obtainWaitHandler())
}
},
verify = {
+ val bitmap: ImageBitmap = layer.toImageBitmap()
assertNotNull(bitmap)
- assertEquals(TEST_SIZE, IntSize(bitmap!!.width, bitmap!!.height))
- bitmap!!.toPixelMap().verifyQuadrants(
+ assertEquals(TEST_SIZE, IntSize(bitmap.width, bitmap.height))
+ bitmap.toPixelMap().verifyQuadrants(
Color.Red,
Color.Blue,
Color.Green,
@@ -206,7 +150,7 @@
// Nulling out the dependency here should be safe despite attempting to obtain an
// ImageBitmap afterwards
provider = null
- graphicsLayer!!.toImageBitmap(obtainWaitHandler()).toPixelMap().verifyQuadrants(
+ graphicsLayer!!.toImageBitmap().toPixelMap().verifyQuadrants(
Color.Red,
Color.Red,
Color.Red,
@@ -238,6 +182,28 @@
}
@Test
+ fun testDrawAfterDiscard() {
+ var layer: GraphicsLayer? = null
+ graphicsLayerTest(
+ block = { graphicsContext ->
+ layer = graphicsContext.createGraphicsLayer().apply {
+ assertEquals(IntSize.Zero, this.size)
+ record {
+ drawRect(Color.Red)
+ }
+ discardDisplayList()
+ }
+ drawLayer(layer!!)
+ },
+ verify = {
+ assertEquals(TEST_SIZE, layer!!.size)
+ assertEquals(IntOffset.Zero, layer!!.topLeft)
+ it.verifyQuadrants(Color.Red, Color.Red, Color.Red, Color.Red)
+ }
+ )
+ }
+
+ @Test
fun testRecordLayerWithSize() {
graphicsLayerTest(
block = { graphicsContext ->
@@ -1290,7 +1256,9 @@
val path = Path().also { it.addOval(Rect(1f, 2f, 3f, 4f)) }
val generic = Outline.Generic(path)
layer.setOutline(generic)
- assertEquals(generic, layer.outline)
+ // We wrap the path in a different Outline object from what we pass in, so compare
+ // the paths instead of the outline instances
+ assertEquals(generic.path, (layer.outline as Outline.Generic).path)
}
)
}
@@ -1355,7 +1323,7 @@
private fun graphicsLayerTest(
block: DrawScope.(GraphicsContext) -> Unit,
- verify: ((PixelMap) -> Unit)? = null,
+ verify: (suspend (PixelMap) -> Unit)? = null,
entireScene: Boolean = false,
usePixelCopy: Boolean = false
) {
@@ -1399,7 +1367,7 @@
resumed.countDown()
}
}
- Assert.assertTrue(resumed.await(300000, TimeUnit.MILLISECONDS))
+ assertTrue(resumed.await(3000, TimeUnit.MILLISECONDS))
if (verify != null) {
val target = if (entireScene) {
@@ -1407,8 +1375,8 @@
} else {
contentView!!
}
- if (usePixelCopy && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- verify(target.captureToImage().toPixelMap())
+ val pixelMap = if (usePixelCopy && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ target.captureToImage().toPixelMap()
} else {
val recordLatch = CountDownLatch(1)
testActivity!!.runOnUiThread {
@@ -1423,11 +1391,14 @@
}
recordLatch.countDown()
}
- assertTrue(recordLatch.await(30000, TimeUnit.MILLISECONDS))
+ assertTrue(recordLatch.await(3000, TimeUnit.MILLISECONDS))
val bitmap = runBlocking {
rootGraphicsLayer!!.toImageBitmap()
}
- verify(bitmap.toPixelMap())
+ bitmap.toPixelMap()
+ }
+ runBlocking {
+ verify(pixelMap)
}
}
} finally {
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidGraphicsContext.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidGraphicsContext.android.kt
index 541181b..ee99a53 100644
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidGraphicsContext.android.kt
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidGraphicsContext.android.kt
@@ -16,8 +16,12 @@
package androidx.compose.ui.graphics
+import android.content.ComponentCallbacks2
+import android.content.Context
+import android.content.res.Configuration
import android.os.Build
import android.view.View
+import android.view.View.OnAttachStateChangeListener
import android.view.ViewGroup
import androidx.annotation.RequiresApi
import androidx.compose.ui.graphics.layer.GraphicsLayer
@@ -42,6 +46,64 @@
private val lock = Any()
private val layerManager = LayerManager(CanvasHolder())
private var viewLayerContainer: DrawChildContainer? = null
+ private var componentCallbackRegistered = false
+ private val componentCallback = object : ComponentCallbacks2 {
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ // NO-OP
+ }
+
+ override fun onLowMemory() {
+ // NO-OP
+ }
+
+ override fun onTrimMemory(level: Int) {
+ // See CacheManager.cpp. HWUI releases graphics resources whenever the trim memory
+ // callback exceed the level of TRIM_MEMORY_BACKGROUND so do the same here to
+ // release and recreate the internal ImageReader used to increment the ref count
+ // of internal RenderNodes
+ // Some devices skip straight to TRIM_COMPLETE so ensure we persist layers if
+ // we receive any trim memory callback that exceeds TRIM_MEMORY_BACKGROUND
+ if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
+ // HardwareRenderer instances would be discarded by HWUI so we need to discard
+ // the existing underlying ImageReader instance and do a placeholder render
+ // to increment the refcount of any outstanding layers again
+ layerManager.updateLayerPersistence()
+ }
+ }
+ }
+
+ init {
+ // Register the component callbacks when the GraphicsContext is created
+ if (ownerView.isAttachedToWindow) {
+ registerComponentCallback(ownerView.context)
+ }
+ ownerView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(v: View) {
+ // If the View is attached to the window again, re-add the component callbacks
+ registerComponentCallback(v.context)
+ }
+
+ override fun onViewDetachedFromWindow(v: View) {
+ // When the View is detached from the window, remove the component callbacks
+ // used to listen to trim memory signals
+ unregisterComponentCallback(v.context)
+ }
+ })
+ }
+
+ private fun registerComponentCallback(context: Context) {
+ if (!componentCallbackRegistered) {
+ context.applicationContext.registerComponentCallbacks(componentCallback)
+ componentCallbackRegistered = true
+ }
+ }
+
+ private fun unregisterComponentCallback(context: Context) {
+ if (componentCallbackRegistered) {
+ context.applicationContext.unregisterComponentCallbacks(componentCallback)
+ componentCallbackRegistered = false
+ }
+ }
override fun createGraphicsLayer(): GraphicsLayer {
synchronized(lock) {
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayer.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayer.android.kt
index 330d8ec..4356050 100644
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayer.android.kt
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayer.android.kt
@@ -421,7 +421,10 @@
this.layoutDirection = layoutDirection
this.drawBlock = block
impl.isInvalidated = true
+ recordInternal()
+ }
+ private fun recordInternal() {
childDependenciesTracker.withTracking(
onDependencyRemoved = { it.onRemovedFromParentLayer() }
) {
@@ -472,6 +475,10 @@
androidCanvas.concat(impl.calculateMatrix())
}
+ internal fun drawForPersistence(canvas: Canvas) {
+ impl.draw(canvas)
+ }
+
/**
* Draw the contents of this [GraphicsLayer] into the specified [Canvas]
*/
@@ -479,6 +486,26 @@
if (isReleased) {
return
}
+
+ // If the displaylist has been discarded from underneath us, attempt to recreate it.
+ // This can happen if the application resumes from a background state after a trim memory
+ // callback has been invoked with a level greater than or equal to hidden. During which
+ // HWUI attempts to cull out resources that can be recreated quickly.
+ // Because recording instructions invokes the draw lambda again, there can be the potential
+ // for the objects referenced to be invalid for example in the case of a lazylist removal
+ // animation for a Composable that has been disposed, but the GraphicsLayer is drawn
+ // for a transient animation. However, when the application is backgrounded, animations are
+ // stopped anyway so attempts to recreate the displaylist from the draw lambda should
+ // be safe as the draw lambdas should still be valid. If not catch potential exceptions
+ // and continue as UI state would be recreated on resume anyway.
+ if (!impl.hasDisplayList) {
+ try {
+ recordInternal()
+ } catch (_: Throwable) {
+ // NO-OP
+ }
+ }
+
if (pivotOffset.isUnspecified) {
impl.pivotOffset = Offset(size.width / 2f, size.height / 2f)
}
@@ -951,6 +978,9 @@
block: DrawScope.() -> Unit
)
+ val hasDisplayList: Boolean
+ get() = true
+
/**
* @see GraphicsLayer.discardDisplayList
*/
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV23.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV23.android.kt
index c8500ae..7860a3a 100644
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV23.android.kt
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV23.android.kt
@@ -277,6 +277,9 @@
override var isInvalidated: Boolean = true
+ override val hasDisplayList: Boolean
+ get() = renderNode.isValid
+
override fun record(
density: Density,
layoutDirection: LayoutDirection,
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV29.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV29.android.kt
index 2d89639..a27f0a4 100644
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV29.android.kt
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV29.android.kt
@@ -247,6 +247,9 @@
return m
}
+ override val hasDisplayList: Boolean
+ get() = renderNode.hasDisplayList()
+
override fun discardDisplayList() {
renderNode.discardDisplayList()
}
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/LayerManager.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/LayerManager.android.kt
index 23b14d9..28e755c 100644
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/LayerManager.android.kt
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/LayerManager.android.kt
@@ -19,11 +19,13 @@
import android.graphics.PixelFormat
import android.media.ImageReader
import android.os.Build
+import android.os.Looper
import android.view.Surface
import androidx.annotation.RequiresApi
import androidx.collection.ObjectList
import androidx.collection.mutableObjectListOf
import androidx.compose.ui.graphics.CanvasHolder
+import androidx.core.os.HandlerCompat
/**
* Class responsible for managing the layer lifecycle to support
@@ -44,10 +46,17 @@
*/
private var imageReader: ImageReader? = null
+ private val handler = HandlerCompat.createAsync(Looper.getMainLooper()) {
+ persistLayers(layerList)
+ true
+ }
+
fun persist(layer: GraphicsLayer) {
if (!layerList.contains(layer)) {
layerList.add(layer)
- persistLayers(layerList)
+ if (!handler.hasMessages(0)) {
+ handler.sendEmptyMessage(0)
+ }
}
}
@@ -65,7 +74,7 @@
* another internal CanvasContext instance owned by the internal HwuiContext instance of
* a Surface. This is only necessary for Android M and above.
*/
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && layers.isNotEmpty()) {
val reader = imageReader ?: ImageReader.newInstance(
1,
1,
@@ -78,7 +87,10 @@
// are not supported
if (canvas.isHardwareAccelerated) {
canvasHolder.drawInto(canvas) {
- layers.forEach { layer -> layer.draw(this, null) }
+ canvas.save()
+ canvas.clipRect(0, 0, 1, 1)
+ layers.forEach { layer -> layer.drawForPersistence(this) }
+ canvas.restore()
}
}
surface.unlockCanvasAndPost(canvas)
@@ -89,6 +101,17 @@
imageReader?.close()
imageReader = null
}
+
+ /**
+ * Discards the corresponding ImageReader used to increment the ref count of each layer
+ * and persists the current layer list creating a new ImageReader. This is useful in scenarios
+ * where HWUI releases graphics resources in response to onTrimMemory often when the application
+ * is backgrounded
+ */
+ fun updateLayerPersistence() {
+ destroy()
+ persistLayers(layerList)
+ }
}
@RequiresApi(Build.VERSION_CODES.M)
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Outline.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Outline.kt
index b3c7de3..0c20ed5 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Outline.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Outline.kt
@@ -106,18 +106,8 @@
override val bounds: Rect
get() = path.getBounds()
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other !is Generic) return false
-
- if (path != other.path) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- return path.hashCode()
- }
+ // No equals or hashcode, two different outlines using the same path shouldn't be considered
+ // equal as the path may have changed since the previous outline was rendered
}
/**
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Shape.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Shape.kt
index e174944..0ce177b 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Shape.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Shape.kt
@@ -16,7 +16,7 @@
package androidx.compose.ui.graphics
-import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.Stable
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
@@ -24,7 +24,7 @@
/**
* Defines a generic shape.
*/
-@Immutable
+@Stable
interface Shape {
/**
* Creates [Outline] of this shape for the given [size].
diff --git a/compose/ui/ui-inspection/src/main/cpp/CMakeLists.txt b/compose/ui/ui-inspection/src/main/cpp/CMakeLists.txt
index 417fea7..81e99e1 100644
--- a/compose/ui/ui-inspection/src/main/cpp/CMakeLists.txt
+++ b/compose/ui/ui-inspection/src/main/cpp/CMakeLists.txt
@@ -14,7 +14,7 @@
# the License.
#
-cmake_minimum_required(VERSION 3.4.1)
+cmake_minimum_required(VERSION 3.22.1)
project(compose_ui_inspection)
diff --git a/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/inputdispatcher/KeyAndMouseEventsTest.kt b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/inputdispatcher/KeyAndMouseEventsTest.kt
index 71d02be..2f412d9 100644
--- a/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/inputdispatcher/KeyAndMouseEventsTest.kt
+++ b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/inputdispatcher/KeyAndMouseEventsTest.kt
@@ -438,7 +438,8 @@
Offset.Zero, 0, expectedMetaState = expectedMetaState)
// Key Toggle Off
- recorder.events[8].verifyKeyEvent(keyDown, key.nativeKeyCode)
+ recorder.events[8].verifyKeyEvent(keyDown, key.nativeKeyCode,
+ expectedMetaState = expectedMetaState)
recorder.events[9].verifyKeyEvent(keyUp, key.nativeKeyCode)
// Mouse Press
diff --git a/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/inputdispatcher/KeyEventsTest.kt b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/inputdispatcher/KeyEventsTest.kt
index 0216a50..4d1f794 100644
--- a/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/inputdispatcher/KeyEventsTest.kt
+++ b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/inputdispatcher/KeyEventsTest.kt
@@ -514,7 +514,8 @@
recorder.events[1].verifyKeyEvent(
keyUp, key.nativeKeyCode, expectedMetaState = expectedMetaState
)
- recorder.events[2].verifyKeyEvent(keyDown, key.nativeKeyCode)
+ recorder.events[2].verifyKeyEvent(keyDown, key.nativeKeyCode,
+ expectedMetaState = expectedMetaState)
recorder.events[3].verifyKeyEvent(keyUp, key.nativeKeyCode)
}
}
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/InputDispatcher.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/InputDispatcher.kt
index 1723384..1d17c65 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/InputDispatcher.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/InputDispatcher.kt
@@ -705,6 +705,30 @@
protected abstract fun KeyInputState.enqueueUp(key: Key)
+ /**
+ * Used to control lock key toggling behaviour on different platforms. Defaults to Android-style
+ * toggling. To change toggling behaviour, override this method and switch to using
+ * [LockKeyState.isLockKeyOnExcludingOffPress], or implement a different toggling behaviour.
+ */
+ protected open val KeyInputState.capsLockOn: Boolean
+ get() = capsLockState.isLockKeyOnIncludingOffPress
+
+ /**
+ * Used to control lock key toggling behaviour on different platforms. Defaults to Android-style
+ * toggling. To change toggling behaviour, override this method and switch to using
+ * [LockKeyState.isLockKeyOnExcludingOffPress], or implement a different toggling behaviour.
+ */
+ protected open val KeyInputState.numLockOn: Boolean
+ get() = numLockState.isLockKeyOnIncludingOffPress
+
+ /**
+ * Used to control lock key toggling behaviour on different platforms. Defaults to Android-style
+ * toggling. To change toggling behaviour, override this method and switch to using
+ * [LockKeyState.isLockKeyOnExcludingOffPress], or implement a different toggling behaviour.
+ */
+ protected open val KeyInputState.scrollLockOn: Boolean
+ get() = scrollLockState.isLockKeyOnIncludingOffPress
+
@OptIn(ExperimentalTestApi::class)
protected abstract fun MouseInputState.enqueueScroll(delta: Float, scrollWheel: ScrollWheel)
@@ -778,6 +802,56 @@
}
/**
+ * Toggling states for lock keys.
+ *
+ * Note that lock keys may not be toggled in the same way across all platforms.
+ *
+ * Take caps lock as an example; consistently, all platforms turn caps lock on upon the first
+ * key down event, and it stays on after the subsequent key up. However, on some platforms caps
+ * lock will turn off immediately upon the next key down event (MacOS for example), whereas
+ * other platforms (e.g. Linux, Android) wait for the next key up event before turning caps
+ * lock off.
+ *
+ * This enum breaks the lock key state down into four possible options - depending upon the
+ * interpretation of these four states, Android-like or MacOS-like behaviour can both be achieved.
+ *
+ * To get Android-like behaviour, use [isLockKeyOnIncludingOffPress],
+ * whereas for MacOS-style behaviour, use [isLockKeyOnExcludingOffPress].
+ */
+internal enum class LockKeyState(val state: Int) {
+ UP_AND_OFF(0),
+ DOWN_AND_ON(1),
+ UP_AND_ON(2),
+ DOWN_AND_OPTIONAL(3);
+
+ /**
+ * Whether or not the lock key is on. The lock key is considered on from the start of the
+ * "on press" until the end of the "off press", i.e. from the first key down event to the
+ * second key up event of the corresponding lock key.
+ */
+ val isLockKeyOnIncludingOffPress get() = state > 0
+
+ /**
+ * Whether or not the lock key is on. The lock key is considered on from the start of the
+ * "on press" until the start of the "off press", i.e. from the first key down event to the
+ * second key down event of the corresponding lock key.
+ */
+ val isLockKeyOnExcludingOffPress get() = this == DOWN_AND_ON || this == UP_AND_ON
+
+ /**
+ * Returns the next state in the cycle of lock key states.
+ */
+ fun next(): LockKeyState {
+ return when (this) {
+ UP_AND_OFF -> DOWN_AND_ON
+ DOWN_AND_ON -> UP_AND_ON
+ UP_AND_ON -> DOWN_AND_OPTIONAL
+ DOWN_AND_OPTIONAL -> UP_AND_OFF
+ }
+ }
+}
+
+/**
* The current key input state. Contains the keys that are pressed, the down time of the
* keyboard (which is the time of the last key down event), the state of the lock keys and
* the device ID.
@@ -789,9 +863,9 @@
var repeatKey: Key? = null
var repeatCount = 0
var lastRepeatTime = downTime
- var capsLockOn = false
- var numLockOn = false
- var scrollLockOn = false
+ var capsLockState: LockKeyState = LockKeyState.UP_AND_OFF
+ var numLockState: LockKeyState = LockKeyState.UP_AND_OFF
+ var scrollLockState: LockKeyState = LockKeyState.UP_AND_OFF
fun isKeyDown(key: Key): Boolean = downKeys.contains(key)
@@ -801,6 +875,7 @@
repeatKey = null
repeatCount = 0
}
+ updateLockKeys(key)
}
fun setKeyDown(key: Key) {
@@ -812,23 +887,12 @@
/**
* Updates lock key state values.
- *
- * Note that lock keys may not be toggled in the same way across all platforms.
- *
- * Take caps lock as an example; consistently, all platforms turn caps lock on upon the first
- * key down event, and it stays on after the subsequent key up. However, on some platforms caps
- * lock will turn off immediately upon the next key down event (MacOS for example), whereas
- * other platforms (e.g. linux) wait for the next key up event before turning caps lock off.
- *
- * By calling this function whenever a lock key is pressed down, MacOS-like behaviour is
- * achieved.
*/
- // TODO(Onadim): Investigate how lock key toggling is handled in Android, ChromeOS and Windows.
private fun updateLockKeys(key: Key) {
when (key) {
- Key.CapsLock -> capsLockOn = !capsLockOn
- Key.NumLock -> numLockOn = !numLockOn
- Key.ScrollLock -> scrollLockOn = !scrollLockOn
+ Key.CapsLock -> capsLockState = capsLockState.next()
+ Key.NumLock -> numLockState = numLockState.next()
+ Key.ScrollLock -> scrollLockState = scrollLockState.next()
}
}
}
diff --git a/compose/ui/ui-text-google-fonts/build.gradle b/compose/ui/ui-text-google-fonts/build.gradle
index cda3047..e932fe0 100644
--- a/compose/ui/ui-text-google-fonts/build.gradle
+++ b/compose/ui/ui-text-google-fonts/build.gradle
@@ -48,7 +48,7 @@
androidx {
name = "Compose Google Fonts integration"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2022"
description = "Compose Downloadable Fonts integration for Google Fonts"
metalavaK2UastEnabled = true
diff --git a/compose/ui/ui-text-google-fonts/lint-baseline.xml b/compose/ui/ui-text-google-fonts/lint-baseline.xml
deleted file mode 100644
index 0ac1c03..0000000
--- a/compose/ui/ui-text-google-fonts/lint-baseline.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.2.0-alpha15" type="baseline" client="gradle" dependencies="false" name="AGP (8.2.0-alpha15)" variant="all" version="8.2.0-alpha15">
-
- <issue
- id="RestrictedApiAndroidX"
- message="FontsContractCompat.requestFont can only be called from within the same library (androidx.core:core)"
- errorLine1=" FontsContractCompat.requestFont("
- errorLine2=" ~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/compose/ui/text/googlefonts/GoogleFont.kt"/>
- </issue>
-
-</issues>
diff --git a/compose/ui/ui-text-google-fonts/src/main/java/androidx/compose/ui/text/googlefonts/GoogleFont.kt b/compose/ui/ui-text-google-fonts/src/main/java/androidx/compose/ui/text/googlefonts/GoogleFont.kt
index 97ee309..c6a24ab 100644
--- a/compose/ui/ui-text-google-fonts/src/main/java/androidx/compose/ui/text/googlefonts/GoogleFont.kt
+++ b/compose/ui/ui-text-google-fonts/src/main/java/androidx/compose/ui/text/googlefonts/GoogleFont.kt
@@ -21,6 +21,7 @@
import android.content.Context
import android.graphics.Typeface
+import android.os.Build
import android.os.Handler
import android.os.Looper
import androidx.annotation.ArrayRes
@@ -291,8 +292,7 @@
return suspendCancellableCoroutine { continuation ->
val callback = object : FontRequestCallback() {
override fun onTypefaceRetrieved(typeface: Typeface?) {
- // this is entered from any thread
- continuation.resume(typeface)
+ continuation.resume(typeface.recreateWithStyle(typefaceStyle))
}
override fun onTypefaceRequestFailed(reason: Int) {
@@ -321,6 +321,23 @@
}
/**
+ * Basically Typeface.create(this, typefaceStyle).
+ *
+ * Can be called from any thread.
+ */
+private fun Typeface?.recreateWithStyle(typefaceStyle: Int): Typeface {
+ return if (Build.VERSION.SDK_INT >= 28) {
+ // typeface create is thread safe
+ return Typeface.create(this, typefaceStyle)
+ } else {
+ // typeface.create has a timing condition
+ synchronized(GoogleFontTypefaceLoader) {
+ Typeface.create(this, typefaceStyle)
+ }
+ }
+}
+
+/**
* To allow mocking for tests
*/
internal interface FontsContractCompatLoader {
@@ -347,11 +364,8 @@
FontsContractCompat.requestFont(
context,
fontRequest,
- typefaceStyle,
- false, /* isBlockingFetch*/
- 0, /* timeout - not used when isBlockingFetch=false */
- handler,
- callback
+ callback,
+ handler
)
}
}
diff --git a/compose/ui/ui-text/build.gradle b/compose/ui/ui-text/build.gradle
index f15b65d..a824148 100644
--- a/compose/ui/ui-text/build.gradle
+++ b/compose/ui/ui-text/build.gradle
@@ -153,7 +153,7 @@
androidx {
name = "Compose UI Text"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2019"
description = "Compose Text primitives and utilities"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/ui/ui-tooling-data/build.gradle b/compose/ui/ui-tooling-data/build.gradle
index f813317..dffb37e 100644
--- a/compose/ui/ui-tooling-data/build.gradle
+++ b/compose/ui/ui-tooling-data/build.gradle
@@ -123,7 +123,7 @@
androidx {
name = "Compose Tooling Data"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2021"
description = "Compose tooling library data. This library provides data about compose" +
" for different tooling purposes."
diff --git a/compose/ui/ui-tooling-preview/build.gradle b/compose/ui/ui-tooling-preview/build.gradle
index b600b85..3922e69 100644
--- a/compose/ui/ui-tooling-preview/build.gradle
+++ b/compose/ui/ui-tooling-preview/build.gradle
@@ -105,7 +105,7 @@
androidx {
name = "Compose UI Preview Tooling"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2021"
description = "Compose tooling library API. This library provides the API required to declare" +
" @Preview composables in user apps."
diff --git a/compose/ui/ui-tooling/build.gradle b/compose/ui/ui-tooling/build.gradle
index 98f9161..2307370 100644
--- a/compose/ui/ui-tooling/build.gradle
+++ b/compose/ui/ui-tooling/build.gradle
@@ -133,7 +133,7 @@
androidx {
name = "Compose Tooling"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2019"
description = "Compose tooling library. This library exposes information to our tools for better IDE support."
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/ui/ui-unit/build.gradle b/compose/ui/ui-unit/build.gradle
index ba792904..32b4d80 100644
--- a/compose/ui/ui-unit/build.gradle
+++ b/compose/ui/ui-unit/build.gradle
@@ -110,7 +110,7 @@
androidx {
name = "Compose Unit"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2020"
description = "Compose classes for simple units"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt
index b63b301..5c5102f 100644
--- a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt
+++ b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt
@@ -257,27 +257,13 @@
* The horizontal aspect of the offset in [Dp]
*/
@Stable
- val x: Dp
- get() {
- // Explicitly compare against packed values to avoid auto-boxing of DpOffset.Unspecified
- checkPrecondition(this.packedValue != UnspecifiedPackedFloats) {
- "DpOffset is unspecified"
- }
- return unpackFloat1(packedValue).dp
- }
+ val x: Dp get() = unpackFloat1(packedValue).dp
/**
* The vertical aspect of the offset in [Dp]
*/
@Stable
- val y: Dp
- get() {
- // Explicitly compare against packed values to avoid auto-boxing of DpOffset.Unspecified
- checkPrecondition(this.packedValue != UnspecifiedPackedFloats) {
- "DpOffset is unspecified"
- }
- return unpackFloat2(packedValue).dp
- }
+ val y: Dp get() = unpackFloat2(packedValue).dp
/**
* Returns a copy of this [DpOffset] instance optionally overriding the
@@ -374,27 +360,13 @@
* The horizontal aspect of the Size in [Dp]
*/
@Stable
- val width: Dp
- get() {
- // Explicitly compare against packed values to avoid auto-boxing of DpSize.Unspecified
- checkPrecondition(packedValue != UnspecifiedPackedFloats) {
- "DpSize is unspecified"
- }
- return unpackFloat1(packedValue).dp
- }
+ val width: Dp get() = unpackFloat1(packedValue).dp
/**
* The vertical aspect of the Size in [Dp]
*/
@Stable
- val height: Dp
- get() {
- // Explicitly compare against packed values to avoid auto-boxing of DpSize.Unspecified
- checkPrecondition(packedValue != UnspecifiedPackedFloats) {
- "DpSize is unspecified"
- }
- return unpackFloat2(packedValue).dp
- }
+ val height: Dp get() = unpackFloat2(packedValue).dp
/**
* Returns a copy of this [DpSize] instance optionally overriding the
diff --git a/compose/ui/ui-util/build.gradle b/compose/ui/ui-util/build.gradle
index 1639263..082d2e32 100644
--- a/compose/ui/ui-util/build.gradle
+++ b/compose/ui/ui-util/build.gradle
@@ -95,7 +95,7 @@
androidx {
name = "Compose Util"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2020"
description = "Internal Compose utilities used by other modules"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/ui/ui-viewbinding/build.gradle b/compose/ui/ui-viewbinding/build.gradle
index 550edf7..5e60265 100644
--- a/compose/ui/ui-viewbinding/build.gradle
+++ b/compose/ui/ui-viewbinding/build.gradle
@@ -50,7 +50,7 @@
androidx {
name = "Compose ViewBinding"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2020"
description = "Compose integration with ViewBinding"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/ui/ui/build.gradle b/compose/ui/ui/build.gradle
index a866f51..6af789d 100644
--- a/compose/ui/ui/build.gradle
+++ b/compose/ui/ui/build.gradle
@@ -226,7 +226,7 @@
androidx {
name = "Compose UI"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2019"
description = "Compose UI primitives. This library contains the primitives that form the Compose UI Toolkit, such as drawing, measurement and layout."
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
index 170a06e..d8ea6c7 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
@@ -17,6 +17,7 @@
package androidx.compose.ui
import android.content.Context.ACCESSIBILITY_SERVICE
+import android.content.res.Resources
import android.graphics.Rect
import android.graphics.RectF
import android.os.Build
@@ -61,6 +62,7 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.LazyRow
@@ -246,6 +248,7 @@
private lateinit var container: OpenComposeView
private lateinit var delegate: AndroidComposeViewAccessibilityDelegateCompat
private lateinit var provider: AccessibilityNodeProviderCompat
+ private lateinit var resources: Resources
private val accessibilityManager: AccessibilityManager
get() = androidComposeView.context
@@ -258,6 +261,7 @@
fun setup() {
// Use uiAutomation to enable accessibility manager.
InstrumentationRegistry.getInstrumentation().uiAutomation
+ resources = InstrumentationRegistry.getInstrumentation().context.resources
rule.activityRule.scenario.onActivity { activity ->
container = spy(OpenComposeView(activity)) {
@@ -678,6 +682,110 @@
}
@Test
+ fun emptyTextField_hasStateDescription() {
+ setContent {
+ BasicTextField(
+ rememberTextFieldState(),
+ modifier = Modifier.testTag(tag)
+ )
+ }
+
+ val virtualId = rule.onNodeWithTag(tag).semanticsId
+ val info = rule.runOnIdle { createAccessibilityNodeInfo(virtualId) }
+
+ rule.runOnIdle {
+ with(info) {
+ assertThat(stateDescription).isEqualTo(resources.getString(R.string.state_empty))
+ }
+ }
+ }
+
+ @Test
+ fun emptyTextField_noSpeakableChild_hasStateDescription() {
+ setContent {
+ BasicTextField(
+ "",
+ {},
+ modifier = Modifier.testTag(tag)
+ ) {
+ Column {
+ it()
+ Button(onClick = {}) {}
+ Box(
+ Modifier
+ .size(10.dp)
+ .semantics { testTag = "unspeakable child" })
+ }
+ }
+ }
+
+ val virtualId = rule.onNodeWithTag(tag).semanticsId
+ val info = rule.runOnIdle { createAccessibilityNodeInfo(virtualId) }
+
+ rule.runOnIdle {
+ with(info) {
+ assertThat(stateDescription).isEqualTo(resources.getString(R.string.state_empty))
+ }
+ }
+ }
+
+ @Test
+ fun emptyTextField_hasSpeakableChild_noStateDescription_() {
+ setContent {
+ BasicTextField(
+ rememberTextFieldState(),
+ modifier = Modifier.testTag(tag),
+ decorator = {
+ Row {
+ it()
+ BasicText(text = "Label")
+ }
+ }
+ )
+ }
+
+ val virtualId = rule.onNodeWithTag(tag).semanticsId
+ val info = rule.runOnIdle { createAccessibilityNodeInfo(virtualId) }
+
+ rule.runOnIdle {
+ with(info) {
+ assertThat(stateDescription).isNull()
+ }
+ }
+ }
+
+ @Test
+ fun emptyTextField_hasSpeakableIndirectChild_noStateDescription_() {
+ setContent {
+ BasicTextField(
+ rememberTextFieldState(),
+ modifier = Modifier.testTag(tag),
+ decorator = {
+ Row {
+ it()
+ Box(modifier = Modifier
+ .wrapContentSize()
+ .semantics {
+ testTag = "box test tag"
+ }) {
+ BasicText(text = "Label")
+ }
+ }
+ }
+ )
+ }
+
+ val virtualId = rule.onNodeWithTag(tag).semanticsId
+ val info = rule.runOnIdle { createAccessibilityNodeInfo(virtualId) }
+
+ rule.runOnIdle {
+ with(info) {
+ assertThat(stateDescription).isNull()
+ }
+ }
+ }
+
+ @Test
fun testCreateAccessibilityNodeInfo_forText() {
// Arrange.
val text = "Test"
@@ -2465,7 +2573,10 @@
setContent {
Row {
// Initially focused item.
- Box(Modifier.size(10.dp).focusable())
+ Box(
+ Modifier
+ .size(10.dp)
+ .focusable())
Box(
Modifier
.testTag(tag)
@@ -2621,7 +2732,10 @@
setContent {
Row {
// Initially focused item.
- Box(Modifier.size(10.dp).focusable())
+ Box(
+ Modifier
+ .size(10.dp)
+ .focusable())
BasicTextField(
modifier = Modifier.testTag(tag),
value = "value",
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
index 2ca13db..ad00359 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
@@ -396,7 +396,9 @@
val scope = ReusableGraphicsLayerScope()
scope.cameraDistance = cameraDistance
scope.compositingStrategy = compositingStrategy
- updateLayerProperties(scope, LayoutDirection.Ltr, Density(1f))
+ scope.layoutDirection = LayoutDirection.Ltr
+ scope.graphicsDensity = Density(1f)
+ updateLayerProperties(scope)
}
return expectedLayerType == view.layerType &&
expectedOverlappingRendering == view.hasOverlappingRendering()
@@ -436,7 +438,9 @@
).apply {
val scope = ReusableGraphicsLayerScope()
scope.cameraDistance = cameraDistance
- updateLayerProperties(scope, LayoutDirection.Ltr, Density(1f))
+ scope.layoutDirection = LayoutDirection.Ltr
+ scope.graphicsDensity = Density(1f)
+ updateLayerProperties(scope)
}
// Verify that the camera distance is applied properly even after accounting for
// the internal dp conversion within View
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draw/ClipDrawTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draw/ClipDrawTest.kt
index 6bc2204..490bb83 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draw/ClipDrawTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draw/ClipDrawTest.kt
@@ -47,6 +47,7 @@
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PathOperation
import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.addOutline
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.toArgb
@@ -471,6 +472,126 @@
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
@Test
+ fun switchBetweenDifferentOutlines_differentPath_observableShape() {
+ var invertedTriangle by mutableStateOf(false)
+ val observableShape = object : Shape {
+ override fun createOutline(
+ size: Size,
+ layoutDirection: LayoutDirection,
+ density: Density
+ ): Outline {
+ return if (invertedTriangle) {
+ invertedTriangleShape.createOutline(size, layoutDirection, density)
+ } else {
+ triangleShape.createOutline(size, layoutDirection, density)
+ }
+ }
+ }
+ // to be replaced with a DrawModifier wrapped into remember, so the recomposition
+ // is not causing invalidation as the DrawModifier didn't change
+ val drawCallback: DrawScope.() -> Unit = {
+ drawRect(
+ Color.Cyan,
+ topLeft = Offset(-100f, -100f),
+ size = Size(size.width + 200f, size.height + 200f)
+ )
+ }
+
+ val clip = Modifier.graphicsLayer {
+ shape = observableShape
+ clip = true
+ drawLatch.countDown()
+ }
+
+ rule.runOnUiThreadIR {
+ activity.setContent {
+ AtLeastSize(
+ size = 30,
+ modifier = Modifier
+ .background(Color.Green)
+ .then(clip)
+ .drawBehind(drawCallback)
+ ) {
+ }
+ }
+ }
+
+ takeScreenShot(30).apply {
+ assertTriangle(Color.Cyan, Color.Green)
+ }
+
+ drawLatch = CountDownLatch(1)
+ rule.runOnUiThreadIR { invertedTriangle = true }
+
+ takeScreenShot(30).apply {
+ assertInvertedTriangle(Color.Cyan, Color.Green)
+ }
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun switchBetweenDifferentOutlines_samePath_observableShape() {
+ var invertedTriangle by mutableStateOf(false)
+ val path = Path()
+ val observableShape = object : Shape {
+ override fun createOutline(
+ size: Size,
+ layoutDirection: LayoutDirection,
+ density: Density
+ ): Outline {
+ val outline = if (invertedTriangle) {
+ invertedTriangleShape.createOutline(size, layoutDirection, density)
+ } else {
+ triangleShape.createOutline(size, layoutDirection, density)
+ }
+ path.reset()
+ path.addOutline(outline)
+ return Outline.Generic(path)
+ }
+ }
+ // to be replaced with a DrawModifier wrapped into remember, so the recomposition
+ // is not causing invalidation as the DrawModifier didn't change
+ val drawCallback: DrawScope.() -> Unit = {
+ drawRect(
+ Color.Cyan,
+ topLeft = Offset(-100f, -100f),
+ size = Size(size.width + 200f, size.height + 200f)
+ )
+ }
+
+ val clip = Modifier.graphicsLayer {
+ shape = observableShape
+ clip = true
+ drawLatch.countDown()
+ }
+
+ rule.runOnUiThreadIR {
+ activity.setContent {
+ AtLeastSize(
+ size = 30,
+ modifier = Modifier
+ .background(Color.Green)
+ .then(clip)
+ .drawBehind(drawCallback)
+ ) {
+ }
+ }
+ }
+
+ takeScreenShot(30).apply {
+ assertTriangle(Color.Cyan, Color.Green)
+ }
+
+ drawLatch = CountDownLatch(1)
+ rule.runOnUiThreadIR { invertedTriangle = true }
+
+ takeScreenShot(30).apply {
+ assertInvertedTriangle(Color.Cyan, Color.Green)
+ }
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
fun emitClipLater() {
val model = mutableStateOf(false)
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt
index d1b40df..c63e2a9 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt
@@ -4172,12 +4172,7 @@
explicitLayer: GraphicsLayer?
): OwnedLayer {
return object : OwnedLayer {
- override fun updateLayerProperties(
- scope: ReusableGraphicsLayerScope,
- layoutDirection: LayoutDirection,
- density: Density
- ) {
- }
+ override fun updateLayerProperties(scope: ReusableGraphicsLayerScope) {}
override fun isInLayer(position: Offset) = true
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/Helpers.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/Helpers.kt
index e8557cf..e0f3bf0 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/Helpers.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/Helpers.kt
@@ -570,12 +570,7 @@
internal open class MockLayer() : OwnedLayer {
- override fun updateLayerProperties(
- scope: ReusableGraphicsLayerScope,
- layoutDirection: LayoutDirection,
- density: Density
- ) {
- }
+ override fun updateLayerProperties(scope: ReusableGraphicsLayerScope) {}
override fun isInLayer(position: Offset) = true
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/NodeChainTester.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/NodeChainTester.kt
index c2ec417..339d19f 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/NodeChainTester.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/NodeChainTester.kt
@@ -508,11 +508,7 @@
drawBlock: (Canvas, GraphicsLayer?) -> Unit,
invalidateParentLayer: () -> Unit
) {}
- override fun updateLayerProperties(
- scope: ReusableGraphicsLayerScope,
- layoutDirection: LayoutDirection,
- density: Density
- ) {
+ override fun updateLayerProperties(scope: ReusableGraphicsLayerScope) {
transform.reset()
// This is not expected to be 100% accurate
transform.scale(scope.scaleX, scope.scaleY)
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
index d21a838..c6254fb 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
@@ -1349,9 +1349,29 @@
}
}
+ if (node.unmergedConfig.contains(SemanticsActions.SetText)) {
+ stateDescription = createStateDescriptionForTextField(node)
+ }
+
return stateDescription
}
+ /**
+ * Empty text field should not be ignored by the TB so we set a state description.
+ * When there is a speakable child, like a label or a placeholder text, setting this state
+ * description is redundant
+ */
+ private fun createStateDescriptionForTextField(node: SemanticsNode): String? {
+ val mergedConfig = node.copyWithMergingEnabled().config
+ val mergedNodeIsUnspeakable =
+ mergedConfig.getOrNull(SemanticsProperties.ContentDescription).isNullOrEmpty() &&
+ mergedConfig.getOrNull(SemanticsProperties.Text).isNullOrEmpty() &&
+ mergedConfig.getOrNull(SemanticsProperties.EditableText).isNullOrEmpty()
+ return if (mergedNodeIsUnspeakable) {
+ view.context.resources.getString(R.string.state_empty)
+ } else null
+ }
+
private fun setStateDescription(
node: SemanticsNode,
info: AccessibilityNodeInfoCompat,
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
index 12737fd..8399995 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
@@ -28,9 +28,7 @@
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.Path
-import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.ReusableGraphicsLayerScope
-import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
import androidx.compose.ui.graphics.drawscope.draw
@@ -80,7 +78,7 @@
private val scope = CanvasDrawScope()
private var mutatedFields: Int = 0
private var transformOrigin: TransformOrigin = TransformOrigin.Center
- private var shape: Shape = RectangleShape
+ private var outline: Outline? = null
private var tmpPath: Path? = null
/**
* Optional paint used when the RenderNode is rendered on a software backed
@@ -88,17 +86,10 @@
*/
private var softwareLayerPaint: Paint? = null
- override fun updateLayerProperties(
- scope: ReusableGraphicsLayerScope,
- layoutDirection: LayoutDirection,
- density: Density,
- ) {
- var maybeChangedFields = scope.mutatedFields or mutatedFields
- if (this.layoutDirection != layoutDirection || this.density != density) {
- this.layoutDirection = layoutDirection
- this.density = density
- maybeChangedFields = maybeChangedFields or Fields.Shape
- }
+ override fun updateLayerProperties(scope: ReusableGraphicsLayerScope) {
+ val maybeChangedFields = scope.mutatedFields or mutatedFields
+ this.layoutDirection = scope.layoutDirection
+ this.density = scope.graphicsDensity
if (maybeChangedFields and Fields.TransformOrigin != 0) {
this.transformOrigin = scope.transformOrigin
}
@@ -152,10 +143,6 @@
transformOrigin.pivotFractionY * size.height
)
}
- if (maybeChangedFields and Fields.Shape != 0) {
- this.shape = scope.shape
- updateOutline()
- }
if (maybeChangedFields and Fields.Clip != 0) {
graphicsLayer.clip = scope.clip
}
@@ -171,8 +158,16 @@
}
}
+ var outlineChanged = false
+
+ if (outline != scope.outline) {
+ outlineChanged = true
+ outline = scope.outline
+ updateOutline()
+ }
+
mutatedFields = scope.mutatedFields
- if (maybeChangedFields != 0) {
+ if (maybeChangedFields != 0 || outlineChanged) {
triggerRepaint()
}
}
@@ -189,7 +184,7 @@
}
private fun updateOutline() {
- val outline = shape.createOutline(size.toSize(), layoutDirection, density)
+ val outline = outline ?: return
graphicsLayer.setOutline(outline)
if (outline is Outline.Generic && Build.VERSION.SDK_INT < 33) {
// before 33 many of the paths are not clipping by rendernode. instead we have to
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/OutlineResolver.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/OutlineResolver.android.kt
index 7f15fef..30d26a5 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/OutlineResolver.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/OutlineResolver.android.kt
@@ -27,17 +27,13 @@
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Path
-import androidx.compose.ui.graphics.RectangleShape
-import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.asAndroidPath
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.util.fastRoundToInt
/**
- * Resolves the [AndroidOutline] from the [Shape] of an [OwnedLayer].
+ * Resolves the [AndroidOutline] from the [Outline] of an [androidx.compose.ui.node.OwnedLayer].
*/
-internal class OutlineResolver(private var density: Density) {
+internal class OutlineResolver {
/**
* Flag to determine if the shape specified on the outline is supported.
@@ -51,14 +47,9 @@
private val cachedOutline = AndroidOutline().apply { alpha = 1f }
/**
- * The size of the layer. This is used in generating the [Outline] from the [Shape].
+ * The [Outline] of the Layer.
*/
- private var size: Size = Size.Zero
-
- /**
- * The [Shape] of the Outline of the Layer.
- */
- private var shape: Shape = RectangleShape
+ private var outline: Outline? = null
/**
* Asymmetric rounded rectangles need to use a Path. This caches that Path so that
@@ -107,7 +98,7 @@
/**
* Returns the Android Outline to be used in the layer.
*/
- val outline: AndroidOutline?
+ val androidOutline: AndroidOutline?
get() {
updateCache()
return if (!outlineNeeded || !isSupportedOutline) null else cachedOutline
@@ -145,7 +136,6 @@
/**
* Returns the size for a rectangular, or rounded rect outline (regardless if it
* is symmetric or asymmetric)
- * For path based outlines this returns [Size.Zero]
*/
private var rectSize: Size = Size.Zero
@@ -154,43 +144,32 @@
*/
private var outlineNeeded = false
- private var layoutDirection = LayoutDirection.Ltr
-
private var tmpTouchPointPath: Path? = null
private var tmpOpPath: Path? = null
- private var calculatedOutline: Outline? = null
/**
* Updates the values of the outline. Returns `true` when the shape has changed.
*/
fun update(
- shape: Shape,
+ outline: Outline?,
alpha: Float,
clipToOutline: Boolean,
elevation: Float,
- layoutDirection: LayoutDirection,
- density: Density
+ size: Size,
): Boolean {
cachedOutline.alpha = alpha
- val shapeChanged = this.shape != shape
- if (shapeChanged) {
- this.shape = shape
+ val outlineChanged = this.outline != outline
+ if (outlineChanged) {
+ this.outline = outline
cacheIsDirty = true
}
- val outlineNeeded = clipToOutline || elevation > 0f
+ this.rectSize = size
+ val outlineNeeded = outline != null && (clipToOutline || elevation > 0f)
if (this.outlineNeeded != outlineNeeded) {
this.outlineNeeded = outlineNeeded
cacheIsDirty = true
}
- if (this.layoutDirection != layoutDirection) {
- this.layoutDirection = layoutDirection
- cacheIsDirty = true
- }
- if (this.density != density) {
- this.density = density
- cacheIsDirty = true
- }
- return shapeChanged
+ return outlineChanged
}
/**
@@ -200,7 +179,7 @@
if (!outlineNeeded) {
return true
}
- val outline = calculatedOutline ?: return true
+ val outline = outline ?: return true
return isInOutline(outline, position.x, position.y, tmpTouchPointPath, tmpOpPath)
}
@@ -256,31 +235,20 @@
}
}
- /**
- * Updates the size.
- */
- fun update(size: Size) {
- if (this.size != size) {
- this.size = size
- cacheIsDirty = true
- }
- }
-
private fun updateCache() {
if (cacheIsDirty) {
rectTopLeft = Offset.Zero
- rectSize = size
roundedCornerRadius = 0f
outlinePath = null
cacheIsDirty = false
usePathForClip = false
- if (outlineNeeded && size.width > 0.0f && size.height > 0.0f) {
+ val outline = outline
+ if (outline != null && outlineNeeded &&
+ rectSize.width > 0.0f && rectSize.height > 0.0f) {
// Always assume the outline type is supported
// The methods to configure the outline will determine/update the flag
// if it not supported on the API level
isSupportedOutline = true
- val outline = shape.createOutline(size, layoutDirection, density)
- calculatedOutline = outline
when (outline) {
is Outline.Rectangle -> updateCacheWithRect(outline.rect)
is Outline.Rounded -> updateCacheWithRoundRect(outline.roundRect)
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.android.kt
index b1c7718..2e6c0be 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.android.kt
@@ -19,10 +19,8 @@
import android.os.Build
import android.view.View
import androidx.annotation.RequiresApi
-import androidx.compose.runtime.snapshots.Snapshot
import androidx.compose.ui.geometry.MutableRect
import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.CanvasHolder
import androidx.compose.ui.graphics.Fields
@@ -36,10 +34,8 @@
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.layout.GraphicLayerInfo
import androidx.compose.ui.node.OwnedLayer
-import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.LayoutDirection
/**
* RenderNode implementation of OwnedLayer.
@@ -63,11 +59,7 @@
ownerView.notifyLayerIsDirty(this, value)
}
}
- private val outlineResolver = Snapshot.withoutReadObservation {
- // we don't really care about observation here as density is applied manually
- // not observing the density changes saves performance on recording reads
- OutlineResolver(ownerView.density)
- }
+ private val outlineResolver = OutlineResolver()
private var isDestroyed = false
private var drawnWithZ = false
@@ -117,11 +109,7 @@
private var mutatedFields: Int = 0
- override fun updateLayerProperties(
- scope: ReusableGraphicsLayerScope,
- layoutDirection: LayoutDirection,
- density: Density,
- ) {
+ override fun updateLayerProperties(scope: ReusableGraphicsLayerScope) {
val maybeChangedFields = scope.mutatedFields or mutatedFields
if (maybeChangedFields and Fields.TransformOrigin != 0) {
this.transformOrigin = scope.transformOrigin
@@ -179,15 +167,14 @@
renderNode.compositingStrategy = scope.compositingStrategy
}
val shapeChanged = outlineResolver.update(
- scope.shape,
+ scope.outline,
scope.alpha,
clipToOutline,
scope.shadowElevation,
- layoutDirection,
- density
+ scope.size,
)
if (outlineResolver.cacheIsDirty) {
- renderNode.setOutline(outlineResolver.outline)
+ renderNode.setOutline(outlineResolver.androidOutline)
}
val isClippingManually = clipToOutline && !outlineResolver.outlineClipSupported
if (wasClippingManually != isClippingManually || (isClippingManually && shapeChanged)) {
@@ -232,8 +219,7 @@
renderNode.top + height
)
) {
- outlineResolver.update(Size(width.toFloat(), height.toFloat()))
- renderNode.setOutline(outlineResolver.outline)
+ renderNode.setOutline(outlineResolver.androidOutline)
invalidate()
matrixCache.invalidate()
}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.android.kt
index 0ccc913..6e820de 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.android.kt
@@ -21,10 +21,8 @@
import android.view.View
import android.view.ViewOutlineProvider
import androidx.annotation.RequiresApi
-import androidx.compose.runtime.snapshots.Snapshot
import androidx.compose.ui.geometry.MutableRect
import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.CanvasHolder
import androidx.compose.ui.graphics.CompositingStrategy
@@ -39,10 +37,8 @@
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.layout.GraphicLayerInfo
import androidx.compose.ui.node.OwnedLayer
-import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.LayoutDirection
import java.lang.reflect.Field
import java.lang.reflect.Method
@@ -58,11 +54,7 @@
private var drawBlock: ((canvas: Canvas, parentLayer: GraphicsLayer?) -> Unit)? = drawBlock
private var invalidateParentLayer: (() -> Unit)? = invalidateParentLayer
- private val outlineResolver = Snapshot.withoutReadObservation {
- // we don't really care about observation here as density is applied manually
- // not observing the density changes saves performance on recording reads
- OutlineResolver(ownerView.density)
- }
+ private val outlineResolver = OutlineResolver()
// Value of the layerModifier's clipToBounds property
private var clipToBounds = false
private var clipBoundsCache: android.graphics.Rect? = null
@@ -132,11 +124,7 @@
private var mutatedFields: Int = 0
- override fun updateLayerProperties(
- scope: ReusableGraphicsLayerScope,
- layoutDirection: LayoutDirection,
- density: Density,
- ) {
+ override fun updateLayerProperties(scope: ReusableGraphicsLayerScope) {
val maybeChangedFields = scope.mutatedFields or mutatedFields
if (maybeChangedFields and Fields.TransformOrigin != 0) {
this.mTransformOrigin = scope.transformOrigin
@@ -181,12 +169,11 @@
this.clipToOutline = clipToOutline
}
val shapeChanged = outlineResolver.update(
- scope.shape,
+ scope.outline,
scope.alpha,
clipToOutline,
scope.shadowElevation,
- layoutDirection,
- density
+ scope.size
)
if (outlineResolver.cacheIsDirty) {
updateOutlineResolver()
@@ -261,7 +248,7 @@
}
private fun updateOutlineResolver() {
- this.outlineProvider = if (outlineResolver.outline != null) {
+ this.outlineProvider = if (outlineResolver.androidOutline != null) {
OutlineProvider
} else {
null
@@ -287,7 +274,6 @@
if (width != this.width || height != this.height) {
pivotX = mTransformOrigin.pivotFractionX * width
pivotY = mTransformOrigin.pivotFractionY * height
- outlineResolver.update(Size(width.toFloat(), height.toFloat()))
updateOutlineResolver()
layout(left, top, left + width, top + height)
resetClipBounds()
@@ -437,7 +423,7 @@
val OutlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: android.graphics.Outline) {
view as ViewLayer
- outline.set(view.outlineResolver.outline!!)
+ outline.set(view.outlineResolver.androidOutline!!)
}
}
private var updateDisplayListIfDirtyMethod: Method? = null
diff --git a/compose/ui/ui/src/androidMain/res/values/strings.xml b/compose/ui/ui/src/androidMain/res/values/strings.xml
index 5d955a7..ae60b26 100644
--- a/compose/ui/ui/src/androidMain/res/values/strings.xml
+++ b/compose/ui/ui/src/androidMain/res/values/strings.xml
@@ -46,6 +46,8 @@
<string name="close_sheet">"Close sheet"</string>
<!-- Default accessibility error text for an editable text field. -->
<string name="default_error_message">"Invalid input"</string>
+ <!-- Spoken description of an empty editable text field -->
+ <string name="state_empty">Empty</string>
<!-- Default title of the popup window to be read by a screen reader. -->
<string name="default_popup_window_title" tools:ignore="ExtraTranslation">"Pop-Up Window"</string>
diff --git a/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
index bb76634..cd0eedd 100644
--- a/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
+++ b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
@@ -2688,11 +2688,7 @@
val transform = Matrix()
val inverseTransform = Matrix()
return object : OwnedLayer {
- override fun updateLayerProperties(
- scope: ReusableGraphicsLayerScope,
- layoutDirection: LayoutDirection,
- density: Density
- ) {
+ override fun updateLayerProperties(scope: ReusableGraphicsLayerScope) {
transform.reset()
// This is not expected to be 100% accurate
transform.scale(scope.scaleX, scope.scaleY)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/GraphicsLayerScope.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/GraphicsLayerScope.kt
index 9cf5728..ca8fd83 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/GraphicsLayerScope.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/GraphicsLayerScope.kt
@@ -16,6 +16,7 @@
package androidx.compose.ui.graphics
+import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ComposableOpenTarget
import androidx.compose.runtime.RememberObserver
@@ -26,6 +27,7 @@
import androidx.compose.ui.layout.PlacementScopeMarker
import androidx.compose.ui.platform.LocalGraphicsContext
import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
/**
* Default camera distance for all layers
@@ -411,6 +413,8 @@
internal var graphicsDensity: Density = Density(1.0f)
+ internal var layoutDirection: LayoutDirection = LayoutDirection.Ltr
+
override val density: Float
get() = graphicsDensity.density
@@ -425,6 +429,10 @@
}
}
+ internal var outline: Outline? = null
+ @VisibleForTesting
+ internal set
+
fun reset() {
scaleX = 1f
scaleY = 1f
@@ -444,7 +452,12 @@
renderEffect = null
compositingStrategy = CompositingStrategy.Auto
size = Size.Unspecified
+ outline = null
// mutatedFields should be reset last as all the setters above modify it.
mutatedFields = 0
}
+
+ internal fun updateOutline() {
+ outline = shape.createOutline(size, layoutDirection, graphicsDensity)
+ }
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
index 2e87f0b..8a6b586 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
@@ -517,18 +517,16 @@
}
graphicsLayerScope.reset()
graphicsLayerScope.graphicsDensity = layoutNode.density
+ graphicsLayerScope.layoutDirection = layoutNode.layoutDirection
graphicsLayerScope.size = size.toSize()
snapshotObserver.observeReads(this, onCommitAffectingLayerParams) {
layerBlock.invoke(graphicsLayerScope)
+ graphicsLayerScope.updateOutline()
}
val layerPositionalProperties = layerPositionalProperties
?: LayerPositionalProperties().also { layerPositionalProperties = it }
layerPositionalProperties.copyFrom(graphicsLayerScope)
- layer.updateLayerProperties(
- graphicsLayerScope,
- layoutNode.layoutDirection,
- layoutNode.density,
- )
+ layer.updateLayerProperties(graphicsLayerScope)
isClipping = graphicsLayerScope.clip
lastLayerAlpha = graphicsLayerScope.alpha
if (invokeOnLayoutChange) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnedLayer.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnedLayer.kt
index f68b387..602a375 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnedLayer.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnedLayer.kt
@@ -22,10 +22,8 @@
import androidx.compose.ui.graphics.Matrix
import androidx.compose.ui.graphics.ReusableGraphicsLayerScope
import androidx.compose.ui.graphics.layer.GraphicsLayer
-import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.LayoutDirection
/**
* A layer returned by [Owner.createLayer] to separate drawn content.
@@ -33,13 +31,9 @@
internal interface OwnedLayer {
/**
- * Applies the new layer properties and causing this layer to be redrawn.
+ * Applies the new layer properties, causing this layer to be redrawn.
*/
- fun updateLayerProperties(
- scope: ReusableGraphicsLayerScope,
- layoutDirection: LayoutDirection,
- density: Density,
- )
+ fun updateLayerProperties(scope: ReusableGraphicsLayerScope)
/**
* Returns `false` if [position] is outside the clipped region or `true` if clipping
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/SkiaLayerTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/SkiaLayerTest.kt
index 082fff8..adc95cf 100644
--- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/SkiaLayerTest.kt
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/SkiaLayerTest.kt
@@ -18,6 +18,7 @@
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.CompositingStrategy
import androidx.compose.ui.graphics.DefaultShadowColor
@@ -351,7 +352,8 @@
layer.resize(IntSize(1, 2))
layer.updateProperties(
- clip = true
+ clip = true,
+ size = Size(1f, 2f)
)
assertFalse(layer.isInLayer(Offset(-1f, -1f)))
@@ -363,7 +365,8 @@
layer.resize(IntSize(100, 200))
layer.updateProperties(
clip = true,
- shape = CircleShape
+ shape = CircleShape,
+ size = Size(100f, 200f)
)
assertFalse(layer.isInLayer(Offset(5f, 5f)))
@@ -394,7 +397,8 @@
shape: Shape = RectangleShape,
clip: Boolean = false,
renderEffect: RenderEffect? = null,
- compositingStrategy: CompositingStrategy = CompositingStrategy.Auto
+ compositingStrategy: CompositingStrategy = CompositingStrategy.Auto,
+ size: Size = Size.Zero
) {
val scope = ReusableGraphicsLayerScope()
scope.cameraDistance = cameraDistance
@@ -415,6 +419,9 @@
scope.clip = clip
scope.renderEffect = renderEffect
scope.compositingStrategy = compositingStrategy
- updateLayerProperties(scope, LayoutDirection.Ltr, Density(1f))
+ scope.layoutDirection = LayoutDirection.Ltr
+ scope.graphicsDensity = Density(1f)
+ scope.outline = shape.createOutline(size, scope.layoutDirection, scope.graphicsDensity)
+ updateLayerProperties(scope)
}
}
diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/OutlineCache.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/OutlineCache.skiko.kt
deleted file mode 100644
index 580fd2b..0000000
--- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/OutlineCache.skiko.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.platform
-
-import androidx.compose.ui.graphics.Outline
-import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.toSize
-
-/**
- * Class for storing outline. Recalculates outline when [size] or [shape] is changed.
- * It' s needed so we don't have to recreate it every time we use it for rendering
- * (it can be expensive to create outline every frame).
- */
-internal class OutlineCache(
- density: Density,
- size: IntSize,
- shape: Shape,
- layoutDirection: LayoutDirection
-) {
- var density = density
- set(value) {
- if (value != field) {
- field = value
- outline = createOutline()
- }
- }
-
- var size = size
- set(value) {
- if (value != field) {
- field = value
- outline = createOutline()
- }
- }
-
- var shape = shape
- set(value) {
- if (value != field) {
- field = value
- outline = createOutline()
- }
- }
-
- var layoutDirection = layoutDirection
- set(value) {
- if (value != field) {
- field = value
- outline = createOutline()
- }
- }
-
- var outline: Outline = createOutline()
- private set
-
- private fun createOutline() =
- shape.createOutline(size.toSize(), layoutDirection, density)
-}
diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaLayer.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaLayer.skiko.kt
index 41bffb8..b39d3ea 100644
--- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaLayer.skiko.kt
+++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaLayer.skiko.kt
@@ -31,7 +31,6 @@
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.Path
-import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.RenderEffect
import androidx.compose.ui.graphics.ReusableGraphicsLayerScope
import androidx.compose.ui.graphics.SkiaBackedCanvas
@@ -47,7 +46,6 @@
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toSize
import org.jetbrains.skia.ClipMode
@@ -64,8 +62,7 @@
) : OwnedLayer {
private var size = IntSize.Zero
private var position = IntOffset.Zero
- private var outlineCache =
- OutlineCache(density, size, RectangleShape, LayoutDirection.Ltr)
+ private var outline: Outline? = null
// Internal for testing
internal val matrix = Matrix()
private val pictureRecorder = PictureRecorder()
@@ -105,7 +102,6 @@
override fun resize(size: IntSize) {
if (size != this.size) {
this.size = size
- outlineCache.size = size
updateMatrix()
invalidate()
}
@@ -133,11 +129,10 @@
val x = position.x
val y = position.y
- if (outlineCache.shape === RectangleShape) {
- return 0f <= x && x < size.width && 0f <= y && y < size.height
- }
- return isInOutline(outlineCache.outline, x, y)
+ val outline = outline ?: return true
+
+ return isInOutline(outline, x, y)
}
private fun getMatrix(inverse: Boolean): Matrix {
@@ -152,11 +147,7 @@
}
private var mutatedFields: Int = 0
- override fun updateLayerProperties(
- scope: ReusableGraphicsLayerScope,
- layoutDirection: LayoutDirection,
- density: Density,
- ) {
+ override fun updateLayerProperties(scope: ReusableGraphicsLayerScope) {
val maybeChangedFields = scope.mutatedFields or mutatedFields
this.transformOrigin = scope.transformOrigin
this.translationX = scope.translationX
@@ -169,14 +160,12 @@
this.alpha = scope.alpha
this.clip = scope.clip
this.shadowElevation = scope.shadowElevation
- this.density = density
+ this.density = scope.graphicsDensity
this.renderEffect = scope.renderEffect
this.ambientShadowColor = scope.ambientShadowColor
this.spotShadowColor = scope.spotShadowColor
this.compositingStrategy = scope.compositingStrategy
- outlineCache.shape = scope.shape
- outlineCache.layoutDirection = layoutDirection
- outlineCache.density = density
+ this.outline = scope.outline
if (maybeChangedFields and Fields.MatrixAffectingFields != 0) {
updateMatrix()
}
@@ -245,13 +234,17 @@
drawShadow(canvas)
}
- if (clip) {
+ val outline = outline
+ val isClipping = if (clip && outline != null) {
canvas.save()
- when (val outline = outlineCache.outline) {
+ when (outline) {
is Outline.Rectangle -> canvas.clipRect(outline.rect)
is Outline.Rounded -> canvas.clipRoundRect(outline.roundRect)
is Outline.Generic -> canvas.clipPath(outline.path)
}
+ true
+ } else {
+ false
}
val currentRenderEffect = renderEffect
@@ -279,7 +272,7 @@
drawBlock(canvas, null)
canvas.restore()
- if (clip) {
+ if (isClipping) {
canvas.restore()
}
}
@@ -299,7 +292,7 @@
override fun updateDisplayList() = Unit
fun drawShadow(canvas: Canvas) = with(density) {
- val path = when (val outline = outlineCache.outline) {
+ val path = when (val outline = outline) {
is Outline.Rectangle -> Path().apply { addRect(outline.rect) }
is Outline.Rounded -> Path().apply { addRoundRect(outline.roundRect) }
is Outline.Generic -> outline.path
diff --git a/concurrent/concurrent-futures-ktx/api/1.2.0-beta01.txt b/concurrent/concurrent-futures-ktx/api/1.2.0-beta01.txt
new file mode 100644
index 0000000..53345f4
--- /dev/null
+++ b/concurrent/concurrent-futures-ktx/api/1.2.0-beta01.txt
@@ -0,0 +1,14 @@
+// Signature format: 4.0
+package androidx.concurrent.futures {
+
+ public final class ListenableFutureKt {
+ method public static suspend <T> Object? await(com.google.common.util.concurrent.ListenableFuture<T>, kotlin.coroutines.Continuation<? super T>);
+ }
+
+ public final class SuspendToFutureAdapter {
+ method public <T> com.google.common.util.concurrent.ListenableFuture<T> launchFuture(optional kotlin.coroutines.CoroutineContext context, optional boolean launchUndispatched, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super T>,?> block);
+ field public static final androidx.concurrent.futures.SuspendToFutureAdapter INSTANCE;
+ }
+
+}
+
diff --git a/concurrent/concurrent-futures-ktx/api/restricted_1.2.0-beta01.txt b/concurrent/concurrent-futures-ktx/api/restricted_1.2.0-beta01.txt
new file mode 100644
index 0000000..53345f4
--- /dev/null
+++ b/concurrent/concurrent-futures-ktx/api/restricted_1.2.0-beta01.txt
@@ -0,0 +1,14 @@
+// Signature format: 4.0
+package androidx.concurrent.futures {
+
+ public final class ListenableFutureKt {
+ method public static suspend <T> Object? await(com.google.common.util.concurrent.ListenableFuture<T>, kotlin.coroutines.Continuation<? super T>);
+ }
+
+ public final class SuspendToFutureAdapter {
+ method public <T> com.google.common.util.concurrent.ListenableFuture<T> launchFuture(optional kotlin.coroutines.CoroutineContext context, optional boolean launchUndispatched, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super T>,?> block);
+ field public static final androidx.concurrent.futures.SuspendToFutureAdapter INSTANCE;
+ }
+
+}
+
diff --git a/concurrent/concurrent-futures-ktx/build.gradle b/concurrent/concurrent-futures-ktx/build.gradle
index 692ada9..a491d54 100644
--- a/concurrent/concurrent-futures-ktx/build.gradle
+++ b/concurrent/concurrent-futures-ktx/build.gradle
@@ -41,7 +41,7 @@
androidx {
name = "Futures Kotlin Extensions"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2019"
description = "Kotlin Extensions for Androidx implementation of Guava's ListenableFuture"
metalavaK2UastEnabled = true
diff --git a/concurrent/concurrent-futures/api/1.2.0-beta01.txt b/concurrent/concurrent-futures/api/1.2.0-beta01.txt
new file mode 100644
index 0000000..9d9c205
--- /dev/null
+++ b/concurrent/concurrent-futures/api/1.2.0-beta01.txt
@@ -0,0 +1,21 @@
+// Signature format: 4.0
+package androidx.concurrent.futures {
+
+ public final class CallbackToFutureAdapter {
+ method public static <T> com.google.common.util.concurrent.ListenableFuture<T!> getFuture(androidx.concurrent.futures.CallbackToFutureAdapter.Resolver<T!>);
+ }
+
+ public static final class CallbackToFutureAdapter.Completer<T> {
+ method public void addCancellationListener(Runnable, java.util.concurrent.Executor);
+ method protected void finalize();
+ method public boolean set(T!);
+ method public boolean setCancelled();
+ method public boolean setException(Throwable);
+ }
+
+ public static interface CallbackToFutureAdapter.Resolver<T> {
+ method public Object? attachCompleter(androidx.concurrent.futures.CallbackToFutureAdapter.Completer<T!>) throws java.lang.Exception;
+ }
+
+}
+
diff --git a/concurrent/concurrent-futures/api/restricted_1.2.0-beta01.txt b/concurrent/concurrent-futures/api/restricted_1.2.0-beta01.txt
new file mode 100644
index 0000000..32be46b
--- /dev/null
+++ b/concurrent/concurrent-futures/api/restricted_1.2.0-beta01.txt
@@ -0,0 +1,45 @@
+// Signature format: 4.0
+package androidx.concurrent.futures {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class AbstractResolvableFuture<V> implements com.google.common.util.concurrent.ListenableFuture<V!> {
+ ctor protected AbstractResolvableFuture();
+ method public final void addListener(Runnable!, java.util.concurrent.Executor!);
+ method protected void afterDone();
+ method public final boolean cancel(boolean);
+ method public final V! get() throws java.util.concurrent.ExecutionException, java.lang.InterruptedException;
+ method public final V! get(long, java.util.concurrent.TimeUnit!) throws java.util.concurrent.ExecutionException, java.lang.InterruptedException, java.util.concurrent.TimeoutException;
+ method protected void interruptTask();
+ method public final boolean isCancelled();
+ method public final boolean isDone();
+ method protected String? pendingToString();
+ method protected boolean set(V?);
+ method protected boolean setException(Throwable!);
+ method protected boolean setFuture(com.google.common.util.concurrent.ListenableFuture<? extends V!>!);
+ method protected final boolean wasInterrupted();
+ }
+
+ public final class CallbackToFutureAdapter {
+ method public static <T> com.google.common.util.concurrent.ListenableFuture<T!> getFuture(androidx.concurrent.futures.CallbackToFutureAdapter.Resolver<T!>);
+ }
+
+ public static final class CallbackToFutureAdapter.Completer<T> {
+ method public void addCancellationListener(Runnable, java.util.concurrent.Executor);
+ method protected void finalize();
+ method public boolean set(T!);
+ method public boolean setCancelled();
+ method public boolean setException(Throwable);
+ }
+
+ public static interface CallbackToFutureAdapter.Resolver<T> {
+ method public Object? attachCompleter(androidx.concurrent.futures.CallbackToFutureAdapter.Completer<T!>) throws java.lang.Exception;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class ResolvableFuture<V> extends androidx.concurrent.futures.AbstractResolvableFuture<V!> {
+ method public static <V> androidx.concurrent.futures.ResolvableFuture<V!> create();
+ method public boolean set(V?);
+ method public boolean setException(Throwable);
+ method public boolean setFuture(com.google.common.util.concurrent.ListenableFuture<? extends V!>);
+ }
+
+}
+
diff --git a/constraintlayout/constraintlayout-compose/build.gradle b/constraintlayout/constraintlayout-compose/build.gradle
index a052358..b1c6c3b 100644
--- a/constraintlayout/constraintlayout-compose/build.gradle
+++ b/constraintlayout/constraintlayout-compose/build.gradle
@@ -104,7 +104,7 @@
androidx {
name = "ConstraintLayout Compose"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
mavenVersion = LibraryVersions.CONSTRAINTLAYOUT_COMPOSE
inceptionYear = "2022"
description = "This library offers a flexible and adaptable way to position and animate widgets in Compose"
diff --git a/core/core-ktx/build.gradle b/core/core-ktx/build.gradle
index 6832072..c425cae 100644
--- a/core/core-ktx/build.gradle
+++ b/core/core-ktx/build.gradle
@@ -34,7 +34,7 @@
androidx {
name = "Core Kotlin Extensions"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
mavenVersion = LibraryVersions.CORE
inceptionYear = "2018"
description = "Kotlin extensions for 'core' artifact"
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index de4ac69..1013fe2 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -2026,6 +2026,7 @@
method public static android.graphics.Typeface? buildTypeface(android.content.Context, android.os.CancellationSignal?, androidx.core.provider.FontsContractCompat.FontInfo![]);
method public static androidx.core.provider.FontsContractCompat.FontFamilyResult fetchFonts(android.content.Context, android.os.CancellationSignal?, androidx.core.provider.FontRequest) throws android.content.pm.PackageManager.NameNotFoundException;
method public static void requestFont(android.content.Context, androidx.core.provider.FontRequest, androidx.core.provider.FontsContractCompat.FontRequestCallback, android.os.Handler);
+ method public static void requestFont(android.content.Context, androidx.core.provider.FontRequest, int, androidx.core.provider.FontsContractCompat.FontRequestCallback, android.os.Handler);
}
public static final class FontsContractCompat.Columns implements android.provider.BaseColumns {
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index fd443b0..17fad94 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -2420,6 +2420,8 @@
method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @VisibleForTesting public static android.content.pm.ProviderInfo? getProvider(android.content.pm.PackageManager, androidx.core.provider.FontRequest, android.content.res.Resources?) throws android.content.pm.PackageManager.NameNotFoundException;
method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static java.util.Map<android.net.Uri!,java.nio.ByteBuffer!>! prepareFontData(android.content.Context!, androidx.core.provider.FontsContractCompat.FontInfo![]!, android.os.CancellationSignal!);
method public static void requestFont(android.content.Context, androidx.core.provider.FontRequest, androidx.core.provider.FontsContractCompat.FontRequestCallback, android.os.Handler);
+ method public static void requestFont(android.content.Context, androidx.core.provider.FontRequest, int, androidx.core.provider.FontsContractCompat.FontRequestCallback, android.os.Handler);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.graphics.Typeface? requestFont(android.content.Context, androidx.core.provider.FontRequest, int, boolean, @IntRange(from=0) int, android.os.Handler, androidx.core.provider.FontsContractCompat.FontRequestCallback);
method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void resetCache();
field @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final String PARCEL_FONT_RESULTS = "font_results";
}
diff --git a/core/core/src/main/java/androidx/core/provider/FontsContractCompat.java b/core/core/src/main/java/androidx/core/provider/FontsContractCompat.java
index 53ed826..25c6094 100644
--- a/core/core/src/main/java/androidx/core/provider/FontsContractCompat.java
+++ b/core/core/src/main/java/androidx/core/provider/FontsContractCompat.java
@@ -124,6 +124,34 @@
}
/**
+ * Create a typeface object given a font request. The font will be asynchronously fetched,
+ * therefore the result is delivered to the given callback. See {@link FontRequest}.
+ * Only one of the methods in callback will be invoked, depending on whether the request
+ * succeeds or fails. These calls will happen on the caller thread.
+ * @param context A context to be used for fetching from font provider.
+ * @param request A {@link FontRequest} object that identifies the provider and query for the
+ * request. May not be null.
+ * @param style Typeface Style such as {@link Typeface#NORMAL}, {@link Typeface#BOLD}
+ * {@link Typeface#ITALIC}, {@link Typeface#BOLD_ITALIC}.
+ * @param callback A callback that will be triggered when results are obtained. May not be null.
+ * @param handler A handler to be processed the font fetching.
+ */
+ // maintain consistency with legacy call signature above, just adding style
+ @SuppressWarnings("ExecutorRegistration")
+ public static void requestFont(
+ final @NonNull Context context,
+ final @NonNull FontRequest request,
+ int style,
+ final @NonNull FontRequestCallback callback,
+ @SuppressWarnings("ListenerLast") final @NonNull Handler handler
+ ) {
+ CallbackWithHandler callbackWrapper = new CallbackWithHandler(callback);
+ Executor executor = RequestExecutor.createHandlerExecutor(handler);
+ FontRequestWorker.requestFontAsync(context.getApplicationContext(), request, style,
+ executor, callbackWrapper);
+ }
+
+ /**
* Loads a Typeface. Based on the parameters isBlockingFetch, and timeoutInMillis, the fetch
* is either sync or async.
* - If timeoutInMillis is infinite, and isBlockingFetch is true -> sync
@@ -146,7 +174,8 @@
* sync request.
*
*/
- @RestrictTo(LIBRARY)
+ // called by Compose Fonts, never binary change
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
@Nullable
public static Typeface requestFont(
@NonNull final Context context,
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/CredentialProviderCreatePublicKeyCredentialController.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/CredentialProviderCreatePublicKeyCredentialController.kt
index d4232ce..93b6872 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/CredentialProviderCreatePublicKeyCredentialController.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/CredentialProviderCreatePublicKeyCredentialController.kt
@@ -50,12 +50,12 @@
*/
@Suppress("deprecation")
internal class CredentialProviderCreatePublicKeyCredentialController(private val context: Context) :
- CredentialProviderController<
- CreatePublicKeyCredentialRequest,
- PublicKeyCredentialCreationOptions,
- PublicKeyCredential,
- CreateCredentialResponse,
- CreateCredentialException>(context) {
+ CredentialProviderController<
+ CreatePublicKeyCredentialRequest,
+ PublicKeyCredentialCreationOptions,
+ PublicKeyCredential,
+ CreateCredentialResponse,
+ CreateCredentialException>(context) {
/**
* The callback object state, used in the protected handleResponse method.
@@ -84,12 +84,17 @@
resultCode: Int,
resultData: Bundle
) {
- if (maybeReportErrorFromResultReceiver(resultData,
+ if (maybeReportErrorFromResultReceiver(
+ resultData,
CredentialProviderBaseController
.Companion::createCredentialExceptionTypeToException,
- executor = executor, callback = callback, cancellationSignal)) return
- handleResponse(resultData.getInt(ACTIVITY_REQUEST_CODE_TAG), resultCode,
- resultData.getParcelable(RESULT_DATA_TAG))
+ executor = executor, callback = callback, cancellationSignal
+ )
+ ) return
+ handleResponse(
+ resultData.getInt(ACTIVITY_REQUEST_CODE_TAG), resultCode,
+ resultData.getParcelable(RESULT_DATA_TAG)
+ )
}
}
@@ -106,13 +111,18 @@
try {
fidoRegistrationRequest = this.convertRequestToPlayServices(request)
} catch (e: JSONException) {
- cancelOrCallbackExceptionOrResult(cancellationSignal) { this.executor.execute {
- this.callback.onError(JSONExceptionToPKCError(e))
- } }
+ cancelOrCallbackExceptionOrResult(cancellationSignal) {
+ this.executor.execute {
+ this.callback.onError(JSONExceptionToPKCError(e))
+ }
+ }
return
} catch (t: Throwable) {
- cancelOrCallbackExceptionOrResult(cancellationSignal) { this.executor.execute {
- this.callback.onError(CreateCredentialUnknownException(t.message)) } }
+ cancelOrCallbackExceptionOrResult(cancellationSignal) {
+ this.executor.execute {
+ this.callback.onError(CreateCredentialUnknownException(t.message))
+ }
+ }
return
}
@@ -121,37 +131,53 @@
}
val hiddenIntent = Intent(context, HiddenActivity::class.java)
hiddenIntent.putExtra(REQUEST_TAG, fidoRegistrationRequest)
- generateHiddenActivityIntent(resultReceiver, hiddenIntent,
- CREATE_PUBLIC_KEY_CREDENTIAL_TAG)
+ generateHiddenActivityIntent(
+ resultReceiver, hiddenIntent,
+ CREATE_PUBLIC_KEY_CREDENTIAL_TAG
+ )
try {
context.startActivity(hiddenIntent)
} catch (e: Exception) {
- cancelOrCallbackExceptionOrResult(cancellationSignal) { this.executor.execute {
- this.callback.onError(
- CreateCredentialUnknownException(ERROR_MESSAGE_START_ACTIVITY_FAILED)) } }
+ cancelOrCallbackExceptionOrResult(cancellationSignal) {
+ this.executor.execute {
+ this.callback.onError(
+ CreateCredentialUnknownException(ERROR_MESSAGE_START_ACTIVITY_FAILED)
+ )
+ }
+ }
}
}
internal fun handleResponse(uniqueRequestCode: Int, resultCode: Int, data: Intent?) {
if (uniqueRequestCode != CONTROLLER_REQUEST_CODE) {
- Log.w(TAG, "Returned request code " +
- "$CONTROLLER_REQUEST_CODE does not match what was given $uniqueRequestCode")
+ Log.w(
+ TAG, "Returned request code " +
+ "$CONTROLLER_REQUEST_CODE does not match what was given $uniqueRequestCode"
+ )
return
}
if (maybeReportErrorResultCodeCreate(resultCode,
- { s, f -> cancelOrCallbackExceptionOrResult(s, f) }, { e -> this.executor.execute {
- this.callback.onError(e) } }, cancellationSignal)) return
+ { s, f -> cancelOrCallbackExceptionOrResult(s, f) }, { e ->
+ this.executor.execute {
+ this.callback.onError(e)
+ }
+ }, cancellationSignal
+ )
+ ) return
val bytes: ByteArray? = data?.getByteArrayExtra(Fido.FIDO2_KEY_CREDENTIAL_EXTRA)
if (bytes == null) {
if (CredentialProviderPlayServicesImpl.cancellationReviewer(cancellationSignal)) {
return
}
- this.executor.execute { this.callback.onError(
- CreatePublicKeyCredentialDomException(
- UnknownError(),
- "Upon handling create public key credential response, fido module giving null " +
- "bytes indicating internal error")
- ) }
+ this.executor.execute {
+ this.callback.onError(
+ CreatePublicKeyCredentialDomException(
+ UnknownError(),
+ "Upon handling create public key credential response, fido module giving" +
+ " null bytes indicating internal error"
+ )
+ )
+ }
return
}
val cred: PublicKeyCredential = PublicKeyCredential.deserializeFromBytes(bytes)
@@ -159,39 +185,57 @@
PublicKeyCredentialControllerUtility.publicKeyCredentialResponseContainsError(cred)
if (exception != null) {
cancelOrCallbackExceptionOrResult(cancellationSignal) {
- executor.execute { callback.onError(exception) } }
+ executor.execute { callback.onError(exception) }
+ }
return
}
try {
val response = this.convertResponseToCredentialManager(cred)
- cancelOrCallbackExceptionOrResult(cancellationSignal) { this.executor.execute {
- this.callback.onResult(response) } }
+ cancelOrCallbackExceptionOrResult(cancellationSignal) {
+ this.executor.execute {
+ this.callback.onResult(response)
+ }
+ }
} catch (e: JSONException) {
cancelOrCallbackExceptionOrResult(
- cancellationSignal) { executor.execute { callback.onError(
- CreatePublicKeyCredentialDomException(EncodingError(), e.message)) } }
+ cancellationSignal
+ ) {
+ executor.execute {
+ callback.onError(
+ CreatePublicKeyCredentialDomException(EncodingError(), e.message)
+ )
+ }
+ }
} catch (t: Throwable) {
- cancelOrCallbackExceptionOrResult(cancellationSignal) { executor.execute {
- callback.onError(CreatePublicKeyCredentialDomException(
- UnknownError(), t.message)) } }
+ cancelOrCallbackExceptionOrResult(cancellationSignal) {
+ executor.execute {
+ callback.onError(
+ CreatePublicKeyCredentialDomException(
+ UnknownError(), t.message
+ )
+ )
+ }
+ }
}
}
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
public override fun convertRequestToPlayServices(request: CreatePublicKeyCredentialRequest):
PublicKeyCredentialCreationOptions {
- return PublicKeyCredentialControllerUtility.convert(request)
+ return PublicKeyCredentialControllerUtility.convert(request, context)
}
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
public override fun convertResponseToCredentialManager(response: PublicKeyCredential):
CreateCredentialResponse {
- try {
- return CreatePublicKeyCredentialResponse(response.toJson())
- } catch (t: Throwable) {
- throw CreateCredentialUnknownException("The PublicKeyCredential response json " +
- "had an unexpected exception when parsing: ${t.message}")
- }
+ try {
+ return CreatePublicKeyCredentialResponse(response.toJson())
+ } catch (t: Throwable) {
+ throw CreateCredentialUnknownException(
+ "The PublicKeyCredential response json " +
+ "had an unexpected exception when parsing: ${t.message}"
+ )
+ }
}
private fun JSONExceptionToPKCError(exception: JSONException):
@@ -215,7 +259,7 @@
@JvmStatic
fun getInstance(context: Context):
CredentialProviderCreatePublicKeyCredentialController {
- return CredentialProviderCreatePublicKeyCredentialController(context)
+ return CredentialProviderCreatePublicKeyCredentialController(context)
}
}
}
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/PublicKeyCredentialControllerUtility.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/PublicKeyCredentialControllerUtility.kt
index b38500b..ac08bef 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/PublicKeyCredentialControllerUtility.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/PublicKeyCredentialControllerUtility.kt
@@ -16,8 +16,14 @@
package androidx.credentials.playservices.controllers.CreatePublicKeyCredential
+import android.content.Context
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.os.Build
import android.util.Base64
import android.util.Log
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
import androidx.credentials.CreatePublicKeyCredentialRequest
import androidx.credentials.GetPublicKeyCredentialOption
import androidx.credentials.exceptions.CreateCredentialCancellationException
@@ -41,6 +47,7 @@
import androidx.credentials.exceptions.publickeycredential.GetPublicKeyCredentialDomException
import com.google.android.gms.auth.api.identity.BeginSignInRequest
import com.google.android.gms.auth.api.identity.SignInCredential
+import com.google.android.gms.common.GoogleApiAvailability
import com.google.android.gms.fido.common.Transport
import com.google.android.gms.fido.fido2.api.common.Attachment
import com.google.android.gms.fido.fido2.api.common.AttestationConveyancePreference
@@ -106,6 +113,8 @@
internal val JSON_KEY_RK = "rk"
internal val JSON_KEY_CRED_PROPS = "credProps"
+ private const val AUTH_MIN_VERSION_JSON_CREATE: Long = 241217000
+
/**
* This function converts a request json to a PublicKeyCredentialCreationOptions, where there
* should be a direct mapping from the input string to this data type. See
@@ -117,10 +126,29 @@
* @throws JSONException If required data is not present in the requestJson
*/
@JvmStatic
- fun convert(request: CreatePublicKeyCredentialRequest): PublicKeyCredentialCreationOptions {
+ fun convert(
+ request: CreatePublicKeyCredentialRequest,
+ context: Context
+ ): PublicKeyCredentialCreationOptions {
+ if (isDeviceGMSVersionOlderThan(context, AUTH_MIN_VERSION_JSON_CREATE)) {
+ return PublicKeyCredentialCreationOptions(request.requestJson)
+ }
+
return convertJSON(JSONObject(request.requestJson))
}
+ private fun isDeviceGMSVersionOlderThan(context: Context, version: Long): Boolean {
+ val packageManager: PackageManager = context.packageManager
+ val packageName = GoogleApiAvailability.GOOGLE_PLAY_SERVICES_PACKAGE
+
+ val currentVersion = if (Build.VERSION.SDK_INT >= 28)
+ GetGMSVersion.getVersionLong(packageManager.getPackageInfo(packageName, 0))
+ else
+ @Suppress("DEPRECATION") packageManager.getPackageInfo(packageName, 0).versionCode.toLong()
+
+ return (currentVersion > version)
+ }
+
internal fun convertJSON(json: JSONObject): PublicKeyCredentialCreationOptions {
val builder = PublicKeyCredentialCreationOptions.Builder()
@@ -160,19 +188,23 @@
authenticatorResponse.errorMessage
)
}
+
is AuthenticatorAssertionResponse -> {
try {
return publicKeyCred.toJson()
} catch (t: Throwable) {
- throw GetCredentialUnknownException("The PublicKeyCredential response json had " +
- "an unexpected exception when parsing: ${t.message}")
+ throw GetCredentialUnknownException(
+ "The PublicKeyCredential response json had " +
+ "an unexpected exception when parsing: ${t.message}"
+ )
}
}
+
else -> {
Log.e(
TAG,
"AuthenticatorResponse expected assertion response but " +
- "got: ${authenticatorResponse.javaClass.name}"
+ "got: ${authenticatorResponse.javaClass.name}"
)
}
}
@@ -279,7 +311,10 @@
val exception: GetCredentialException
if (exceptionError == null) {
exception =
- GetPublicKeyCredentialDomException(UnknownError(), "unknown fido gms exception - $msg")
+ GetPublicKeyCredentialDomException(
+ UnknownError(),
+ "unknown fido gms exception - $msg"
+ )
} else {
// This fix is quite fragile because it relies on that the fido module
// does not change its error message, but is the only viable solution
@@ -307,7 +342,8 @@
if (appIdExtension.isNotEmpty()) {
extensionBuilder.setFido2Extension(FidoAppIdExtension(appIdExtension))
}
- val thirdPartyPaymentExtension = extensions.optBoolean(JSON_KEY_THIRD_PARTY_PAYMENT, false)
+ val thirdPartyPaymentExtension =
+ extensions.optBoolean(JSON_KEY_THIRD_PARTY_PAYMENT, false)
if (thirdPartyPaymentExtension) {
extensionBuilder.setGoogleThirdPartyPaymentExtension(
GoogleThirdPartyPaymentExtension(true)
@@ -315,7 +351,9 @@
}
val uvmStatus = extensions.optBoolean("uvm", false)
if (uvmStatus) {
- extensionBuilder.setUserVerificationMethodExtension(UserVerificationMethodExtension(true))
+ extensionBuilder.setUserVerificationMethodExtension(
+ UserVerificationMethodExtension(true)
+ )
}
builder.setAuthenticationExtensions(extensionBuilder.build())
}
@@ -328,7 +366,8 @@
if (json.has(JSON_KEY_AUTH_SELECTION)) {
val authenticatorSelection = json.getJSONObject(JSON_KEY_AUTH_SELECTION)
val authSelectionBuilder = AuthenticatorSelectionCriteria.Builder()
- val requireResidentKey = authenticatorSelection.optBoolean(JSON_KEY_REQUIRE_RES_KEY, false)
+ val requireResidentKey =
+ authenticatorSelection.optBoolean(JSON_KEY_REQUIRE_RES_KEY, false)
val residentKey = authenticatorSelection.optString(JSON_KEY_RES_KEY, "")
var residentKeyRequirement: ResidentKeyRequirement? = null
if (residentKey.isNotEmpty()) {
@@ -340,7 +379,11 @@
val authenticatorAttachmentString =
authenticatorSelection.optString(JSON_KEY_AUTH_ATTACHMENT, "")
if (authenticatorAttachmentString.isNotEmpty()) {
- authSelectionBuilder.setAttachment(Attachment.fromString(authenticatorAttachmentString))
+ authSelectionBuilder.setAttachment(
+ Attachment.fromString(
+ authenticatorAttachmentString
+ )
+ )
}
builder.setAuthenticatorSelection(authSelectionBuilder.build())
}
@@ -385,7 +428,10 @@
try {
transports.add(Transport.fromString(descriptorTransports.getString(j)))
} catch (e: Transport.UnsupportedTransportException) {
- throw CreatePublicKeyCredentialDomException(EncodingError(), e.message)
+ throw CreatePublicKeyCredentialDomException(
+ EncodingError(),
+ e.message
+ )
}
}
}
@@ -510,7 +556,8 @@
try {
COSEAlgorithmIdentifier.fromCoseValue(alg)
return true
- } catch (_: Throwable) {}
+ } catch (_: Throwable) {
+ }
return false
}
@@ -532,4 +579,11 @@
ErrorCode.TIMEOUT_ERR to TimeoutError()
)
}
+
+ @RequiresApi(28)
+ private object GetGMSVersion {
+ @JvmStatic
+ @DoNotInline
+ fun getVersionLong(info: PackageInfo): Long = info.getLongVersionCode()
+ }
}
diff --git a/datastore/datastore-core/src/androidMain/cpp/CMakeLists.txt b/datastore/datastore-core/src/androidMain/cpp/CMakeLists.txt
index a6bd4f3..28fe6f7 100644
--- a/datastore/datastore-core/src/androidMain/cpp/CMakeLists.txt
+++ b/datastore/datastore-core/src/androidMain/cpp/CMakeLists.txt
@@ -14,7 +14,7 @@
# the License.
#
-cmake_minimum_required(VERSION 3.22)
+cmake_minimum_required(VERSION 3.22.1)
project(datastore_shared_counter)
diff --git a/development/project-creator/native-template/groupId/artifactId/src/main/cpp/CMakeLists.txt b/development/project-creator/native-template/groupId/artifactId/src/main/cpp/CMakeLists.txt
index 3c1ddca..c1ae2b9 100644
--- a/development/project-creator/native-template/groupId/artifactId/src/main/cpp/CMakeLists.txt
+++ b/development/project-creator/native-template/groupId/artifactId/src/main/cpp/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.10.2)
+cmake_minimum_required(VERSION 3.22.1)
project(<NAME> LANGUAGES CXX)
diff --git a/docs/api_guidelines/dependencies.md b/docs/api_guidelines/dependencies.md
index 770ad70..74b88cd 100644
--- a/docs/api_guidelines/dependencies.md
+++ b/docs/api_guidelines/dependencies.md
@@ -299,6 +299,11 @@
#### Protobuf {#dependencies-protobuf}
+**Note**: It is preferred to use the [`wire`](https://github.com/square/wire)
+library for handling protocol buffers in Android libraries as it has a binary
+stable runtime. An example of its usage can be found
+[here](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:benchmark/benchmark-common/build.gradle?q=wireRuntime%20file:gradle&ss=androidx%2Fplatform%2Fframeworks%2Fsupport).
+
[Protocol buffers](https://developers.google.com/protocol-buffers) provide a
language- and platform-neutral mechanism for serializing structured data. The
implementation enables developers to maintain protocol compatibility across
@@ -306,8 +311,18 @@
library versions included in their APKs.
The Protobuf library itself, however, does not guarantee ABI compatibility
-across minor versions and a specific version **must** be bundled with a library
-to avoid conflict with other dependencies used by the developer.
+across minor versions and a specific version **must** be used with a library to
+avoid conflict with other dependencies used by the developer. To do this, you
+must first create a new project to repackage the protobuf runtime classes, and
+then have it as a dependency in the project you generate protos in. In the
+project that generates protos, you must also relocate any import statements
+containing `com.google.protobuf` to your target package name. The
+[AndroidXRepackagePlugin](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:buildSrc/private/src/main/kotlin/androidx/build/AndroidXRepackageImplPlugin.kt)
+abstracts this for you. An example of its use to repackage the protobuf runtime
+library can be found
+[here](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:wear/protolayout/protolayout-external-protobuf/build.gradle)
+and its associated use in the library that generates protos can be found
+[here](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:wear/protolayout/protolayout-proto/build.gradle).
Additionally, the Java API surface generated by the Protobuf compiler is not
guaranteed to be stable and **must not** be exposed to developers. Library
diff --git a/dynamicanimation/dynamicanimation-ktx/build.gradle b/dynamicanimation/dynamicanimation-ktx/build.gradle
index 3271478..326d890 100644
--- a/dynamicanimation/dynamicanimation-ktx/build.gradle
+++ b/dynamicanimation/dynamicanimation-ktx/build.gradle
@@ -45,7 +45,7 @@
androidx {
name = "Dynamic animation Kotlin Extensions"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
mavenVersion = LibraryVersions.DYNAMICANIMATION_KTX
inceptionYear = "2018"
description = "Kotlin extensions for 'dynamicanimation' artifact"
diff --git a/fragment/fragment-compose/build.gradle b/fragment/fragment-compose/build.gradle
index ea43573..ba45262 100644
--- a/fragment/fragment-compose/build.gradle
+++ b/fragment/fragment-compose/build.gradle
@@ -21,8 +21,6 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
-import androidx.build.RunApiTasks
import androidx.build.LibraryType
@@ -59,8 +57,7 @@
androidx {
name = "Fragment Compose"
- type = LibraryType.PUBLISHED_LIBRARY
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2024"
description = "Integrate Fragments with Compose to provide helper APIs for using Fragments in" +
"Compose or Compose inside of Fragments"
diff --git a/fragment/fragment-ktx/build.gradle b/fragment/fragment-ktx/build.gradle
index 83d6c56..1070d06 100644
--- a/fragment/fragment-ktx/build.gradle
+++ b/fragment/fragment-ktx/build.gradle
@@ -21,8 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -62,7 +61,7 @@
androidx {
name = "Fragment Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2018"
description = "Kotlin extensions for 'fragment' artifact"
metalavaK2UastEnabled = true
diff --git a/glance/glance-appwidget-testing/build.gradle b/glance/glance-appwidget-testing/build.gradle
index 95bbf25..9390065 100644
--- a/glance/glance-appwidget-testing/build.gradle
+++ b/glance/glance-appwidget-testing/build.gradle
@@ -62,7 +62,7 @@
androidx {
name = "androidx.glance:glance-appwidget-testing"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2023"
description = "This library provides APIs for developers to use for testing their appWidget specific Glance composables."
samples(projectOrArtifact(":glance:glance-appwidget-testing:glance-appwidget-testing-samples"))
diff --git a/glance/glance-appwidget/build.gradle b/glance/glance-appwidget/build.gradle
index e0dbb33..b863b58 100644
--- a/glance/glance-appwidget/build.gradle
+++ b/glance/glance-appwidget/build.gradle
@@ -106,7 +106,7 @@
androidx {
name = "Glance For App Widgets"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2021"
description = "Glance-appwidgets allows developers to build layouts for Android AppWidgets " +
"using a Jetpack Compose-style API."
diff --git a/glance/glance-material3/build.gradle b/glance/glance-material3/build.gradle
index 10e4fe4..46cec12 100644
--- a/glance/glance-material3/build.gradle
+++ b/glance/glance-material3/build.gradle
@@ -32,7 +32,7 @@
androidx {
name = "Glance Material"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2022"
description = "Glance Material integration library." +
" This library provides interop APIs with Material 3."
diff --git a/glance/glance-template/build.gradle b/glance/glance-template/build.gradle
index 0b87855..623fb45 100644
--- a/glance/glance-template/build.gradle
+++ b/glance/glance-template/build.gradle
@@ -79,7 +79,7 @@
androidx {
name = "Glance Templates"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
mavenVersion = LibraryVersions.GLANCE_TEMPLATE
inceptionYear = "2021"
description = "Glance allows developers to build layouts for remote surfaces using a Jetpack " +
diff --git a/glance/glance-testing/build.gradle b/glance/glance-testing/build.gradle
index de552f57..ea041d0 100644
--- a/glance/glance-testing/build.gradle
+++ b/glance/glance-testing/build.gradle
@@ -59,7 +59,7 @@
androidx {
name = "Glance Testing"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2023"
description = "This library provides base APIs to enable testing Glance"
metalavaK2UastEnabled = true
diff --git a/glance/glance-wear-tiles/build.gradle b/glance/glance-wear-tiles/build.gradle
index 7d8a4d5..67e95fe 100644
--- a/glance/glance-wear-tiles/build.gradle
+++ b/glance/glance-wear-tiles/build.gradle
@@ -93,7 +93,7 @@
androidx {
name = "Glance for Wear Tiles"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
mavenVersion = LibraryVersions.GLANCE_WEAR_TILES
inceptionYear = "2021"
description = "Glance allows developers to build layouts for Wear Tiles using a Jetpack " +
diff --git a/glance/glance/build.gradle b/glance/glance/build.gradle
index a15d0fa..e54af2c 100644
--- a/glance/glance/build.gradle
+++ b/glance/glance/build.gradle
@@ -92,7 +92,7 @@
androidx {
name = "Glance"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2021"
description = "Glance allows developers to build layouts for remote surfaces using a Jetpack " +
"Compose-style API."
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index cd86e73..9c01869 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -244,7 +244,7 @@
playServicesBase = { module = "com.google.android.gms:play-services-base", version = "17.0.0" }
playServicesBasement = { module = "com.google.android.gms:play-services-basement", version = "17.0.0" }
playServicesDevicePerformance = { module = "com.google.android.gms:play-services-deviceperformance", version = "16.0.0" }
-playServicesFido = {module = "com.google.android.gms:play-services-fido", version = "20.1.0"}
+playServicesFido = {module = "com.google.android.gms:play-services-fido", version = "21.0.0"}
playServicesWearable = { module = "com.google.android.gms:play-services-wearable", version = "17.1.0" }
protobuf = { module = "com.google.protobuf:protobuf-java", version.ref = "protobuf" }
protobufCompiler = { module = "com.google.protobuf:protoc", version.ref = "protobuf" }
diff --git a/graphics/graphics-core/src/main/cpp/CMakeLists.txt b/graphics/graphics-core/src/main/cpp/CMakeLists.txt
index bc21166..91e7e97 100644
--- a/graphics/graphics-core/src/main/cpp/CMakeLists.txt
+++ b/graphics/graphics-core/src/main/cpp/CMakeLists.txt
@@ -4,7 +4,7 @@
# Sets the minimum version of CMake required to build the native library.
-cmake_minimum_required(VERSION 3.18.1)
+cmake_minimum_required(VERSION 3.22.1)
# Declares and names the project.
diff --git a/graphics/graphics-path/build.gradle b/graphics/graphics-path/build.gradle
index 753e958..f24d85e 100644
--- a/graphics/graphics-path/build.gradle
+++ b/graphics/graphics-path/build.gradle
@@ -55,7 +55,6 @@
"-std=c++17",
"-Wno-unused-command-line-argument",
"-Wl,--hash-style=both", // Required to support API levels below 23
- "-fno-stack-protector",
"-fno-exceptions",
"-fno-unwind-tables",
"-fno-asynchronous-unwind-tables",
@@ -67,7 +66,6 @@
"-fomit-frame-pointer",
"-ffunction-sections",
"-fdata-sections",
- "-fstack-protector",
"-Wl,--gc-sections",
"-Wl,-Bsymbolic-functions",
"-nostdlib++"
diff --git a/graphics/graphics-path/src/main/cpp/CMakeLists.txt b/graphics/graphics-path/src/main/cpp/CMakeLists.txt
index 722eb2d..540919f 100644
--- a/graphics/graphics-path/src/main/cpp/CMakeLists.txt
+++ b/graphics/graphics-path/src/main/cpp/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.18.1)
+cmake_minimum_required(VERSION 3.22.1)
project("androidx.graphics.path")
set(VERSION_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/libandroidx.graphics.path.map")
diff --git a/hilt/hilt-navigation-compose/build.gradle b/hilt/hilt-navigation-compose/build.gradle
index 5383e0d..cba9404 100644
--- a/hilt/hilt-navigation-compose/build.gradle
+++ b/hilt/hilt-navigation-compose/build.gradle
@@ -21,8 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
-import androidx.build.RunApiTasks
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -72,11 +71,10 @@
androidx {
name = "Navigation Compose Hilt Integration"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
mavenVersion = LibraryVersions.HILT_NAVIGATION_COMPOSE
inceptionYear = "2021"
description = "Navigation Compose Hilt Integration"
- runApiTasks = new RunApiTasks.Yes()
legacyDisableKotlinStrictApiMode = true
metalavaK2UastEnabled = true
samples(projectOrArtifact(":hilt:hilt-navigation-compose-samples"))
diff --git a/hilt/hilt-navigation/build.gradle b/hilt/hilt-navigation/build.gradle
index 4fe350f..07b1c52 100644
--- a/hilt/hilt-navigation/build.gradle
+++ b/hilt/hilt-navigation/build.gradle
@@ -27,7 +27,7 @@
id("AndroidXPlugin")
id("com.android.library")
id("kotlin-android")
- id("kotlin-kapt")
+ id("com.google.devtools.ksp")
}
dependencies {
@@ -35,7 +35,7 @@
api("androidx.annotation:annotation:1.1.0")
api("androidx.navigation:navigation-runtime:2.5.1")
api(libs.hiltAndroid)
- kapt(libs.hiltCompiler)
+ ksp(libs.hiltCompiler)
}
androidx {
diff --git a/inspection/inspection/src/main/native/CMakeLists.txt b/inspection/inspection/src/main/native/CMakeLists.txt
index 0897442..b030418 100644
--- a/inspection/inspection/src/main/native/CMakeLists.txt
+++ b/inspection/inspection/src/main/native/CMakeLists.txt
@@ -14,7 +14,7 @@
# the License.
#
-cmake_minimum_required(VERSION 3.4.1)
+cmake_minimum_required(VERSION 3.22.1)
project(compose_inspection_inspection)
diff --git a/libraryversions.toml b/libraryversions.toml
index 138ac26..60f6e35 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -65,7 +65,7 @@
ENTERPRISE = "1.1.0-rc01"
EXIFINTERFACE = "1.4.0-alpha01"
FRAGMENT = "1.8.0-alpha02"
-FUTURES = "1.2.0-alpha03"
+FUTURES = "1.2.0-beta01"
GLANCE = "1.1.0-beta02"
GLANCE_TEMPLATE = "1.0.0-alpha06"
GLANCE_WEAR_TILES = "1.0.0-alpha06"
@@ -107,7 +107,7 @@
PREFERENCE = "1.3.0-alpha01"
PRINT = "1.1.0-beta01"
PRIVACYSANDBOX_ACTIVITY = "1.0.0-alpha01"
-PRIVACYSANDBOX_ADS = "1.1.0-beta06"
+PRIVACYSANDBOX_ADS = "1.1.0-beta07"
PRIVACYSANDBOX_PLUGINS = "1.0.0-alpha03"
PRIVACYSANDBOX_SDKRUNTIME = "1.0.0-alpha13"
PRIVACYSANDBOX_TOOLS = "1.0.0-alpha08"
@@ -171,7 +171,7 @@
WEBKIT = "1.12.0-alpha01"
# Adding a comment to prevent merge conflicts for Window artifact
WINDOW = "1.3.0-beta02"
-WINDOW_EXTENSIONS = "1.3.0-beta01"
+WINDOW_EXTENSIONS = "1.3.0-rc01"
WINDOW_EXTENSIONS_CORE = "1.1.0-alpha01"
WINDOW_SIDECAR = "1.0.0-rc01"
WORK = "2.10.0-alpha02"
diff --git a/lifecycle/lifecycle-livedata-core-ktx/build.gradle b/lifecycle/lifecycle-livedata-core-ktx/build.gradle
index 249fa37..df08ba90 100644
--- a/lifecycle/lifecycle-livedata-core-ktx/build.gradle
+++ b/lifecycle/lifecycle-livedata-core-ktx/build.gradle
@@ -21,8 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -37,7 +36,7 @@
androidx {
name = "LiveData Core Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2018"
description = "Kotlin extensions for 'livedata-core' artifact"
metalavaK2UastEnabled = true
diff --git a/lifecycle/lifecycle-livedata-ktx/build.gradle b/lifecycle/lifecycle-livedata-ktx/build.gradle
index b54fd32..d059f30 100644
--- a/lifecycle/lifecycle-livedata-ktx/build.gradle
+++ b/lifecycle/lifecycle-livedata-ktx/build.gradle
@@ -21,8 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -51,7 +50,7 @@
androidx {
name = "LiveData Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2018"
description = "Kotlin extensions for 'livedata' artifact"
metalavaK2UastEnabled = true
diff --git a/lifecycle/lifecycle-reactivestreams-ktx/build.gradle b/lifecycle/lifecycle-reactivestreams-ktx/build.gradle
index 3bf9a40..e1ae335 100644
--- a/lifecycle/lifecycle-reactivestreams-ktx/build.gradle
+++ b/lifecycle/lifecycle-reactivestreams-ktx/build.gradle
@@ -21,8 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -44,7 +43,7 @@
androidx {
name = "Lifecycle ReactiveStreams KTX"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2018"
description = "Kotlin extensions for Lifecycle ReactiveStreams"
metalavaK2UastEnabled = true
diff --git a/lifecycle/lifecycle-runtime-compose/build.gradle b/lifecycle/lifecycle-runtime-compose/build.gradle
index 57cbcba..31bae0b 100644
--- a/lifecycle/lifecycle-runtime-compose/build.gradle
+++ b/lifecycle/lifecycle-runtime-compose/build.gradle
@@ -72,7 +72,7 @@
androidx {
name = "Lifecycle Runtime Compose"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2021"
description = "Compose integration with Lifecycle"
samples(project(":lifecycle:lifecycle-runtime-compose:lifecycle-runtime-compose-samples"))
diff --git a/lifecycle/lifecycle-runtime-ktx/build.gradle b/lifecycle/lifecycle-runtime-ktx/build.gradle
index 78fe658..ff62f2b 100644
--- a/lifecycle/lifecycle-runtime-ktx/build.gradle
+++ b/lifecycle/lifecycle-runtime-ktx/build.gradle
@@ -22,8 +22,8 @@
* modifying its settings.
*/
+import androidx.build.LibraryType
import androidx.build.PlatformIdentifier
-import androidx.build.Publish
plugins {
id("AndroidXPlugin")
@@ -55,7 +55,7 @@
androidx {
name = "Lifecycle Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2019"
description = "Kotlin extensions for 'lifecycle' artifact"
metalavaK2UastEnabled = true
diff --git a/lifecycle/lifecycle-viewmodel-compose/build.gradle b/lifecycle/lifecycle-viewmodel-compose/build.gradle
index b3f68562..6341854 100644
--- a/lifecycle/lifecycle-viewmodel-compose/build.gradle
+++ b/lifecycle/lifecycle-viewmodel-compose/build.gradle
@@ -21,11 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-
+import androidx.build.LibraryType
import androidx.build.PlatformIdentifier
-import androidx.build.Publish
-import androidx.build.RunApiTasks
-import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode
plugins {
id("AndroidXPlugin")
@@ -94,10 +91,9 @@
androidx {
name = "Lifecycle ViewModel Compose"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2021"
description = "Compose integration with Lifecycle ViewModel"
- runApiTasks = new RunApiTasks.Yes()
samples(projectOrArtifact(":lifecycle:lifecycle-viewmodel-compose:lifecycle-viewmodel-compose-samples"))
}
diff --git a/lifecycle/lifecycle-viewmodel-ktx/build.gradle b/lifecycle/lifecycle-viewmodel-ktx/build.gradle
index 5f99fe4..c488ac6 100644
--- a/lifecycle/lifecycle-viewmodel-ktx/build.gradle
+++ b/lifecycle/lifecycle-viewmodel-ktx/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -37,7 +37,7 @@
androidx {
name = "Lifecycle ViewModel Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2018"
description = "Kotlin extensions for 'viewmodel' artifact"
metalavaK2UastEnabled = true
diff --git a/loader/loader-ktx/build.gradle b/loader/loader-ktx/build.gradle
index 6472e3a..c700ba7 100644
--- a/loader/loader-ktx/build.gradle
+++ b/loader/loader-ktx/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -49,7 +49,7 @@
androidx {
name = "Loader Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2019"
description = "Kotlin extensions for 'loader' artifact"
metalavaK2UastEnabled = true
diff --git a/navigation/navigation-common-ktx/build.gradle b/navigation/navigation-common-ktx/build.gradle
index b78112e..142ea22 100644
--- a/navigation/navigation-common-ktx/build.gradle
+++ b/navigation/navigation-common-ktx/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
+import androidx.build.LibraryType
import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode
plugins {
@@ -40,7 +40,7 @@
androidx {
name = "Navigation Common Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2018"
description = "Android Navigation-Common-Ktx"
metalavaK2UastEnabled = true
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt
index 8a849eb..0eabbd1 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt
@@ -232,6 +232,24 @@
?: if (searchParents && parent != null) parent!!.findNode(route) else null
}
+ // searches through child nodes, does not search through parents
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public fun findChildNode(
+ @IdRes resId: Int,
+ ): NavDestination? {
+ // first search through children directly added to graph
+ var destination = nodes[resId]
+ if (destination != null) return destination
+
+ // then search through child graphs
+ destination = nodes.valueIterator().asSequence().firstNotNullOfOrNull { child ->
+ if (child is NavGraph) {
+ child.findChildNode(resId)
+ } else null
+ }
+ return destination
+ }
+
/**
* @throws NoSuchElementException if there no more elements
*/
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/serialization/RouteEncoder.kt b/navigation/navigation-common/src/main/java/androidx/navigation/serialization/RouteEncoder.kt
index 719dab4..417450f 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/serialization/RouteEncoder.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/serialization/RouteEncoder.kt
@@ -14,11 +14,8 @@
* limitations under the License.
*/
-@file:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-
package androidx.navigation.serialization
-import androidx.annotation.RestrictTo
import androidx.navigation.NavType
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
@@ -36,8 +33,7 @@
private val serializer: KSerializer<T>,
private val typeMap: Map<String, NavType<Any?>>
) : AbstractEncoder() {
- @Suppress("DEPRECATION") // deprecated in 1.6.3
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
private val builder = RouteBuilder.Filled(serializer, typeMap)
/**
diff --git a/navigation/navigation-compose/build.gradle b/navigation/navigation-compose/build.gradle
index 12c8132..1dfe8fc 100644
--- a/navigation/navigation-compose/build.gradle
+++ b/navigation/navigation-compose/build.gradle
@@ -62,7 +62,7 @@
androidx {
name = "Compose Navigation"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2020"
description = "Compose integration with Navigation"
legacyDisableKotlinStrictApiMode = true
diff --git a/navigation/navigation-fragment-compose/build.gradle b/navigation/navigation-fragment-compose/build.gradle
index d1ea6149..24d4568 100644
--- a/navigation/navigation-fragment-compose/build.gradle
+++ b/navigation/navigation-fragment-compose/build.gradle
@@ -51,7 +51,7 @@
androidx {
name = "Navigation with Fragments with Compose"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2024"
description = "Add Compose destinations to Navigation with Fragments"
}
diff --git a/navigation/navigation-fragment-ktx/build.gradle b/navigation/navigation-fragment-ktx/build.gradle
index 84b6d20..f38f638 100644
--- a/navigation/navigation-fragment-ktx/build.gradle
+++ b/navigation/navigation-fragment-ktx/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -38,7 +38,7 @@
androidx {
name = "Navigation Fragment Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2018"
description = "Android Navigation-Fragment-Ktx"
metalavaK2UastEnabled = true
diff --git a/navigation/navigation-runtime-ktx/build.gradle b/navigation/navigation-runtime-ktx/build.gradle
index 93fa350..36897df 100644
--- a/navigation/navigation-runtime-ktx/build.gradle
+++ b/navigation/navigation-runtime-ktx/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -38,7 +38,7 @@
androidx {
name = "Navigation Runtime Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2018"
description = "Android Navigation-Runtime-Ktx"
metalavaK2UastEnabled = true
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
index 89b8bed..a308c35 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
@@ -1484,6 +1484,79 @@
@UiThreadTest
@Test
+ fun testGetBackStackEntryWithKClassNested() {
+ val navController = createNavController()
+ navController.graph = navController.createGraph(startDestination = "start") {
+ test("start")
+ // a sibling graph with nested KClass destination
+ navigation<TestGraph>(startDestination = TestClass::class) {
+ test<TestClass>()
+ }
+ test("second")
+ }
+
+ navController.navigate(TestClass())
+
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ assertThat(navigator.backStack.size).isEqualTo(2)
+ assertThat(navController.currentBackStackEntry?.destination?.route).isEqualTo(
+ TEST_CLASS_ROUTE
+ )
+
+ navController.navigate("second")
+
+ assertThat(navigator.backStack.size).isEqualTo(3)
+ assertThat(navController.currentBackStackEntry?.destination?.route).isEqualTo("second")
+
+ val entry = navController.getBackStackEntry<TestClass>()
+ assertThat(entry.destination.route).isEqualTo(TEST_CLASS_ROUTE)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testGetBackStackEntryWithKClassNotInGraph() {
+ val navController = createNavController()
+ navController.graph = navController.createGraph(startDestination = "start") {
+ test("start")
+ test<TestClass>()
+ }
+ navController.navigate(TestClass())
+
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ assertThat(navigator.backStack.size).isEqualTo(2)
+
+ val exception = assertFailsWith<IllegalArgumentException> {
+ navController.getBackStackEntry<TestClassPathArg>()
+ }
+ assertThat(exception.message).isEqualTo(
+ "Destination with route TestClassPathArg cannot be found in " +
+ "navigation graph ${navController.graph}"
+ )
+ }
+
+ @UiThreadTest
+ @Test
+ fun testGetBackStackEntryWithKClassNotInBackstack() {
+ val navController = createNavController()
+ navController.graph = navController.createGraph(startDestination = "start") {
+ test("start")
+ test<TestClass>()
+ }
+
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ assertThat(navigator.backStack.size).isEqualTo(1)
+
+ val exception = assertFailsWith<IllegalArgumentException> {
+ navController.getBackStackEntry<TestClass>()
+ }
+ assertThat(exception.message).isEqualTo(
+ "No destination with route TestClass is on the NavController's " +
+ "back stack. The current destination is ${navController.currentDestination}"
+ )
+ }
+
+ @UiThreadTest
+ @Test
fun testGetBackStackEntryWithObject() {
val navController = createNavController()
navController.graph = navController.createGraph(startDestination = "start") {
@@ -1561,6 +1634,79 @@
@UiThreadTest
@Test
+ fun testGetBackStackEntryWithObjectNested() {
+ val navController = createNavController()
+ navController.graph = navController.createGraph(startDestination = "start") {
+ test("start")
+ // a sibling graph with nested KClass destination
+ navigation<TestGraph>(startDestination = TestClassPathArg::class) {
+ test<TestClassPathArg>()
+ }
+ test("second")
+ }
+
+ navController.navigate(TestClassPathArg(1))
+
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ assertThat(navigator.backStack.size).isEqualTo(2)
+ assertThat(navController.currentBackStackEntry?.destination?.route).isEqualTo(
+ TEST_CLASS_PATH_ARG_ROUTE
+ )
+
+ navController.navigate("second")
+
+ assertThat(navigator.backStack.size).isEqualTo(3)
+ assertThat(navController.currentBackStackEntry?.destination?.route).isEqualTo("second")
+
+ val entry = navController.getBackStackEntry(TestClassPathArg(1))
+ assertThat(entry.destination.route).isEqualTo(TEST_CLASS_PATH_ARG_ROUTE)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testGetBackStackEntryWithObjectNotInGraph() {
+ val navController = createNavController()
+ navController.graph = navController.createGraph(startDestination = "start") {
+ test("start")
+ test<TestClass>()
+ }
+ navController.navigate(TestClass())
+
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ assertThat(navigator.backStack.size).isEqualTo(2)
+
+ val exception = assertFailsWith<IllegalArgumentException> {
+ navController.getBackStackEntry(TestClassPathArg(1))
+ }
+ assertThat(exception.message).isEqualTo(
+ "Destination with route TestClassPathArg cannot be found in " +
+ "navigation graph ${navController.graph}"
+ )
+ }
+
+ @UiThreadTest
+ @Test
+ fun testGetBackStackEntryWithObjectNotInBackstack() {
+ val navController = createNavController()
+ navController.graph = navController.createGraph(startDestination = "start") {
+ test("start")
+ test<TestClass>()
+ }
+
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ assertThat(navigator.backStack.size).isEqualTo(1)
+
+ val exception = assertFailsWith<IllegalArgumentException> {
+ navController.getBackStackEntry(TestClass())
+ }
+ assertThat(exception.message).isEqualTo(
+ "No destination with route $TEST_CLASS_ROUTE is on the NavController's " +
+ "back stack. The current destination is ${navController.currentDestination}"
+ )
+ }
+
+ @UiThreadTest
+ @Test
fun testPopBackStack() {
val navController = createNavController()
navController.graph = nav_singleArg_graph
@@ -1778,19 +1924,14 @@
test("start")
test<TestClass>()
}
-
- // first nav
- navController.navigate("start")
-
- // second nav
- navController.navigate(TEST_CLASS_ROUTE)
+ navController.navigate(TestClass())
val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
- assertThat(navigator.backStack.size).isEqualTo(3)
+ assertThat(navigator.backStack.size).isEqualTo(2)
val popped = navController.popBackStack<TestClass>(true)
assertThat(popped).isTrue()
- assertThat(navigator.backStack.size).isEqualTo(2)
+ assertThat(navigator.backStack.size).isEqualTo(1)
}
@UiThreadTest
@@ -1818,6 +1959,37 @@
@UiThreadTest
@Test
+ fun testPopBackStackWithKClassNested() {
+ val navController = createNavController()
+ navController.graph = navController.createGraph(startDestination = "start") {
+ test("start")
+ // a sibling graph with nested KClass destination
+ navigation<TestGraph>(startDestination = TestClass::class) {
+ test<TestClass>()
+ }
+ test("second")
+ }
+
+ navController.navigate(TestClass())
+
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ assertThat(navigator.backStack.size).isEqualTo(2)
+ assertThat(navController.currentBackStackEntry?.destination?.route).isEqualTo(
+ TEST_CLASS_ROUTE
+ )
+
+ navController.navigate("second")
+
+ assertThat(navigator.backStack.size).isEqualTo(3)
+ assertThat(navController.currentBackStackEntry?.destination?.route).isEqualTo("second")
+
+ val popped = navController.popBackStack<TestClass>(true)
+ assertThat(popped).isTrue()
+ assertThat(navigator.backStack.size).isEqualTo(1)
+ }
+
+ @UiThreadTest
+ @Test
fun testPopBackStackWithKClassArg() {
val navController = createNavController()
navController.graph = navController.createGraph(startDestination = "start") {
@@ -1841,6 +2013,44 @@
@UiThreadTest
@Test
+ fun testPopBackStackWithKClassNotInGraph() {
+ val navController = createNavController()
+ navController.graph = navController.createGraph(startDestination = "start") {
+ test("start")
+ test<TestClass>()
+ }
+ navController.navigate(TestClass())
+
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ assertThat(navigator.backStack.size).isEqualTo(2)
+
+ val exception = assertFailsWith<IllegalArgumentException> {
+ navController.popBackStack<TestClassPathArg>(true)
+ }
+ assertThat(exception.message).isEqualTo(
+ "Destination with route TestClassPathArg cannot be found in " +
+ "navigation graph ${navController.graph}"
+ )
+ }
+
+ @UiThreadTest
+ @Test
+ fun testPopBackStackWithKClassNotInBackStack() {
+ val navController = createNavController()
+ navController.graph = navController.createGraph(startDestination = "start") {
+ test("start")
+ test<TestClass>()
+ }
+
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ assertThat(navigator.backStack.size).isEqualTo(1)
+
+ val popped = navController.popBackStack<TestClass>(true)
+ assertThat(popped).isFalse()
+ }
+
+ @UiThreadTest
+ @Test
fun testPopBackStackWithObject() {
val navController = createNavController()
navController.graph = navController.createGraph(startDestination = "start") {
@@ -1933,6 +2143,75 @@
@UiThreadTest
@Test
+ fun testPopBackStackWithObjectNested() {
+ val navController = createNavController()
+ navController.graph = navController.createGraph(startDestination = "start") {
+ test("start")
+ // a sibling graph with nested KClass destination
+ navigation<TestGraph>(startDestination = TestClass::class) {
+ test<TestClass>()
+ }
+ test("second")
+ }
+
+ navController.navigate(TestClass())
+
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ assertThat(navigator.backStack.size).isEqualTo(2)
+ assertThat(navController.currentBackStackEntry?.destination?.route).isEqualTo(
+ TEST_CLASS_ROUTE
+ )
+
+ navController.navigate("second")
+
+ assertThat(navigator.backStack.size).isEqualTo(3)
+ assertThat(navController.currentBackStackEntry?.destination?.route).isEqualTo("second")
+
+ val popped = navController.popBackStack(TestClass(), true)
+ assertThat(popped).isTrue()
+ assertThat(navigator.backStack.size).isEqualTo(1)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testPopBackStackWithObjectNotInGraph() {
+ val navController = createNavController()
+ navController.graph = navController.createGraph(startDestination = "start") {
+ test("start")
+ test<TestClass>()
+ }
+ navController.navigate(TestClass())
+
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ assertThat(navigator.backStack.size).isEqualTo(2)
+
+ val exception = assertFailsWith<IllegalArgumentException> {
+ navController.popBackStack(TestClassPathArg(1), true)
+ }
+ assertThat(exception.message).isEqualTo(
+ "Destination with route TestClassPathArg cannot be found in " +
+ "navigation graph ${navController.graph}"
+ )
+ }
+
+ @UiThreadTest
+ @Test
+ fun testPopBackStackWithObjectNotInBackStack() {
+ val navController = createNavController()
+ navController.graph = navController.createGraph(startDestination = "start") {
+ test("start")
+ test<TestClass>()
+ }
+
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ assertThat(navigator.backStack.size).isEqualTo(1)
+
+ val popped = navController.popBackStack(TestClass(), true)
+ assertThat(popped).isFalse()
+ }
+
+ @UiThreadTest
+ @Test
fun testFindDestinationWithRoute() {
val navController = createNavController()
navController.graph = nav_singleArg_graph
@@ -3409,6 +3688,27 @@
@UiThreadTest
@Test
+ fun testNavigateWithObjectNotInGraph() {
+ val navController = createNavController()
+ navController.graph = navController.createGraph(
+ startDestination = TestClass::class
+ ) {
+ test<TestClass>()
+ }
+ assertThat(navController.currentDestination?.route).isEqualTo(
+ TEST_CLASS_ROUTE
+ )
+ val exception = assertFailsWith<IllegalArgumentException> {
+ navController.navigate(TestClassPathArg(1))
+ }
+ assertThat(exception.message).isEqualTo(
+ "Destination with route TestClassPathArg cannot be found in navigation " +
+ "graph ${navController.graph}"
+ )
+ }
+
+ @UiThreadTest
+ @Test
fun testDeepLinkFromNavGraph() {
val navController = createNavController()
navController.graph = nav_simple_route_graph
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
index 39c0994..d0b7a2e 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
@@ -547,7 +547,14 @@
public inline fun <reified T : Any> popBackStack(
inclusive: Boolean,
saveState: Boolean = false
- ): Boolean = popBackStack(serializer<T>().hashCode(), inclusive, saveState)
+ ): Boolean {
+ val id = serializer<T>().hashCode()
+ requireNotNull(findDestinationFromRoot(id)) {
+ "Destination with route ${T::class.simpleName} cannot be found in navigation " +
+ "graph $graph"
+ }
+ return popBackStack(id, inclusive, saveState)
+ }
/**
* Attempts to pop the controller's back stack back to a specific destination.
@@ -640,11 +647,7 @@
): Boolean {
// route contains arguments so we need to generate and pop with the populated route
// rather than popping based on route pattern
- val finalRoute = generateRouteFilled(route, fromBackStack = true)
- requireNotNull(finalRoute) {
- "PopBackStack failed: route $route cannot be found from" +
- "the current backstack. The current destination is $currentDestination"
- }
+ val finalRoute = generateRouteFilled(route)
return popBackStackInternal(finalRoute, inclusive, saveState)
}
@@ -904,7 +907,7 @@
public fun <T : Any> clearBackStack(route: T): Boolean {
// route contains arguments so we need to generate and clear with the populated route
// rather than clearing based on route pattern
- val finalRoute = generateRouteFilled(route) ?: return false
+ val finalRoute = generateRouteFilled(route)
val cleared = clearBackStackInternal(finalRoute)
// Only return true if the clear succeeded and we've dispatched
// the change to a new destination
@@ -1640,6 +1643,17 @@
return currentNode.findDestination(destinationId)
}
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public fun findDestinationFromRoot(@IdRes destinationId: Int): NavDestination? {
+ if (_graph == null) {
+ return null
+ }
+ if (_graph!!.id == destinationId) {
+ return _graph
+ }
+ return _graph!!.findChildNode(destinationId)
+ }
+
private fun NavDestination.findDestination(@IdRes destinationId: Int): NavDestination? {
if (id == destinationId) {
return this
@@ -1662,21 +1676,18 @@
return currentGraph.findNode(route)
}
- // Finds destination and generates a route filled with args based on the serializable object.
- // `fromBackStack` is for efficiency - if left false, the worst case scenario is searching
- // from entire graph when we only care about backstack.
+ // Finds destination within _graph including its children and
+ // generates a route filled with args based on the serializable object.
+ // Throws if destination with `route` is not found
@OptIn(InternalSerializationApi::class)
- private fun <T : Any> generateRouteFilled(route: T, fromBackStack: Boolean = false): String? {
- val destination = if (fromBackStack) {
- // limit search within backstack
- backQueue.lastOrNull {
- it.destination.id == route::class.serializer().hashCode()
- }?.destination
- } else {
- // search from within root graph
- findDestination(route::class.serializer().hashCode())
+ private fun <T : Any> generateRouteFilled(route: T): String {
+ val id = route::class.serializer().hashCode()
+ val destination = findDestinationFromRoot(id)
+ // throw immediately if destination is not found within the graph
+ requireNotNull(destination) {
+ "Destination with route ${route::class.simpleName} cannot be found " +
+ "in navigation graph $_graph"
}
- if (destination == null) return null
return route.generateRouteWithArgs(
// get argument typeMap
destination.arguments.mapValues { it.value.type }
@@ -2688,8 +2699,21 @@
* target NavBackStackEntry's [NavDestination] must have been created with route from [KClass].
* @throws IllegalArgumentException if the destination is not on the back stack
*/
- public inline fun <reified T : Any> getBackStackEntry(): NavBackStackEntry =
- getBackStackEntry(serializer<T>().hashCode())
+ public inline fun <reified T : Any> getBackStackEntry(): NavBackStackEntry {
+ val id = serializer<T>().hashCode()
+ requireNotNull(findDestinationFromRoot(id)) {
+ "Destination with route ${T::class.simpleName} cannot be found in navigation " +
+ "graph $graph"
+ }
+ val lastFromBackStack = currentBackStack.value.lastOrNull { entry ->
+ entry.destination.id == id
+ }
+ requireNotNull(lastFromBackStack) {
+ "No destination with route ${T::class.simpleName} is on the NavController's " +
+ "back stack. The current destination is $currentDestination"
+ }
+ return lastFromBackStack
+ }
/**
* Gets the topmost [NavBackStackEntry] for a route from an Object.
@@ -2705,11 +2729,7 @@
public fun <T : Any> getBackStackEntry(route: T): NavBackStackEntry {
// route contains arguments so we need to generate the populated route
// rather than getting entry based on route pattern
- val finalRoute = generateRouteFilled(route, fromBackStack = true)
- requireNotNull(finalRoute) {
- "No destination with route $finalRoute is on the NavController's back stack. The " +
- "current destination is $currentDestination"
- }
+ val finalRoute = generateRouteFilled(route)
return getBackStackEntry(finalRoute)
}
diff --git a/navigation/navigation-ui-ktx/build.gradle b/navigation/navigation-ui-ktx/build.gradle
index 8b47902..0a1ede1 100644
--- a/navigation/navigation-ui-ktx/build.gradle
+++ b/navigation/navigation-ui-ktx/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -38,7 +38,7 @@
androidx {
name = "Navigation UI Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2018"
description = "Android Navigation-UI-Ktx"
metalavaK2UastEnabled = true
diff --git a/paging/paging-common-ktx/build.gradle b/paging/paging-common-ktx/build.gradle
index b4dfab7..0f607d3 100644
--- a/paging/paging-common-ktx/build.gradle
+++ b/paging/paging-common-ktx/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -34,7 +34,7 @@
androidx {
name = "Paging-Common Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2018"
description = "Kotlin extensions for 'paging-common' artifact"
metalavaK2UastEnabled = true
diff --git a/paging/paging-compose/build.gradle b/paging/paging-compose/build.gradle
index 7810e16..f52c01c 100644
--- a/paging/paging-compose/build.gradle
+++ b/paging/paging-compose/build.gradle
@@ -21,8 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
-import androidx.build.RunApiTasks
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -79,10 +78,9 @@
androidx {
name = "Paging-Compose"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2020"
description = "Compose integration with Paging"
- runApiTasks = new RunApiTasks.Yes()
legacyDisableKotlinStrictApiMode = true
samples(project(":paging:paging-compose:paging-compose-samples"))
}
diff --git a/paging/paging-runtime-ktx/build.gradle b/paging/paging-runtime-ktx/build.gradle
index 1399fe5..dc1b808 100644
--- a/paging/paging-runtime-ktx/build.gradle
+++ b/paging/paging-runtime-ktx/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -37,7 +37,7 @@
androidx {
name = "Paging-Runtime Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2018"
description = "Kotlin extensions for 'paging-runtime' artifact"
metalavaK2UastEnabled = true
diff --git a/paging/paging-rxjava2-ktx/build.gradle b/paging/paging-rxjava2-ktx/build.gradle
index f41e14e..9bd8dab 100644
--- a/paging/paging-rxjava2-ktx/build.gradle
+++ b/paging/paging-rxjava2-ktx/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -44,7 +44,7 @@
androidx {
name = "Paging RxJava2 Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2018"
description = "Kotlin extensions for 'paging-rxjava2' artifact"
metalavaK2UastEnabled = true
diff --git a/palette/palette-ktx/build.gradle b/palette/palette-ktx/build.gradle
index 98748c6..206b05c 100644
--- a/palette/palette-ktx/build.gradle
+++ b/palette/palette-ktx/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -40,7 +40,7 @@
androidx {
name = "Palette Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2018"
description = "Kotlin extensions for 'palette' artifact"
metalavaK2UastEnabled = true
diff --git a/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/aidl/Dimensions.aidl b/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/models/Dimensions.aidl
similarity index 97%
rename from pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/aidl/Dimensions.aidl
rename to pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/models/Dimensions.aidl
index 1dfe734..b6b6775 100644
--- a/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/aidl/Dimensions.aidl
+++ b/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/models/Dimensions.aidl
@@ -16,6 +16,6 @@
// with such a backward incompatible change, it has a high risk of breaking
// later when a module using the interface is updated, e.g., Mainline modules.
-package androidx.pdf.aidl;
+package androidx.pdf.models;
@JavaOnlyStableParcelable @JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
parcelable Dimensions;
diff --git a/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/aidl/LinkRects.aidl b/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/models/GotoLink.aidl
similarity index 95%
copy from pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/aidl/LinkRects.aidl
copy to pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/models/GotoLink.aidl
index 028ecf0..b1a881c 100644
--- a/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/aidl/LinkRects.aidl
+++ b/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/models/GotoLink.aidl
@@ -16,6 +16,6 @@
// with such a backward incompatible change, it has a high risk of breaking
// later when a module using the interface is updated, e.g., Mainline modules.
-package androidx.pdf.aidl;
+package androidx.pdf.models;
@JavaOnlyStableParcelable @JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
-parcelable LinkRects;
+parcelable GotoLink;
diff --git a/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/aidl/LinkRects.aidl b/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/models/GotoLinkDestination.aidl
similarity index 94%
copy from pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/aidl/LinkRects.aidl
copy to pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/models/GotoLinkDestination.aidl
index 028ecf0..cbfa741 100644
--- a/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/aidl/LinkRects.aidl
+++ b/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/models/GotoLinkDestination.aidl
@@ -16,6 +16,6 @@
// with such a backward incompatible change, it has a high risk of breaking
// later when a module using the interface is updated, e.g., Mainline modules.
-package androidx.pdf.aidl;
+package androidx.pdf.models;
@JavaOnlyStableParcelable @JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
-parcelable LinkRects;
+parcelable GotoLinkDestination;
diff --git a/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/aidl/LinkRects.aidl b/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/models/LinkRects.aidl
similarity index 97%
rename from pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/aidl/LinkRects.aidl
rename to pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/models/LinkRects.aidl
index 028ecf0..59da54a 100644
--- a/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/aidl/LinkRects.aidl
+++ b/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/models/LinkRects.aidl
@@ -16,6 +16,6 @@
// with such a backward incompatible change, it has a high risk of breaking
// later when a module using the interface is updated, e.g., Mainline modules.
-package androidx.pdf.aidl;
+package androidx.pdf.models;
@JavaOnlyStableParcelable @JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
parcelable LinkRects;
diff --git a/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/aidl/MatchRects.aidl b/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/models/MatchRects.aidl
similarity index 97%
rename from pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/aidl/MatchRects.aidl
rename to pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/models/MatchRects.aidl
index aef58dd..3e0d4aa 100644
--- a/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/aidl/MatchRects.aidl
+++ b/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/models/MatchRects.aidl
@@ -16,6 +16,6 @@
// with such a backward incompatible change, it has a high risk of breaking
// later when a module using the interface is updated, e.g., Mainline modules.
-package androidx.pdf.aidl;
+package androidx.pdf.models;
@JavaOnlyStableParcelable @JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
parcelable MatchRects;
diff --git a/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/aidl/PageSelection.aidl b/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/models/PageSelection.aidl
similarity index 97%
rename from pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/aidl/PageSelection.aidl
rename to pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/models/PageSelection.aidl
index 7915e96..fe2285f 100644
--- a/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/aidl/PageSelection.aidl
+++ b/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/models/PageSelection.aidl
@@ -16,6 +16,6 @@
// with such a backward incompatible change, it has a high risk of breaking
// later when a module using the interface is updated, e.g., Mainline modules.
-package androidx.pdf.aidl;
+package androidx.pdf.models;
@JavaOnlyStableParcelable @JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
parcelable PageSelection;
diff --git a/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/aidl/PdfDocumentRemote.aidl b/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/models/PdfDocumentRemote.aidl
similarity index 67%
rename from pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/aidl/PdfDocumentRemote.aidl
rename to pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/models/PdfDocumentRemote.aidl
index af61188..fac5a69 100644
--- a/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/aidl/PdfDocumentRemote.aidl
+++ b/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/models/PdfDocumentRemote.aidl
@@ -16,21 +16,22 @@
// with such a backward incompatible change, it has a high risk of breaking
// later when a module using the interface is updated, e.g., Mainline modules.
-package androidx.pdf.aidl;
+package androidx.pdf.models;
@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
interface PdfDocumentRemote {
int create(in ParcelFileDescriptor pfd, String password);
int numPages();
- androidx.pdf.aidl.Dimensions getPageDimensions(int pageNum);
- boolean renderPage(int pageNum, in androidx.pdf.aidl.Dimensions size, boolean hideTextAnnots, in ParcelFileDescriptor output);
- boolean renderTile(int pageNum, int pageWidth, int pageHeight, int left, int top, in androidx.pdf.aidl.Dimensions tileSize, boolean hideTextAnnots, in ParcelFileDescriptor output);
+ androidx.pdf.models.Dimensions getPageDimensions(int pageNum);
+ android.graphics.Bitmap renderPage(int pageNum, int pageWidth, int pageHeight, boolean hideTextAnnots);
+ android.graphics.Bitmap renderTile(int pageNum, int tileWidth, int tileHeight, int scaledPageWidth, int scaledPageHeight, int left, int top, boolean hideTextAnnots);
String getPageText(int pageNum);
List<String> getPageAltText(int pageNum);
- androidx.pdf.aidl.MatchRects searchPageText(int pageNum, String query);
- androidx.pdf.aidl.PageSelection selectPageText(int pageNum, in androidx.pdf.aidl.SelectionBoundary start, in androidx.pdf.aidl.SelectionBoundary stop);
- androidx.pdf.aidl.LinkRects getPageLinks(int pageNum);
- byte[] getPageGotoLinksByteArray(int pageNum);
+ androidx.pdf.models.MatchRects searchPageText(int pageNum, String query);
+ androidx.pdf.models.PageSelection selectPageText(int pageNum, in androidx.pdf.models.SelectionBoundary start, in androidx.pdf.models.SelectionBoundary stop);
+ androidx.pdf.models.LinkRects getPageLinks(int pageNum);
+ List<androidx.pdf.models.GotoLink> getPageGotoLinks(int pageNum);
boolean isPdfLinearized();
+ int getFormType();
boolean cloneWithoutSecurity(in ParcelFileDescriptor destination);
boolean saveAs(in ParcelFileDescriptor destination);
}
diff --git a/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/aidl/SelectionBoundary.aidl b/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/models/SelectionBoundary.aidl
similarity index 97%
rename from pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/aidl/SelectionBoundary.aidl
rename to pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/models/SelectionBoundary.aidl
index 8348dad..975af72 100644
--- a/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/aidl/SelectionBoundary.aidl
+++ b/pdf/pdf-viewer/api/aidlRelease/current/androidx/pdf/models/SelectionBoundary.aidl
@@ -16,6 +16,6 @@
// with such a backward incompatible change, it has a high risk of breaking
// later when a module using the interface is updated, e.g., Mainline modules.
-package androidx.pdf.aidl;
+package androidx.pdf.models;
@JavaOnlyStableParcelable @JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
parcelable SelectionBoundary;
diff --git a/pdf/pdf-viewer/src/androidTest/java/androidx/pdf/viewer/loader/PdfLoaderTest.java b/pdf/pdf-viewer/src/androidTest/java/androidx/pdf/viewer/loader/PdfLoaderTest.java
index 635e63b..eff313b 100644
--- a/pdf/pdf-viewer/src/androidTest/java/androidx/pdf/viewer/loader/PdfLoaderTest.java
+++ b/pdf/pdf-viewer/src/androidTest/java/androidx/pdf/viewer/loader/PdfLoaderTest.java
@@ -30,15 +30,15 @@
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
-import androidx.pdf.aidl.Dimensions;
-import androidx.pdf.aidl.LinkRects;
-import androidx.pdf.aidl.MatchRects;
-import androidx.pdf.aidl.PageSelection;
-import androidx.pdf.aidl.PdfDocumentRemote;
-import androidx.pdf.aidl.SelectionBoundary;
import androidx.pdf.data.DisplayData;
import androidx.pdf.data.Opener;
import androidx.pdf.data.PdfStatus;
+import androidx.pdf.models.Dimensions;
+import androidx.pdf.models.LinkRects;
+import androidx.pdf.models.MatchRects;
+import androidx.pdf.models.PageSelection;
+import androidx.pdf.models.PdfDocumentRemote;
+import androidx.pdf.models.SelectionBoundary;
import androidx.pdf.util.RectUtils;
import androidx.pdf.util.TileBoard;
import androidx.pdf.util.TileBoard.CancelTilesCallback;
diff --git a/pdf/pdf-viewer/src/androidTest/java/androidx/pdf/viewer/loader/PdfTaskExecutorTest.java b/pdf/pdf-viewer/src/androidTest/java/androidx/pdf/viewer/loader/PdfTaskExecutorTest.java
index e901d9d..1466502 100644
--- a/pdf/pdf-viewer/src/androidTest/java/androidx/pdf/viewer/loader/PdfTaskExecutorTest.java
+++ b/pdf/pdf-viewer/src/androidTest/java/androidx/pdf/viewer/loader/PdfTaskExecutorTest.java
@@ -24,7 +24,7 @@
import android.os.RemoteException;
-import androidx.pdf.aidl.PdfDocumentRemote;
+import androidx.pdf.models.PdfDocumentRemote;
import androidx.pdf.pdflib.PdfDocumentRemoteProto;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
diff --git a/pdf/pdf-viewer/src/main/AndroidManifest.xml b/pdf/pdf-viewer/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..149f357
--- /dev/null
+++ b/pdf/pdf-viewer/src/main/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools">
+
+ <application android:label="PdfViewer">
+
+ <service
+ android:name="androidx.pdf.pdflib.PdfDocumentService"
+ android:isolatedProcess="true"
+ tools:ignore="MissingServiceExportedEqualsTrue" />
+
+ </application>
+</manifest>
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/data/ContentOpenable.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/data/ContentOpenable.java
index 72c3be7..2d3c15e 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/data/ContentOpenable.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/data/ContentOpenable.java
@@ -24,7 +24,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
-import androidx.pdf.aidl.Dimensions;
+import androidx.pdf.models.Dimensions;
import androidx.pdf.util.Preconditions;
import androidx.pdf.util.Uris;
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/data/TextSelection.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/data/TextSelection.java
index cdb2107..a48a9fb 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/data/TextSelection.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/data/TextSelection.java
@@ -20,7 +20,7 @@
import android.os.Parcelable;
import androidx.annotation.RestrictTo;
-import androidx.pdf.aidl.SelectionBoundary;
+import androidx.pdf.models.SelectionBoundary;
/** Represents the selection of part of a piece of text - a start and a stop. */
@RestrictTo(RestrictTo.Scope.LIBRARY)
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/fetcher/Fetcher.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/fetcher/Fetcher.java
index ac4f5ee..68735fe 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/fetcher/Fetcher.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/fetcher/Fetcher.java
@@ -20,13 +20,13 @@
import android.net.Uri;
import androidx.annotation.RestrictTo;
-import androidx.pdf.aidl.Dimensions;
import androidx.pdf.data.ContentOpenable;
import androidx.pdf.data.FileOpenable;
import androidx.pdf.data.FutureValue;
import androidx.pdf.data.Openable;
import androidx.pdf.data.Opener;
import androidx.pdf.data.UiFutureValues;
+import androidx.pdf.models.Dimensions;
import androidx.pdf.util.Preconditions;
import androidx.pdf.util.StrictModeUtils;
import androidx.pdf.util.Uris;
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/aidl/Dimensions.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/models/Dimensions.java
similarity index 91%
rename from pdf/pdf-viewer/src/main/java/androidx/pdf/aidl/Dimensions.java
rename to pdf/pdf-viewer/src/main/java/androidx/pdf/models/Dimensions.java
index 07462ab..d852a22c 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/aidl/Dimensions.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/models/Dimensions.java
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package androidx.pdf.aidl;
+package androidx.pdf.models;
+import android.annotation.SuppressLint;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
@@ -28,6 +29,7 @@
* Objects of this class are immutable.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressLint("BanParcelableUsage")
public class Dimensions implements Parcelable {
public static final Creator<Dimensions> CREATOR = new Creator<Dimensions>() {
@Override
@@ -49,7 +51,7 @@
this.mHeight = height;
}
- public Dimensions(Rect rect) {
+ public Dimensions(@NonNull Rect rect) {
this.mWidth = rect.width();
this.mHeight = rect.height();
}
@@ -88,7 +90,7 @@
}
@Override
- public void writeToParcel(Parcel parcel, int flags) {
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
parcel.writeInt(mWidth);
parcel.writeInt(mHeight);
}
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/models/GotoLink.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/models/GotoLink.java
new file mode 100644
index 0000000..b51abea
--- /dev/null
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/models/GotoLink.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2024 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.pdf.models;
+
+import android.annotation.SuppressLint;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+import com.google.common.base.Preconditions;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Represents the content associated with a goto link on a page in the PDF document. GotoLink is an
+ * internal navigation link which directs the user to a different location within the same pdf
+ * document.
+ */
+// TODO: Use android.graphics.pdf.content.PdfPageGotoLinkContent and remove this class
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressWarnings("deprecation")
+@SuppressLint("BanParcelableUsage")
+public class GotoLink implements Parcelable {
+
+ public static final Creator<GotoLink> CREATOR = new Creator<GotoLink>() {
+ @SuppressWarnings("unchecked")
+ @Override
+ public GotoLink createFromParcel(Parcel parcel) {
+ return new GotoLink((List<Rect>) Objects.requireNonNull(
+ parcel.readArrayList(Rect.class.getClassLoader())),
+ (GotoLinkDestination) Objects.requireNonNull(parcel.readParcelable(
+ GotoLinkDestination.class.getClassLoader())));
+ }
+
+ @Override
+ public GotoLink[] newArray(int size) {
+ return new GotoLink[size];
+ }
+ };
+
+ private final List<Rect> mBounds;
+ private final GotoLinkDestination mDestination;
+
+ /**
+ * Creates a new instance of {@link GotoLink} using the bounds of the goto link
+ * and the destination where it is directing
+ *
+ * @param bounds Bounds which envelop the goto link
+ * @param destination Destination where the goto link is directing
+ * @throws NullPointerException If bounds or destination is null.
+ * @throws IllegalArgumentException If the bounds list is empty.
+ */
+ public GotoLink(@NonNull List<Rect> bounds, @NonNull GotoLinkDestination destination) {
+ Preconditions.checkNotNull(bounds, "Bounds cannot be null");
+ Preconditions.checkArgument(!bounds.isEmpty(), "Bounds cannot be empty");
+ Preconditions.checkNotNull(destination, "Destination cannot be null");
+ this.mBounds = bounds;
+ this.mDestination = destination;
+ }
+
+ /**
+ * Gets the bounds of a {@link GotoLink} represented as a list of {@link Rect}.
+ * Links which are spread across multiple lines will be surrounded by multiple {@link Rect}
+ * in order of viewing.
+ *
+ * @return The bounds of the goto link.
+ */
+ @NonNull
+ public List<Rect> getBounds() {
+ return mBounds;
+ }
+
+ /**
+ * Gets the destination {@link GotoLinkDestination} of the {@link GotoLink}.
+ *
+ * @return Destination where goto link is directing the user.
+ */
+ @NonNull
+ public GotoLinkDestination getDestination() {
+ return mDestination;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "GotoLink{" + "mBounds=" + mBounds + ", mDestination=" + mDestination + '}';
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeList(mBounds);
+ parcel.writeParcelable(mDestination, 0);
+ }
+}
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/models/GotoLinkDestination.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/models/GotoLinkDestination.java
new file mode 100644
index 0000000..53c05ec
--- /dev/null
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/models/GotoLinkDestination.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2024 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.pdf.models;
+
+import android.annotation.SuppressLint;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * Represents the content associated with the destination where a goto link is directing.
+ * Should be a nested class of {@link GotoLink}, but AIDL prevents that.
+ */
+// TODO: Use android.graphics.pdf.content.PdfPageGotoLinkContent#Destination and remove this class
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressLint("BanParcelableUsage")
+public class GotoLinkDestination implements Parcelable {
+
+ public static final Creator<GotoLinkDestination> CREATOR =
+ new Creator<GotoLinkDestination>() {
+ @Override
+ public GotoLinkDestination createFromParcel(Parcel parcel) {
+ return new GotoLinkDestination(parcel.readInt(), parcel.readFloat(),
+ parcel.readFloat(), parcel.readFloat());
+ }
+
+ @Override
+ public GotoLinkDestination[] newArray(int size) {
+ return new GotoLinkDestination[size];
+ }
+ };
+
+ private final int mPageNumber;
+ private final float mXCoordinate;
+ private final float mYCoordinate;
+ private final float mZoom;
+
+ /**
+ * Creates a new instance of {@link GotoLinkDestination} using the page number, x coordinate,
+ * and y coordinate of the destination where goto link is directing, and the zoom factor of the
+ * page when goto link takes to the destination.
+ *
+ * @param pageNumber Page number of the goto link Destination
+ * @param xCoordinate X coordinate of the goto link Destination in points (1/72")
+ * @param yCoordinate Y coordinate of the goto link Destination in points (1/72")
+ * @param zoom Zoom factor {@link GotoLinkDestination#getZoom()} of the page when
+ * goto link
+ * takes to the destination
+ * @throws IllegalArgumentException If pageNumber or either of the coordinates or zoom are
+ * less than zero
+ */
+ public GotoLinkDestination(int pageNumber, float xCoordinate, float yCoordinate, float zoom) {
+ Preconditions.checkArgument(pageNumber >= 0,
+ "Page number must be" + " greater than or equal to 0");
+ Preconditions.checkArgument(xCoordinate >= 0,
+ "X coordinate " + "must be greater than or equal to 0");
+ Preconditions.checkArgument(yCoordinate >= 0,
+ "Y coordinate must " + "be greater than or equal to 0");
+ Preconditions.checkArgument(zoom >= 0,
+ "Zoom factor number must be " + "greater than or equal to 0");
+ this.mPageNumber = pageNumber;
+ this.mXCoordinate = xCoordinate;
+ this.mYCoordinate = yCoordinate;
+ this.mZoom = zoom;
+ }
+
+ @Override
+ public String toString() {
+ return "GotoLinkDestination{" + "mPageNumber=" + mPageNumber + ", mXCoordinate="
+ + mXCoordinate + ", mYCoordinate=" + mYCoordinate + ", mZoom=" + mZoom + '}';
+ }
+
+ /**
+ * Gets the page number of the destination where the {@link GotoLink} is directing.
+ *
+ * @return page number of the destination where goto link is directing the user.
+ */
+ public int getPageNumber() {
+ return mPageNumber;
+ }
+
+ /**
+ * Gets the x coordinate of the destination where the {@link GotoLink} is directing.
+ * <p><strong>Note:</strong> If underlying pdfium library can't determine the x coordinate,
+ * it will be set to 0
+ *
+ * @return x coordinate of the Destination where the goto link is directing the user.
+ */
+ public float getXCoordinate() {
+ return mXCoordinate;
+ }
+
+ /**
+ * Gets the y coordinate of the destination where the {@link GotoLink} is directing.
+ * <p><strong>Note:</strong> If underlying pdfium library can't determine the y coordinate,
+ * it will be set to 0
+ *
+ * @return y coordinate of the Destination where the goto link is directing the user.
+ */
+ public float getYCoordinate() {
+ return mYCoordinate;
+ }
+
+ /**
+ * Gets the zoom factor of the page when the goto link takes to the destination
+ * <p><strong>Note:</strong> If there is no zoom value embedded, default value of zoom
+ * will be zero. Otherwise it will be less than 1.0f in case of zoom out and greater
+ * than 1.0f in case of zoom in.
+ *
+ * @return zoom factor of the page when the goto link takes to the destination
+ */
+ public float getZoom() {
+ return mZoom;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeInt(mPageNumber);
+ parcel.writeFloat(mXCoordinate);
+ parcel.writeFloat(mYCoordinate);
+ parcel.writeFloat(mZoom);
+ }
+}
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/aidl/LinkRects.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/models/LinkRects.java
similarity index 90%
rename from pdf/pdf-viewer/src/main/java/androidx/pdf/aidl/LinkRects.java
rename to pdf/pdf-viewer/src/main/java/androidx/pdf/models/LinkRects.java
index 3739224..499cb82 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/aidl/LinkRects.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/models/LinkRects.java
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package androidx.pdf.aidl;
+package androidx.pdf.models;
+import android.annotation.SuppressLint;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.pdf.data.ListOfList;
@@ -42,6 +44,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@SuppressWarnings("deprecation")
+@SuppressLint("BanParcelableUsage")
public class LinkRects extends ListOfList<Rect> implements Parcelable {
public static final LinkRects NO_LINKS = new LinkRects(Collections.emptyList(),
Collections.emptyList(), Collections.emptyList());
@@ -65,7 +68,8 @@
private final List<Integer> mLinkToRect;
private final List<String> mUrls;
- public LinkRects(List<Rect> rects, List<Integer> linkToRect, List<String> urls) {
+ public LinkRects(@NonNull List<Rect> rects, @NonNull List<Integer> linkToRect,
+ @NonNull List<String> urls) {
super(rects, linkToRect);
this.mRects = Preconditions.checkNotNull(rects);
this.mLinkToRect = Preconditions.checkNotNull(linkToRect);
@@ -73,6 +77,7 @@
}
/** Return the URL corresponding to the given link. */
+ @NonNull
public String getUrl(int link) {
return mUrls.get(link);
}
@@ -103,7 +108,7 @@
}
@Override
- public void writeToParcel(Parcel parcel, int flags) {
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
parcel.writeList(mRects);
parcel.writeList(mLinkToRect);
parcel.writeList(mUrls);
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/aidl/MatchRects.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/models/MatchRects.java
similarity index 93%
rename from pdf/pdf-viewer/src/main/java/androidx/pdf/aidl/MatchRects.java
rename to pdf/pdf-viewer/src/main/java/androidx/pdf/models/MatchRects.java
index 4ab9bed..98a9695 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/aidl/MatchRects.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/models/MatchRects.java
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package androidx.pdf.aidl;
+package androidx.pdf.models;
+import android.annotation.SuppressLint;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
+import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.pdf.data.ListOfList;
import androidx.pdf.util.Preconditions;
@@ -42,6 +44,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@SuppressWarnings("deprecation")
+@SuppressLint("BanParcelableUsage")
public class MatchRects extends ListOfList<Rect> implements Parcelable {
public static final MatchRects NO_MATCHES = new MatchRects(Collections.emptyList(),
Collections.emptyList(), Collections.emptyList());
@@ -65,7 +68,8 @@
private final List<Integer> mMatchToRect;
private final List<Integer> mCharIndexes;
- public MatchRects(List<Rect> rects, List<Integer> matchToRect, List<Integer> charIndexes) {
+ public MatchRects(@NonNull List<Rect> rects, @NonNull List<Integer> matchToRect,
+ @NonNull List<Integer> charIndexes) {
super(rects, matchToRect);
this.mRects = Preconditions.checkNotNull(rects);
this.mMatchToRect = Preconditions.checkNotNull(matchToRect);
@@ -94,6 +98,7 @@
}
/** Returns the first rect for a given match. */
+ @NonNull
public Rect getFirstRect(int match) {
return mRects.get(mMatchToRect.get(match));
}
@@ -102,6 +107,7 @@
* Returns the flattened, one-dimensional list of all rectangles that surround
* all matches <strong>except</strong> for the given match.
*/
+ @NonNull
public List<Rect> flattenExcludingMatch(int match) {
if (match < 0 || match >= mMatchToRect.size()) {
throw new ArrayIndexOutOfBoundsException(match);
@@ -149,7 +155,7 @@
}
@Override
- public void writeToParcel(Parcel parcel, int flags) {
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
parcel.writeList(mRects);
parcel.writeList(mMatchToRect);
parcel.writeList(mCharIndexes);
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/aidl/PageSelection.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/models/PageSelection.java
similarity index 87%
rename from pdf/pdf-viewer/src/main/java/androidx/pdf/aidl/PageSelection.java
rename to pdf/pdf-viewer/src/main/java/androidx/pdf/models/PageSelection.java
index 794db95..c2460c2 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/aidl/PageSelection.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/models/PageSelection.java
@@ -14,18 +14,19 @@
* limitations under the License.
*/
-package androidx.pdf.aidl;
+package androidx.pdf.models;
import android.graphics.Rect;
import android.os.Parcel;
+import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.pdf.data.TextSelection;
import java.util.List;
-
/** Represents text selection on a particular page of a PDF. Immutable. */
+// TODO: Use android.graphics.pdf.models.selection.PageSelection and remove this class
@RestrictTo(RestrictTo.Scope.LIBRARY)
@SuppressWarnings("deprecation")
public class PageSelection extends TextSelection {
@@ -58,8 +59,8 @@
/** The highlighted text. */
private final String mText;
- public PageSelection(int page, SelectionBoundary start, SelectionBoundary stop,
- List<Rect> rects, String text) {
+ public PageSelection(int page, @NonNull SelectionBoundary start,
+ @NonNull SelectionBoundary stop, @NonNull List<Rect> rects, @NonNull String text) {
super(start, stop);
this.mPage = page;
this.mRects = rects;
@@ -70,10 +71,12 @@
return mPage;
}
+ @NonNull
public List<Rect> getRects() {
return mRects;
}
+ @NonNull
public String getText() {
return mText;
}
@@ -86,7 +89,7 @@
}
@Override
- public void writeToParcel(Parcel parcel, int flags) {
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
parcel.writeInt(mPage);
parcel.writeParcelable(getStart(), 0);
parcel.writeParcelable(getStop(), 0);
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/aidl/SelectionBoundary.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/models/SelectionBoundary.java
similarity index 90%
rename from pdf/pdf-viewer/src/main/java/androidx/pdf/aidl/SelectionBoundary.java
rename to pdf/pdf-viewer/src/main/java/androidx/pdf/models/SelectionBoundary.java
index 14c6157..01294ab 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/aidl/SelectionBoundary.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/models/SelectionBoundary.java
@@ -14,21 +14,24 @@
* limitations under the License.
*/
-package androidx.pdf.aidl;
+package androidx.pdf.models;
+import android.annotation.SuppressLint;
import android.graphics.Point;
import android.os.Parcel;
import android.os.Parcelable;
+import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
-
/**
* Represents one edge of the selected text. A boundary can be defined by
* either an index into the text, a point on the page, or both.
* Should be a nested class of {@link PageSelection}, but AIDL prevents that.
*/
+// TODO: Use android.graphics.pdf.models.selection.SelectionBoundary and remove this class
@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressLint("BanParcelableUsage")
public class SelectionBoundary implements Parcelable {
public static final SelectionBoundary PAGE_START = SelectionBoundary.atIndex(0);
public static final SelectionBoundary PAGE_END = SelectionBoundary.atIndex(Integer.MAX_VALUE);
@@ -80,17 +83,20 @@
}
/** Create a boundary that has a particular index, but the position is not known. */
+ @NonNull
public static SelectionBoundary atIndex(int index) {
return new SelectionBoundary(index, -1, -1, false);
}
/** Create a boundary at a particular point, but the index is not known. */
+ @NonNull
public static SelectionBoundary atPoint(int x, int y) {
return new SelectionBoundary(-1, x, y, false);
}
/** Create a boundary at a particular point, but the index is not known. */
- public static SelectionBoundary atPoint(Point p) {
+ @NonNull
+ public static SelectionBoundary atPoint(@NonNull Point p) {
return new SelectionBoundary(-1, p.x, p.y, false);
}
@@ -101,7 +107,7 @@
}
@Override
- public void writeToParcel(Parcel parcel, int flags) {
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
parcel.writeIntArray(new int[]{mIndex, mX, mY, mIsRtl ? 1 : 0});
}
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/pdflib/LoadPdfResult.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/pdflib/LoadPdfResult.java
new file mode 100644
index 0000000..5ce75a3
--- /dev/null
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/pdflib/LoadPdfResult.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2024 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.pdf.pdflib;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.pdf.data.PdfStatus;
+import androidx.pdf.util.Preconditions;
+
+/**
+ * A struct that holds either a successfully loaded PdfDocument, or the reason why it failed.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class LoadPdfResult {
+
+ private final PdfStatus mStatus;
+ @Nullable
+ private final PdfDocument mPdfDocument;
+
+ public LoadPdfResult(int status, @Nullable PdfDocument pdfDocument) {
+ if (status == PdfStatus.LOADED.getNumber()) {
+ Preconditions.checkArgument(pdfDocument != null, "Missing pdfDocument");
+ } else {
+ Preconditions.checkArgument(pdfDocument == null,
+ "Shouldn't construct " + "broken pdfDocument");
+ }
+ this.mStatus = PdfStatus.values()[status];
+ this.mPdfDocument = pdfDocument;
+ }
+
+ @NonNull
+ public PdfStatus getStatus() {
+ return mStatus;
+ }
+
+ @Nullable
+ public PdfDocument getPdfDocument() {
+ return mPdfDocument;
+ }
+
+ public boolean isLoaded() {
+ return mStatus == PdfStatus.LOADED;
+ }
+}
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/pdflib/PdfDocument.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/pdflib/PdfDocument.java
new file mode 100644
index 0000000..66fef2ba
--- /dev/null
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/pdflib/PdfDocument.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2024 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.pdf.pdflib;
+
+import android.graphics.Bitmap;
+import android.os.ParcelFileDescriptor;
+
+import androidx.annotation.RestrictTo;
+import androidx.pdf.models.Dimensions;
+import androidx.pdf.models.GotoLink;
+import androidx.pdf.models.LinkRects;
+import androidx.pdf.models.MatchRects;
+import androidx.pdf.models.PageSelection;
+import androidx.pdf.models.SelectionBoundary;
+import androidx.pdf.util.StrictModeUtils;
+
+import java.util.List;
+
+// TODO: Delete this class once framework API calls are added
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+class PdfDocument {
+ private static final String LIB_NAME = "pdfclient";
+
+ private final long mPdfDocPtr;
+
+ private final int mNumPages;
+
+ protected PdfDocument(long pdfDocPtr, int numPages) {
+ this.mPdfDocPtr = pdfDocPtr;
+ this.mNumPages = numPages;
+ }
+
+ public static LoadPdfResult createFromFd(int fd, String password) {
+ return null;
+ }
+
+ static void loadLibPdf() {
+ StrictModeUtils.bypass(() -> System.loadLibrary(LIB_NAME));
+ }
+
+ public void destroy() {
+ }
+
+ public boolean saveAs(ParcelFileDescriptor destination) {
+ return false;
+ }
+
+ public int numPages() {
+ return mNumPages;
+ }
+
+ public Dimensions getPageDimensions(int pageNum) {
+ return null;
+ }
+
+ public Bitmap renderPageFd(int pageNum, int pageWidth, int pageHeight, boolean hideTextAnnots) {
+ return null;
+ }
+
+ public Bitmap renderTileFd(int pageNum, int tileWidth, int tileHeight, int scaledPageWidth,
+ int scaledPageHeight, int left, int top, boolean hideTextAnnots) {
+ return null;
+ }
+
+ public boolean cloneWithoutSecurity(ParcelFileDescriptor destination) {
+ return false;
+ }
+
+ public String getPageText(int pageNum) {
+ return null;
+ }
+
+ public List<String> getPageAltText(int pageNum) {
+ return null;
+ }
+
+ public MatchRects searchPageText(int pageNum, String query) {
+ return null;
+ }
+
+ public PageSelection selectPageText(int pageNum, SelectionBoundary start,
+ SelectionBoundary stop) {
+ return null;
+ }
+
+ public LinkRects getPageLinks(int pageNum) {
+ return null;
+ }
+
+ public List<GotoLink> getPageGotoLinks(int pageNum) {
+ return null;
+ }
+
+ public boolean isPdfLinearized() {
+ return false;
+ }
+
+ public int getFormType() {
+ return -1;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("PdfDocument(%x, %d pages)", mPdfDocPtr, mNumPages);
+ }
+}
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/pdflib/PdfDocumentRemoteProto.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/pdflib/PdfDocumentRemoteProto.java
index 31f31f5..2023539 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/pdflib/PdfDocumentRemoteProto.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/pdflib/PdfDocumentRemoteProto.java
@@ -17,7 +17,7 @@
package androidx.pdf.pdflib;
import androidx.annotation.RestrictTo;
-import androidx.pdf.aidl.PdfDocumentRemote;
+import androidx.pdf.models.PdfDocumentRemote;
/**
*
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/pdflib/PdfDocumentService.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/pdflib/PdfDocumentService.java
new file mode 100644
index 0000000..e7f6c28
--- /dev/null
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/pdflib/PdfDocumentService.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2024 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.pdf.pdflib;
+
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.pdf.data.FutureValues;
+import androidx.pdf.models.Dimensions;
+import androidx.pdf.models.GotoLink;
+import androidx.pdf.models.LinkRects;
+import androidx.pdf.models.MatchRects;
+import androidx.pdf.models.PageSelection;
+import androidx.pdf.models.PdfDocumentRemote;
+import androidx.pdf.models.SelectionBoundary;
+
+import java.util.List;
+
+/** Isolated Service wrapper around the PdfClient native lib, for security purposes. */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class PdfDocumentService extends Service {
+
+ private static final String TAG = "PdfDocumentService";
+
+ @NonNull
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new PdfDocumentRemoteImpl();
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ return super.onUnbind(intent);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ }
+
+ private static class PdfDocumentRemoteImpl extends PdfDocumentRemote.Stub {
+
+ private final FutureValues.BlockingCallback<Boolean> mLoaderCallback =
+ new FutureValues.BlockingCallback<>();
+
+ private PdfDocument mPdfDocument;
+
+ PdfDocumentRemoteImpl() {
+ }
+
+ @Override
+ public int create(ParcelFileDescriptor pfd, String password) throws RemoteException {
+ mLoaderCallback.getBlocking();
+ ensurePdfDestroyed();
+ int fd = pfd.detachFd();
+ LoadPdfResult result = PdfDocument.createFromFd(fd, password);
+ if (result.isLoaded()) {
+ mPdfDocument = result.getPdfDocument();
+ }
+ return result.getStatus().getNumber();
+ }
+
+ @Override
+ public int numPages() {
+ mLoaderCallback.getBlocking();
+ return mPdfDocument.numPages();
+ }
+
+ @Override
+ public Dimensions getPageDimensions(int pageNum) {
+ mLoaderCallback.getBlocking();
+ return mPdfDocument.getPageDimensions(pageNum);
+ }
+
+ @Override
+ public String getPageText(int pageNum) {
+ mLoaderCallback.getBlocking();
+ return mPdfDocument.getPageText(pageNum);
+ }
+
+ @Override
+ public List<String> getPageAltText(int pageNum) {
+ mLoaderCallback.getBlocking();
+ return mPdfDocument.getPageAltText(pageNum);
+ }
+
+ @Override
+ public Bitmap renderPage(int pageNum, int pageWidth, int pageHeight,
+ boolean hideTextAnnots) {
+ mLoaderCallback.getBlocking();
+ return mPdfDocument.renderPageFd(pageNum, pageWidth, pageHeight, hideTextAnnots);
+ }
+
+ @Override
+ public Bitmap renderTile(int pageNum, int tileWidth, int tileHeight, int scaledPageWidth,
+ int scaledPageHeight, int left, int top, boolean hideTextAnnots) {
+ mLoaderCallback.getBlocking();
+ return mPdfDocument.renderTileFd(pageNum, tileWidth, tileHeight, scaledPageWidth,
+ scaledPageHeight, left, top, hideTextAnnots);
+ }
+
+ @Override
+ public MatchRects searchPageText(int pageNum, String query) {
+ mLoaderCallback.getBlocking();
+ return mPdfDocument.searchPageText(pageNum, query);
+ }
+
+ @Override
+ public PageSelection selectPageText(int pageNum, SelectionBoundary start,
+ SelectionBoundary stop) {
+ mLoaderCallback.getBlocking();
+ return mPdfDocument.selectPageText(pageNum, start, stop);
+ }
+
+ @Override
+ public LinkRects getPageLinks(int pageNum) {
+ mLoaderCallback.getBlocking();
+ return mPdfDocument.getPageLinks(pageNum);
+ }
+
+ @Override
+ public List<GotoLink> getPageGotoLinks(int pageNum) {
+ mLoaderCallback.getBlocking();
+ return mPdfDocument.getPageGotoLinks(pageNum);
+ }
+
+ @Override
+ public boolean isPdfLinearized() {
+ mLoaderCallback.getBlocking();
+ return mPdfDocument.isPdfLinearized();
+ }
+
+ @Override
+ public int getFormType() {
+ mLoaderCallback.getBlocking();
+ return mPdfDocument.getFormType();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ mLoaderCallback.getBlocking();
+ ensurePdfDestroyed();
+ super.finalize();
+ }
+
+ private void ensurePdfDestroyed() {
+ if (mPdfDocument != null) {
+ try {
+ mPdfDocument.destroy();
+ } catch (Throwable ignored) {
+ }
+ }
+ mPdfDocument = null;
+ }
+
+ @Override
+ public boolean cloneWithoutSecurity(ParcelFileDescriptor destination) {
+ mLoaderCallback.getBlocking();
+ return mPdfDocument.cloneWithoutSecurity(destination);
+ }
+
+ @Override
+ public boolean saveAs(ParcelFileDescriptor destination) {
+ mLoaderCallback.getBlocking();
+ return mPdfDocument.saveAs(destination);
+ }
+ }
+}
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/select/SelectionModel.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/select/SelectionModel.java
index 5f8428b..56a76cc1 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/select/SelectionModel.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/select/SelectionModel.java
@@ -17,7 +17,7 @@
package androidx.pdf.select;
import androidx.annotation.RestrictTo;
-import androidx.pdf.aidl.SelectionBoundary;
+import androidx.pdf.models.SelectionBoundary;
import androidx.pdf.util.ObservableValue;
import androidx.pdf.util.Observables;
import androidx.pdf.util.Observables.ExposedValue;
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/util/BitmapRecycler.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/util/BitmapRecycler.java
index b4c90d1..e412209 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/util/BitmapRecycler.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/util/BitmapRecycler.java
@@ -21,7 +21,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
-import androidx.pdf.aidl.Dimensions;
+import androidx.pdf.models.Dimensions;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/util/RectUtils.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/util/RectUtils.java
index 9574758..7544beb 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/util/RectUtils.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/util/RectUtils.java
@@ -20,7 +20,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
-import androidx.pdf.aidl.Dimensions;
+import androidx.pdf.models.Dimensions;
/**
* Utilities related to {@link Rect}s.
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/util/TileBoard.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/util/TileBoard.java
index e1a0496..455ee35 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/util/TileBoard.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/util/TileBoard.java
@@ -24,7 +24,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
-import androidx.pdf.aidl.Dimensions;
+import androidx.pdf.models.Dimensions;
import java.util.ArrayList;
import java.util.Arrays;
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/AccessibilityPageWrapper.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/AccessibilityPageWrapper.java
index f7cc886..a314022 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/AccessibilityPageWrapper.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/AccessibilityPageWrapper.java
@@ -22,7 +22,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
-import androidx.pdf.aidl.LinkRects;
+import androidx.pdf.models.LinkRects;
/**
* Container to hold a {@link PageMosaicView}, a {@link PageLinksView} and sometimes a {@link
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PageLinksView.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PageLinksView.java
index f74d742..7b95410 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PageLinksView.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PageLinksView.java
@@ -32,7 +32,7 @@
import androidx.core.view.ViewCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.customview.widget.ExploreByTouchHelper;
-import androidx.pdf.aidl.LinkRects;
+import androidx.pdf.models.LinkRects;
import androidx.pdf.util.ObservableValue;
import androidx.pdf.widget.ZoomView;
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PageMosaicView.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PageMosaicView.java
index fe5a196..5fa1106 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PageMosaicView.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PageMosaicView.java
@@ -23,8 +23,8 @@
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
-import androidx.pdf.aidl.Dimensions;
-import androidx.pdf.aidl.LinkRects;
+import androidx.pdf.models.Dimensions;
+import androidx.pdf.models.LinkRects;
import androidx.pdf.util.Accessibility;
import androidx.pdf.util.BitmapRecycler;
import androidx.pdf.widget.MosaicView;
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PageViewFactory.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PageViewFactory.java
index aadf6f4d..801d7a4 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PageViewFactory.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PageViewFactory.java
@@ -21,8 +21,8 @@
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
-import androidx.pdf.aidl.Dimensions;
-import androidx.pdf.aidl.LinkRects;
+import androidx.pdf.models.Dimensions;
+import androidx.pdf.models.LinkRects;
import androidx.pdf.util.Accessibility;
import androidx.pdf.util.BitmapRecycler;
import androidx.pdf.util.ObservableValue;
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PaginationModel.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PaginationModel.java
index ba67ec2..42e3765 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PaginationModel.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PaginationModel.java
@@ -20,8 +20,8 @@
import android.util.Log;
import androidx.annotation.RestrictTo;
-import androidx.pdf.aidl.Dimensions;
import androidx.pdf.data.Range;
+import androidx.pdf.models.Dimensions;
import androidx.pdf.util.ErrorLog;
import androidx.pdf.util.Preconditions;
import androidx.pdf.util.ProjectorContext;
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfHighlightOverlay.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfHighlightOverlay.java
index f7fcfc0..4f33f41 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfHighlightOverlay.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfHighlightOverlay.java
@@ -17,8 +17,8 @@
package androidx.pdf.viewer;
import androidx.annotation.RestrictTo;
-import androidx.pdf.aidl.MatchRects;
-import androidx.pdf.aidl.PageSelection;
+import androidx.pdf.models.MatchRects;
+import androidx.pdf.models.PageSelection;
import androidx.pdf.util.HighlightOverlay;
import androidx.pdf.util.HighlightPaint;
import androidx.pdf.util.RectDrawSpec;
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfSelectionHandles.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfSelectionHandles.java
index e6d93f3..a1757b8 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfSelectionHandles.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfSelectionHandles.java
@@ -22,8 +22,8 @@
import androidx.annotation.RestrictTo;
import androidx.pdf.R;
-import androidx.pdf.aidl.PageSelection;
-import androidx.pdf.aidl.SelectionBoundary;
+import androidx.pdf.models.PageSelection;
+import androidx.pdf.models.SelectionBoundary;
import androidx.pdf.select.SelectionModel;
import androidx.pdf.util.Preconditions;
import androidx.pdf.widget.ZoomView;
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfSelectionModel.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfSelectionModel.java
index c83cbb8c..7846195 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfSelectionModel.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfSelectionModel.java
@@ -17,8 +17,8 @@
package androidx.pdf.viewer;
import androidx.annotation.RestrictTo;
-import androidx.pdf.aidl.PageSelection;
-import androidx.pdf.aidl.SelectionBoundary;
+import androidx.pdf.models.PageSelection;
+import androidx.pdf.models.SelectionBoundary;
import androidx.pdf.select.SelectionModel;
import androidx.pdf.viewer.loader.PdfLoader;
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfViewer.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfViewer.java
index cb320f0..238fa63 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfViewer.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfViewer.java
@@ -37,11 +37,6 @@
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
import androidx.pdf.R;
-import androidx.pdf.aidl.Dimensions;
-import androidx.pdf.aidl.LinkRects;
-import androidx.pdf.aidl.MatchRects;
-import androidx.pdf.aidl.PageSelection;
-import androidx.pdf.aidl.SelectionBoundary;
import androidx.pdf.data.DisplayData;
import androidx.pdf.data.FutureValue;
import androidx.pdf.data.FutureValues.SettableFutureValue;
@@ -49,6 +44,11 @@
import androidx.pdf.data.PdfStatus;
import androidx.pdf.data.Range;
import androidx.pdf.fetcher.Fetcher;
+import androidx.pdf.models.Dimensions;
+import androidx.pdf.models.LinkRects;
+import androidx.pdf.models.MatchRects;
+import androidx.pdf.models.PageSelection;
+import androidx.pdf.models.SelectionBoundary;
import androidx.pdf.util.ErrorLog;
import androidx.pdf.util.ExternalLinks;
import androidx.pdf.util.GestureTracker;
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/SearchModel.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/SearchModel.java
index 4ac0d36..27b92c7 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/SearchModel.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/SearchModel.java
@@ -20,8 +20,8 @@
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
-import androidx.pdf.aidl.MatchRects;
import androidx.pdf.find.MatchCount;
+import androidx.pdf.models.MatchRects;
import androidx.pdf.util.CycleRange;
import androidx.pdf.util.CycleRange.Direction;
import androidx.pdf.util.ObservableValue;
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/SelectedMatch.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/SelectedMatch.java
index 1741885..3639a61 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/SelectedMatch.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/SelectedMatch.java
@@ -20,7 +20,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
-import androidx.pdf.aidl.MatchRects;
+import androidx.pdf.models.MatchRects;
import androidx.pdf.util.CycleRange.Direction;
import androidx.pdf.util.Preconditions;
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/AbstractPdfTask.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/AbstractPdfTask.java
index 184ff87..1f575e0 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/AbstractPdfTask.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/AbstractPdfTask.java
@@ -21,7 +21,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
-import androidx.pdf.aidl.PdfDocumentRemote;
+import androidx.pdf.models.PdfDocumentRemote;
import androidx.pdf.pdflib.PdfDocumentRemoteProto;
import androidx.pdf.util.ErrorLog;
import androidx.pdf.util.ThreadUtils;
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfConnection.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfConnection.java
index bab8271..5735011 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfConnection.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfConnection.java
@@ -25,7 +25,8 @@
import android.util.Log;
import androidx.annotation.RestrictTo;
-import androidx.pdf.aidl.PdfDocumentRemote;
+import androidx.pdf.models.PdfDocumentRemote;
+import androidx.pdf.pdflib.PdfDocumentService;
import androidx.pdf.util.ErrorLog;
import androidx.pdf.util.Preconditions;
@@ -174,12 +175,8 @@
if (mConnected) {
return;
}
- Intent intent = new Intent();
+ Intent intent = new Intent(mContext, PdfDocumentService.class);
// Data is only required here to make sure we start a new service per document.
- // TODO: Update after porting service
- intent.setComponent(new ComponentName(
- /* pkg = */ "com.androidx.pdf",
- /* cls = */ "com.androidx.pdf.PdfDocumentService"));
intent.setData(uri);
Log.d(TAG, "Connecting to service " + uri);
mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfLoader.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfLoader.java
index a37b159..bcf8f0b 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfLoader.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfLoader.java
@@ -26,12 +26,12 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
-import androidx.pdf.aidl.Dimensions;
-import androidx.pdf.aidl.PdfDocumentRemote;
-import androidx.pdf.aidl.SelectionBoundary;
import androidx.pdf.data.DisplayData;
import androidx.pdf.data.Opener;
import androidx.pdf.data.PdfStatus;
+import androidx.pdf.models.Dimensions;
+import androidx.pdf.models.PdfDocumentRemote;
+import androidx.pdf.models.SelectionBoundary;
import androidx.pdf.pdflib.PdfDocumentRemoteProto;
import androidx.pdf.util.BitmapRecycler;
import androidx.pdf.util.ErrorLog;
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfLoaderCallbacks.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfLoaderCallbacks.java
index bd2fba6..7296882 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfLoaderCallbacks.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfLoaderCallbacks.java
@@ -20,11 +20,11 @@
import android.graphics.Rect;
import androidx.annotation.RestrictTo;
-import androidx.pdf.aidl.Dimensions;
-import androidx.pdf.aidl.LinkRects;
-import androidx.pdf.aidl.MatchRects;
-import androidx.pdf.aidl.PageSelection;
import androidx.pdf.data.PdfStatus;
+import androidx.pdf.models.Dimensions;
+import androidx.pdf.models.LinkRects;
+import androidx.pdf.models.MatchRects;
+import androidx.pdf.models.PageSelection;
import androidx.pdf.util.TileBoard.TileInfo;
import java.io.FileOutputStream;
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfPageLoader.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfPageLoader.java
index 6d712d6..b45874c 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfPageLoader.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfPageLoader.java
@@ -19,17 +19,16 @@
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.Point;
-import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
import androidx.annotation.RestrictTo;
import androidx.pdf.R;
-import androidx.pdf.aidl.Dimensions;
-import androidx.pdf.aidl.LinkRects;
-import androidx.pdf.aidl.MatchRects;
-import androidx.pdf.aidl.PageSelection;
-import androidx.pdf.aidl.SelectionBoundary;
+import androidx.pdf.models.Dimensions;
+import androidx.pdf.models.LinkRects;
+import androidx.pdf.models.MatchRects;
+import androidx.pdf.models.PageSelection;
+import androidx.pdf.models.SelectionBoundary;
import androidx.pdf.pdflib.PdfDocumentRemoteProto;
import androidx.pdf.util.BitmapParcel;
import androidx.pdf.util.StrictModeUtils;
@@ -56,16 +55,17 @@
/** Arbitrary dimensions used for pages that are broken. */
private static final Dimensions DEFAULT_PAGE = new Dimensions(400, 400);
+ static {
+ // TODO: StrictMode- disk read 14ms.
+ // NOTE: this line can break when running with --noforge, such as when debugging with
+ // Android Studio. You may need to comment it out locally if you see errors like
+ // `java.lang.UnsatisfiedLinkError: no bitmap_parcel in java.library.path`.
+ StrictModeUtils.bypass(() -> BitmapParcel.loadNdkLib());
+ }
+
private final PdfLoader mParent;
private final int mPageNum;
private final boolean mHideTextAnnotations;
-
- /**
- * This flag is set when this page makes pdfClient crash, and we'd better avoid crashing it
- * again.
- */
- private boolean mIsBroken = false;
-
/** Currently scheduled tasks - null if no task of this type is scheduled. */
GetDimensionsTask mDimensionsTask;
RenderBitmapTask mBitmapTask;
@@ -86,14 +86,11 @@
/** The reference pageWidth for all tile related tasks. */
int mTilePageWidth;
-
- static {
- // TODO: StrictMode- disk read 14ms.
- // NOTE: this line can break when running with --noforge, such as when debugging with
- // Android Studio. You may need to comment it out locally if you see errors like
- // `java.lang.UnsatisfiedLinkError: no bitmap_parcel in java.library.path`.
- StrictModeUtils.bypass(() -> BitmapParcel.loadNdkLib());
- }
+ /**
+ * This flag is set when this page makes pdfClient crash, and we'd better avoid crashing it
+ * again.
+ */
+ private boolean mIsBroken = false;
PdfPageLoader(PdfLoader parent, int pageNum, boolean hideTextAnnotations) {
this.mParent = parent;
@@ -342,23 +339,9 @@
@Override
protected Bitmap doInBackground(PdfDocumentRemoteProto pdfDocument) throws RemoteException {
- Bitmap bitmap = mParent.mBitmapRecycler.obtainBitmap(mDimensions);
- if (bitmap != null) {
- BitmapParcel bitmapParcel = null;
- try {
- bitmapParcel = new BitmapParcel(bitmap);
- ParcelFileDescriptor fd = bitmapParcel.openOutputFd();
- if (fd != null) {
- pdfDocument.getPdfDocumentRemote().renderPage(mPageNum, mDimensions,
- mHideTextAnnotations, fd);
- }
- } finally {
- if (bitmapParcel != null) {
- bitmapParcel.close();
- }
- }
- }
- return bitmap;
+ return pdfDocument.getPdfDocumentRemote().renderPage(mPageNum, mDimensions.getWidth(),
+ mDimensions.getHeight(),
+ mHideTextAnnotations);
}
@Override
@@ -404,33 +387,16 @@
@Override
protected Bitmap doInBackground(PdfDocumentRemoteProto pdfDocument) throws RemoteException {
- Bitmap bitmap = mParent.mBitmapRecycler.obtainBitmap(mTileInfo.getSize());
- if (bitmap != null) {
- Point offset = mTileInfo.getOffset();
-
- BitmapParcel bitmapParcel = null;
- try {
- bitmapParcel = new BitmapParcel(bitmap);
- ParcelFileDescriptor fd = bitmapParcel.openOutputFd();
- if (fd != null) {
- pdfDocument.getPdfDocumentRemote().renderTile(
- mPageNum,
- mPageSize.getWidth(),
- mPageSize.getHeight(),
- offset.x,
- offset.y,
- mTileInfo.getSize(),
- mHideTextAnnotations,
- fd);
- }
- } finally {
- if (bitmapParcel != null) {
- bitmapParcel.close();
- }
- }
-
- }
- return bitmap;
+ Point offset = mTileInfo.getOffset();
+ return pdfDocument.getPdfDocumentRemote().renderTile(
+ mPageNum,
+ mTileInfo.getSize().getWidth(),
+ mTileInfo.getSize().getHeight(),
+ mPageSize.getWidth(),
+ mPageSize.getHeight(),
+ offset.x,
+ offset.y,
+ mHideTextAnnotations);
}
@Override
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/WeakPdfLoaderCallbacks.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/WeakPdfLoaderCallbacks.java
index 89625b7..e271017 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/WeakPdfLoaderCallbacks.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/WeakPdfLoaderCallbacks.java
@@ -22,11 +22,11 @@
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
-import androidx.pdf.aidl.Dimensions;
-import androidx.pdf.aidl.LinkRects;
-import androidx.pdf.aidl.MatchRects;
-import androidx.pdf.aidl.PageSelection;
import androidx.pdf.data.PdfStatus;
+import androidx.pdf.models.Dimensions;
+import androidx.pdf.models.LinkRects;
+import androidx.pdf.models.MatchRects;
+import androidx.pdf.models.PageSelection;
import androidx.pdf.util.TileBoard.TileInfo;
import java.lang.ref.WeakReference;
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/widget/MosaicView.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/widget/MosaicView.java
index 0e4d5e4..1889976 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/widget/MosaicView.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/widget/MosaicView.java
@@ -39,7 +39,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
-import androidx.pdf.aidl.Dimensions;
+import androidx.pdf.models.Dimensions;
import androidx.pdf.util.BitmapRecycler;
import androidx.pdf.util.ErrorLog;
import androidx.pdf.util.Preconditions;
diff --git a/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/aidl/PdfDocumentRemote.aidl b/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/aidl/PdfDocumentRemote.aidl
deleted file mode 100644
index 381fae7..0000000
--- a/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/aidl/PdfDocumentRemote.aidl
+++ /dev/null
@@ -1,42 +0,0 @@
-package androidx.pdf.aidl;
-
-import android.graphics.Rect;
-
-import android.os.ParcelFileDescriptor;
-import androidx.pdf.aidl.Dimensions;
-import androidx.pdf.aidl.MatchRects;
-import androidx.pdf.aidl.PageSelection;
-import androidx.pdf.aidl.SelectionBoundary;
-import androidx.pdf.aidl.LinkRects;
-
-/** Remote interface around a PdfDocument. */
-@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
-interface PdfDocumentRemote {
- int create(in ParcelFileDescriptor pfd, String password);
-
- int numPages();
- Dimensions getPageDimensions(int pageNum);
-
- boolean renderPage(int pageNum, in Dimensions size, boolean hideTextAnnots,
- in ParcelFileDescriptor output);
- boolean renderTile(int pageNum, int pageWidth, int pageHeight, int left, int top,
- in Dimensions tileSize, boolean hideTextAnnots, in ParcelFileDescriptor output);
-
- String getPageText(int pageNum);
- List<String> getPageAltText(int pageNum);
-
- MatchRects searchPageText(int pageNum, String query);
- PageSelection selectPageText(int pageNum, in SelectionBoundary start, in SelectionBoundary stop);
-
- LinkRects getPageLinks(int pageNum);
-
- byte[] getPageGotoLinksByteArray(int pageNum);
-
- boolean isPdfLinearized();
-
- boolean cloneWithoutSecurity(in ParcelFileDescriptor destination);
-
- boolean saveAs(in ParcelFileDescriptor destination);
-
- // The PdfDocument is destroyed when this service is destroyed.
-}
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/aidl/Dimensions.aidl b/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/models/Dimensions.aidl
similarity index 84%
rename from pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/aidl/Dimensions.aidl
rename to pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/models/Dimensions.aidl
index 1ebe1e2..e12ed67 100644
--- a/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/aidl/Dimensions.aidl
+++ b/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/models/Dimensions.aidl
@@ -1,4 +1,4 @@
-package androidx.pdf.aidl;
+package androidx.pdf.models;
@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
@JavaOnlyStableParcelable parcelable Dimensions;
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/models/GotoLink.aidl b/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/models/GotoLink.aidl
new file mode 100644
index 0000000..060bd91
--- /dev/null
+++ b/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/models/GotoLink.aidl
@@ -0,0 +1,4 @@
+package androidx.pdf.models;
+
+@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
+@JavaOnlyStableParcelable parcelable GotoLink;
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/models/GotoLinkDestination.aidl b/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/models/GotoLinkDestination.aidl
new file mode 100644
index 0000000..86c00c7
--- /dev/null
+++ b/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/models/GotoLinkDestination.aidl
@@ -0,0 +1,4 @@
+package androidx.pdf.models;
+
+@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
+@JavaOnlyStableParcelable parcelable GotoLinkDestination;
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/aidl/LinkRects.aidl b/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/models/LinkRects.aidl
similarity index 84%
rename from pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/aidl/LinkRects.aidl
rename to pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/models/LinkRects.aidl
index ebdca9a..a0b6cf1 100644
--- a/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/aidl/LinkRects.aidl
+++ b/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/models/LinkRects.aidl
@@ -1,4 +1,4 @@
-package androidx.pdf.aidl;
+package androidx.pdf.models;
@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
@JavaOnlyStableParcelable parcelable LinkRects;
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/aidl/MatchRects.aidl b/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/models/MatchRects.aidl
similarity index 84%
rename from pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/aidl/MatchRects.aidl
rename to pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/models/MatchRects.aidl
index 243db41..cdc81375 100644
--- a/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/aidl/MatchRects.aidl
+++ b/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/models/MatchRects.aidl
@@ -1,4 +1,4 @@
-package androidx.pdf.aidl;
+package androidx.pdf.models;
@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
@JavaOnlyStableParcelable parcelable MatchRects;
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/aidl/PageSelection.aidl b/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/models/PageSelection.aidl
similarity index 84%
rename from pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/aidl/PageSelection.aidl
rename to pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/models/PageSelection.aidl
index 053a22b..e0e5a55 100644
--- a/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/aidl/PageSelection.aidl
+++ b/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/models/PageSelection.aidl
@@ -1,4 +1,4 @@
-package androidx.pdf.aidl;
+package androidx.pdf.models;
@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
@JavaOnlyStableParcelable parcelable PageSelection;
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/models/PdfDocumentRemote.aidl b/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/models/PdfDocumentRemote.aidl
new file mode 100644
index 0000000..7f09642
--- /dev/null
+++ b/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/models/PdfDocumentRemote.aidl
@@ -0,0 +1,45 @@
+package androidx.pdf.models;
+
+import android.graphics.Rect;
+import android.graphics.Bitmap;
+
+import android.os.ParcelFileDescriptor;
+
+import androidx.pdf.models.Dimensions;
+import androidx.pdf.models.GotoLink;
+import androidx.pdf.models.MatchRects;
+import androidx.pdf.models.PageSelection;
+import androidx.pdf.models.SelectionBoundary;
+import androidx.pdf.models.LinkRects;
+
+/** Remote interface around a PdfDocument. */
+@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
+interface PdfDocumentRemote {
+ int create(in ParcelFileDescriptor pfd, String password);
+
+ int numPages();
+ Dimensions getPageDimensions(int pageNum);
+
+ Bitmap renderPage(int pageNum, int pageWidth, int pageHeight, boolean hideTextAnnots);
+ Bitmap renderTile(int pageNum, int tileWidth, int tileHeight, int scaledPageWidth,
+ int scaledPageHeight, int left, int top, boolean hideTextAnnots);
+
+ String getPageText(int pageNum);
+ List<String> getPageAltText(int pageNum);
+
+ MatchRects searchPageText(int pageNum, String query);
+ PageSelection selectPageText(int pageNum, in SelectionBoundary start, in SelectionBoundary stop);
+
+ LinkRects getPageLinks(int pageNum);
+
+ List<GotoLink> getPageGotoLinks(int pageNum);
+
+ boolean isPdfLinearized();
+ int getFormType();
+
+ boolean cloneWithoutSecurity(in ParcelFileDescriptor destination);
+
+ boolean saveAs(in ParcelFileDescriptor destination);
+
+ // The PdfDocument is destroyed when this service is destroyed.
+}
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/aidl/SelectionBoundary.aidl b/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/models/SelectionBoundary.aidl
similarity index 84%
rename from pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/aidl/SelectionBoundary.aidl
rename to pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/models/SelectionBoundary.aidl
index 2cb0a5b..c8bc815 100644
--- a/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/aidl/SelectionBoundary.aidl
+++ b/pdf/pdf-viewer/src/main/stableAidl/androidx/pdf/models/SelectionBoundary.aidl
@@ -1,4 +1,4 @@
-package androidx.pdf.aidl;
+package androidx.pdf.models;
@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
@JavaOnlyStableParcelable parcelable SelectionBoundary;
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/test/java/androidx/pdf/aidl/LinkRectsTest.java b/pdf/pdf-viewer/src/test/java/androidx/pdf/aidl/LinkRectsTest.java
index 9f93dc8..3c3ebdb 100644
--- a/pdf/pdf-viewer/src/test/java/androidx/pdf/aidl/LinkRectsTest.java
+++ b/pdf/pdf-viewer/src/test/java/androidx/pdf/aidl/LinkRectsTest.java
@@ -16,17 +16,20 @@
package androidx.pdf.aidl;
-
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertTrue;
+
import android.graphics.Rect;
+import androidx.pdf.models.LinkRects;
import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
+import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -54,6 +57,13 @@
}
@Test
+ public void testGetUrl_returnsUrlCorrespondingToLink() {
+ assertThat(mLinkRects.getUrl(0)).isEqualTo("http://first.com");
+ assertThat(mLinkRects.getUrl(1)).isEqualTo("http://second.org");
+ assertThat(mLinkRects.getUrl(2)).isEqualTo("http://third.net");
+ }
+
+ @Test
public void testGetUrlAtPoint() {
assertThat(mLinkRects.getUrlAtPoint(100, 100)).isEqualTo("http://first.com");
assertThat(mLinkRects.getUrlAtPoint(200, 201)).isEqualTo("http://first.com");
@@ -66,6 +76,23 @@
assertThat(mLinkRects.getUrlAtPoint(510, 500)).isNull();
}
+ @Test
+ public void testClassFields_flagsFieldModification() {
+ List<String> fields = new ArrayList<>();
+ fields.add("NO_LINKS");
+ fields.add("CREATOR");
+ fields.add("mRects");
+ fields.add("mLinkToRect");
+ fields.add("mUrls");
+
+ List<String> declaredFields = new ArrayList<>();
+ for (Field field : LinkRects.class.getDeclaredFields()) {
+ declaredFields.add(field.getName());
+ }
+
+ assertTrue(fields.containsAll(declaredFields));
+ }
+
private static LinkRects createLinkRects(int numRects, Integer[] linkToRect, String[] urls) {
List<Rect> rects = new ArrayList<Rect>();
for (int i = 1; i <= numRects; i++) {
diff --git a/pdf/pdf-viewer/src/test/java/androidx/pdf/aidl/MatchRectsTest.java b/pdf/pdf-viewer/src/test/java/androidx/pdf/aidl/MatchRectsTest.java
index fd27016..059e0b2 100644
--- a/pdf/pdf-viewer/src/test/java/androidx/pdf/aidl/MatchRectsTest.java
+++ b/pdf/pdf-viewer/src/test/java/androidx/pdf/aidl/MatchRectsTest.java
@@ -16,17 +16,20 @@
package androidx.pdf.aidl;
-
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertTrue;
+
import android.graphics.Rect;
+import androidx.pdf.models.MatchRects;
import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
+import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -51,6 +54,27 @@
}
@Test
+ public void testGetCharIndex_returnsIndexCorrespondingToMatch() {
+ assertThat(mMatchRects.getCharIndex(0)).isEqualTo(0);
+ assertThat(mMatchRects.getCharIndex(1)).isEqualTo(10);
+ assertThat(mMatchRects.getCharIndex(2)).isEqualTo(20);
+ }
+
+ @Test
+ public void testGetMatchNearestCharIndex_returnsMatchIndexCorrespondingToCharIndex() {
+ assertThat(mMatchRects.getMatchNearestCharIndex(0)).isEqualTo(0);
+ assertThat(mMatchRects.getMatchNearestCharIndex(10)).isEqualTo(1);
+ assertThat(mMatchRects.getMatchNearestCharIndex(20)).isEqualTo(2);
+ }
+
+ @Test
+ public void testGetFirstRect_returnsFirstRectForMatch() {
+ assertThat(mMatchRects.getFirstRect(0)).isEqualTo(new Rect(0, 0, 0, 0));
+ assertThat(mMatchRects.getFirstRect(1)).isEqualTo(new Rect(200, 200, 202, 202));
+ assertThat(mMatchRects.getFirstRect(2)).isEqualTo(new Rect(300, 300, 303, 303));
+ }
+
+ @Test
public void testFlatten() {
List<Rect> rects = mMatchRects.flatten();
assertThat(rects.size()).isEqualTo(5);
@@ -60,6 +84,31 @@
assertThat(mMatchRects.flattenExcludingMatch(1)).isEqualTo(rectsExcludingMatchOne);
}
+ @Test
+ public void testRectsExcludingMatchOne_returnsFlatListOfRectsForAllMatchesExceptGivenMatch() {
+ List<Rect> rects = mMatchRects.flatten();
+ List<Rect> rectsExcludingMatchOne = Arrays.asList(rects.get(0), rects.get(1), rects.get(3),
+ rects.get(4));
+ assertThat(mMatchRects.flattenExcludingMatch(1)).isEqualTo(rectsExcludingMatchOne);
+ }
+
+ @Test
+ public void testClassFields_flagsFieldModification() {
+ List<String> fields = new ArrayList<>();
+ fields.add("NO_MATCHES");
+ fields.add("CREATOR");
+ fields.add("mRects");
+ fields.add("mMatchToRect");
+ fields.add("mCharIndexes");
+
+ List<String> declaredFields = new ArrayList<>();
+ for (Field field : MatchRects.class.getDeclaredFields()) {
+ declaredFields.add(field.getName());
+ }
+
+ assertTrue(fields.containsAll(declaredFields));
+ }
+
private static MatchRects createMatchRects(int numRects, Integer... matchToRect) {
List<Rect> rects = new ArrayList<>();
List<Integer> charIndexes = new ArrayList<>();
diff --git a/pdf/pdf-viewer/src/test/java/androidx/pdf/aidl/SelectionBoundaryTest.java b/pdf/pdf-viewer/src/test/java/androidx/pdf/aidl/SelectionBoundaryTest.java
new file mode 100644
index 0000000..7ac3ddc
--- /dev/null
+++ b/pdf/pdf-viewer/src/test/java/androidx/pdf/aidl/SelectionBoundaryTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2024 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.pdf.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Point;
+
+import androidx.pdf.models.SelectionBoundary;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(RobolectricTestRunner.class)
+public class SelectionBoundaryTest {
+
+ @Test
+ public void testAtIndex_selectionBoundaryCreatedWithCorrectValues() {
+ assertThat(SelectionBoundary.atIndex(4)).isEqualTo(new SelectionBoundary(4, -1, -1, false));
+ }
+ @Test
+ public void testAtPoint_selectionBoundaryCreatedWithCorrectValues() {
+ assertThat(SelectionBoundary.atPoint(new Point(3, 4))).isEqualTo(
+ new SelectionBoundary(-1, 3, 4, false));
+ }
+
+ @Test
+ public void testAtPoint_pointContainsXAndY_selectionBoundaryCreatedWithCorrectValues() {
+ assertThat(SelectionBoundary.atPoint(1, 2)).isEqualTo(
+ new SelectionBoundary(-1, 1, 2, false));
+ }
+ @Test
+ public void testClassFields() {
+ List<String> fields = new ArrayList<>();
+ fields.add("PAGE_START");
+ fields.add("PAGE_END");
+ fields.add("CREATOR");
+ fields.add("mIndex");
+ fields.add("mX");
+ fields.add("mY");
+ fields.add("mIsRtl");
+
+ List<String> declaredFields = new ArrayList<>();
+ for (Field field : SelectionBoundary.class.getDeclaredFields()) {
+ declaredFields.add(field.getName());
+ }
+
+ assertTrue(fields.containsAll(declaredFields));
+ }
+}
diff --git a/pdf/pdf-viewer/src/test/java/androidx/pdf/data/ContentOpenableTest.java b/pdf/pdf-viewer/src/test/java/androidx/pdf/data/ContentOpenableTest.java
index 99a7cfe..bd49042 100644
--- a/pdf/pdf-viewer/src/test/java/androidx/pdf/data/ContentOpenableTest.java
+++ b/pdf/pdf-viewer/src/test/java/androidx/pdf/data/ContentOpenableTest.java
@@ -21,7 +21,7 @@
import android.net.Uri;
import android.os.Parcel;
-import androidx.pdf.aidl.Dimensions;
+import androidx.pdf.models.Dimensions;
import androidx.test.filters.SmallTest;
import org.junit.Test;
diff --git a/pdf/pdf-viewer/src/test/java/androidx/pdf/util/TileBoardTest.java b/pdf/pdf-viewer/src/test/java/androidx/pdf/util/TileBoardTest.java
index 8ff6447..61e39e0 100644
--- a/pdf/pdf-viewer/src/test/java/androidx/pdf/util/TileBoardTest.java
+++ b/pdf/pdf-viewer/src/test/java/androidx/pdf/util/TileBoardTest.java
@@ -27,7 +27,7 @@
import android.graphics.Point;
import android.graphics.Rect;
-import androidx.pdf.aidl.Dimensions;
+import androidx.pdf.models.Dimensions;
import androidx.pdf.util.TileBoard.CancelTilesCallback;
import androidx.pdf.util.TileBoard.TileInfo;
import androidx.pdf.util.TileBoard.ViewAreaUpdateCallback;
diff --git a/pdf/pdf-viewer/src/test/java/androidx/pdf/viewer/PaginatedViewTest.java b/pdf/pdf-viewer/src/test/java/androidx/pdf/viewer/PaginatedViewTest.java
index 3f01e68..53f5c10 100644
--- a/pdf/pdf-viewer/src/test/java/androidx/pdf/viewer/PaginatedViewTest.java
+++ b/pdf/pdf-viewer/src/test/java/androidx/pdf/viewer/PaginatedViewTest.java
@@ -20,7 +20,7 @@
import android.content.Context;
-import androidx.pdf.aidl.Dimensions;
+import androidx.pdf.models.Dimensions;
import androidx.pdf.util.BitmapRecycler;
import androidx.pdf.util.ProjectorContext;
import androidx.pdf.viewer.PageViewFactory.PageView;
diff --git a/pdf/pdf-viewer/src/test/java/androidx/pdf/viewer/PaginationModelTest.java b/pdf/pdf-viewer/src/test/java/androidx/pdf/viewer/PaginationModelTest.java
index 8997889..4303ef7 100644
--- a/pdf/pdf-viewer/src/test/java/androidx/pdf/viewer/PaginationModelTest.java
+++ b/pdf/pdf-viewer/src/test/java/androidx/pdf/viewer/PaginationModelTest.java
@@ -22,8 +22,8 @@
import android.content.Context;
import android.graphics.Rect;
-import androidx.pdf.aidl.Dimensions;
import androidx.pdf.data.Range;
+import androidx.pdf.models.Dimensions;
import androidx.pdf.util.ProjectorContext;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
diff --git a/pdf/pdf-viewer/src/test/java/androidx/pdf/viewer/SearchModelTest.java b/pdf/pdf-viewer/src/test/java/androidx/pdf/viewer/SearchModelTest.java
index 5242d1a..293eeb4 100644
--- a/pdf/pdf-viewer/src/test/java/androidx/pdf/viewer/SearchModelTest.java
+++ b/pdf/pdf-viewer/src/test/java/androidx/pdf/viewer/SearchModelTest.java
@@ -22,8 +22,8 @@
import android.graphics.Rect;
-import androidx.pdf.aidl.MatchRects;
import androidx.pdf.find.MatchCount;
+import androidx.pdf.models.MatchRects;
import androidx.pdf.viewer.loader.PdfLoader;
import androidx.test.filters.SmallTest;
diff --git a/pdf/pdf-viewer/src/test/java/androidx/pdf/viewer/SelectedMatchTest.java b/pdf/pdf-viewer/src/test/java/androidx/pdf/viewer/SelectedMatchTest.java
index 142c74d..6d37daf 100644
--- a/pdf/pdf-viewer/src/test/java/androidx/pdf/viewer/SelectedMatchTest.java
+++ b/pdf/pdf-viewer/src/test/java/androidx/pdf/viewer/SelectedMatchTest.java
@@ -23,7 +23,7 @@
import android.graphics.Rect;
-import androidx.pdf.aidl.MatchRects;
+import androidx.pdf.models.MatchRects;
import androidx.test.filters.SmallTest;
import org.junit.Test;
diff --git a/pdf/pdf-viewer/src/test/java/androidx/pdf/widget/MosaicViewTest.java b/pdf/pdf-viewer/src/test/java/androidx/pdf/widget/MosaicViewTest.java
index e872f88..8fe7eba 100644
--- a/pdf/pdf-viewer/src/test/java/androidx/pdf/widget/MosaicViewTest.java
+++ b/pdf/pdf-viewer/src/test/java/androidx/pdf/widget/MosaicViewTest.java
@@ -38,7 +38,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.pdf.aidl.Dimensions;
+import androidx.pdf.models.Dimensions;
import androidx.pdf.util.BitmapRecycler;
import androidx.pdf.util.TileBoard;
import androidx.pdf.util.TileBoard.TileInfo;
diff --git a/playground-common/playground-plugin/build.gradle b/playground-common/playground-plugin/build.gradle
index cc6735b..c5b8bb8 100644
--- a/playground-common/playground-plugin/build.gradle
+++ b/playground-common/playground-plugin/build.gradle
@@ -21,8 +21,8 @@
dependencies {
implementation(project(":shared"))
- implementation("com.gradle:gradle-enterprise-gradle-plugin:3.16")
- implementation("com.gradle:common-custom-user-data-gradle-plugin:1.12")
+ implementation("com.gradle:develocity-gradle-plugin:3.17.2")
+ implementation("com.gradle:common-custom-user-data-gradle-plugin:2.0.1")
implementation("supportBuildSrc:private")
implementation("supportBuildSrc:public")
implementation("supportBuildSrc:plugins")
@@ -37,8 +37,8 @@
implementationClass = "androidx.playground.PlaygroundPlugin"
}
gradleEnterpriseConventions {
- id = "playground-ge-conventions"
- implementationClass = "androidx.playground.GradleEnterpriseConventionsPlugin"
+ id = "playground-develocity-conventions"
+ implementationClass = "androidx.playground.GradleDevelocityConventionsPlugin"
}
}
}
diff --git a/playground-common/playground-plugin/settings.gradle b/playground-common/playground-plugin/settings.gradle
index 1c6802c..9680ada 100644
--- a/playground-common/playground-plugin/settings.gradle
+++ b/playground-common/playground-plugin/settings.gradle
@@ -29,7 +29,7 @@
mavenCentral()
google()
gradlePluginPortal().content {
- it.includeModule("com.gradle", "gradle-enterprise-gradle-plugin")
+ it.includeModule("com.gradle", "develocity-gradle-plugin")
it.includeModule("com.gradle", "common-custom-user-data-gradle-plugin")
it.includeModule("org.spdx", "spdx-gradle-plugin")
it.includeModule("com.github.johnrengelman.shadow",
diff --git a/playground-common/playground-plugin/src/main/kotlin/androidx/playground/GradleEnterpriseConventionsPlugin.kt b/playground-common/playground-plugin/src/main/kotlin/androidx/playground/GradleDevelocityConventionsPlugin.kt
similarity index 82%
rename from playground-common/playground-plugin/src/main/kotlin/androidx/playground/GradleEnterpriseConventionsPlugin.kt
rename to playground-common/playground-plugin/src/main/kotlin/androidx/playground/GradleDevelocityConventionsPlugin.kt
index aa5351f..08b00ca 100644
--- a/playground-common/playground-plugin/src/main/kotlin/androidx/playground/GradleEnterpriseConventionsPlugin.kt
+++ b/playground-common/playground-plugin/src/main/kotlin/androidx/playground/GradleDevelocityConventionsPlugin.kt
@@ -16,34 +16,32 @@
package androidx.playground
-import com.gradle.enterprise.gradleplugin.internal.extension.BuildScanExtensionWithHiddenFeatures
-import org.gradle.api.Plugin
-import org.gradle.api.initialization.Settings
-import org.gradle.caching.http.HttpBuildCache
-import org.gradle.kotlin.dsl.gradleEnterprise
import java.net.InetAddress
import java.net.URI
import java.util.function.Function
+import org.gradle.api.Plugin
+import org.gradle.api.initialization.Settings
+import org.gradle.caching.http.HttpBuildCache
+import org.gradle.kotlin.dsl.develocity
-class GradleEnterpriseConventionsPlugin : Plugin<Settings> {
+class GradleDevelocityConventionsPlugin : Plugin<Settings> {
override fun apply(settings: Settings) {
- settings.apply(mapOf("plugin" to "com.gradle.enterprise"))
+ settings.apply(mapOf("plugin" to "com.gradle.develocity"))
settings.apply(mapOf("plugin" to "com.gradle.common-custom-user-data-gradle-plugin"))
// Github Actions always sets a "CI" environment variable
val isCI = System.getenv("CI") != null
- settings.gradleEnterprise {
- server = "https://ge.androidx.dev"
-
+ settings.develocity {
+ server.set("https://ge.androidx.dev")
buildScan.apply {
- publishAlways()
- (this as BuildScanExtensionWithHiddenFeatures).publishIfAuthenticated()
- isUploadInBackground = !isCI
- capture.isTaskInputFiles = true
-
+ uploadInBackground.set(!isCI)
+ capture.fileFingerprints.set(true)
obfuscation.hostname(HostnameHider())
obfuscation.ipAddresses(IpAddressHider())
+ publishing.onlyIf {
+ it.isAuthenticated
+ }
}
}
@@ -88,4 +86,4 @@
return listOf("0.0.0.0")
}
}
-}
\ No newline at end of file
+}
diff --git a/playground-common/playground-plugin/src/main/kotlin/androidx/playground/PlaygroundPlugin.kt b/playground-common/playground-plugin/src/main/kotlin/androidx/playground/PlaygroundPlugin.kt
index b6960f9..22c1f44 100644
--- a/playground-common/playground-plugin/src/main/kotlin/androidx/playground/PlaygroundPlugin.kt
+++ b/playground-common/playground-plugin/src/main/kotlin/androidx/playground/PlaygroundPlugin.kt
@@ -21,7 +21,7 @@
class PlaygroundPlugin : Plugin<Settings> {
override fun apply(settings: Settings) {
- settings.apply(mapOf("plugin" to "playground-ge-conventions"))
+ settings.apply(mapOf("plugin" to "playground-develocity-conventions"))
settings.extensions.create("playground", PlaygroundExtension::class.java, settings)
validateJvm(settings)
}
diff --git a/preference/preference-ktx/build.gradle b/preference/preference-ktx/build.gradle
index eaca3dc..b76c3a6 100644
--- a/preference/preference-ktx/build.gradle
+++ b/preference/preference-ktx/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -51,7 +51,7 @@
androidx {
name = "Preferences KTX"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2018"
description = "Kotlin extensions for preferences"
metalavaK2UastEnabled = true
diff --git a/privacysandbox/ads/ads-adservices-java/api/1.1.0-beta07.txt b/privacysandbox/ads/ads-adservices-java/api/1.1.0-beta07.txt
new file mode 100644
index 0000000..232abf5
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices-java/api/1.1.0-beta07.txt
@@ -0,0 +1,99 @@
+// Signature format: 4.0
+package androidx.privacysandbox.ads.adservices.java.adid {
+
+ public abstract class AdIdManagerFutures {
+ method public static final androidx.privacysandbox.ads.adservices.java.adid.AdIdManagerFutures? from(android.content.Context context);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_AD_ID) public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adid.AdId> getAdIdAsync();
+ field public static final androidx.privacysandbox.ads.adservices.java.adid.AdIdManagerFutures.Companion Companion;
+ }
+
+ public static final class AdIdManagerFutures.Companion {
+ method public androidx.privacysandbox.ads.adservices.java.adid.AdIdManagerFutures? from(android.content.Context context);
+ }
+
+}
+
+package androidx.privacysandbox.ads.adservices.java.adselection {
+
+ public abstract class AdSelectionManagerFutures {
+ method public static final androidx.privacysandbox.ads.adservices.java.adselection.AdSelectionManagerFutures? from(android.content.Context context);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataOutcome> getAdSelectionDataAsync(androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataRequest getAdSelectionDataRequest);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome> persistAdSelectionResultAsync(androidx.privacysandbox.ads.adservices.adselection.PersistAdSelectionResultRequest persistAdSelectionResultRequest);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> reportEventAsync(androidx.privacysandbox.ads.adservices.adselection.ReportEventRequest reportEventRequest);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> reportImpressionAsync(androidx.privacysandbox.ads.adservices.adselection.ReportImpressionRequest reportImpressionRequest);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome> selectAdsAsync(androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig adSelectionConfig);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome> selectAdsAsync(androidx.privacysandbox.ads.adservices.adselection.AdSelectionFromOutcomesConfig adSelectionFromOutcomesConfig);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> updateAdCounterHistogramAsync(androidx.privacysandbox.ads.adservices.adselection.UpdateAdCounterHistogramRequest updateAdCounterHistogramRequest);
+ field public static final androidx.privacysandbox.ads.adservices.java.adselection.AdSelectionManagerFutures.Companion Companion;
+ }
+
+ public static final class AdSelectionManagerFutures.Companion {
+ method public androidx.privacysandbox.ads.adservices.java.adselection.AdSelectionManagerFutures? from(android.content.Context context);
+ }
+
+}
+
+package androidx.privacysandbox.ads.adservices.java.appsetid {
+
+ public abstract class AppSetIdManagerFutures {
+ method public static final androidx.privacysandbox.ads.adservices.java.appsetid.AppSetIdManagerFutures? from(android.content.Context context);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.appsetid.AppSetId> getAppSetIdAsync();
+ field public static final androidx.privacysandbox.ads.adservices.java.appsetid.AppSetIdManagerFutures.Companion Companion;
+ }
+
+ public static final class AppSetIdManagerFutures.Companion {
+ method public androidx.privacysandbox.ads.adservices.java.appsetid.AppSetIdManagerFutures? from(android.content.Context context);
+ }
+
+}
+
+package androidx.privacysandbox.ads.adservices.java.customaudience {
+
+ public abstract class CustomAudienceManagerFutures {
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> fetchAndJoinCustomAudienceAsync(androidx.privacysandbox.ads.adservices.customaudience.FetchAndJoinCustomAudienceRequest request);
+ method public static final androidx.privacysandbox.ads.adservices.java.customaudience.CustomAudienceManagerFutures? from(android.content.Context context);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> joinCustomAudienceAsync(androidx.privacysandbox.ads.adservices.customaudience.JoinCustomAudienceRequest request);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> leaveCustomAudienceAsync(androidx.privacysandbox.ads.adservices.customaudience.LeaveCustomAudienceRequest request);
+ field public static final androidx.privacysandbox.ads.adservices.java.customaudience.CustomAudienceManagerFutures.Companion Companion;
+ }
+
+ public static final class CustomAudienceManagerFutures.Companion {
+ method public androidx.privacysandbox.ads.adservices.java.customaudience.CustomAudienceManagerFutures? from(android.content.Context context);
+ }
+
+}
+
+package androidx.privacysandbox.ads.adservices.java.measurement {
+
+ public abstract class MeasurementManagerFutures {
+ method public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> deleteRegistrationsAsync(androidx.privacysandbox.ads.adservices.measurement.DeletionRequest deletionRequest);
+ method public static final androidx.privacysandbox.ads.adservices.java.measurement.MeasurementManagerFutures? from(android.content.Context context);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract com.google.common.util.concurrent.ListenableFuture<java.lang.Integer> getMeasurementApiStatusAsync();
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> registerSourceAsync(android.net.Uri attributionSource, android.view.InputEvent? inputEvent);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.RegisterSourceOptIn public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> registerSourceAsync(androidx.privacysandbox.ads.adservices.measurement.SourceRegistrationRequest request);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> registerTriggerAsync(android.net.Uri trigger);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> registerWebSourceAsync(androidx.privacysandbox.ads.adservices.measurement.WebSourceRegistrationRequest request);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> registerWebTriggerAsync(androidx.privacysandbox.ads.adservices.measurement.WebTriggerRegistrationRequest request);
+ field public static final androidx.privacysandbox.ads.adservices.java.measurement.MeasurementManagerFutures.Companion Companion;
+ }
+
+ public static final class MeasurementManagerFutures.Companion {
+ method public androidx.privacysandbox.ads.adservices.java.measurement.MeasurementManagerFutures? from(android.content.Context context);
+ }
+
+}
+
+package androidx.privacysandbox.ads.adservices.java.topics {
+
+ public abstract class TopicsManagerFutures {
+ method public static final androidx.privacysandbox.ads.adservices.java.topics.TopicsManagerFutures? from(android.content.Context context);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_TOPICS) public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.topics.GetTopicsResponse> getTopicsAsync(androidx.privacysandbox.ads.adservices.topics.GetTopicsRequest request);
+ field public static final androidx.privacysandbox.ads.adservices.java.topics.TopicsManagerFutures.Companion Companion;
+ }
+
+ public static final class TopicsManagerFutures.Companion {
+ method public androidx.privacysandbox.ads.adservices.java.topics.TopicsManagerFutures? from(android.content.Context context);
+ }
+
+}
+
diff --git a/privacysandbox/ads/ads-adservices-java/api/res-1.1.0-beta07.txt b/privacysandbox/ads/ads-adservices-java/api/res-1.1.0-beta07.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices-java/api/res-1.1.0-beta07.txt
diff --git a/privacysandbox/ads/ads-adservices-java/api/restricted_1.1.0-beta07.txt b/privacysandbox/ads/ads-adservices-java/api/restricted_1.1.0-beta07.txt
new file mode 100644
index 0000000..232abf5
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices-java/api/restricted_1.1.0-beta07.txt
@@ -0,0 +1,99 @@
+// Signature format: 4.0
+package androidx.privacysandbox.ads.adservices.java.adid {
+
+ public abstract class AdIdManagerFutures {
+ method public static final androidx.privacysandbox.ads.adservices.java.adid.AdIdManagerFutures? from(android.content.Context context);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_AD_ID) public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adid.AdId> getAdIdAsync();
+ field public static final androidx.privacysandbox.ads.adservices.java.adid.AdIdManagerFutures.Companion Companion;
+ }
+
+ public static final class AdIdManagerFutures.Companion {
+ method public androidx.privacysandbox.ads.adservices.java.adid.AdIdManagerFutures? from(android.content.Context context);
+ }
+
+}
+
+package androidx.privacysandbox.ads.adservices.java.adselection {
+
+ public abstract class AdSelectionManagerFutures {
+ method public static final androidx.privacysandbox.ads.adservices.java.adselection.AdSelectionManagerFutures? from(android.content.Context context);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataOutcome> getAdSelectionDataAsync(androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataRequest getAdSelectionDataRequest);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome> persistAdSelectionResultAsync(androidx.privacysandbox.ads.adservices.adselection.PersistAdSelectionResultRequest persistAdSelectionResultRequest);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> reportEventAsync(androidx.privacysandbox.ads.adservices.adselection.ReportEventRequest reportEventRequest);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> reportImpressionAsync(androidx.privacysandbox.ads.adservices.adselection.ReportImpressionRequest reportImpressionRequest);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome> selectAdsAsync(androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig adSelectionConfig);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome> selectAdsAsync(androidx.privacysandbox.ads.adservices.adselection.AdSelectionFromOutcomesConfig adSelectionFromOutcomesConfig);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> updateAdCounterHistogramAsync(androidx.privacysandbox.ads.adservices.adselection.UpdateAdCounterHistogramRequest updateAdCounterHistogramRequest);
+ field public static final androidx.privacysandbox.ads.adservices.java.adselection.AdSelectionManagerFutures.Companion Companion;
+ }
+
+ public static final class AdSelectionManagerFutures.Companion {
+ method public androidx.privacysandbox.ads.adservices.java.adselection.AdSelectionManagerFutures? from(android.content.Context context);
+ }
+
+}
+
+package androidx.privacysandbox.ads.adservices.java.appsetid {
+
+ public abstract class AppSetIdManagerFutures {
+ method public static final androidx.privacysandbox.ads.adservices.java.appsetid.AppSetIdManagerFutures? from(android.content.Context context);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.appsetid.AppSetId> getAppSetIdAsync();
+ field public static final androidx.privacysandbox.ads.adservices.java.appsetid.AppSetIdManagerFutures.Companion Companion;
+ }
+
+ public static final class AppSetIdManagerFutures.Companion {
+ method public androidx.privacysandbox.ads.adservices.java.appsetid.AppSetIdManagerFutures? from(android.content.Context context);
+ }
+
+}
+
+package androidx.privacysandbox.ads.adservices.java.customaudience {
+
+ public abstract class CustomAudienceManagerFutures {
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> fetchAndJoinCustomAudienceAsync(androidx.privacysandbox.ads.adservices.customaudience.FetchAndJoinCustomAudienceRequest request);
+ method public static final androidx.privacysandbox.ads.adservices.java.customaudience.CustomAudienceManagerFutures? from(android.content.Context context);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> joinCustomAudienceAsync(androidx.privacysandbox.ads.adservices.customaudience.JoinCustomAudienceRequest request);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> leaveCustomAudienceAsync(androidx.privacysandbox.ads.adservices.customaudience.LeaveCustomAudienceRequest request);
+ field public static final androidx.privacysandbox.ads.adservices.java.customaudience.CustomAudienceManagerFutures.Companion Companion;
+ }
+
+ public static final class CustomAudienceManagerFutures.Companion {
+ method public androidx.privacysandbox.ads.adservices.java.customaudience.CustomAudienceManagerFutures? from(android.content.Context context);
+ }
+
+}
+
+package androidx.privacysandbox.ads.adservices.java.measurement {
+
+ public abstract class MeasurementManagerFutures {
+ method public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> deleteRegistrationsAsync(androidx.privacysandbox.ads.adservices.measurement.DeletionRequest deletionRequest);
+ method public static final androidx.privacysandbox.ads.adservices.java.measurement.MeasurementManagerFutures? from(android.content.Context context);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract com.google.common.util.concurrent.ListenableFuture<java.lang.Integer> getMeasurementApiStatusAsync();
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> registerSourceAsync(android.net.Uri attributionSource, android.view.InputEvent? inputEvent);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.RegisterSourceOptIn public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> registerSourceAsync(androidx.privacysandbox.ads.adservices.measurement.SourceRegistrationRequest request);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> registerTriggerAsync(android.net.Uri trigger);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> registerWebSourceAsync(androidx.privacysandbox.ads.adservices.measurement.WebSourceRegistrationRequest request);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> registerWebTriggerAsync(androidx.privacysandbox.ads.adservices.measurement.WebTriggerRegistrationRequest request);
+ field public static final androidx.privacysandbox.ads.adservices.java.measurement.MeasurementManagerFutures.Companion Companion;
+ }
+
+ public static final class MeasurementManagerFutures.Companion {
+ method public androidx.privacysandbox.ads.adservices.java.measurement.MeasurementManagerFutures? from(android.content.Context context);
+ }
+
+}
+
+package androidx.privacysandbox.ads.adservices.java.topics {
+
+ public abstract class TopicsManagerFutures {
+ method public static final androidx.privacysandbox.ads.adservices.java.topics.TopicsManagerFutures? from(android.content.Context context);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_TOPICS) public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.topics.GetTopicsResponse> getTopicsAsync(androidx.privacysandbox.ads.adservices.topics.GetTopicsRequest request);
+ field public static final androidx.privacysandbox.ads.adservices.java.topics.TopicsManagerFutures.Companion Companion;
+ }
+
+ public static final class TopicsManagerFutures.Companion {
+ method public androidx.privacysandbox.ads.adservices.java.topics.TopicsManagerFutures? from(android.content.Context context);
+ }
+
+}
+
diff --git a/privacysandbox/ads/ads-adservices/api/1.1.0-beta07.txt b/privacysandbox/ads/ads-adservices/api/1.1.0-beta07.txt
new file mode 100644
index 0000000..7574be5
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/api/1.1.0-beta07.txt
@@ -0,0 +1,530 @@
+// Signature format: 4.0
+package androidx.privacysandbox.ads.adservices.adid {
+
+ public final class AdId {
+ method public String getAdId();
+ method public boolean isLimitAdTrackingEnabled();
+ property public final String adId;
+ property public final boolean isLimitAdTrackingEnabled;
+ }
+
+ public abstract class AdIdManager {
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_AD_ID) public abstract suspend Object? getAdId(kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adid.AdId>);
+ method public static final androidx.privacysandbox.ads.adservices.adid.AdIdManager? obtain(android.content.Context context);
+ field public static final androidx.privacysandbox.ads.adservices.adid.AdIdManager.Companion Companion;
+ }
+
+ public static final class AdIdManager.Companion {
+ method public androidx.privacysandbox.ads.adservices.adid.AdIdManager? obtain(android.content.Context context);
+ }
+
+}
+
+package androidx.privacysandbox.ads.adservices.adselection {
+
+ public final class AdSelectionConfig {
+ ctor public AdSelectionConfig(androidx.privacysandbox.ads.adservices.common.AdTechIdentifier seller, android.net.Uri decisionLogicUri, java.util.List<androidx.privacysandbox.ads.adservices.common.AdTechIdentifier> customAudienceBuyers, androidx.privacysandbox.ads.adservices.common.AdSelectionSignals adSelectionSignals, androidx.privacysandbox.ads.adservices.common.AdSelectionSignals sellerSignals, java.util.Map<androidx.privacysandbox.ads.adservices.common.AdTechIdentifier,androidx.privacysandbox.ads.adservices.common.AdSelectionSignals> perBuyerSignals, android.net.Uri trustedScoringSignalsUri);
+ method public androidx.privacysandbox.ads.adservices.common.AdSelectionSignals getAdSelectionSignals();
+ method public java.util.List<androidx.privacysandbox.ads.adservices.common.AdTechIdentifier> getCustomAudienceBuyers();
+ method public android.net.Uri getDecisionLogicUri();
+ method public java.util.Map<androidx.privacysandbox.ads.adservices.common.AdTechIdentifier,androidx.privacysandbox.ads.adservices.common.AdSelectionSignals> getPerBuyerSignals();
+ method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier getSeller();
+ method public androidx.privacysandbox.ads.adservices.common.AdSelectionSignals getSellerSignals();
+ method public android.net.Uri getTrustedScoringSignalsUri();
+ property public final androidx.privacysandbox.ads.adservices.common.AdSelectionSignals adSelectionSignals;
+ property public final java.util.List<androidx.privacysandbox.ads.adservices.common.AdTechIdentifier> customAudienceBuyers;
+ property public final android.net.Uri decisionLogicUri;
+ property public final java.util.Map<androidx.privacysandbox.ads.adservices.common.AdTechIdentifier,androidx.privacysandbox.ads.adservices.common.AdSelectionSignals> perBuyerSignals;
+ property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier seller;
+ property public final androidx.privacysandbox.ads.adservices.common.AdSelectionSignals sellerSignals;
+ property public final android.net.Uri trustedScoringSignalsUri;
+ }
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class AdSelectionFromOutcomesConfig {
+ ctor public AdSelectionFromOutcomesConfig(androidx.privacysandbox.ads.adservices.common.AdTechIdentifier seller, java.util.List<java.lang.Long> adSelectionIds, androidx.privacysandbox.ads.adservices.common.AdSelectionSignals adSelectionSignals, android.net.Uri selectionLogicUri);
+ method public java.util.List<java.lang.Long> getAdSelectionIds();
+ method public androidx.privacysandbox.ads.adservices.common.AdSelectionSignals getAdSelectionSignals();
+ method public android.net.Uri getSelectionLogicUri();
+ method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier getSeller();
+ method public void setSelectionLogicUri(android.net.Uri);
+ property public final java.util.List<java.lang.Long> adSelectionIds;
+ property public final androidx.privacysandbox.ads.adservices.common.AdSelectionSignals adSelectionSignals;
+ property public final android.net.Uri selectionLogicUri;
+ property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier seller;
+ }
+
+ public abstract class AdSelectionManager {
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract suspend Object? getAdSelectionData(androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataRequest getAdSelectionDataRequest, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataOutcome>);
+ method public static final androidx.privacysandbox.ads.adservices.adselection.AdSelectionManager? obtain(android.content.Context context);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract suspend Object? persistAdSelectionResult(androidx.privacysandbox.ads.adservices.adselection.PersistAdSelectionResultRequest persistAdSelectionResultRequest, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome>);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public abstract suspend Object? reportEvent(androidx.privacysandbox.ads.adservices.adselection.ReportEventRequest reportEventRequest, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract suspend Object? reportImpression(androidx.privacysandbox.ads.adservices.adselection.ReportImpressionRequest reportImpressionRequest, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract suspend Object? selectAds(androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig adSelectionConfig, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome>);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract suspend Object? selectAds(androidx.privacysandbox.ads.adservices.adselection.AdSelectionFromOutcomesConfig adSelectionFromOutcomesConfig, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome>);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public abstract suspend Object? updateAdCounterHistogram(androidx.privacysandbox.ads.adservices.adselection.UpdateAdCounterHistogramRequest updateAdCounterHistogramRequest, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ field public static final androidx.privacysandbox.ads.adservices.adselection.AdSelectionManager.Companion Companion;
+ }
+
+ public static final class AdSelectionManager.Companion {
+ method public androidx.privacysandbox.ads.adservices.adselection.AdSelectionManager? obtain(android.content.Context context);
+ }
+
+ public final class AdSelectionOutcome {
+ ctor public AdSelectionOutcome(long adSelectionId, android.net.Uri renderUri);
+ method public long getAdSelectionId();
+ method public android.net.Uri getRenderUri();
+ method @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public boolean hasOutcome();
+ property public final long adSelectionId;
+ property public final android.net.Uri renderUri;
+ field public static final androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome.Companion Companion;
+ field @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public static final androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome NO_OUTCOME;
+ }
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public static final class AdSelectionOutcome.Companion {
+ }
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class GetAdSelectionDataOutcome {
+ ctor public GetAdSelectionDataOutcome(long adSelectionId, optional byte[]? adSelectionData);
+ method public byte[]? getAdSelectionData();
+ method public long getAdSelectionId();
+ property public final byte[]? adSelectionData;
+ property public final long adSelectionId;
+ }
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class GetAdSelectionDataRequest {
+ ctor public GetAdSelectionDataRequest(optional androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller);
+ method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? getSeller();
+ property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller;
+ }
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class PersistAdSelectionResultRequest {
+ ctor public PersistAdSelectionResultRequest(long adSelectionId, optional androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller, optional byte[]? adSelectionResult);
+ method public long getAdSelectionId();
+ method public byte[]? getAdSelectionResult();
+ method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? getSeller();
+ property public final long adSelectionId;
+ property public final byte[]? adSelectionResult;
+ property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller;
+ }
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class ReportEventRequest {
+ ctor public ReportEventRequest(long adSelectionId, String eventKey, String eventData, int reportingDestinations, optional android.view.InputEvent? inputEvent);
+ method public long getAdSelectionId();
+ method public String getEventData();
+ method public String getEventKey();
+ method public android.view.InputEvent? getInputEvent();
+ method public int getReportingDestinations();
+ property public final long adSelectionId;
+ property public final String eventData;
+ property public final String eventKey;
+ property public final android.view.InputEvent? inputEvent;
+ property public final int reportingDestinations;
+ field public static final androidx.privacysandbox.ads.adservices.adselection.ReportEventRequest.Companion Companion;
+ field public static final int FLAG_REPORTING_DESTINATION_BUYER = 2; // 0x2
+ field public static final int FLAG_REPORTING_DESTINATION_SELLER = 1; // 0x1
+ }
+
+ public static final class ReportEventRequest.Companion {
+ }
+
+ public final class ReportImpressionRequest {
+ ctor @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public ReportImpressionRequest(long adSelectionId);
+ ctor public ReportImpressionRequest(long adSelectionId, androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig adSelectionConfig);
+ method public androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig getAdSelectionConfig();
+ method public long getAdSelectionId();
+ property public final androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig adSelectionConfig;
+ property public final long adSelectionId;
+ }
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class UpdateAdCounterHistogramRequest {
+ ctor public UpdateAdCounterHistogramRequest(long adSelectionId, int adEventType, androidx.privacysandbox.ads.adservices.common.AdTechIdentifier callerAdTech);
+ method public int getAdEventType();
+ method public long getAdSelectionId();
+ method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier getCallerAdTech();
+ property public final int adEventType;
+ property public final long adSelectionId;
+ property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier callerAdTech;
+ }
+
+}
+
+package androidx.privacysandbox.ads.adservices.appsetid {
+
+ public final class AppSetId {
+ ctor public AppSetId(String id, int scope);
+ method public String getId();
+ method public int getScope();
+ property public final String id;
+ property public final int scope;
+ field public static final androidx.privacysandbox.ads.adservices.appsetid.AppSetId.Companion Companion;
+ field public static final int SCOPE_APP = 1; // 0x1
+ field public static final int SCOPE_DEVELOPER = 2; // 0x2
+ }
+
+ public static final class AppSetId.Companion {
+ }
+
+ public abstract class AppSetIdManager {
+ method public abstract suspend Object? getAppSetId(kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.appsetid.AppSetId>);
+ method public static final androidx.privacysandbox.ads.adservices.appsetid.AppSetIdManager? obtain(android.content.Context context);
+ field public static final androidx.privacysandbox.ads.adservices.appsetid.AppSetIdManager.Companion Companion;
+ }
+
+ public static final class AppSetIdManager.Companion {
+ method public androidx.privacysandbox.ads.adservices.appsetid.AppSetIdManager? obtain(android.content.Context context);
+ }
+
+}
+
+package androidx.privacysandbox.ads.adservices.common {
+
+ public final class AdData {
+ ctor public AdData(android.net.Uri renderUri, String metadata);
+ ctor @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public AdData(android.net.Uri renderUri, String metadata, optional java.util.Set<java.lang.Integer> adCounterKeys, optional androidx.privacysandbox.ads.adservices.common.AdFilters? adFilters);
+ ctor @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public AdData(android.net.Uri renderUri, String metadata, optional java.util.Set<java.lang.Integer> adCounterKeys, optional androidx.privacysandbox.ads.adservices.common.AdFilters? adFilters, optional String? adRenderId);
+ method public java.util.Set<java.lang.Integer> getAdCounterKeys();
+ method public androidx.privacysandbox.ads.adservices.common.AdFilters? getAdFilters();
+ method public String? getAdRenderId();
+ method public String getMetadata();
+ method public android.net.Uri getRenderUri();
+ property public final java.util.Set<java.lang.Integer> adCounterKeys;
+ property public final androidx.privacysandbox.ads.adservices.common.AdFilters? adFilters;
+ property public final String? adRenderId;
+ property public final String metadata;
+ property public final android.net.Uri renderUri;
+ }
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class AdFilters {
+ ctor public AdFilters(androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters? frequencyCapFilters);
+ method public androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters? getFrequencyCapFilters();
+ property public final androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters? frequencyCapFilters;
+ }
+
+ public final class AdSelectionSignals {
+ ctor public AdSelectionSignals(String signals);
+ method public String getSignals();
+ property public final String signals;
+ }
+
+ public final class AdTechIdentifier {
+ ctor public AdTechIdentifier(String identifier);
+ method public String getIdentifier();
+ property public final String identifier;
+ }
+
+ public sealed interface ExperimentalFeatures {
+ }
+
+ @SuppressCompatibility @kotlin.RequiresOptIn(message="The Ext10 API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.Ext10OptIn {
+ }
+
+ @SuppressCompatibility @kotlin.RequiresOptIn(message="The Ext11 API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.Ext11OptIn {
+ }
+
+ @SuppressCompatibility @kotlin.RequiresOptIn(message="The Ext8 API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.Ext8OptIn {
+ }
+
+ @SuppressCompatibility @kotlin.RequiresOptIn(message="This API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.RegisterSourceOptIn {
+ }
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class FrequencyCapFilters {
+ ctor public FrequencyCapFilters();
+ ctor public FrequencyCapFilters(optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents);
+ ctor public FrequencyCapFilters(optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents);
+ ctor public FrequencyCapFilters(optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForViewEvents);
+ ctor public FrequencyCapFilters(optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForViewEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForClickEvents);
+ method public java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> getKeyedFrequencyCapsForClickEvents();
+ method public java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> getKeyedFrequencyCapsForImpressionEvents();
+ method public java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> getKeyedFrequencyCapsForViewEvents();
+ method public java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> getKeyedFrequencyCapsForWinEvents();
+ property public final java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForClickEvents;
+ property public final java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents;
+ property public final java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForViewEvents;
+ property public final java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents;
+ field public static final int AD_EVENT_TYPE_CLICK = 3; // 0x3
+ field public static final int AD_EVENT_TYPE_IMPRESSION = 1; // 0x1
+ field public static final int AD_EVENT_TYPE_VIEW = 2; // 0x2
+ field public static final int AD_EVENT_TYPE_WIN = 0; // 0x0
+ field public static final androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters.Companion Companion;
+ }
+
+ public static final class FrequencyCapFilters.Companion {
+ }
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class KeyedFrequencyCap {
+ ctor public KeyedFrequencyCap(int adCounterKey, int maxCount, java.time.Duration interval);
+ method public int getAdCounterKey();
+ method public java.time.Duration getInterval();
+ method public int getMaxCount();
+ property public final int adCounterKey;
+ property public final java.time.Duration interval;
+ property public final int maxCount;
+ }
+
+}
+
+package androidx.privacysandbox.ads.adservices.customaudience {
+
+ public final class CustomAudience {
+ ctor public CustomAudience(androidx.privacysandbox.ads.adservices.common.AdTechIdentifier buyer, String name, android.net.Uri dailyUpdateUri, android.net.Uri biddingLogicUri, java.util.List<androidx.privacysandbox.ads.adservices.common.AdData> ads, optional java.time.Instant? activationTime, optional java.time.Instant? expirationTime, optional androidx.privacysandbox.ads.adservices.common.AdSelectionSignals? userBiddingSignals, optional androidx.privacysandbox.ads.adservices.customaudience.TrustedBiddingData? trustedBiddingSignals);
+ method public java.time.Instant? getActivationTime();
+ method public java.util.List<androidx.privacysandbox.ads.adservices.common.AdData> getAds();
+ method public android.net.Uri getBiddingLogicUri();
+ method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier getBuyer();
+ method public android.net.Uri getDailyUpdateUri();
+ method public java.time.Instant? getExpirationTime();
+ method public String getName();
+ method public androidx.privacysandbox.ads.adservices.customaudience.TrustedBiddingData? getTrustedBiddingSignals();
+ method public androidx.privacysandbox.ads.adservices.common.AdSelectionSignals? getUserBiddingSignals();
+ property public final java.time.Instant? activationTime;
+ property public final java.util.List<androidx.privacysandbox.ads.adservices.common.AdData> ads;
+ property public final android.net.Uri biddingLogicUri;
+ property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier buyer;
+ property public final android.net.Uri dailyUpdateUri;
+ property public final java.time.Instant? expirationTime;
+ property public final String name;
+ property public final androidx.privacysandbox.ads.adservices.customaudience.TrustedBiddingData? trustedBiddingSignals;
+ property public final androidx.privacysandbox.ads.adservices.common.AdSelectionSignals? userBiddingSignals;
+ }
+
+ public static final class CustomAudience.Builder {
+ ctor public CustomAudience.Builder(androidx.privacysandbox.ads.adservices.common.AdTechIdentifier buyer, String name, android.net.Uri dailyUpdateUri, android.net.Uri biddingLogicUri, java.util.List<androidx.privacysandbox.ads.adservices.common.AdData> ads);
+ method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience build();
+ method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience.Builder setActivationTime(java.time.Instant activationTime);
+ method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience.Builder setAds(java.util.List<androidx.privacysandbox.ads.adservices.common.AdData> ads);
+ method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience.Builder setBiddingLogicUri(android.net.Uri biddingLogicUri);
+ method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience.Builder setBuyer(androidx.privacysandbox.ads.adservices.common.AdTechIdentifier buyer);
+ method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience.Builder setDailyUpdateUri(android.net.Uri dailyUpdateUri);
+ method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience.Builder setExpirationTime(java.time.Instant expirationTime);
+ method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience.Builder setName(String name);
+ method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience.Builder setTrustedBiddingData(androidx.privacysandbox.ads.adservices.customaudience.TrustedBiddingData trustedBiddingSignals);
+ method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience.Builder setUserBiddingSignals(androidx.privacysandbox.ads.adservices.common.AdSelectionSignals userBiddingSignals);
+ }
+
+ public abstract class CustomAudienceManager {
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract suspend Object? fetchAndJoinCustomAudience(androidx.privacysandbox.ads.adservices.customaudience.FetchAndJoinCustomAudienceRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract suspend Object? joinCustomAudience(androidx.privacysandbox.ads.adservices.customaudience.JoinCustomAudienceRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract suspend Object? leaveCustomAudience(androidx.privacysandbox.ads.adservices.customaudience.LeaveCustomAudienceRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method public static final androidx.privacysandbox.ads.adservices.customaudience.CustomAudienceManager? obtain(android.content.Context context);
+ field public static final androidx.privacysandbox.ads.adservices.customaudience.CustomAudienceManager.Companion Companion;
+ }
+
+ public static final class CustomAudienceManager.Companion {
+ method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudienceManager? obtain(android.content.Context context);
+ }
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class FetchAndJoinCustomAudienceRequest {
+ ctor public FetchAndJoinCustomAudienceRequest(android.net.Uri fetchUri, optional String? name, optional java.time.Instant? activationTime, optional java.time.Instant? expirationTime, optional androidx.privacysandbox.ads.adservices.common.AdSelectionSignals? userBiddingSignals);
+ method public java.time.Instant? getActivationTime();
+ method public java.time.Instant? getExpirationTime();
+ method public android.net.Uri getFetchUri();
+ method public String? getName();
+ method public androidx.privacysandbox.ads.adservices.common.AdSelectionSignals? getUserBiddingSignals();
+ property public final java.time.Instant? activationTime;
+ property public final java.time.Instant? expirationTime;
+ property public final android.net.Uri fetchUri;
+ property public final String? name;
+ property public final androidx.privacysandbox.ads.adservices.common.AdSelectionSignals? userBiddingSignals;
+ }
+
+ public final class JoinCustomAudienceRequest {
+ ctor public JoinCustomAudienceRequest(androidx.privacysandbox.ads.adservices.customaudience.CustomAudience customAudience);
+ method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience getCustomAudience();
+ property public final androidx.privacysandbox.ads.adservices.customaudience.CustomAudience customAudience;
+ }
+
+ public final class LeaveCustomAudienceRequest {
+ ctor public LeaveCustomAudienceRequest(androidx.privacysandbox.ads.adservices.common.AdTechIdentifier buyer, String name);
+ method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier getBuyer();
+ method public String getName();
+ property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier buyer;
+ property public final String name;
+ }
+
+ public final class TrustedBiddingData {
+ ctor public TrustedBiddingData(android.net.Uri trustedBiddingUri, java.util.List<java.lang.String> trustedBiddingKeys);
+ method public java.util.List<java.lang.String> getTrustedBiddingKeys();
+ method public android.net.Uri getTrustedBiddingUri();
+ property public final java.util.List<java.lang.String> trustedBiddingKeys;
+ property public final android.net.Uri trustedBiddingUri;
+ }
+
+}
+
+package androidx.privacysandbox.ads.adservices.measurement {
+
+ @RequiresApi(android.os.Build.VERSION_CODES.O) public final class DeletionRequest {
+ ctor public DeletionRequest(int deletionMode, int matchBehavior, optional java.time.Instant start, optional java.time.Instant end, optional java.util.List<? extends android.net.Uri> domainUris, optional java.util.List<? extends android.net.Uri> originUris);
+ method public int getDeletionMode();
+ method public java.util.List<android.net.Uri> getDomainUris();
+ method public java.time.Instant getEnd();
+ method public int getMatchBehavior();
+ method public java.util.List<android.net.Uri> getOriginUris();
+ method public java.time.Instant getStart();
+ property public final int deletionMode;
+ property public final java.util.List<android.net.Uri> domainUris;
+ property public final java.time.Instant end;
+ property public final int matchBehavior;
+ property public final java.util.List<android.net.Uri> originUris;
+ property public final java.time.Instant start;
+ field public static final androidx.privacysandbox.ads.adservices.measurement.DeletionRequest.Companion Companion;
+ field public static final int DELETION_MODE_ALL = 0; // 0x0
+ field public static final int DELETION_MODE_EXCLUDE_INTERNAL_DATA = 1; // 0x1
+ field public static final int MATCH_BEHAVIOR_DELETE = 0; // 0x0
+ field public static final int MATCH_BEHAVIOR_PRESERVE = 1; // 0x1
+ }
+
+ @RequiresApi(android.os.Build.VERSION_CODES.O) public static final class DeletionRequest.Builder {
+ ctor public DeletionRequest.Builder(int deletionMode, int matchBehavior);
+ method public androidx.privacysandbox.ads.adservices.measurement.DeletionRequest build();
+ method public androidx.privacysandbox.ads.adservices.measurement.DeletionRequest.Builder setDomainUris(java.util.List<? extends android.net.Uri> domainUris);
+ method public androidx.privacysandbox.ads.adservices.measurement.DeletionRequest.Builder setEnd(java.time.Instant end);
+ method public androidx.privacysandbox.ads.adservices.measurement.DeletionRequest.Builder setOriginUris(java.util.List<? extends android.net.Uri> originUris);
+ method public androidx.privacysandbox.ads.adservices.measurement.DeletionRequest.Builder setStart(java.time.Instant start);
+ }
+
+ public static final class DeletionRequest.Companion {
+ }
+
+ public abstract class MeasurementManager {
+ ctor public MeasurementManager();
+ method public abstract suspend Object? deleteRegistrations(androidx.privacysandbox.ads.adservices.measurement.DeletionRequest deletionRequest, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract suspend Object? getMeasurementApiStatus(kotlin.coroutines.Continuation<? super java.lang.Integer>);
+ method public static final androidx.privacysandbox.ads.adservices.measurement.MeasurementManager? obtain(android.content.Context context);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract suspend Object? registerSource(android.net.Uri attributionSource, android.view.InputEvent? inputEvent, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.RegisterSourceOptIn public abstract suspend Object? registerSource(androidx.privacysandbox.ads.adservices.measurement.SourceRegistrationRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract suspend Object? registerTrigger(android.net.Uri trigger, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract suspend Object? registerWebSource(androidx.privacysandbox.ads.adservices.measurement.WebSourceRegistrationRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract suspend Object? registerWebTrigger(androidx.privacysandbox.ads.adservices.measurement.WebTriggerRegistrationRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ field public static final androidx.privacysandbox.ads.adservices.measurement.MeasurementManager.Companion Companion;
+ field public static final int MEASUREMENT_API_STATE_DISABLED = 0; // 0x0
+ field public static final int MEASUREMENT_API_STATE_ENABLED = 1; // 0x1
+ }
+
+ public static final class MeasurementManager.Companion {
+ method public androidx.privacysandbox.ads.adservices.measurement.MeasurementManager? obtain(android.content.Context context);
+ }
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.RegisterSourceOptIn public final class SourceRegistrationRequest {
+ ctor public SourceRegistrationRequest(java.util.List<? extends android.net.Uri> registrationUris, optional android.view.InputEvent? inputEvent);
+ method public android.view.InputEvent? getInputEvent();
+ method public java.util.List<android.net.Uri> getRegistrationUris();
+ property public final android.view.InputEvent? inputEvent;
+ property public final java.util.List<android.net.Uri> registrationUris;
+ }
+
+ public static final class SourceRegistrationRequest.Builder {
+ ctor public SourceRegistrationRequest.Builder(java.util.List<? extends android.net.Uri> registrationUris);
+ method public androidx.privacysandbox.ads.adservices.measurement.SourceRegistrationRequest build();
+ method public androidx.privacysandbox.ads.adservices.measurement.SourceRegistrationRequest.Builder setInputEvent(android.view.InputEvent inputEvent);
+ }
+
+ public final class WebSourceParams {
+ ctor public WebSourceParams(android.net.Uri registrationUri, boolean debugKeyAllowed);
+ method public boolean getDebugKeyAllowed();
+ method public android.net.Uri getRegistrationUri();
+ property public final boolean debugKeyAllowed;
+ property public final android.net.Uri registrationUri;
+ }
+
+ public final class WebSourceRegistrationRequest {
+ ctor public WebSourceRegistrationRequest(java.util.List<androidx.privacysandbox.ads.adservices.measurement.WebSourceParams> webSourceParams, android.net.Uri topOriginUri, optional android.view.InputEvent? inputEvent, optional android.net.Uri? appDestination, optional android.net.Uri? webDestination, optional android.net.Uri? verifiedDestination);
+ method public android.net.Uri? getAppDestination();
+ method public android.view.InputEvent? getInputEvent();
+ method public android.net.Uri getTopOriginUri();
+ method public android.net.Uri? getVerifiedDestination();
+ method public android.net.Uri? getWebDestination();
+ method public java.util.List<androidx.privacysandbox.ads.adservices.measurement.WebSourceParams> getWebSourceParams();
+ property public final android.net.Uri? appDestination;
+ property public final android.view.InputEvent? inputEvent;
+ property public final android.net.Uri topOriginUri;
+ property public final android.net.Uri? verifiedDestination;
+ property public final android.net.Uri? webDestination;
+ property public final java.util.List<androidx.privacysandbox.ads.adservices.measurement.WebSourceParams> webSourceParams;
+ }
+
+ public static final class WebSourceRegistrationRequest.Builder {
+ ctor public WebSourceRegistrationRequest.Builder(java.util.List<androidx.privacysandbox.ads.adservices.measurement.WebSourceParams> webSourceParams, android.net.Uri topOriginUri);
+ method public androidx.privacysandbox.ads.adservices.measurement.WebSourceRegistrationRequest build();
+ method public androidx.privacysandbox.ads.adservices.measurement.WebSourceRegistrationRequest.Builder setAppDestination(android.net.Uri? appDestination);
+ method public androidx.privacysandbox.ads.adservices.measurement.WebSourceRegistrationRequest.Builder setInputEvent(android.view.InputEvent inputEvent);
+ method public androidx.privacysandbox.ads.adservices.measurement.WebSourceRegistrationRequest.Builder setVerifiedDestination(android.net.Uri? verifiedDestination);
+ method public androidx.privacysandbox.ads.adservices.measurement.WebSourceRegistrationRequest.Builder setWebDestination(android.net.Uri? webDestination);
+ }
+
+ public final class WebTriggerParams {
+ ctor public WebTriggerParams(android.net.Uri registrationUri, boolean debugKeyAllowed);
+ method public boolean getDebugKeyAllowed();
+ method public android.net.Uri getRegistrationUri();
+ property public final boolean debugKeyAllowed;
+ property public final android.net.Uri registrationUri;
+ }
+
+ public final class WebTriggerRegistrationRequest {
+ ctor public WebTriggerRegistrationRequest(java.util.List<androidx.privacysandbox.ads.adservices.measurement.WebTriggerParams> webTriggerParams, android.net.Uri destination);
+ method public android.net.Uri getDestination();
+ method public java.util.List<androidx.privacysandbox.ads.adservices.measurement.WebTriggerParams> getWebTriggerParams();
+ property public final android.net.Uri destination;
+ property public final java.util.List<androidx.privacysandbox.ads.adservices.measurement.WebTriggerParams> webTriggerParams;
+ }
+
+}
+
+package androidx.privacysandbox.ads.adservices.topics {
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext11OptIn public final class EncryptedTopic {
+ ctor public EncryptedTopic(byte[] encryptedTopic, String keyIdentifier, byte[] encapsulatedKey);
+ method public byte[] getEncapsulatedKey();
+ method public byte[] getEncryptedTopic();
+ method public String getKeyIdentifier();
+ property public final byte[] encapsulatedKey;
+ property public final byte[] encryptedTopic;
+ property public final String keyIdentifier;
+ }
+
+ public final class GetTopicsRequest {
+ ctor public GetTopicsRequest(optional String adsSdkName, optional boolean shouldRecordObservation);
+ method public String getAdsSdkName();
+ method public boolean shouldRecordObservation();
+ property public final String adsSdkName;
+ property public final boolean shouldRecordObservation;
+ }
+
+ public static final class GetTopicsRequest.Builder {
+ ctor public GetTopicsRequest.Builder();
+ method public androidx.privacysandbox.ads.adservices.topics.GetTopicsRequest build();
+ method public androidx.privacysandbox.ads.adservices.topics.GetTopicsRequest.Builder setAdsSdkName(String adsSdkName);
+ method public androidx.privacysandbox.ads.adservices.topics.GetTopicsRequest.Builder setShouldRecordObservation(boolean shouldRecordObservation);
+ }
+
+ public final class GetTopicsResponse {
+ ctor public GetTopicsResponse(java.util.List<androidx.privacysandbox.ads.adservices.topics.Topic> topics);
+ ctor @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext11OptIn public GetTopicsResponse(java.util.List<androidx.privacysandbox.ads.adservices.topics.Topic> topics, java.util.List<androidx.privacysandbox.ads.adservices.topics.EncryptedTopic> encryptedTopics);
+ method public java.util.List<androidx.privacysandbox.ads.adservices.topics.EncryptedTopic> getEncryptedTopics();
+ method public java.util.List<androidx.privacysandbox.ads.adservices.topics.Topic> getTopics();
+ property public final java.util.List<androidx.privacysandbox.ads.adservices.topics.EncryptedTopic> encryptedTopics;
+ property public final java.util.List<androidx.privacysandbox.ads.adservices.topics.Topic> topics;
+ }
+
+ public final class Topic {
+ ctor public Topic(long taxonomyVersion, long modelVersion, int topicId);
+ method public long getModelVersion();
+ method public long getTaxonomyVersion();
+ method public int getTopicId();
+ property public final long modelVersion;
+ property public final long taxonomyVersion;
+ property public final int topicId;
+ }
+
+ public abstract class TopicsManager {
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_TOPICS) public abstract suspend Object? getTopics(androidx.privacysandbox.ads.adservices.topics.GetTopicsRequest request, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.topics.GetTopicsResponse>);
+ method public static final androidx.privacysandbox.ads.adservices.topics.TopicsManager? obtain(android.content.Context context);
+ field public static final androidx.privacysandbox.ads.adservices.topics.TopicsManager.Companion Companion;
+ }
+
+ public static final class TopicsManager.Companion {
+ method public androidx.privacysandbox.ads.adservices.topics.TopicsManager? obtain(android.content.Context context);
+ }
+
+}
+
diff --git a/privacysandbox/ads/ads-adservices/api/current.txt b/privacysandbox/ads/ads-adservices/api/current.txt
index 32149c9..7574be5 100644
--- a/privacysandbox/ads/ads-adservices/api/current.txt
+++ b/privacysandbox/ads/ads-adservices/api/current.txt
@@ -218,6 +218,9 @@
@SuppressCompatibility @kotlin.RequiresOptIn(message="The Ext10 API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.Ext10OptIn {
}
+ @SuppressCompatibility @kotlin.RequiresOptIn(message="The Ext11 API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.Ext11OptIn {
+ }
+
@SuppressCompatibility @kotlin.RequiresOptIn(message="The Ext8 API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.Ext8OptIn {
}
@@ -469,6 +472,16 @@
package androidx.privacysandbox.ads.adservices.topics {
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext11OptIn public final class EncryptedTopic {
+ ctor public EncryptedTopic(byte[] encryptedTopic, String keyIdentifier, byte[] encapsulatedKey);
+ method public byte[] getEncapsulatedKey();
+ method public byte[] getEncryptedTopic();
+ method public String getKeyIdentifier();
+ property public final byte[] encapsulatedKey;
+ property public final byte[] encryptedTopic;
+ property public final String keyIdentifier;
+ }
+
public final class GetTopicsRequest {
ctor public GetTopicsRequest(optional String adsSdkName, optional boolean shouldRecordObservation);
method public String getAdsSdkName();
@@ -486,7 +499,10 @@
public final class GetTopicsResponse {
ctor public GetTopicsResponse(java.util.List<androidx.privacysandbox.ads.adservices.topics.Topic> topics);
+ ctor @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext11OptIn public GetTopicsResponse(java.util.List<androidx.privacysandbox.ads.adservices.topics.Topic> topics, java.util.List<androidx.privacysandbox.ads.adservices.topics.EncryptedTopic> encryptedTopics);
+ method public java.util.List<androidx.privacysandbox.ads.adservices.topics.EncryptedTopic> getEncryptedTopics();
method public java.util.List<androidx.privacysandbox.ads.adservices.topics.Topic> getTopics();
+ property public final java.util.List<androidx.privacysandbox.ads.adservices.topics.EncryptedTopic> encryptedTopics;
property public final java.util.List<androidx.privacysandbox.ads.adservices.topics.Topic> topics;
}
diff --git a/privacysandbox/ads/ads-adservices/api/res-1.1.0-beta07.txt b/privacysandbox/ads/ads-adservices/api/res-1.1.0-beta07.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/api/res-1.1.0-beta07.txt
diff --git a/privacysandbox/ads/ads-adservices/api/restricted_1.1.0-beta07.txt b/privacysandbox/ads/ads-adservices/api/restricted_1.1.0-beta07.txt
new file mode 100644
index 0000000..7574be5
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/api/restricted_1.1.0-beta07.txt
@@ -0,0 +1,530 @@
+// Signature format: 4.0
+package androidx.privacysandbox.ads.adservices.adid {
+
+ public final class AdId {
+ method public String getAdId();
+ method public boolean isLimitAdTrackingEnabled();
+ property public final String adId;
+ property public final boolean isLimitAdTrackingEnabled;
+ }
+
+ public abstract class AdIdManager {
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_AD_ID) public abstract suspend Object? getAdId(kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adid.AdId>);
+ method public static final androidx.privacysandbox.ads.adservices.adid.AdIdManager? obtain(android.content.Context context);
+ field public static final androidx.privacysandbox.ads.adservices.adid.AdIdManager.Companion Companion;
+ }
+
+ public static final class AdIdManager.Companion {
+ method public androidx.privacysandbox.ads.adservices.adid.AdIdManager? obtain(android.content.Context context);
+ }
+
+}
+
+package androidx.privacysandbox.ads.adservices.adselection {
+
+ public final class AdSelectionConfig {
+ ctor public AdSelectionConfig(androidx.privacysandbox.ads.adservices.common.AdTechIdentifier seller, android.net.Uri decisionLogicUri, java.util.List<androidx.privacysandbox.ads.adservices.common.AdTechIdentifier> customAudienceBuyers, androidx.privacysandbox.ads.adservices.common.AdSelectionSignals adSelectionSignals, androidx.privacysandbox.ads.adservices.common.AdSelectionSignals sellerSignals, java.util.Map<androidx.privacysandbox.ads.adservices.common.AdTechIdentifier,androidx.privacysandbox.ads.adservices.common.AdSelectionSignals> perBuyerSignals, android.net.Uri trustedScoringSignalsUri);
+ method public androidx.privacysandbox.ads.adservices.common.AdSelectionSignals getAdSelectionSignals();
+ method public java.util.List<androidx.privacysandbox.ads.adservices.common.AdTechIdentifier> getCustomAudienceBuyers();
+ method public android.net.Uri getDecisionLogicUri();
+ method public java.util.Map<androidx.privacysandbox.ads.adservices.common.AdTechIdentifier,androidx.privacysandbox.ads.adservices.common.AdSelectionSignals> getPerBuyerSignals();
+ method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier getSeller();
+ method public androidx.privacysandbox.ads.adservices.common.AdSelectionSignals getSellerSignals();
+ method public android.net.Uri getTrustedScoringSignalsUri();
+ property public final androidx.privacysandbox.ads.adservices.common.AdSelectionSignals adSelectionSignals;
+ property public final java.util.List<androidx.privacysandbox.ads.adservices.common.AdTechIdentifier> customAudienceBuyers;
+ property public final android.net.Uri decisionLogicUri;
+ property public final java.util.Map<androidx.privacysandbox.ads.adservices.common.AdTechIdentifier,androidx.privacysandbox.ads.adservices.common.AdSelectionSignals> perBuyerSignals;
+ property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier seller;
+ property public final androidx.privacysandbox.ads.adservices.common.AdSelectionSignals sellerSignals;
+ property public final android.net.Uri trustedScoringSignalsUri;
+ }
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class AdSelectionFromOutcomesConfig {
+ ctor public AdSelectionFromOutcomesConfig(androidx.privacysandbox.ads.adservices.common.AdTechIdentifier seller, java.util.List<java.lang.Long> adSelectionIds, androidx.privacysandbox.ads.adservices.common.AdSelectionSignals adSelectionSignals, android.net.Uri selectionLogicUri);
+ method public java.util.List<java.lang.Long> getAdSelectionIds();
+ method public androidx.privacysandbox.ads.adservices.common.AdSelectionSignals getAdSelectionSignals();
+ method public android.net.Uri getSelectionLogicUri();
+ method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier getSeller();
+ method public void setSelectionLogicUri(android.net.Uri);
+ property public final java.util.List<java.lang.Long> adSelectionIds;
+ property public final androidx.privacysandbox.ads.adservices.common.AdSelectionSignals adSelectionSignals;
+ property public final android.net.Uri selectionLogicUri;
+ property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier seller;
+ }
+
+ public abstract class AdSelectionManager {
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract suspend Object? getAdSelectionData(androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataRequest getAdSelectionDataRequest, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataOutcome>);
+ method public static final androidx.privacysandbox.ads.adservices.adselection.AdSelectionManager? obtain(android.content.Context context);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract suspend Object? persistAdSelectionResult(androidx.privacysandbox.ads.adservices.adselection.PersistAdSelectionResultRequest persistAdSelectionResultRequest, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome>);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public abstract suspend Object? reportEvent(androidx.privacysandbox.ads.adservices.adselection.ReportEventRequest reportEventRequest, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract suspend Object? reportImpression(androidx.privacysandbox.ads.adservices.adselection.ReportImpressionRequest reportImpressionRequest, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract suspend Object? selectAds(androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig adSelectionConfig, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome>);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract suspend Object? selectAds(androidx.privacysandbox.ads.adservices.adselection.AdSelectionFromOutcomesConfig adSelectionFromOutcomesConfig, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome>);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public abstract suspend Object? updateAdCounterHistogram(androidx.privacysandbox.ads.adservices.adselection.UpdateAdCounterHistogramRequest updateAdCounterHistogramRequest, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ field public static final androidx.privacysandbox.ads.adservices.adselection.AdSelectionManager.Companion Companion;
+ }
+
+ public static final class AdSelectionManager.Companion {
+ method public androidx.privacysandbox.ads.adservices.adselection.AdSelectionManager? obtain(android.content.Context context);
+ }
+
+ public final class AdSelectionOutcome {
+ ctor public AdSelectionOutcome(long adSelectionId, android.net.Uri renderUri);
+ method public long getAdSelectionId();
+ method public android.net.Uri getRenderUri();
+ method @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public boolean hasOutcome();
+ property public final long adSelectionId;
+ property public final android.net.Uri renderUri;
+ field public static final androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome.Companion Companion;
+ field @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public static final androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome NO_OUTCOME;
+ }
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public static final class AdSelectionOutcome.Companion {
+ }
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class GetAdSelectionDataOutcome {
+ ctor public GetAdSelectionDataOutcome(long adSelectionId, optional byte[]? adSelectionData);
+ method public byte[]? getAdSelectionData();
+ method public long getAdSelectionId();
+ property public final byte[]? adSelectionData;
+ property public final long adSelectionId;
+ }
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class GetAdSelectionDataRequest {
+ ctor public GetAdSelectionDataRequest(optional androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller);
+ method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? getSeller();
+ property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller;
+ }
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class PersistAdSelectionResultRequest {
+ ctor public PersistAdSelectionResultRequest(long adSelectionId, optional androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller, optional byte[]? adSelectionResult);
+ method public long getAdSelectionId();
+ method public byte[]? getAdSelectionResult();
+ method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? getSeller();
+ property public final long adSelectionId;
+ property public final byte[]? adSelectionResult;
+ property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller;
+ }
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class ReportEventRequest {
+ ctor public ReportEventRequest(long adSelectionId, String eventKey, String eventData, int reportingDestinations, optional android.view.InputEvent? inputEvent);
+ method public long getAdSelectionId();
+ method public String getEventData();
+ method public String getEventKey();
+ method public android.view.InputEvent? getInputEvent();
+ method public int getReportingDestinations();
+ property public final long adSelectionId;
+ property public final String eventData;
+ property public final String eventKey;
+ property public final android.view.InputEvent? inputEvent;
+ property public final int reportingDestinations;
+ field public static final androidx.privacysandbox.ads.adservices.adselection.ReportEventRequest.Companion Companion;
+ field public static final int FLAG_REPORTING_DESTINATION_BUYER = 2; // 0x2
+ field public static final int FLAG_REPORTING_DESTINATION_SELLER = 1; // 0x1
+ }
+
+ public static final class ReportEventRequest.Companion {
+ }
+
+ public final class ReportImpressionRequest {
+ ctor @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public ReportImpressionRequest(long adSelectionId);
+ ctor public ReportImpressionRequest(long adSelectionId, androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig adSelectionConfig);
+ method public androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig getAdSelectionConfig();
+ method public long getAdSelectionId();
+ property public final androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig adSelectionConfig;
+ property public final long adSelectionId;
+ }
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class UpdateAdCounterHistogramRequest {
+ ctor public UpdateAdCounterHistogramRequest(long adSelectionId, int adEventType, androidx.privacysandbox.ads.adservices.common.AdTechIdentifier callerAdTech);
+ method public int getAdEventType();
+ method public long getAdSelectionId();
+ method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier getCallerAdTech();
+ property public final int adEventType;
+ property public final long adSelectionId;
+ property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier callerAdTech;
+ }
+
+}
+
+package androidx.privacysandbox.ads.adservices.appsetid {
+
+ public final class AppSetId {
+ ctor public AppSetId(String id, int scope);
+ method public String getId();
+ method public int getScope();
+ property public final String id;
+ property public final int scope;
+ field public static final androidx.privacysandbox.ads.adservices.appsetid.AppSetId.Companion Companion;
+ field public static final int SCOPE_APP = 1; // 0x1
+ field public static final int SCOPE_DEVELOPER = 2; // 0x2
+ }
+
+ public static final class AppSetId.Companion {
+ }
+
+ public abstract class AppSetIdManager {
+ method public abstract suspend Object? getAppSetId(kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.appsetid.AppSetId>);
+ method public static final androidx.privacysandbox.ads.adservices.appsetid.AppSetIdManager? obtain(android.content.Context context);
+ field public static final androidx.privacysandbox.ads.adservices.appsetid.AppSetIdManager.Companion Companion;
+ }
+
+ public static final class AppSetIdManager.Companion {
+ method public androidx.privacysandbox.ads.adservices.appsetid.AppSetIdManager? obtain(android.content.Context context);
+ }
+
+}
+
+package androidx.privacysandbox.ads.adservices.common {
+
+ public final class AdData {
+ ctor public AdData(android.net.Uri renderUri, String metadata);
+ ctor @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public AdData(android.net.Uri renderUri, String metadata, optional java.util.Set<java.lang.Integer> adCounterKeys, optional androidx.privacysandbox.ads.adservices.common.AdFilters? adFilters);
+ ctor @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public AdData(android.net.Uri renderUri, String metadata, optional java.util.Set<java.lang.Integer> adCounterKeys, optional androidx.privacysandbox.ads.adservices.common.AdFilters? adFilters, optional String? adRenderId);
+ method public java.util.Set<java.lang.Integer> getAdCounterKeys();
+ method public androidx.privacysandbox.ads.adservices.common.AdFilters? getAdFilters();
+ method public String? getAdRenderId();
+ method public String getMetadata();
+ method public android.net.Uri getRenderUri();
+ property public final java.util.Set<java.lang.Integer> adCounterKeys;
+ property public final androidx.privacysandbox.ads.adservices.common.AdFilters? adFilters;
+ property public final String? adRenderId;
+ property public final String metadata;
+ property public final android.net.Uri renderUri;
+ }
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class AdFilters {
+ ctor public AdFilters(androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters? frequencyCapFilters);
+ method public androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters? getFrequencyCapFilters();
+ property public final androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters? frequencyCapFilters;
+ }
+
+ public final class AdSelectionSignals {
+ ctor public AdSelectionSignals(String signals);
+ method public String getSignals();
+ property public final String signals;
+ }
+
+ public final class AdTechIdentifier {
+ ctor public AdTechIdentifier(String identifier);
+ method public String getIdentifier();
+ property public final String identifier;
+ }
+
+ public sealed interface ExperimentalFeatures {
+ }
+
+ @SuppressCompatibility @kotlin.RequiresOptIn(message="The Ext10 API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.Ext10OptIn {
+ }
+
+ @SuppressCompatibility @kotlin.RequiresOptIn(message="The Ext11 API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.Ext11OptIn {
+ }
+
+ @SuppressCompatibility @kotlin.RequiresOptIn(message="The Ext8 API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.Ext8OptIn {
+ }
+
+ @SuppressCompatibility @kotlin.RequiresOptIn(message="This API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.RegisterSourceOptIn {
+ }
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class FrequencyCapFilters {
+ ctor public FrequencyCapFilters();
+ ctor public FrequencyCapFilters(optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents);
+ ctor public FrequencyCapFilters(optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents);
+ ctor public FrequencyCapFilters(optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForViewEvents);
+ ctor public FrequencyCapFilters(optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForViewEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForClickEvents);
+ method public java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> getKeyedFrequencyCapsForClickEvents();
+ method public java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> getKeyedFrequencyCapsForImpressionEvents();
+ method public java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> getKeyedFrequencyCapsForViewEvents();
+ method public java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> getKeyedFrequencyCapsForWinEvents();
+ property public final java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForClickEvents;
+ property public final java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents;
+ property public final java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForViewEvents;
+ property public final java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents;
+ field public static final int AD_EVENT_TYPE_CLICK = 3; // 0x3
+ field public static final int AD_EVENT_TYPE_IMPRESSION = 1; // 0x1
+ field public static final int AD_EVENT_TYPE_VIEW = 2; // 0x2
+ field public static final int AD_EVENT_TYPE_WIN = 0; // 0x0
+ field public static final androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters.Companion Companion;
+ }
+
+ public static final class FrequencyCapFilters.Companion {
+ }
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class KeyedFrequencyCap {
+ ctor public KeyedFrequencyCap(int adCounterKey, int maxCount, java.time.Duration interval);
+ method public int getAdCounterKey();
+ method public java.time.Duration getInterval();
+ method public int getMaxCount();
+ property public final int adCounterKey;
+ property public final java.time.Duration interval;
+ property public final int maxCount;
+ }
+
+}
+
+package androidx.privacysandbox.ads.adservices.customaudience {
+
+ public final class CustomAudience {
+ ctor public CustomAudience(androidx.privacysandbox.ads.adservices.common.AdTechIdentifier buyer, String name, android.net.Uri dailyUpdateUri, android.net.Uri biddingLogicUri, java.util.List<androidx.privacysandbox.ads.adservices.common.AdData> ads, optional java.time.Instant? activationTime, optional java.time.Instant? expirationTime, optional androidx.privacysandbox.ads.adservices.common.AdSelectionSignals? userBiddingSignals, optional androidx.privacysandbox.ads.adservices.customaudience.TrustedBiddingData? trustedBiddingSignals);
+ method public java.time.Instant? getActivationTime();
+ method public java.util.List<androidx.privacysandbox.ads.adservices.common.AdData> getAds();
+ method public android.net.Uri getBiddingLogicUri();
+ method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier getBuyer();
+ method public android.net.Uri getDailyUpdateUri();
+ method public java.time.Instant? getExpirationTime();
+ method public String getName();
+ method public androidx.privacysandbox.ads.adservices.customaudience.TrustedBiddingData? getTrustedBiddingSignals();
+ method public androidx.privacysandbox.ads.adservices.common.AdSelectionSignals? getUserBiddingSignals();
+ property public final java.time.Instant? activationTime;
+ property public final java.util.List<androidx.privacysandbox.ads.adservices.common.AdData> ads;
+ property public final android.net.Uri biddingLogicUri;
+ property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier buyer;
+ property public final android.net.Uri dailyUpdateUri;
+ property public final java.time.Instant? expirationTime;
+ property public final String name;
+ property public final androidx.privacysandbox.ads.adservices.customaudience.TrustedBiddingData? trustedBiddingSignals;
+ property public final androidx.privacysandbox.ads.adservices.common.AdSelectionSignals? userBiddingSignals;
+ }
+
+ public static final class CustomAudience.Builder {
+ ctor public CustomAudience.Builder(androidx.privacysandbox.ads.adservices.common.AdTechIdentifier buyer, String name, android.net.Uri dailyUpdateUri, android.net.Uri biddingLogicUri, java.util.List<androidx.privacysandbox.ads.adservices.common.AdData> ads);
+ method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience build();
+ method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience.Builder setActivationTime(java.time.Instant activationTime);
+ method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience.Builder setAds(java.util.List<androidx.privacysandbox.ads.adservices.common.AdData> ads);
+ method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience.Builder setBiddingLogicUri(android.net.Uri biddingLogicUri);
+ method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience.Builder setBuyer(androidx.privacysandbox.ads.adservices.common.AdTechIdentifier buyer);
+ method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience.Builder setDailyUpdateUri(android.net.Uri dailyUpdateUri);
+ method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience.Builder setExpirationTime(java.time.Instant expirationTime);
+ method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience.Builder setName(String name);
+ method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience.Builder setTrustedBiddingData(androidx.privacysandbox.ads.adservices.customaudience.TrustedBiddingData trustedBiddingSignals);
+ method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience.Builder setUserBiddingSignals(androidx.privacysandbox.ads.adservices.common.AdSelectionSignals userBiddingSignals);
+ }
+
+ public abstract class CustomAudienceManager {
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract suspend Object? fetchAndJoinCustomAudience(androidx.privacysandbox.ads.adservices.customaudience.FetchAndJoinCustomAudienceRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract suspend Object? joinCustomAudience(androidx.privacysandbox.ads.adservices.customaudience.JoinCustomAudienceRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract suspend Object? leaveCustomAudience(androidx.privacysandbox.ads.adservices.customaudience.LeaveCustomAudienceRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method public static final androidx.privacysandbox.ads.adservices.customaudience.CustomAudienceManager? obtain(android.content.Context context);
+ field public static final androidx.privacysandbox.ads.adservices.customaudience.CustomAudienceManager.Companion Companion;
+ }
+
+ public static final class CustomAudienceManager.Companion {
+ method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudienceManager? obtain(android.content.Context context);
+ }
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class FetchAndJoinCustomAudienceRequest {
+ ctor public FetchAndJoinCustomAudienceRequest(android.net.Uri fetchUri, optional String? name, optional java.time.Instant? activationTime, optional java.time.Instant? expirationTime, optional androidx.privacysandbox.ads.adservices.common.AdSelectionSignals? userBiddingSignals);
+ method public java.time.Instant? getActivationTime();
+ method public java.time.Instant? getExpirationTime();
+ method public android.net.Uri getFetchUri();
+ method public String? getName();
+ method public androidx.privacysandbox.ads.adservices.common.AdSelectionSignals? getUserBiddingSignals();
+ property public final java.time.Instant? activationTime;
+ property public final java.time.Instant? expirationTime;
+ property public final android.net.Uri fetchUri;
+ property public final String? name;
+ property public final androidx.privacysandbox.ads.adservices.common.AdSelectionSignals? userBiddingSignals;
+ }
+
+ public final class JoinCustomAudienceRequest {
+ ctor public JoinCustomAudienceRequest(androidx.privacysandbox.ads.adservices.customaudience.CustomAudience customAudience);
+ method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience getCustomAudience();
+ property public final androidx.privacysandbox.ads.adservices.customaudience.CustomAudience customAudience;
+ }
+
+ public final class LeaveCustomAudienceRequest {
+ ctor public LeaveCustomAudienceRequest(androidx.privacysandbox.ads.adservices.common.AdTechIdentifier buyer, String name);
+ method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier getBuyer();
+ method public String getName();
+ property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier buyer;
+ property public final String name;
+ }
+
+ public final class TrustedBiddingData {
+ ctor public TrustedBiddingData(android.net.Uri trustedBiddingUri, java.util.List<java.lang.String> trustedBiddingKeys);
+ method public java.util.List<java.lang.String> getTrustedBiddingKeys();
+ method public android.net.Uri getTrustedBiddingUri();
+ property public final java.util.List<java.lang.String> trustedBiddingKeys;
+ property public final android.net.Uri trustedBiddingUri;
+ }
+
+}
+
+package androidx.privacysandbox.ads.adservices.measurement {
+
+ @RequiresApi(android.os.Build.VERSION_CODES.O) public final class DeletionRequest {
+ ctor public DeletionRequest(int deletionMode, int matchBehavior, optional java.time.Instant start, optional java.time.Instant end, optional java.util.List<? extends android.net.Uri> domainUris, optional java.util.List<? extends android.net.Uri> originUris);
+ method public int getDeletionMode();
+ method public java.util.List<android.net.Uri> getDomainUris();
+ method public java.time.Instant getEnd();
+ method public int getMatchBehavior();
+ method public java.util.List<android.net.Uri> getOriginUris();
+ method public java.time.Instant getStart();
+ property public final int deletionMode;
+ property public final java.util.List<android.net.Uri> domainUris;
+ property public final java.time.Instant end;
+ property public final int matchBehavior;
+ property public final java.util.List<android.net.Uri> originUris;
+ property public final java.time.Instant start;
+ field public static final androidx.privacysandbox.ads.adservices.measurement.DeletionRequest.Companion Companion;
+ field public static final int DELETION_MODE_ALL = 0; // 0x0
+ field public static final int DELETION_MODE_EXCLUDE_INTERNAL_DATA = 1; // 0x1
+ field public static final int MATCH_BEHAVIOR_DELETE = 0; // 0x0
+ field public static final int MATCH_BEHAVIOR_PRESERVE = 1; // 0x1
+ }
+
+ @RequiresApi(android.os.Build.VERSION_CODES.O) public static final class DeletionRequest.Builder {
+ ctor public DeletionRequest.Builder(int deletionMode, int matchBehavior);
+ method public androidx.privacysandbox.ads.adservices.measurement.DeletionRequest build();
+ method public androidx.privacysandbox.ads.adservices.measurement.DeletionRequest.Builder setDomainUris(java.util.List<? extends android.net.Uri> domainUris);
+ method public androidx.privacysandbox.ads.adservices.measurement.DeletionRequest.Builder setEnd(java.time.Instant end);
+ method public androidx.privacysandbox.ads.adservices.measurement.DeletionRequest.Builder setOriginUris(java.util.List<? extends android.net.Uri> originUris);
+ method public androidx.privacysandbox.ads.adservices.measurement.DeletionRequest.Builder setStart(java.time.Instant start);
+ }
+
+ public static final class DeletionRequest.Companion {
+ }
+
+ public abstract class MeasurementManager {
+ ctor public MeasurementManager();
+ method public abstract suspend Object? deleteRegistrations(androidx.privacysandbox.ads.adservices.measurement.DeletionRequest deletionRequest, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract suspend Object? getMeasurementApiStatus(kotlin.coroutines.Continuation<? super java.lang.Integer>);
+ method public static final androidx.privacysandbox.ads.adservices.measurement.MeasurementManager? obtain(android.content.Context context);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract suspend Object? registerSource(android.net.Uri attributionSource, android.view.InputEvent? inputEvent, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.RegisterSourceOptIn public abstract suspend Object? registerSource(androidx.privacysandbox.ads.adservices.measurement.SourceRegistrationRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract suspend Object? registerTrigger(android.net.Uri trigger, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract suspend Object? registerWebSource(androidx.privacysandbox.ads.adservices.measurement.WebSourceRegistrationRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract suspend Object? registerWebTrigger(androidx.privacysandbox.ads.adservices.measurement.WebTriggerRegistrationRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ field public static final androidx.privacysandbox.ads.adservices.measurement.MeasurementManager.Companion Companion;
+ field public static final int MEASUREMENT_API_STATE_DISABLED = 0; // 0x0
+ field public static final int MEASUREMENT_API_STATE_ENABLED = 1; // 0x1
+ }
+
+ public static final class MeasurementManager.Companion {
+ method public androidx.privacysandbox.ads.adservices.measurement.MeasurementManager? obtain(android.content.Context context);
+ }
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.RegisterSourceOptIn public final class SourceRegistrationRequest {
+ ctor public SourceRegistrationRequest(java.util.List<? extends android.net.Uri> registrationUris, optional android.view.InputEvent? inputEvent);
+ method public android.view.InputEvent? getInputEvent();
+ method public java.util.List<android.net.Uri> getRegistrationUris();
+ property public final android.view.InputEvent? inputEvent;
+ property public final java.util.List<android.net.Uri> registrationUris;
+ }
+
+ public static final class SourceRegistrationRequest.Builder {
+ ctor public SourceRegistrationRequest.Builder(java.util.List<? extends android.net.Uri> registrationUris);
+ method public androidx.privacysandbox.ads.adservices.measurement.SourceRegistrationRequest build();
+ method public androidx.privacysandbox.ads.adservices.measurement.SourceRegistrationRequest.Builder setInputEvent(android.view.InputEvent inputEvent);
+ }
+
+ public final class WebSourceParams {
+ ctor public WebSourceParams(android.net.Uri registrationUri, boolean debugKeyAllowed);
+ method public boolean getDebugKeyAllowed();
+ method public android.net.Uri getRegistrationUri();
+ property public final boolean debugKeyAllowed;
+ property public final android.net.Uri registrationUri;
+ }
+
+ public final class WebSourceRegistrationRequest {
+ ctor public WebSourceRegistrationRequest(java.util.List<androidx.privacysandbox.ads.adservices.measurement.WebSourceParams> webSourceParams, android.net.Uri topOriginUri, optional android.view.InputEvent? inputEvent, optional android.net.Uri? appDestination, optional android.net.Uri? webDestination, optional android.net.Uri? verifiedDestination);
+ method public android.net.Uri? getAppDestination();
+ method public android.view.InputEvent? getInputEvent();
+ method public android.net.Uri getTopOriginUri();
+ method public android.net.Uri? getVerifiedDestination();
+ method public android.net.Uri? getWebDestination();
+ method public java.util.List<androidx.privacysandbox.ads.adservices.measurement.WebSourceParams> getWebSourceParams();
+ property public final android.net.Uri? appDestination;
+ property public final android.view.InputEvent? inputEvent;
+ property public final android.net.Uri topOriginUri;
+ property public final android.net.Uri? verifiedDestination;
+ property public final android.net.Uri? webDestination;
+ property public final java.util.List<androidx.privacysandbox.ads.adservices.measurement.WebSourceParams> webSourceParams;
+ }
+
+ public static final class WebSourceRegistrationRequest.Builder {
+ ctor public WebSourceRegistrationRequest.Builder(java.util.List<androidx.privacysandbox.ads.adservices.measurement.WebSourceParams> webSourceParams, android.net.Uri topOriginUri);
+ method public androidx.privacysandbox.ads.adservices.measurement.WebSourceRegistrationRequest build();
+ method public androidx.privacysandbox.ads.adservices.measurement.WebSourceRegistrationRequest.Builder setAppDestination(android.net.Uri? appDestination);
+ method public androidx.privacysandbox.ads.adservices.measurement.WebSourceRegistrationRequest.Builder setInputEvent(android.view.InputEvent inputEvent);
+ method public androidx.privacysandbox.ads.adservices.measurement.WebSourceRegistrationRequest.Builder setVerifiedDestination(android.net.Uri? verifiedDestination);
+ method public androidx.privacysandbox.ads.adservices.measurement.WebSourceRegistrationRequest.Builder setWebDestination(android.net.Uri? webDestination);
+ }
+
+ public final class WebTriggerParams {
+ ctor public WebTriggerParams(android.net.Uri registrationUri, boolean debugKeyAllowed);
+ method public boolean getDebugKeyAllowed();
+ method public android.net.Uri getRegistrationUri();
+ property public final boolean debugKeyAllowed;
+ property public final android.net.Uri registrationUri;
+ }
+
+ public final class WebTriggerRegistrationRequest {
+ ctor public WebTriggerRegistrationRequest(java.util.List<androidx.privacysandbox.ads.adservices.measurement.WebTriggerParams> webTriggerParams, android.net.Uri destination);
+ method public android.net.Uri getDestination();
+ method public java.util.List<androidx.privacysandbox.ads.adservices.measurement.WebTriggerParams> getWebTriggerParams();
+ property public final android.net.Uri destination;
+ property public final java.util.List<androidx.privacysandbox.ads.adservices.measurement.WebTriggerParams> webTriggerParams;
+ }
+
+}
+
+package androidx.privacysandbox.ads.adservices.topics {
+
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext11OptIn public final class EncryptedTopic {
+ ctor public EncryptedTopic(byte[] encryptedTopic, String keyIdentifier, byte[] encapsulatedKey);
+ method public byte[] getEncapsulatedKey();
+ method public byte[] getEncryptedTopic();
+ method public String getKeyIdentifier();
+ property public final byte[] encapsulatedKey;
+ property public final byte[] encryptedTopic;
+ property public final String keyIdentifier;
+ }
+
+ public final class GetTopicsRequest {
+ ctor public GetTopicsRequest(optional String adsSdkName, optional boolean shouldRecordObservation);
+ method public String getAdsSdkName();
+ method public boolean shouldRecordObservation();
+ property public final String adsSdkName;
+ property public final boolean shouldRecordObservation;
+ }
+
+ public static final class GetTopicsRequest.Builder {
+ ctor public GetTopicsRequest.Builder();
+ method public androidx.privacysandbox.ads.adservices.topics.GetTopicsRequest build();
+ method public androidx.privacysandbox.ads.adservices.topics.GetTopicsRequest.Builder setAdsSdkName(String adsSdkName);
+ method public androidx.privacysandbox.ads.adservices.topics.GetTopicsRequest.Builder setShouldRecordObservation(boolean shouldRecordObservation);
+ }
+
+ public final class GetTopicsResponse {
+ ctor public GetTopicsResponse(java.util.List<androidx.privacysandbox.ads.adservices.topics.Topic> topics);
+ ctor @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext11OptIn public GetTopicsResponse(java.util.List<androidx.privacysandbox.ads.adservices.topics.Topic> topics, java.util.List<androidx.privacysandbox.ads.adservices.topics.EncryptedTopic> encryptedTopics);
+ method public java.util.List<androidx.privacysandbox.ads.adservices.topics.EncryptedTopic> getEncryptedTopics();
+ method public java.util.List<androidx.privacysandbox.ads.adservices.topics.Topic> getTopics();
+ property public final java.util.List<androidx.privacysandbox.ads.adservices.topics.EncryptedTopic> encryptedTopics;
+ property public final java.util.List<androidx.privacysandbox.ads.adservices.topics.Topic> topics;
+ }
+
+ public final class Topic {
+ ctor public Topic(long taxonomyVersion, long modelVersion, int topicId);
+ method public long getModelVersion();
+ method public long getTaxonomyVersion();
+ method public int getTopicId();
+ property public final long modelVersion;
+ property public final long taxonomyVersion;
+ property public final int topicId;
+ }
+
+ public abstract class TopicsManager {
+ method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_TOPICS) public abstract suspend Object? getTopics(androidx.privacysandbox.ads.adservices.topics.GetTopicsRequest request, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.topics.GetTopicsResponse>);
+ method public static final androidx.privacysandbox.ads.adservices.topics.TopicsManager? obtain(android.content.Context context);
+ field public static final androidx.privacysandbox.ads.adservices.topics.TopicsManager.Companion Companion;
+ }
+
+ public static final class TopicsManager.Companion {
+ method public androidx.privacysandbox.ads.adservices.topics.TopicsManager? obtain(android.content.Context context);
+ }
+
+}
+
diff --git a/privacysandbox/ads/ads-adservices/api/restricted_current.txt b/privacysandbox/ads/ads-adservices/api/restricted_current.txt
index 32149c9..7574be5 100644
--- a/privacysandbox/ads/ads-adservices/api/restricted_current.txt
+++ b/privacysandbox/ads/ads-adservices/api/restricted_current.txt
@@ -218,6 +218,9 @@
@SuppressCompatibility @kotlin.RequiresOptIn(message="The Ext10 API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.Ext10OptIn {
}
+ @SuppressCompatibility @kotlin.RequiresOptIn(message="The Ext11 API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.Ext11OptIn {
+ }
+
@SuppressCompatibility @kotlin.RequiresOptIn(message="The Ext8 API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.Ext8OptIn {
}
@@ -469,6 +472,16 @@
package androidx.privacysandbox.ads.adservices.topics {
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext11OptIn public final class EncryptedTopic {
+ ctor public EncryptedTopic(byte[] encryptedTopic, String keyIdentifier, byte[] encapsulatedKey);
+ method public byte[] getEncapsulatedKey();
+ method public byte[] getEncryptedTopic();
+ method public String getKeyIdentifier();
+ property public final byte[] encapsulatedKey;
+ property public final byte[] encryptedTopic;
+ property public final String keyIdentifier;
+ }
+
public final class GetTopicsRequest {
ctor public GetTopicsRequest(optional String adsSdkName, optional boolean shouldRecordObservation);
method public String getAdsSdkName();
@@ -486,7 +499,10 @@
public final class GetTopicsResponse {
ctor public GetTopicsResponse(java.util.List<androidx.privacysandbox.ads.adservices.topics.Topic> topics);
+ ctor @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext11OptIn public GetTopicsResponse(java.util.List<androidx.privacysandbox.ads.adservices.topics.Topic> topics, java.util.List<androidx.privacysandbox.ads.adservices.topics.EncryptedTopic> encryptedTopics);
+ method public java.util.List<androidx.privacysandbox.ads.adservices.topics.EncryptedTopic> getEncryptedTopics();
method public java.util.List<androidx.privacysandbox.ads.adservices.topics.Topic> getTopics();
+ property public final java.util.List<androidx.privacysandbox.ads.adservices.topics.EncryptedTopic> encryptedTopics;
property public final java.util.List<androidx.privacysandbox.ads.adservices.topics.Topic> topics;
}
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/topics/EncryptedTopicTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/topics/EncryptedTopicTest.kt
new file mode 100644
index 0000000..b374d2e
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/topics/EncryptedTopicTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2024 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.ads.adservices.topics
+
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@ExperimentalFeatures.Ext11OptIn
+class EncryptedTopicTest {
+ @Test
+ fun testToString() {
+ val result =
+ "EncryptedTopic { EncryptedTopic=encryptedTopic1, KeyIdentifier=publicKey1," +
+ " EncapsulatedKey=encapsulatedKey1 }"
+ var encryptedTopic =
+ EncryptedTopic(
+ "encryptedTopic1".toByteArray(),
+ "publicKey1",
+ "encapsulatedKey1".toByteArray(),
+ )
+ Truth.assertThat(encryptedTopic.toString()).isEqualTo(result)
+ }
+
+ @Test
+ fun testEquals() {
+ var encryptedTopic1 =
+ EncryptedTopic(
+ "encryptedTopic".toByteArray(),
+ "publicKey",
+ "encapsulatedKey".toByteArray(),
+ )
+ var encryptedTopic2 =
+ EncryptedTopic(
+ "encryptedTopic".toByteArray(),
+ "publicKey",
+ "encapsulatedKey".toByteArray(),
+ )
+
+ Truth.assertThat(encryptedTopic1 == encryptedTopic2).isTrue()
+ }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/topics/GetTopicsResponseHelperTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/topics/GetTopicsResponseHelperTest.kt
index 27d3385..5f95b62 100644
--- a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/topics/GetTopicsResponseHelperTest.kt
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/topics/GetTopicsResponseHelperTest.kt
@@ -16,9 +16,11 @@
package androidx.privacysandbox.ads.adservices.topics
+import android.adservices.topics.EncryptedTopic
import android.adservices.topics.GetTopicsResponse
import android.adservices.topics.Topic
import android.annotation.SuppressLint
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -31,16 +33,21 @@
@SuppressLint("NewApi")
@SmallTest
@RunWith(AndroidJUnit4::class)
+@ExperimentalFeatures.Ext11OptIn
class GetTopicsResponseHelperTest {
private val mValidAdServicesSdkExt4Version = AdServicesInfo.adServicesVersion() >= 4
- private val mValidAdExtServicesSdkExtVersion = AdServicesInfo.extServicesVersionS() >= 9
+ private val mValidAdServicesSdkExt11Version = AdServicesInfo.adServicesVersion() >= 11
+ private val mValidAdExtServicesSdkExt9Version = AdServicesInfo.extServicesVersionS() >= 9
+ private val mValidAdExtServicesSdkExt11Version = AdServicesInfo.extServicesVersionS() >= 11
// Verify legacy tests with just plaintext topics
@Suppress("DEPRECATION")
@Test
fun testResponse() {
- Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
- mValidAdServicesSdkExt4Version || mValidAdExtServicesSdkExtVersion)
+ Assume.assumeTrue(
+ "minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+ mValidAdServicesSdkExt4Version || mValidAdExtServicesSdkExt9Version,
+ )
var topic1 = Topic(3, 7, 10023)
var topic2 = Topic(3, 7, 10024)
@@ -54,4 +61,41 @@
androidx.privacysandbox.ads.adservices.topics.Topic(3, 7, 10023),
)
}
+
+ @Test
+ fun testResponseWithEncryptedTopics() {
+ Assume.assumeTrue(
+ "minSdkVersion = API 33 ext 11 or API 31/32 ext 11",
+ mValidAdServicesSdkExt11Version || mValidAdExtServicesSdkExt11Version,
+ )
+
+ var topic1 = Topic(3, 7, 10023)
+ var topic2 = Topic(3, 7, 10024)
+ var encryptedTopic1 =
+ EncryptedTopic(
+ "encryptedTopic".toByteArray(),
+ "publicKey",
+ "encapsulatedKey".toByteArray(),
+ )
+
+ var response = GetTopicsResponse.Builder(listOf(topic1, topic2),
+ listOf(encryptedTopic1)).build()
+ var convertedResponse =
+ GetTopicsResponseHelper.convertResponseWithEncryptedTopics(response)
+
+ assertEquals(2, convertedResponse.topics.size)
+ assertEquals(1, convertedResponse.encryptedTopics.size)
+ assertContains(
+ convertedResponse.topics,
+ androidx.privacysandbox.ads.adservices.topics.Topic(3, 7, 10023),
+ )
+ assertContains(
+ convertedResponse.encryptedTopics,
+ androidx.privacysandbox.ads.adservices.topics.EncryptedTopic(
+ "encryptedTopic".toByteArray(),
+ "publicKey",
+ "encapsulatedKey".toByteArray(),
+ ),
+ )
+ }
}
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/topics/GetTopicsResponseTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/topics/GetTopicsResponseTest.kt
index 25c0699..cab8b28 100644
--- a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/topics/GetTopicsResponseTest.kt
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/topics/GetTopicsResponseTest.kt
@@ -16,6 +16,7 @@
package androidx.privacysandbox.ads.adservices.topics
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth
@@ -24,12 +25,14 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@ExperimentalFeatures.Ext11OptIn
class GetTopicsResponseTest {
@Test
- fun testToString() {
- val topicsString = "Topic { TaxonomyVersion=1, ModelVersion=10, TopicCode=100 }, " +
- "Topic { TaxonomyVersion=2, ModelVersion=20, TopicCode=200 }"
- val result = "Topics=[$topicsString]"
+ fun testToString_onlyPlaintextTopics() {
+ val topicsString =
+ "Topic { TaxonomyVersion=1, ModelVersion=10, TopicCode=100 }, " +
+ "Topic { TaxonomyVersion=2, ModelVersion=20, TopicCode=200 }"
+ val result = "GetTopicsResponse: Topics=[$topicsString], EncryptedTopics=[]"
val topic1 = Topic(1, 10, 100)
var topic2 = Topic(2, 20, 200)
@@ -38,11 +41,69 @@
}
@Test
- fun testEquals() {
+ fun testToString_plaintextAndEncryptedTopics() {
+ val topicsString =
+ "Topic { TaxonomyVersion=1, ModelVersion=10, TopicCode=100 }, " +
+ "Topic { TaxonomyVersion=2, ModelVersion=20, TopicCode=200 }"
+ val encryptedTopicsString =
+ "EncryptedTopic { EncryptedTopic=encryptedTopic1, KeyIdentifier=publicKey1," +
+ " EncapsulatedKey=encapsulatedKey1 }, EncryptedTopic " +
+ "{ EncryptedTopic=encryptedTopic2, KeyIdentifier=publicKey2," +
+ " EncapsulatedKey=encapsulatedKey2 }"
+
+ val result =
+ "GetTopicsResponse: Topics=[$topicsString], EncryptedTopics=[$encryptedTopicsString]"
+
+ val topic1 = Topic(1, 10, 100)
+ var topic2 = Topic(2, 20, 200)
+ var encryptedTopic1 =
+ EncryptedTopic(
+ "encryptedTopic1".toByteArray(),
+ "publicKey1",
+ "encapsulatedKey1".toByteArray(),
+ )
+ var encryptedTopic2 =
+ EncryptedTopic(
+ "encryptedTopic2".toByteArray(),
+ "publicKey2",
+ "encapsulatedKey2".toByteArray(),
+ )
+
+ val response1 =
+ GetTopicsResponse(listOf(topic1, topic2), listOf(encryptedTopic1, encryptedTopic2))
+ Truth.assertThat(response1.toString()).isEqualTo(result)
+ }
+
+ @Test
+ fun testEquals_onlyPlaintextTopics() {
val topic1 = Topic(1, 10, 100)
var topic2 = Topic(2, 20, 200)
val response1 = GetTopicsResponse(listOf(topic1, topic2))
val response2 = GetTopicsResponse(listOf(topic2, topic1))
Truth.assertThat(response1 == response2).isTrue()
}
+
+ @Test
+ fun testEquals_plaintextAndEncryptedTopics() {
+ val topic1 = Topic(1, 10, 100)
+ var topic2 = Topic(2, 20, 200)
+ var encryptedTopic1 =
+ EncryptedTopic(
+ "encryptedTopic1".toByteArray(),
+ "publicKey1",
+ "encapsulatedKey1".toByteArray(),
+ )
+ var encryptedTopic2 =
+ EncryptedTopic(
+ "encryptedTopic2".toByteArray(),
+ "publicKey2",
+ "encapsulatedKey2".toByteArray(),
+ )
+
+ val response1 =
+ GetTopicsResponse(listOf(topic1, topic2), listOf(encryptedTopic1, encryptedTopic2))
+ val response2 =
+ GetTopicsResponse(listOf(topic2, topic1), listOf(encryptedTopic2, encryptedTopic1))
+ Truth.assertThat(response1 == response2).isTrue()
+ }
}
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/topics/TopicsManagerTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/topics/TopicsManagerTest.kt
index b7c0324..7b00f71 100644
--- a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/topics/TopicsManagerTest.kt
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/topics/TopicsManagerTest.kt
@@ -16,10 +16,12 @@
package androidx.privacysandbox.ads.adservices.topics
+import android.adservices.topics.EncryptedTopic
import android.adservices.topics.Topic
import android.adservices.topics.TopicsManager
import android.content.Context
import android.os.OutcomeReceiver
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo
import androidx.privacysandbox.ads.adservices.topics.TopicsManager.Companion.obtain
import androidx.test.core.app.ApplicationProvider
@@ -50,18 +52,21 @@
@SuppressWarnings("NewApi")
@RunWith(AndroidJUnit4::class)
@SdkSuppress(minSdkVersion = 30)
+@ExperimentalFeatures.Ext11OptIn
class TopicsManagerTest {
private var mSession: StaticMockitoSession? = null
private val mValidAdServicesSdkExt4Version = AdServicesInfo.adServicesVersion() >= 4
private val mValidAdServicesSdkExt5Version = AdServicesInfo.adServicesVersion() >= 5
- private val mValidAdExtServicesSdkExtVersion = AdServicesInfo.extServicesVersionS() >= 9
+ private val mValidAdServicesSdkExt11Version = AdServicesInfo.adServicesVersion() >= 11
+ private val mValidAdExtServicesSdkExt9Version = AdServicesInfo.extServicesVersionS() >= 9
+ private val mValidAdExtServicesSdkExt11Version = AdServicesInfo.extServicesVersionS() >= 11
@Before
fun setUp() {
mContext = spy(ApplicationProvider.getApplicationContext<Context>())
- if (mValidAdExtServicesSdkExtVersion) {
+ if (mValidAdExtServicesSdkExt9Version) {
// setup a mockitoSession to return the mocked manager
// when the static method .get() is called
mSession = ExtendedMockito.mockitoSession()
@@ -80,13 +85,13 @@
@SdkSuppress(maxSdkVersion = 33, minSdkVersion = 30)
fun testTopicsOlderVersions() {
Assume.assumeTrue("maxSdkVersion = API 33 ext 3", !mValidAdServicesSdkExt4Version)
- Assume.assumeTrue("maxSdkVersion = API 31/32 ext 8", !mValidAdExtServicesSdkExtVersion)
+ Assume.assumeTrue("maxSdkVersion = API 31/32 ext 8", !mValidAdExtServicesSdkExt9Version)
assertThat(obtain(mContext)).isNull()
}
@Test
fun testTopicsManagerNoClassDefFoundError() {
- Assume.assumeTrue("minSdkVersion = API 31/32 ext 9", mValidAdExtServicesSdkExtVersion);
+ Assume.assumeTrue("minSdkVersion = API 31/32 ext 9", mValidAdExtServicesSdkExt9Version);
`when`(TopicsManager.get(any())).thenThrow(NoClassDefFoundError())
assertThat(obtain(mContext)).isNull()
@@ -94,10 +99,12 @@
@Test
fun testTopicsAsync() {
- Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
- mValidAdServicesSdkExt4Version || mValidAdExtServicesSdkExtVersion)
+ Assume.assumeTrue(
+ "minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+ mValidAdServicesSdkExt4Version || mValidAdExtServicesSdkExt9Version
+ )
- val topicsManager = mockTopicsManager(mContext, mValidAdExtServicesSdkExtVersion)
+ val topicsManager = mockTopicsManager(mContext, mValidAdExtServicesSdkExt9Version)
setupTopicsResponse(topicsManager)
val managerCompat = obtain(mContext)
@@ -124,11 +131,46 @@
}
@Test
- fun testTopicsAsyncPreviewSupported() {
- Assume.assumeTrue("minSdkVersion = API 33 ext 5 or API 31/32 ext 9",
- mValidAdServicesSdkExt5Version || mValidAdExtServicesSdkExtVersion)
+ fun testEncryptedTopicsAsync() {
+ Assume.assumeTrue(
+ "minSdkVersion = API 33 ext 11 or API 31/32 ext 11",
+ mValidAdServicesSdkExt11Version || mValidAdExtServicesSdkExt11Version,
+ )
- val topicsManager = mockTopicsManager(mContext, mValidAdExtServicesSdkExtVersion)
+ val topicsManager = mockTopicsManager(mContext, false)
+ setupEncryptedTopicsResponse(topicsManager)
+ val managerCompat = obtain(mContext)
+
+ // Actually invoke the compat code.
+ val result = runBlocking {
+ val request = GetTopicsRequest.Builder()
+ .setAdsSdkName(mSdkName)
+ .setShouldRecordObservation(true)
+ .build()
+
+ managerCompat!!.getTopics(request)
+ }
+
+ // Verify that the compat code was invoked correctly.
+ val captor = ArgumentCaptor
+ .forClass(android.adservices.topics.GetTopicsRequest::class.java)
+ verify(topicsManager).getTopics(captor.capture(), any(), any())
+
+ // Verify that the request that the compat code makes to the platform is correct.
+ verifyRequest(captor.value)
+
+ // Verify that the result of the compat call is correct.
+ verifyEncryptedResponse(result)
+ }
+
+ @Test
+ fun testTopicsAsyncPreviewSupported() {
+ Assume.assumeTrue(
+ "minSdkVersion = API 33 ext 5 or API 31/32 ext 9",
+ mValidAdServicesSdkExt5Version || mValidAdExtServicesSdkExt9Version
+ )
+
+ val topicsManager = mockTopicsManager(mContext, mValidAdExtServicesSdkExt9Version)
setupTopicsResponse(topicsManager)
val managerCompat = obtain(mContext)
@@ -188,6 +230,37 @@
)
}
+ @Suppress("deprecation")
+ private fun setupEncryptedTopicsResponse(topicsManager: TopicsManager) {
+ // Set up the response that TopicsManager will return when the compat code calls it.
+ val topic1 = Topic(1, 1, 1)
+ val topic2 = Topic(2, 2, 2)
+ var encryptedTopic1 = EncryptedTopic(
+ "encryptedTopic1".toByteArray(),
+ "publicKey1",
+ "encapsulatedKey1".toByteArray()
+ )
+ var encryptedTopic2 = EncryptedTopic(
+ "encryptedTopic2".toByteArray(),
+ "publicKey2",
+ "encapsulatedKey2".toByteArray()
+ )
+
+ val topics = listOf(topic1, topic2)
+ val encryptedTopics = listOf(encryptedTopic1, encryptedTopic2)
+ val response =
+ android.adservices.topics.GetTopicsResponse.Builder(topics, encryptedTopics).build()
+ val answer = { args: InvocationOnMock ->
+ val receiver = args.getArgument<
+ OutcomeReceiver<android.adservices.topics.GetTopicsResponse, Exception>>(2)
+ receiver.onResult(response)
+ null
+ }
+ doAnswer(answer).`when`(topicsManager).getTopics(
+ any(), any(), any()
+ )
+ }
+
private fun verifyRequest(topicsRequest: android.adservices.topics.GetTopicsRequest) {
// Set up the request that we expect the compat code to invoke.
val expectedRequest =
@@ -209,6 +282,33 @@
Assert.assertEquals(expectedRequest.adsSdkName, topicsRequest.adsSdkName)
}
+ private fun verifyEncryptedResponse(getTopicsResponse: GetTopicsResponse) {
+ Assert.assertEquals(2, getTopicsResponse.encryptedTopics.size)
+ val encryptedTopic1 = getTopicsResponse.encryptedTopics[0]
+ Assert.assertArrayEquals(
+ "encryptedTopic1".toByteArray(),
+ encryptedTopic1.encryptedTopic
+ )
+ Assert.assertEquals("publicKey1", encryptedTopic1.keyIdentifier)
+ Assert.assertArrayEquals(
+ "encapsulatedKey1".toByteArray(),
+ encryptedTopic1.encapsulatedKey
+ )
+ val encryptedTopic2 = getTopicsResponse.encryptedTopics[1]
+ Assert.assertArrayEquals(
+ "encryptedTopic2".toByteArray(),
+ encryptedTopic2.encryptedTopic
+ )
+ Assert.assertEquals("publicKey2", encryptedTopic2.keyIdentifier)
+ Assert.assertArrayEquals(
+ "encapsulatedKey2".toByteArray(),
+ encryptedTopic2.encapsulatedKey
+ )
+
+ // Verify plaintext topic fields
+ verifyResponse(getTopicsResponse)
+ }
+
private fun verifyResponse(getTopicsResponse: GetTopicsResponse) {
Assert.assertEquals(2, getTopicsResponse.topics.size)
val topic1 = getTopicsResponse.topics[0]
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/ReportEventRequest.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/ReportEventRequest.kt
index 43320f6..fbad84b 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/ReportEventRequest.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/ReportEventRequest.kt
@@ -21,6 +21,7 @@
import android.os.ext.SdkExtensions
import android.util.Log
import android.view.InputEvent
+import androidx.annotation.IntDef
import androidx.annotation.RequiresExtension
import androidx.annotation.RestrictTo
import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
@@ -43,7 +44,7 @@
val adSelectionId: Long,
val eventKey: String,
val eventData: String,
- val reportingDestinations: Int,
+ @ReportingDestination val reportingDestinations: Int,
@property:ExperimentalFeatures.Ext10OptIn val inputEvent: InputEvent? = null
) {
init {
@@ -82,6 +83,15 @@
"inputEvent=$inputEvent"
}
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(
+ flag = true,
+ value = [
+ Companion.FLAG_REPORTING_DESTINATION_SELLER,
+ Companion.FLAG_REPORTING_DESTINATION_BUYER])
+ annotation class ReportingDestination
+
companion object {
const val FLAG_REPORTING_DESTINATION_SELLER: Int =
android.adservices.adselection.ReportEventRequest.FLAG_REPORTING_DESTINATION_SELLER
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/UpdateAdCounterHistogramRequest.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/UpdateAdCounterHistogramRequest.kt
index 5528a9f..38d8db8 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/UpdateAdCounterHistogramRequest.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/UpdateAdCounterHistogramRequest.kt
@@ -38,7 +38,7 @@
@ExperimentalFeatures.Ext8OptIn
class UpdateAdCounterHistogramRequest public constructor(
val adSelectionId: Long,
- val adEventType: Int,
+ @FrequencyCapFilters.AdEventType val adEventType: Int,
val callerAdTech: AdTechIdentifier
) {
init {
@@ -77,7 +77,9 @@
val adEventTypeStr = when (adEventType) {
FrequencyCapFilters.AD_EVENT_TYPE_IMPRESSION -> "AD_EVENT_TYPE_IMPRESSION"
FrequencyCapFilters.AD_EVENT_TYPE_VIEW -> "AD_EVENT_TYPE_VIEW"
- else -> "AD_EVENT_TYPE_CLICK"
+ FrequencyCapFilters.AD_EVENT_TYPE_WIN -> "AD_EVENT_TYPE_WIN"
+ FrequencyCapFilters.AD_EVENT_TYPE_CLICK -> "AD_EVENT_TYPE_CLICK"
+ else -> "Invalid ad event type"
}
return "UpdateAdCounterHistogramRequest: adSelectionId=$adSelectionId, " +
"adEventType=$adEventTypeStr, callerAdTech=$callerAdTech"
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/AdData.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/AdData.kt
index 8c89de6..10cf1ee 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/AdData.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/AdData.kt
@@ -31,7 +31,7 @@
* @param renderUri a URI pointing to the ad's rendering assets
* @param metadata buyer ad metadata represented as a JSON string
* @param adCounterKeys the set of keys used in counting events
- * @param adFilters all [AdFilters] associated with the ad
+ * @param adFilters all [AdFilters] associated with the ad, it's optional and can be null as well
* @param adRenderId ad render id for server auctions
*/
@OptIn(ExperimentalFeatures.Ext8OptIn::class, ExperimentalFeatures.Ext10OptIn::class)
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/ExperimentalFeatures.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/ExperimentalFeatures.kt
index c7ac0e5..cdec101 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/ExperimentalFeatures.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/ExperimentalFeatures.kt
@@ -31,4 +31,7 @@
@RequiresOptIn("The Ext10 API is experimental.", RequiresOptIn.Level.WARNING)
annotation class Ext10OptIn
+
+ @RequiresOptIn("The Ext11 API is experimental.", RequiresOptIn.Level.WARNING)
+ annotation class Ext11OptIn
}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/FrequencyCapFilters.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/FrequencyCapFilters.kt
index 502262d..9f1df84 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/FrequencyCapFilters.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/FrequencyCapFilters.kt
@@ -18,6 +18,7 @@
import android.os.Build
import android.os.ext.SdkExtensions
+import androidx.annotation.IntDef
import androidx.annotation.RequiresExtension
import androidx.annotation.RestrictTo
@@ -73,8 +74,19 @@
"keyedFrequencyCapsForClickEvents=$keyedFrequencyCapsForClickEvents"
}
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(
+ Companion.AD_EVENT_TYPE_WIN,
+ Companion.AD_EVENT_TYPE_VIEW,
+ Companion.AD_EVENT_TYPE_CLICK,
+ Companion.AD_EVENT_TYPE_IMPRESSION)
+ annotation class AdEventType
+
companion object {
/**
+ * Represents the Win event for ads that were selected as winners in ad selection.
+ *
* The WIN ad event type is automatically populated within the Protected Audience service
* for any winning ad which is returned from Protected Audience ad selection.
*
@@ -82,10 +94,23 @@
*/
public const val AD_EVENT_TYPE_WIN: Int =
android.adservices.common.FrequencyCapFilters.AD_EVENT_TYPE_WIN
+
+ /**
+ * Represents the Impression event type which correlate to an impression as interpreted by
+ * an adtech.
+ */
public const val AD_EVENT_TYPE_IMPRESSION: Int =
android.adservices.common.FrequencyCapFilters.AD_EVENT_TYPE_IMPRESSION
+
+ /**
+ * Represents the View event type which correlate to a view as interpreted by an adtech.
+ */
public const val AD_EVENT_TYPE_VIEW: Int =
android.adservices.common.FrequencyCapFilters.AD_EVENT_TYPE_VIEW
+
+ /**
+ * Represents the Click event type which correlate to a click as interpreted by an adtech.
+ */
public const val AD_EVENT_TYPE_CLICK: Int =
android.adservices.common.FrequencyCapFilters.AD_EVENT_TYPE_CLICK
}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/KeyedFrequencyCap.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/KeyedFrequencyCap.kt
index 7e95473..37f606b 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/KeyedFrequencyCap.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/KeyedFrequencyCap.kt
@@ -28,9 +28,9 @@
* Frequency caps define the maximum rate an event can occur within a given time interval. If the
* frequency cap is exceeded, the associated ad will be filtered out of ad selection.
*
- * @param adCounterKey The ad counter key that the frequency cap is applied to.
- * @param maxCount A render URL for the winning ad.
- * @param interval The caller adtech entity's [AdTechIdentifier].
+ * @param adCounterKey The ad counter key that the frequency cap is applied to
+ * @param maxCount The maximum count of event occurrences allowed within a given time interval
+ * @param interval The interval, as a [Duration] over which the frequency cap is calculated
*/
@ExperimentalFeatures.Ext8OptIn
class KeyedFrequencyCap public constructor(
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/EncryptedTopic.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/EncryptedTopic.kt
new file mode 100644
index 0000000..716c6ed
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/EncryptedTopic.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2024 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.ads.adservices.topics
+
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+import java.util.Objects
+
+/**
+ * This class will be used to return encrypted topic cipher text along with necessary fields required to decrypt it.
+ *
+ * <p>Decryption of {@link EncryptedTopic#getEncryptedTopic()} should give json string for {@link
+ * Topic}. Example of decrypted json string: {@code { "taxonomy_version": 5, "model_version": 2,
+ * "topic_id": 10010 }}
+ *
+ * <p>Decryption of cipher text is expected to happen on the server with the corresponding algorithm
+ * and private key for the public key {@link EncryptedTopic#getKeyIdentifier()}}.
+ *
+ * <p>Detailed steps on decryption can be found on <a
+ * href="https://developer.android.com/design-for-safety/privacy-sandbox/guides/topics">Developer
+ * Guide</a>.
+ */
+@ExperimentalFeatures.Ext11OptIn
+class EncryptedTopic public constructor(
+ val encryptedTopic: ByteArray,
+ val keyIdentifier: String,
+ val encapsulatedKey: ByteArray
+) {
+ override fun toString(): String {
+ val encryptedTopicString = "EncryptedTopic=${encryptedTopic.decodeToString()}" +
+ ", KeyIdentifier=$keyIdentifier" +
+ ", EncapsulatedKey=${encapsulatedKey.decodeToString()} }"
+ return "EncryptedTopic { $encryptedTopicString"
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is EncryptedTopic) return false
+ return this.encryptedTopic.contentEquals(other.encryptedTopic) &&
+ this.keyIdentifier.contentEquals(other.keyIdentifier) &&
+ this.encapsulatedKey.contentEquals(other.encapsulatedKey)
+ }
+
+ override fun hashCode(): Int {
+ return Objects.hash(
+ encryptedTopic.contentHashCode(),
+ keyIdentifier,
+ encapsulatedKey.contentHashCode()
+ )
+ }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/GetTopicsResponse.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/GetTopicsResponse.kt
index f6f828a..dbfcf53 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/GetTopicsResponse.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/GetTopicsResponse.kt
@@ -16,22 +16,31 @@
package androidx.privacysandbox.ads.adservices.topics
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
import java.util.Objects
/** Represent the result from the getTopics API. */
-class GetTopicsResponse(val topics: List<Topic>) {
+@OptIn(ExperimentalFeatures.Ext11OptIn::class)
+class GetTopicsResponse @ExperimentalFeatures.Ext11OptIn constructor(
+ val topics: List<Topic>,
+ val encryptedTopics: List<EncryptedTopic>,
+) {
+ constructor(topics: List<Topic>) : this(topics, listOf())
+
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is GetTopicsResponse) return false
- if (topics.size != other.topics.size) return false
- return HashSet(this.topics) == HashSet(other.topics)
+ if (topics.size != other.topics.size ||
+ encryptedTopics.size != other.encryptedTopics.size) return false
+ return HashSet(this.topics) == HashSet(other.topics) &&
+ HashSet(this.encryptedTopics) == HashSet(other.encryptedTopics)
}
override fun hashCode(): Int {
- return Objects.hash(topics)
+ return Objects.hash(topics, encryptedTopics)
}
override fun toString(): String {
- return "Topics=$topics"
+ return "GetTopicsResponse: Topics=$topics, EncryptedTopics=$encryptedTopics"
}
}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/GetTopicsResponseHelper.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/GetTopicsResponseHelper.kt
index 5636137..d22fa00 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/GetTopicsResponseHelper.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/GetTopicsResponseHelper.kt
@@ -21,6 +21,7 @@
import android.os.ext.SdkExtensions
import androidx.annotation.RequiresExtension
import androidx.annotation.RestrictTo
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
/**
* Helper class to consolidate conversion logic for GetTopicsResponse.
@@ -30,12 +31,36 @@
object GetTopicsResponseHelper {
@RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
@RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
- internal fun convertResponse(response: android.adservices.topics.GetTopicsResponse):
- GetTopicsResponse {
+ internal fun convertResponse(
+ response: android.adservices.topics.GetTopicsResponse,
+ ): GetTopicsResponse {
val topics = mutableListOf<Topic>()
for (topic in response.topics) {
topics.add(Topic(topic.taxonomyVersion, topic.modelVersion, topic.topicId))
}
return GetTopicsResponse(topics)
}
+
+ @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 11)
+ @RequiresExtension(extension = Build.VERSION_CODES.S, version = 11)
+ @ExperimentalFeatures.Ext11OptIn
+ internal fun convertResponseWithEncryptedTopics(
+ response: android.adservices.topics.GetTopicsResponse,
+ ): GetTopicsResponse {
+ val topics = mutableListOf<Topic>()
+ for (topic in response.topics) {
+ topics.add(Topic(topic.taxonomyVersion, topic.modelVersion, topic.topicId))
+ }
+ val encryptedTopics = mutableListOf<EncryptedTopic>()
+ for (encryptedTopic in response.encryptedTopics) {
+ encryptedTopics.add(
+ EncryptedTopic(
+ encryptedTopic.encryptedTopic,
+ encryptedTopic.keyIdentifier,
+ encryptedTopic.encapsulatedKey,
+ ),
+ )
+ }
+ return GetTopicsResponse(topics, encryptedTopics)
+ }
}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/TopicsManager.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/TopicsManager.kt
index 97ec4ba..0ead244 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/TopicsManager.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/TopicsManager.kt
@@ -51,7 +51,9 @@
@JvmStatic
@SuppressLint("NewApi", "ClassVerificationFailure")
fun obtain(context: Context): TopicsManager? {
- return if (AdServicesInfo.adServicesVersion() >= 5) {
+ return if (AdServicesInfo.adServicesVersion() >= 11) {
+ TopicsManagerApi33Ext11Impl(context)
+ } else if (AdServicesInfo.adServicesVersion() >= 5) {
TopicsManagerApi33Ext5Impl(context)
} else if (AdServicesInfo.adServicesVersion() == 4) {
TopicsManagerApi33Ext4Impl(context)
@@ -59,6 +61,10 @@
BackCompatManager.getManager(context, "TopicsManager") {
TopicsManagerApi31Ext9Impl(context)
}
+ } else if (AdServicesInfo.extServicesVersionS() >= 11) {
+ BackCompatManager.getManager(context, "TopicsManager") {
+ TopicsManagerApi31Ext11Impl(context)
+ }
} else {
null
}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/TopicsManagerApi31Ext11Impl.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/TopicsManagerApi31Ext11Impl.kt
new file mode 100644
index 0000000..b6473f2
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/TopicsManagerApi31Ext11Impl.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2024 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.ads.adservices.topics
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.Build
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressLint("NewApi", "ClassVerificationFailure")
+@RequiresExtension(extension = Build.VERSION_CODES.S, version = 11)
+class TopicsManagerApi31Ext11Impl(context: Context) : TopicsManagerImplCommon(
+ android.adservices.topics.TopicsManager.get(context),
+) {
+ override fun convertRequest(
+ request: GetTopicsRequest
+ ): android.adservices.topics.GetTopicsRequest {
+ return GetTopicsRequestHelper.convertRequestWithRecordObservation(request)
+ }
+
+ @ExperimentalFeatures.Ext11OptIn
+ override fun convertResponse(
+ response: android.adservices.topics.GetTopicsResponse
+ ): GetTopicsResponse {
+ return GetTopicsResponseHelper.convertResponseWithEncryptedTopics(response)
+ }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/TopicsManagerApi33Ext11Impl.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/TopicsManagerApi33Ext11Impl.kt
new file mode 100644
index 0000000..c5f2524
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/TopicsManagerApi33Ext11Impl.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2024 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.ads.adservices.topics
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.ext.SdkExtensions
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressLint("NewApi", "ClassVerificationFailure")
+@RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 11)
+class TopicsManagerApi33Ext11Impl(context: Context) : TopicsManagerImplCommon(
+ context.getSystemService(android.adservices.topics.TopicsManager::class.java)
+) {
+
+ override fun convertRequest(
+ request: GetTopicsRequest
+ ): android.adservices.topics.GetTopicsRequest {
+ return GetTopicsRequestHelper.convertRequestWithRecordObservation(request)
+ }
+
+ @ExperimentalFeatures.Ext11OptIn
+ override fun convertResponse(
+ response: android.adservices.topics.GetTopicsResponse
+ ): GetTopicsResponse {
+ return GetTopicsResponseHelper.convertResponseWithEncryptedTopics(response)
+ }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/TopicsManagerImplCommon.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/TopicsManagerImplCommon.kt
index 3fe0c44..1c46ec7 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/TopicsManagerImplCommon.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/TopicsManagerImplCommon.kt
@@ -58,7 +58,7 @@
return GetTopicsRequestHelper.convertRequestWithoutRecordObservation(request)
}
- internal fun convertResponse(
+ internal open fun convertResponse(
response: android.adservices.topics.GetTopicsResponse
): GetTopicsResponse {
return GetTopicsResponseHelper.convertResponse(response)
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/BaseFragment.kt b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/BaseFragment.kt
index 2513b5d..f253f94 100644
--- a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/BaseFragment.kt
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/BaseFragment.kt
@@ -16,11 +16,20 @@
package androidx.privacysandbox.ui.integration.testapp
+import android.app.Activity
import android.os.Bundle
+import android.util.Log
+import android.view.ViewGroup
+import android.widget.TextView
+import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat
+import androidx.privacysandbox.sdkruntime.client.SdkSandboxProcessDeathCallbackCompat
+import androidx.privacysandbox.ui.client.view.SandboxedSdkUiSessionState
+import androidx.privacysandbox.ui.client.view.SandboxedSdkUiSessionStateChangedListener
import androidx.privacysandbox.ui.client.view.SandboxedSdkView
import androidx.privacysandbox.ui.integration.testaidl.ISdkApi
+import kotlinx.coroutines.runBlocking
/**
* Base fragment to be used for testing different manual flows.
@@ -31,16 +40,22 @@
*/
abstract class BaseFragment : Fragment() {
private lateinit var sdkApi: ISdkApi
+ private lateinit var sdkSandboxManager: SdkSandboxManagerCompat
+ private lateinit var activity: Activity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- val sdkSandboxManager = SdkSandboxManagerCompat.from(requireContext())
- val loadedSdks = sdkSandboxManager.getSandboxedSdks()
- val loadedSdk = loadedSdks.firstOrNull { it.getSdkInfo()?.name == SDK_NAME }
- if (loadedSdk == null) {
- throw IllegalStateException("SDK not loaded")
+ activity = requireActivity()
+ sdkSandboxManager = SdkSandboxManagerCompat.from(requireContext())
+ runBlocking {
+ val loadedSdks = sdkSandboxManager.getSandboxedSdks()
+ var loadedSdk = loadedSdks.firstOrNull { it.getSdkInfo()?.name == SDK_NAME }
+ if (loadedSdk == null) {
+ loadedSdk = sdkSandboxManager.loadSdk(SDK_NAME, Bundle())
+ sdkSandboxManager.loadSdk(MEDIATEE_SDK_NAME, Bundle())
+ }
+ sdkApi = ISdkApi.Stub.asInterface(loadedSdk.getInterface())
}
- sdkApi = ISdkApi.Stub.asInterface(loadedSdk.getInterface())
}
/**
@@ -50,6 +65,20 @@
return sdkApi
}
+ fun SandboxedSdkView.addStateChangedListener() {
+ addStateChangedListener(StateChangeListener(this))
+ }
+
+ /**
+ * Unloads all SDKs, resulting in sandbox death. This method registers a death callback to
+ * ensure that the app is not also killed.
+ */
+ fun unloadAllSdks() {
+ sdkSandboxManager.addSdkSandboxProcessDeathCallback(Runnable::run, DeathCallbackImpl())
+ sdkSandboxManager.unloadSdk(SDK_NAME)
+ sdkSandboxManager.unloadSdk(MEDIATEE_SDK_NAME)
+ }
+
/**
* Called when the app's drawer layout state changes. When called, change the Z-order of
* any [SandboxedSdkView] owned by the fragment to ensure that the remote UI is not drawn over
@@ -58,8 +87,37 @@
*/
abstract fun handleDrawerStateChange(isDrawerOpen: Boolean)
+ private inner class StateChangeListener(val view: SandboxedSdkView) :
+ SandboxedSdkUiSessionStateChangedListener {
+ override fun onStateChanged(state: SandboxedSdkUiSessionState) {
+ Log.i(TAG, "UI session state changed to: $state")
+ if (state is SandboxedSdkUiSessionState.Error) {
+ // If the session fails to open, display the error.
+ val parent = view.parent as ViewGroup
+ val index = parent.indexOfChild(view)
+ val textView = TextView(requireActivity())
+ textView.text = state.throwable.message
+
+ requireActivity().runOnUiThread {
+ parent.removeView(view)
+ parent.addView(textView, index)
+ }
+ }
+ }
+ }
+
+ private inner class DeathCallbackImpl : SdkSandboxProcessDeathCallbackCompat {
+ override fun onSdkSandboxDied() {
+ activity.runOnUiThread {
+ Toast.makeText(activity, "Sandbox died", Toast.LENGTH_LONG).show()
+ }
+ }
+ }
+
companion object {
private const val SDK_NAME = "androidx.privacysandbox.ui.integration.testsdkprovider"
+ private const val MEDIATEE_SDK_NAME =
+ "androidx.privacysandbox.ui.integration.mediateesdkprovider"
const val TAG = "TestSandboxClient"
}
}
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt
index ae13cb1..8d7af17 100644
--- a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt
@@ -109,7 +109,7 @@
val itemId = it.itemId
when (itemId) {
R.id.item_main -> switchContentFragment(MainFragment(), it.title)
- R.id.item_empty -> switchContentFragment(EmptyFragment(), it.title)
+ R.id.item_sandbox_death -> switchContentFragment(SandboxDeathFragment(), it.title)
else -> {
Log.e(TAG, "Invalid fragment option")
true
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainFragment.kt b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainFragment.kt
index 7944323..e9b2ccc 100644
--- a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainFragment.kt
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainFragment.kt
@@ -17,16 +17,12 @@
package androidx.privacysandbox.ui.integration.testapp
import android.os.Bundle
-import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.LinearLayout
-import android.widget.TextView
import androidx.privacysandbox.ui.client.SandboxedUiAdapterFactory
-import androidx.privacysandbox.ui.client.view.SandboxedSdkUiSessionState
-import androidx.privacysandbox.ui.client.view.SandboxedSdkUiSessionStateChangedListener
import androidx.privacysandbox.ui.client.view.SandboxedSdkView
import androidx.privacysandbox.ui.integration.testaidl.ISdkApi
import com.google.android.material.switchmaterial.SwitchMaterial
@@ -79,7 +75,7 @@
}
private fun loadWebViewBannerAd() {
- webViewBannerView.addStateChangedListener(StateChangeListener(webViewBannerView))
+ webViewBannerView.addStateChangedListener()
webViewBannerView.setAdapter(
SandboxedUiAdapterFactory.createFromCoreLibInfo(
sdkApi.loadLocalWebViewAd()
@@ -101,7 +97,7 @@
}
private fun loadBottomBannerAd() {
- bottomBannerView.addStateChangedListener(StateChangeListener(bottomBannerView))
+ bottomBannerView.addStateChangedListener()
bottomBannerView.layoutParams = inflatedView.findViewById<LinearLayout>(
R.id.bottom_banner_container).layoutParams
requireActivity().runOnUiThread {
@@ -115,8 +111,7 @@
}
private fun loadResizableBannerAd() {
- resizableBannerView.addStateChangedListener(
- StateChangeListener(resizableBannerView))
+ resizableBannerView.addStateChangedListener()
resizableBannerView.setAdapter(
SandboxedUiAdapterFactory.createFromCoreLibInfo(
sdkApi.loadTestAdWithWaitInsideOnDraw(/*text=*/ "Resizable View")
@@ -164,23 +159,4 @@
sdkApi.requestResize(newWidth, newHeight)
}
}
-
- private inner class StateChangeListener(val view: SandboxedSdkView) :
- SandboxedSdkUiSessionStateChangedListener {
- override fun onStateChanged(state: SandboxedSdkUiSessionState) {
- Log.i(TAG, "UI session state changed to: $state")
- if (state is SandboxedSdkUiSessionState.Error) {
- // If the session fails to open, display the error.
- val parent = view.parent as ViewGroup
- val index = parent.indexOfChild(view)
- val textView = TextView(requireActivity())
- textView.text = state.throwable.message
-
- requireActivity().runOnUiThread {
- parent.removeView(view)
- parent.addView(textView, index)
- }
- }
- }
- }
}
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/SandboxDeathFragment.kt b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/SandboxDeathFragment.kt
new file mode 100644
index 0000000..2c85ccd
--- /dev/null
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/SandboxDeathFragment.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2024 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.ui.integration.testapp
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import androidx.privacysandbox.ui.client.SandboxedUiAdapterFactory
+import androidx.privacysandbox.ui.client.view.SandboxedSdkView
+import androidx.privacysandbox.ui.integration.testaidl.ISdkApi
+
+class SandboxDeathFragment : BaseFragment() {
+ private lateinit var sdkApi: ISdkApi
+ private lateinit var inflatedView: View
+ private lateinit var sandboxedSdkView: SandboxedSdkView
+
+ override fun handleDrawerStateChange(isDrawerOpen: Boolean) {
+ sandboxedSdkView.orderProviderUiAboveClientUi(!isDrawerOpen)
+ }
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ inflatedView = inflater.inflate(R.layout.fragment_sandbox_death, container, false)
+ sdkApi = getSdkApi()
+ onLoaded()
+ return inflatedView
+ }
+
+ private fun onLoaded() {
+ sandboxedSdkView = inflatedView.findViewById(R.id.remote_view)
+ sandboxedSdkView.addStateChangedListener()
+ sandboxedSdkView.setAdapter(
+ SandboxedUiAdapterFactory.createFromCoreLibInfo(sdkApi.loadTestAd("Test Ad")))
+ val unloadSdksButton: Button = inflatedView.findViewById(R.id.unload_all_sdks_button)
+ unloadSdksButton.setOnClickListener {
+ unloadAllSdks()
+ }
+ }
+}
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/action_menu.xml b/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/action_menu.xml
index 836375e..1cde836 100644
--- a/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/action_menu.xml
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/action_menu.xml
@@ -19,6 +19,6 @@
android:id="@+id/item_main"
android:title="Main CUJ" />
<item
- android:id="@+id/item_empty"
- android:title="Empty CUJ" />
+ android:id="@+id/item_sandbox_death"
+ android:title="Sandbox Death CUJ" />
</menu>
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/fragment_sandbox_death.xml b/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/fragment_sandbox_death.xml
new file mode 100644
index 0000000..02015bb
--- /dev/null
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/fragment_sandbox_death.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2024 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.
+ -->
+
+<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <androidx.privacysandbox.ui.client.view.SandboxedSdkView
+ android:layout_width="500dp"
+ android:layout_height="500dp"
+ android:id="@+id/remote_view"/>
+ <Button
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ android:id="@+id/unload_all_sdks_button"
+ android:text="@string/unload_sdks_button"/>
+</androidx.appcompat.widget.LinearLayoutCompat>
\ No newline at end of file
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/res/values/strings.xml b/privacysandbox/ui/integration-tests/testapp/src/main/res/values/strings.xml
index 5ecbc59..33f5242 100644
--- a/privacysandbox/ui/integration-tests/testapp/src/main/res/values/strings.xml
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/res/values/strings.xml
@@ -21,4 +21,5 @@
<string name="local_to_internet_switch">local webview</string>
<string name="mediation_switch">Mediation</string>
<string name="app_owned_mediatee_switch">AppOwnedMediatee</string>
+ <string name="unload_sdks_button">Unload SDKs</string>
</resources>
\ No newline at end of file
diff --git a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SandboxedUiAdapterFactory.kt b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SandboxedUiAdapterFactory.kt
index a94fd8f..d25dc59 100644
--- a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SandboxedUiAdapterFactory.kt
+++ b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SandboxedUiAdapterFactory.kt
@@ -23,6 +23,7 @@
import android.os.Build
import android.os.Bundle
import android.os.IBinder
+import android.os.RemoteException
import android.util.Log
import android.view.Display
import android.view.SurfaceControlViewHost
@@ -233,14 +234,16 @@
context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
val displayId = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY).displayId
- adapterInterface.openRemoteSession(
- windowInputToken,
- displayId,
- initialWidth,
- initialHeight,
- isZOrderOnTop,
- RemoteSessionClient(context, client, clientExecutor)
- )
+ tryToCallRemoteObject {
+ adapterInterface.openRemoteSession(
+ windowInputToken,
+ displayId,
+ initialWidth,
+ initialHeight,
+ isZOrderOnTop,
+ RemoteSessionClient(context, client, clientExecutor)
+ )
+ }
}
class RemoteSessionClient(
@@ -263,6 +266,11 @@
.onSessionOpened(SessionImpl(surfaceView,
remoteSessionController, surfacePackage))
}
+ tryToCallRemoteObject {
+ remoteSessionController.asBinder().linkToDeath({
+ onRemoteSessionError("Remote process died")
+ }, 0)
+ }
}
override fun onRemoteSessionError(errorString: String) {
@@ -287,7 +295,9 @@
override val view: View = surfaceView
override fun notifyConfigurationChanged(configuration: Configuration) {
- remoteSessionController.notifyConfigurationChanged(configuration)
+ tryToCallRemoteObject {
+ remoteSessionController.notifyConfigurationChanged(configuration)
+ }
}
@SuppressLint("ClassVerificationFailure")
@@ -302,7 +312,9 @@
}
val providerResizeRunnable = Runnable {
- remoteSessionController.notifyResized(width, height)
+ tryToCallRemoteObject {
+ remoteSessionController.notifyResized(width, height)
+ }
}
val syncGroup = SurfaceSyncGroup("AppAndSdkViewsSurfaceSync")
@@ -314,11 +326,27 @@
override fun notifyZOrderChanged(isZOrderOnTop: Boolean) {
surfaceView.setZOrderOnTop(isZOrderOnTop)
- remoteSessionController.notifyZOrderChanged(isZOrderOnTop)
+ tryToCallRemoteObject {
+ remoteSessionController.notifyZOrderChanged(isZOrderOnTop)
+ }
}
override fun close() {
- remoteSessionController.close()
+ tryToCallRemoteObject { remoteSessionController.close() }
+ }
+ }
+
+ private companion object {
+
+ /**
+ * Tries to call the remote object and handles exceptions if the remote object has died.
+ */
+ private inline fun tryToCallRemoteObject(function: () -> Unit) {
+ try {
+ function()
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Calling remote object failed: $e")
+ }
}
}
}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/AutoClosingRoomOpenHelperTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/AutoClosingRoomOpenHelperTest.java
index 0419088..79fe546 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/AutoClosingRoomOpenHelperTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/AutoClosingRoomOpenHelperTest.java
@@ -43,6 +43,7 @@
import org.jetbrains.annotations.NotNull;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
@@ -253,6 +254,8 @@
assertFalse(testDatabase.isOpen());
}
+ // TODO(336671494): broken test
+ @Ignore
@Test
@MediumTest
public void invalidationObserver_isCalledOnEachInvalidation()
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java
index bf9a575..43e65e1 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java
@@ -208,6 +208,8 @@
assertThat(db1.getCustomerDao().countCustomers(), is(1));
}
+ // TODO(335890993): broken test
+ @Ignore
@Test
public void invalidationInAnotherInstance_closed() throws Exception {
final SampleDatabase db1 = openDatabase(true);
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt
index da629f7..46e753d 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt
@@ -18,6 +18,7 @@
import androidx.kruth.assertThat
import androidx.room.compiler.codegen.JArrayTypeName
+import androidx.room.compiler.processing.compat.XConverters.toKS
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.asJClassName
@@ -1284,7 +1285,10 @@
val kClassKTypeName = kotlin.reflect.KClass::class.asKClassName().parameterizedBy(STAR)
fun checkSingleValue(annotationValue: XAnnotationValue, expectedValue: String) {
// TODO(bcorso): Consider making the value types match in this case.
- if (!invocation.isKsp || (sourceKind == SourceKind.JAVA && !isPreCompiled)) {
+ if (!invocation.isKsp ||
+ (invocation.processingEnv.toKS().kspVersion < KotlinVersion(2, 0) &&
+ sourceKind == SourceKind.JAVA &&
+ !isPreCompiled)) {
assertThat(annotationValue.valueType.asTypeName().java)
.isEqualTo(classJTypeName)
} else {
@@ -1299,7 +1303,10 @@
fun checkListValues(annotationValue: XAnnotationValue, vararg expectedValues: String) {
// TODO(bcorso): Consider making the value types match in this case.
- if (!invocation.isKsp || (sourceKind == SourceKind.JAVA && !isPreCompiled)) {
+ if (!invocation.isKsp ||
+ (invocation.processingEnv.toKS().kspVersion < KotlinVersion(2, 0) &&
+ sourceKind == SourceKind.JAVA &&
+ !isPreCompiled)) {
assertThat(annotationValue.valueType.asTypeName().java)
.isEqualTo(JArrayTypeName.of(classJTypeName))
} else {
diff --git a/room/room-ktx/build.gradle b/room/room-ktx/build.gradle
index 58a0f23..9dafedb 100644
--- a/room/room-ktx/build.gradle
+++ b/room/room-ktx/build.gradle
@@ -21,8 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -49,7 +48,7 @@
androidx {
name = "Room Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2019"
description = "Android Room Kotlin Extensions"
metalavaK2UastEnabled = true
diff --git a/savedstate/savedstate-ktx/build.gradle b/savedstate/savedstate-ktx/build.gradle
index 915bed4..b54f221 100644
--- a/savedstate/savedstate-ktx/build.gradle
+++ b/savedstate/savedstate-ktx/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -41,7 +41,7 @@
androidx {
name = "SavedState Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2020"
description = "Kotlin extensions for 'savedstate' artifact"
metalavaK2UastEnabled = true
diff --git a/security/security-crypto-ktx/build.gradle b/security/security-crypto-ktx/build.gradle
index 3ad3bb3..d78c899 100644
--- a/security/security-crypto-ktx/build.gradle
+++ b/security/security-crypto-ktx/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -50,7 +50,7 @@
androidx {
name = "Security Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
mavenVersion = LibraryVersions.SECURITY
inceptionYear = "2020"
description = "Kotlin Extensions for the androidx.security:Security-crypto artifact"
diff --git a/settings.gradle b/settings.gradle
index 8473061..51022df 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -28,8 +28,8 @@
classpath("com.google.protobuf:protobuf-java:3.22.3")
// upgrade okio for gcpbuildcache that is compatible with the wire plugin used by androidx
classpath("com.squareup.okio:okio:3.3.0")
- classpath("com.gradle:gradle-enterprise-gradle-plugin:3.16")
- classpath("com.gradle:common-custom-user-data-gradle-plugin:1.12")
+ classpath("com.gradle:develocity-gradle-plugin:3.17.2")
+ classpath("com.gradle:common-custom-user-data-gradle-plugin:2.0.1")
classpath("androidx.build.gradle.gcpbuildcache:gcpbuildcache:1.0.0-beta07")
}
}
@@ -72,17 +72,17 @@
)
}
-apply(plugin: "com.gradle.enterprise")
+apply(plugin: "com.gradle.develocity")
apply(plugin: "com.gradle.common-custom-user-data-gradle-plugin")
apply(plugin: "androidx.build.gradle.gcpbuildcache")
def BUILD_NUMBER = System.getenv("BUILD_NUMBER")
-gradleEnterprise {
+develocity {
server = "https://ge.androidx.dev"
buildScan {
capture {
- taskInputFiles = true
+ fileFingerprints.set(true)
}
obfuscation {
hostname { host -> "unset" }
@@ -95,9 +95,7 @@
value("androidx.projects", getRequestedProjectSubsetName() ?: "Unset")
value("androidx.useMaxDepVersions", providers.gradleProperty("androidx.useMaxDepVersions").isPresent().toString())
- // Publish scan for androidx-main
- publishAlways()
- publishIfAuthenticated()
+ publishing.onlyIf { it.authenticated }
}
}
diff --git a/sqlite/sqlite-ktx/build.gradle b/sqlite/sqlite-ktx/build.gradle
index ac587d8..fd9573a 100644
--- a/sqlite/sqlite-ktx/build.gradle
+++ b/sqlite/sqlite-ktx/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -39,7 +39,7 @@
androidx {
name = "SQLite Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2018"
description = "Kotlin extensions for DB"
metalavaK2UastEnabled = true
diff --git a/test/ext/junit-gtest/src/main/cpp/CMakeLists.txt b/test/ext/junit-gtest/src/main/cpp/CMakeLists.txt
index c563898..5b4f8d7 100644
--- a/test/ext/junit-gtest/src/main/cpp/CMakeLists.txt
+++ b/test/ext/junit-gtest/src/main/cpp/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.10.2)
+cmake_minimum_required(VERSION 3.22.1)
project(junit-gtest LANGUAGES CXX)
diff --git a/test/integration-tests/junit-gtest-test/src/main/cpp/CMakeLists.txt b/test/integration-tests/junit-gtest-test/src/main/cpp/CMakeLists.txt
index cd8a95e..d3898fb 100644
--- a/test/integration-tests/junit-gtest-test/src/main/cpp/CMakeLists.txt
+++ b/test/integration-tests/junit-gtest-test/src/main/cpp/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.10.2)
+cmake_minimum_required(VERSION 3.22.1)
project(junit-gtest-test LANGUAGES CXX)
diff --git a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/ComposeTest.java b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/ComposeTest.java
index d21461e..f4a1f50 100644
--- a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/ComposeTest.java
+++ b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/ComposeTest.java
@@ -43,7 +43,7 @@
UiObject2 column = mDevice.findObject(By.scrollable(true));
assertNotNull("Scrollable container not found", column);
UiObject2 button = column.scrollUntil(Direction.DOWN,
- Until.findObject(By.clazz(Button.class).hasParent(By.hasChild(By.text("Update")))));
+ Until.findObject(By.clazz(Button.class)));
assertNotNull("Button not found after scrolling", button);
// Click and wait for change.
diff --git a/test/uiautomator/integration-tests/testapp/src/main/java/androidx/test/uiautomator/testapp/ComposeTestActivity.kt b/test/uiautomator/integration-tests/testapp/src/main/java/androidx/test/uiautomator/testapp/ComposeTestActivity.kt
index bba1130..bdf6446 100644
--- a/test/uiautomator/integration-tests/testapp/src/main/java/androidx/test/uiautomator/testapp/ComposeTestActivity.kt
+++ b/test/uiautomator/integration-tests/testapp/src/main/java/androidx/test/uiautomator/testapp/ComposeTestActivity.kt
@@ -22,7 +22,6 @@
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
@@ -69,14 +68,14 @@
) {
Text("Top", modifier = Modifier.padding(top = 20.dp).testTag("top-text"))
Spacer(modifier = Modifier.size(scrollHeight.dp))
- Row(
+ Column(
modifier = Modifier.padding(bottom = 20.dp),
- verticalAlignment = Alignment.CenterVertically,
+ horizontalAlignment = Alignment.CenterHorizontally,
) {
+ Text(text)
Button(onClick = { text = "Updated" }) {
Text("Update")
}
- Text(text)
}
}
}
diff --git a/tracing/tracing-ktx/build.gradle b/tracing/tracing-ktx/build.gradle
index b764032..4a33a5e 100644
--- a/tracing/tracing-ktx/build.gradle
+++ b/tracing/tracing-ktx/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -30,7 +30,7 @@
}
dependencies {
- api project(":tracing:tracing")
+ api(project(":tracing:tracing"))
api(libs.kotlinStdlib)
androidTestImplementation(libs.testExtJunit)
@@ -42,7 +42,7 @@
androidx {
name = "Tracing Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2020"
description = "Android Tracing"
metalavaK2UastEnabled = true
diff --git a/tracing/tracing-perfetto-binary/src/main/cpp/CMakeLists.txt b/tracing/tracing-perfetto-binary/src/main/cpp/CMakeLists.txt
index 4c98782..c5cbe48 100644
--- a/tracing/tracing-perfetto-binary/src/main/cpp/CMakeLists.txt
+++ b/tracing/tracing-perfetto-binary/src/main/cpp/CMakeLists.txt
@@ -14,7 +14,7 @@
# the License.
#
-cmake_minimum_required(VERSION 3.22)
+cmake_minimum_required(VERSION 3.22.1)
project(tracing_perfetto)
diff --git a/transition/transition-ktx/build.gradle b/transition/transition-ktx/build.gradle
index 9ca355f..3b67de1b 100644
--- a/transition/transition-ktx/build.gradle
+++ b/transition/transition-ktx/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -42,7 +42,7 @@
androidx {
name = "Transition Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2019"
description = "Kotlin extensions for 'transition' artifact"
metalavaK2UastEnabled = true
diff --git a/tv/tv-foundation/build.gradle b/tv/tv-foundation/build.gradle
index 5ec0bb0..1e83ff7 100644
--- a/tv/tv-foundation/build.gradle
+++ b/tv/tv-foundation/build.gradle
@@ -67,7 +67,7 @@
androidx {
name = "TV Foundation"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
mavenVersion = LibraryVersions.TV
inceptionYear = "2022"
description = "This library makes it easier for developers" +
diff --git a/tv/tv-material/build.gradle b/tv/tv-material/build.gradle
index e443ad2..a347f5a 100644
--- a/tv/tv-material/build.gradle
+++ b/tv/tv-material/build.gradle
@@ -71,7 +71,7 @@
androidx {
name = "TV Material"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
mavenVersion = LibraryVersions.TV_MATERIAL
inceptionYear = "2022"
description = "build TV applications using controls that adhere to Material Design Language."
diff --git a/wear/compose/compose-foundation/build.gradle b/wear/compose/compose-foundation/build.gradle
index 9ac320a..02c4122 100644
--- a/wear/compose/compose-foundation/build.gradle
+++ b/wear/compose/compose-foundation/build.gradle
@@ -81,7 +81,7 @@
androidx {
name = "Android Wear Compose Foundation"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2021"
description = "WearOS Compose Foundation Library. This library makes it easier for developers" +
"to write Jetpack Compose applications for Wearable devices by providing " +
diff --git a/wear/compose/compose-material-core/build.gradle b/wear/compose/compose-material-core/build.gradle
index 7f7f700..0a0d543 100644
--- a/wear/compose/compose-material-core/build.gradle
+++ b/wear/compose/compose-material-core/build.gradle
@@ -75,7 +75,7 @@
androidx {
name = "Android Wear Compose Material Core"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2022"
description = "WearOS Compose Material Core Library. This library contains themeless " +
"components that are shared between different WearOS Compose Material libraries. It " +
diff --git a/wear/compose/compose-material/build.gradle b/wear/compose/compose-material/build.gradle
index 04c1634..4abcef7 100644
--- a/wear/compose/compose-material/build.gradle
+++ b/wear/compose/compose-material/build.gradle
@@ -77,7 +77,7 @@
androidx {
name = "Android Wear Compose Material"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2021"
description = "WearOS Compose Material Library. This library makes it easier for developers " +
"to write Jetpack Compose applications for Wearable devices that implement Wear " +
diff --git a/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/SwipeToRevealSample.kt b/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/SwipeToRevealSample.kt
index 7e565fd..44ff9ba 100644
--- a/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/SwipeToRevealSample.kt
+++ b/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/SwipeToRevealSample.kt
@@ -105,10 +105,11 @@
modifier = Modifier.fillMaxWidth(),
onClick = { /* Add the chip click handler here */ },
colors = ChipDefaults.primaryChipColors(),
- border = ChipDefaults.outlinedChipBorder()
- ) {
- Text("SwipeToReveal Chip")
- }
+ border = ChipDefaults.outlinedChipBorder(),
+ label = {
+ Text("SwipeToReveal Chip", maxLines = 3)
+ }
+ )
}
}
diff --git a/wear/compose/compose-material3/api/current.txt b/wear/compose/compose-material3/api/current.txt
index eb3d98e..eaa6197 100644
--- a/wear/compose/compose-material3/api/current.txt
+++ b/wear/compose/compose-material3/api/current.txt
@@ -136,12 +136,14 @@
}
@androidx.compose.runtime.Immutable @androidx.compose.runtime.Stable public final class ColorScheme {
- ctor public ColorScheme(optional long primary, optional long primaryDim, optional long primaryContainer, optional long onPrimary, optional long onPrimaryContainer, optional long secondary, optional long secondaryDim, optional long secondaryContainer, optional long onSecondary, optional long onSecondaryContainer, optional long tertiary, optional long tertiaryDim, optional long tertiaryContainer, optional long onTertiary, optional long onTertiaryContainer, optional long surfaceDim, optional long surface, optional long surfaceBright, optional long onSurface, optional long onSurfaceVariant, optional long outline, optional long outlineVariant, optional long background, optional long onBackground, optional long error, optional long onError);
- method public androidx.wear.compose.material3.ColorScheme copy(optional long primary, optional long primaryDim, optional long primaryContainer, optional long onPrimary, optional long onPrimaryContainer, optional long secondary, optional long secondaryDim, optional long secondaryContainer, optional long onSecondary, optional long onSecondaryContainer, optional long tertiary, optional long tertiaryDim, optional long tertiaryContainer, optional long onTertiary, optional long onTertiaryContainer, optional long surfaceDim, optional long surface, optional long surfaceBright, optional long onSurface, optional long onSurfaceVariant, optional long outline, optional long outlineVariant, optional long background, optional long onBackground, optional long error, optional long onError);
+ ctor public ColorScheme(optional long primary, optional long primaryDim, optional long primaryContainer, optional long onPrimary, optional long onPrimaryContainer, optional long secondary, optional long secondaryDim, optional long secondaryContainer, optional long onSecondary, optional long onSecondaryContainer, optional long tertiary, optional long tertiaryDim, optional long tertiaryContainer, optional long onTertiary, optional long onTertiaryContainer, optional long surfaceContainerLow, optional long surfaceContainer, optional long surfaceContainerHigh, optional long onSurface, optional long onSurfaceVariant, optional long outline, optional long outlineVariant, optional long background, optional long onBackground, optional long error, optional long onError, optional long errorContainer, optional long onErrorContainer);
+ method public androidx.wear.compose.material3.ColorScheme copy(optional long primary, optional long primaryDim, optional long primaryContainer, optional long onPrimary, optional long onPrimaryContainer, optional long secondary, optional long secondaryDim, optional long secondaryContainer, optional long onSecondary, optional long onSecondaryContainer, optional long tertiary, optional long tertiaryDim, optional long tertiaryContainer, optional long onTertiary, optional long onTertiaryContainer, optional long surfaceContainerLow, optional long surfaceContainer, optional long surfaceContainerHigh, optional long onSurface, optional long onSurfaceVariant, optional long outline, optional long outlineVariant, optional long background, optional long onBackground, optional long error, optional long onError, optional long errorContainer, optional long onErrorContainer);
method public long getBackground();
method public long getError();
+ method public long getErrorContainer();
method public long getOnBackground();
method public long getOnError();
+ method public long getOnErrorContainer();
method public long getOnPrimary();
method public long getOnPrimaryContainer();
method public long getOnSecondary();
@@ -158,16 +160,18 @@
method public long getSecondary();
method public long getSecondaryContainer();
method public long getSecondaryDim();
- method public long getSurface();
- method public long getSurfaceBright();
- method public long getSurfaceDim();
+ method public long getSurfaceContainer();
+ method public long getSurfaceContainerHigh();
+ method public long getSurfaceContainerLow();
method public long getTertiary();
method public long getTertiaryContainer();
method public long getTertiaryDim();
property public final long background;
property public final long error;
+ property public final long errorContainer;
property public final long onBackground;
property public final long onError;
+ property public final long onErrorContainer;
property public final long onPrimary;
property public final long onPrimaryContainer;
property public final long onSecondary;
@@ -184,9 +188,9 @@
property public final long secondary;
property public final long secondaryContainer;
property public final long secondaryDim;
- property public final long surface;
- property public final long surfaceBright;
- property public final long surfaceDim;
+ property public final long surfaceContainer;
+ property public final long surfaceContainerHigh;
+ property public final long surfaceContainerLow;
property public final long tertiary;
property public final long tertiaryContainer;
property public final long tertiaryDim;
diff --git a/wear/compose/compose-material3/api/restricted_current.txt b/wear/compose/compose-material3/api/restricted_current.txt
index eb3d98e..eaa6197 100644
--- a/wear/compose/compose-material3/api/restricted_current.txt
+++ b/wear/compose/compose-material3/api/restricted_current.txt
@@ -136,12 +136,14 @@
}
@androidx.compose.runtime.Immutable @androidx.compose.runtime.Stable public final class ColorScheme {
- ctor public ColorScheme(optional long primary, optional long primaryDim, optional long primaryContainer, optional long onPrimary, optional long onPrimaryContainer, optional long secondary, optional long secondaryDim, optional long secondaryContainer, optional long onSecondary, optional long onSecondaryContainer, optional long tertiary, optional long tertiaryDim, optional long tertiaryContainer, optional long onTertiary, optional long onTertiaryContainer, optional long surfaceDim, optional long surface, optional long surfaceBright, optional long onSurface, optional long onSurfaceVariant, optional long outline, optional long outlineVariant, optional long background, optional long onBackground, optional long error, optional long onError);
- method public androidx.wear.compose.material3.ColorScheme copy(optional long primary, optional long primaryDim, optional long primaryContainer, optional long onPrimary, optional long onPrimaryContainer, optional long secondary, optional long secondaryDim, optional long secondaryContainer, optional long onSecondary, optional long onSecondaryContainer, optional long tertiary, optional long tertiaryDim, optional long tertiaryContainer, optional long onTertiary, optional long onTertiaryContainer, optional long surfaceDim, optional long surface, optional long surfaceBright, optional long onSurface, optional long onSurfaceVariant, optional long outline, optional long outlineVariant, optional long background, optional long onBackground, optional long error, optional long onError);
+ ctor public ColorScheme(optional long primary, optional long primaryDim, optional long primaryContainer, optional long onPrimary, optional long onPrimaryContainer, optional long secondary, optional long secondaryDim, optional long secondaryContainer, optional long onSecondary, optional long onSecondaryContainer, optional long tertiary, optional long tertiaryDim, optional long tertiaryContainer, optional long onTertiary, optional long onTertiaryContainer, optional long surfaceContainerLow, optional long surfaceContainer, optional long surfaceContainerHigh, optional long onSurface, optional long onSurfaceVariant, optional long outline, optional long outlineVariant, optional long background, optional long onBackground, optional long error, optional long onError, optional long errorContainer, optional long onErrorContainer);
+ method public androidx.wear.compose.material3.ColorScheme copy(optional long primary, optional long primaryDim, optional long primaryContainer, optional long onPrimary, optional long onPrimaryContainer, optional long secondary, optional long secondaryDim, optional long secondaryContainer, optional long onSecondary, optional long onSecondaryContainer, optional long tertiary, optional long tertiaryDim, optional long tertiaryContainer, optional long onTertiary, optional long onTertiaryContainer, optional long surfaceContainerLow, optional long surfaceContainer, optional long surfaceContainerHigh, optional long onSurface, optional long onSurfaceVariant, optional long outline, optional long outlineVariant, optional long background, optional long onBackground, optional long error, optional long onError, optional long errorContainer, optional long onErrorContainer);
method public long getBackground();
method public long getError();
+ method public long getErrorContainer();
method public long getOnBackground();
method public long getOnError();
+ method public long getOnErrorContainer();
method public long getOnPrimary();
method public long getOnPrimaryContainer();
method public long getOnSecondary();
@@ -158,16 +160,18 @@
method public long getSecondary();
method public long getSecondaryContainer();
method public long getSecondaryDim();
- method public long getSurface();
- method public long getSurfaceBright();
- method public long getSurfaceDim();
+ method public long getSurfaceContainer();
+ method public long getSurfaceContainerHigh();
+ method public long getSurfaceContainerLow();
method public long getTertiary();
method public long getTertiaryContainer();
method public long getTertiaryDim();
property public final long background;
property public final long error;
+ property public final long errorContainer;
property public final long onBackground;
property public final long onError;
+ property public final long onErrorContainer;
property public final long onPrimary;
property public final long onPrimaryContainer;
property public final long onSecondary;
@@ -184,9 +188,9 @@
property public final long secondary;
property public final long secondaryContainer;
property public final long secondaryDim;
- property public final long surface;
- property public final long surfaceBright;
- property public final long surfaceDim;
+ property public final long surfaceContainer;
+ property public final long surfaceContainerHigh;
+ property public final long surfaceContainerLow;
property public final long tertiary;
property public final long tertiaryContainer;
property public final long tertiaryDim;
diff --git a/wear/compose/compose-material3/benchmark/src/androidTest/java/androidx/wear/compose/material3/benchmark/ColorSchemeBenchmark.kt b/wear/compose/compose-material3/benchmark/src/androidTest/java/androidx/wear/compose/material3/benchmark/ColorSchemeBenchmark.kt
index cdbce14..2e6efe4 100644
--- a/wear/compose/compose-material3/benchmark/src/androidTest/java/androidx/wear/compose/material3/benchmark/ColorSchemeBenchmark.kt
+++ b/wear/compose/compose-material3/benchmark/src/androidTest/java/androidx/wear/compose/material3/benchmark/ColorSchemeBenchmark.kt
@@ -135,10 +135,14 @@
)
// Surface
- Box(modifier = Modifier.size(1.dp).background(MaterialTheme.colorScheme.surface))
- Box(
- modifier = Modifier.size(1.dp).background(
- MaterialTheme.colorScheme.contentColorFor(MaterialTheme.colorScheme.surface)
+ Box(modifier = Modifier
+ .size(1.dp)
+ .background(MaterialTheme.colorScheme.surfaceContainer))
+ Box(modifier = Modifier
+ .size(1.dp)
+ .background(
+ MaterialTheme.colorScheme
+ .contentColorFor(MaterialTheme.colorScheme.surfaceContainer)
)
)
diff --git a/wear/compose/compose-material3/build.gradle b/wear/compose/compose-material3/build.gradle
index d18ada1..a6c3a81 100644
--- a/wear/compose/compose-material3/build.gradle
+++ b/wear/compose/compose-material3/build.gradle
@@ -78,7 +78,7 @@
androidx {
name = "Android Wear Compose Material 3"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
mavenVersion = LibraryVersions.WEAR_COMPOSE_MATERIAL3
inceptionYear = "2022"
description = "WearOS Compose Material 3 Library. This library makes it easier for " +
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/SwipeToDismissBoxSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/SwipeToDismissBoxSample.kt
index 2058328..9a49f2e 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/SwipeToDismissBoxSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/SwipeToDismissBoxSample.kt
@@ -127,7 +127,7 @@
modifier = Modifier
.height(40.dp)
.background(
- color = MaterialTheme.colorScheme.surface,
+ color = MaterialTheme.colorScheme.surfaceContainer,
shape = CircleShape
)
.padding(horizontal = 12.dp),
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ButtonTest.kt
index ed0430c..a7e67ae 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ButtonTest.kt
@@ -400,7 +400,7 @@
fun gives_enabled_filled_tonal_base_button_correct_colors() {
rule.verifyButtonColors(
status = Status.Enabled,
- expectedContainerColor = { MaterialTheme.colorScheme.surface },
+ expectedContainerColor = { MaterialTheme.colorScheme.surfaceContainer },
expectedContentColor = { MaterialTheme.colorScheme.onSurface },
content = { FilledTonalButton(Status.Enabled) }
)
@@ -1049,7 +1049,7 @@
val padding = 0.dp
setContentWithTheme {
- background = MaterialTheme.colorScheme.surface
+ background = MaterialTheme.colorScheme.surfaceContainer
Box(Modifier.background(background)) {
buttonColor = (colors().containerPainter(true) as ColorPainter).color
if (buttonColor == Color.Transparent) {
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonTest.kt
index d9d875a..4c33129 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonTest.kt
@@ -398,7 +398,7 @@
rule.verifyIconButtonColors(
status = Status.Enabled,
colors = { IconButtonDefaults.filledTonalIconButtonColors() },
- expectedContainerColor = { MaterialTheme.colorScheme.surface },
+ expectedContainerColor = { MaterialTheme.colorScheme.surfaceContainer },
expectedContentColor = { MaterialTheme.colorScheme.onSurfaceVariant }
)
}
@@ -536,7 +536,7 @@
val padding = 0.dp
setContentWithTheme {
- background = MaterialTheme.colorScheme.surface
+ background = MaterialTheme.colorScheme.surfaceContainer
Box(Modifier.background(background)) {
buttonColor = colors().containerColor(true)
if (buttonColor == Color.Transparent) {
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconToggleButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconToggleButtonTest.kt
index e8d1930..8d6e1bc 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconToggleButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconToggleButtonTest.kt
@@ -414,7 +414,7 @@
status = Status.Enabled,
checked = false,
colors = { IconButtonDefaults.iconToggleButtonColors() },
- containerColor = { MaterialTheme.colorScheme.surface },
+ containerColor = { MaterialTheme.colorScheme.surfaceContainer },
contentColor = { MaterialTheme.colorScheme.onSurfaceVariant }
)
@@ -425,7 +425,7 @@
status = Status.Disabled,
checked = false,
colors = { IconButtonDefaults.iconToggleButtonColors() },
- containerColor = { MaterialTheme.colorScheme.surface.toDisabledColor() },
+ containerColor = { MaterialTheme.colorScheme.surfaceContainer.toDisabledColor() },
contentColor = { MaterialTheme.colorScheme.onSurfaceVariant.toDisabledColor() }
)
@@ -507,7 +507,7 @@
uncheckedContentColor = overrideColor
)
},
- containerColor = { MaterialTheme.colorScheme.surface },
+ containerColor = { MaterialTheme.colorScheme.surfaceContainer },
contentColor = { overrideColor }
)
}
@@ -584,7 +584,7 @@
)
},
contentColor = { overrideColor },
- containerColor = { MaterialTheme.colorScheme.surface.toDisabledColor() }
+ containerColor = { MaterialTheme.colorScheme.surfaceContainer.toDisabledColor() }
)
}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SelectionControlsScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SelectionControlsScreenshotTest.kt
index 9a176be..bb5a14a 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SelectionControlsScreenshotTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SelectionControlsScreenshotTest.kt
@@ -99,6 +99,6 @@
.background(
MaterialTheme.colorScheme.primary
.copy(alpha = 0.5f)
- .compositeOver(MaterialTheme.colorScheme.surface)
+ .compositeOver(MaterialTheme.colorScheme.surfaceContainer)
)
}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonTest.kt
index 87e1a71..f00b3af 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonTest.kt
@@ -387,7 +387,7 @@
rule.verifyTextButtonColors(
status = Status.Enabled,
colors = { TextButtonDefaults.filledTonalTextButtonColors() },
- expectedContainerColor = { MaterialTheme.colorScheme.surface },
+ expectedContainerColor = { MaterialTheme.colorScheme.surfaceContainer },
expectedContentColor = { MaterialTheme.colorScheme.onSurface }
)
}
@@ -530,7 +530,7 @@
val padding = 0.dp
setContentWithTheme {
- background = MaterialTheme.colorScheme.surface
+ background = MaterialTheme.colorScheme.surfaceContainer
Box(Modifier.background(background)) {
buttonColor = colors().containerColor(true)
if (buttonColor == Color.Transparent) {
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextToggleButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextToggleButtonTest.kt
index d1202bbb..08d4b07 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextToggleButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextToggleButtonTest.kt
@@ -404,7 +404,7 @@
status = Status.Enabled,
checked = false,
colors = { TextButtonDefaults.textToggleButtonColors() },
- containerColor = { MaterialTheme.colorScheme.surface },
+ containerColor = { MaterialTheme.colorScheme.surfaceContainer },
contentColor = { MaterialTheme.colorScheme.onSurfaceVariant }
)
@@ -415,7 +415,7 @@
status = Status.Disabled,
checked = false,
colors = { TextButtonDefaults.textToggleButtonColors() },
- containerColor = { MaterialTheme.colorScheme.surface.toDisabledColor() },
+ containerColor = { MaterialTheme.colorScheme.surfaceContainer.toDisabledColor() },
contentColor = { MaterialTheme.colorScheme.onSurfaceVariant.toDisabledColor() }
)
@@ -497,7 +497,7 @@
uncheckedContentColor = override
)
},
- containerColor = { MaterialTheme.colorScheme.surface },
+ containerColor = { MaterialTheme.colorScheme.surfaceContainer },
contentColor = { override }
)
}
@@ -584,7 +584,7 @@
},
contentColor = { override },
containerColor = {
- MaterialTheme.colorScheme.surface.toDisabledColor()
+ MaterialTheme.colorScheme.surfaceContainer.toDisabledColor()
}
)
}
@@ -671,6 +671,7 @@
}
)
}
+
@RequiresApi(Build.VERSION_CODES.O)
private fun ComposeContentTestRule.isShape(
shape: Shape = CircleShape,
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ToggleButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ToggleButtonTest.kt
index 95f5df1..5f43605 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ToggleButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ToggleButtonTest.kt
@@ -816,7 +816,7 @@
checked: Boolean
): Color {
return if (checked) MaterialTheme.colorScheme.primaryContainer
- else MaterialTheme.colorScheme.surface
+ else MaterialTheme.colorScheme.surfaceContainer
}
@Composable
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ToggleControlsScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ToggleControlsScreenshotTest.kt
index 14cdb66..85d3f0e 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ToggleControlsScreenshotTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ToggleControlsScreenshotTest.kt
@@ -174,6 +174,6 @@
.background(
MaterialTheme.colorScheme.primary
.copy(alpha = 0.5f)
- .compositeOver(MaterialTheme.colorScheme.surface)
+ .compositeOver(MaterialTheme.colorScheme.surfaceContainer)
)
}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ColorScheme.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ColorScheme.kt
index 9b830dc..3256c14 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ColorScheme.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ColorScheme.kt
@@ -28,53 +28,56 @@
/**
* A [ColorScheme] holds all the named color parameters for a [MaterialTheme].
*
- * Color schemes are designed to be harmonious, ensure accessible text, and distinguish UI
- * elements and surfaces from one another.
+ * Color schemes are designed to be harmonious, ensure accessible text, and distinguish UI elements
+ * and surfaces from one another.
*
* The Material color system and custom schemes provide default values for color as a starting point
* for customization.
*
- * To learn more about color schemes,
- * see [Material Design Color System](https://m3.material.io/styles/color/the-color-system/color-roles).
+ * To learn more about color schemes, see
+ * [Material Design Color System](https://m3.material.io/styles/color/the-color-system/color-roles).
*
* @property primary The primary color is the color displayed most frequently across your app’s
- * screens and components.
+ * screens and components.
* @property primaryDim is less prominent than [primary] for component backgrounds
* @property primaryContainer is a standout container color for key components.
* @property onPrimary Color used for text and icons displayed on top of the primary color.
* @property onPrimaryContainer The color (and state variants) that should be used for content on
- * top of [primaryContainer].
+ * top of [primaryContainer].
* @property secondary The secondary color provides more ways to accent and distinguish your
- * product.
+ * product.
* @property secondaryDim is less prominent than [secondary] for component backgrounds.
* @property secondaryContainer A tonal color to be used in containers.
* @property onSecondary Color used for text and icons displayed on top of the secondary color.
* @property onSecondaryContainer The color (and state variants) that should be used for content on
- * top of [secondaryContainer].
- * @property tertiary The tertiary color that can be used to balance primary and secondary
- * colors, or bring heightened attention to an element.
- * @property tertiaryDim A less prominent tertiary color that can be used to balance
- * primary and secondary colors, or bring heightened attention to an element.
+ * top of [secondaryContainer].
+ * @property tertiary The tertiary color that can be used to balance primary and secondary colors,
+ * or bring heightened attention to an element.
+ * @property tertiaryDim A less prominent tertiary color that can be used to balance primary and
+ * secondary colors, or bring heightened attention to an element.
* @property tertiaryContainer A tonal color to be used in containers.
* @property onTertiary Color used for text and icons displayed on top of the tertiary color.
* @property onTertiaryContainer The color (and state variants) that should be used for content on
- * top of [tertiaryContainer].
- * @property surfaceDim A surface color used for large containment components
- * such as Card and Button with low prominence.
- * @property surface The main surface color that affect surfaces of components with large
- * containment areas, such as Card and Button.
- * @property surfaceBright A surface color used for large containment components
- * such Card and Button with high prominence.
+ * top of [tertiaryContainer].
+ * @property surfaceContainerLow A surface color used for large containment components such as Card
+ * and Button with low prominence.
+ * @property surfaceContainer The main surface color that affect surfaces of components with large
+ * containment areas, such as Card and Button.
+ * @property surfaceContainerHigh A surface color used for large containment components such Card
+ * and Button with high prominence.
* @property onSurface Color used for text and icons displayed on top of the surface color.
- * @property onSurfaceVariant The color for secondary text and icons on top of
- * [surface].
- * @property outline The main color for primary outline components.
- * The outline color role adds contrast for accessibility purposes.
+ * @property onSurfaceVariant The color for secondary text and icons on top of [surfaceContainer].
+ * @property outline The main color for primary outline components. The outline color role adds
+ * contrast for accessibility purposes.
* @property outlineVariant The secondary color for secondary outline components.
* @property background The background color that appears behind other content.
* @property onBackground Color used for text and icons displayed on top of the background color.
- * @property error The error color is used to indicate errors.
+ * @property error Color that indicates remove, delete, close or dismiss actions, such as Swipe to
+ * Reveal. Added as an errorContainer alternative that is slightly less alarming and urgent color.
* @property onError Color used for text and icons displayed on top of the error color.
+ * @property errorContainer Color that indicates errors or emergency actions, such as safety alerts.
+ * This color is for use-cases that are more alarming and urgent than the error color.
+ * @property onErrorContainer Color used for text and icons on the errorContainer color.
*/
@Immutable
@Stable
@@ -94,9 +97,9 @@
val tertiaryContainer: Color = ColorTokens.TertiaryContainer,
val onTertiary: Color = ColorTokens.OnTertiary,
val onTertiaryContainer: Color = ColorTokens.OnTertiaryContainer,
- val surfaceDim: Color = ColorTokens.SurfaceDim,
- val surface: Color = ColorTokens.Surface,
- val surfaceBright: Color = ColorTokens.SurfaceBright,
+ val surfaceContainerLow: Color = ColorTokens.SurfaceContainerLow,
+ val surfaceContainer: Color = ColorTokens.SurfaceContainer,
+ val surfaceContainerHigh: Color = ColorTokens.SurfaceContainerHigh,
val onSurface: Color = ColorTokens.OnSurface,
val onSurfaceVariant: Color = ColorTokens.OnSurfaceVariant,
val outline: Color = ColorTokens.Outline,
@@ -105,10 +108,10 @@
val onBackground: Color = ColorTokens.OnBackground,
val error: Color = ColorTokens.Error,
val onError: Color = ColorTokens.OnError,
+ val errorContainer: Color = ColorTokens.ErrorContainer,
+ val onErrorContainer: Color = ColorTokens.OnErrorContainer,
) {
- /**
- * Returns a copy of this Colors, optionally overriding some of the values.
- */
+ /** Returns a copy of this Colors, optionally overriding some of the values. */
fun copy(
primary: Color = this.primary,
primaryDim: Color = this.primaryDim,
@@ -125,9 +128,9 @@
tertiaryContainer: Color = this.tertiaryContainer,
onTertiary: Color = this.onTertiary,
onTertiaryContainer: Color = this.onTertiaryContainer,
- surfaceDim: Color = this.surfaceDim,
- surface: Color = this.surface,
- surfaceBright: Color = this.surfaceBright,
+ surfaceContainerLow: Color = this.surfaceContainerLow,
+ surfaceContainer: Color = this.surfaceContainer,
+ surfaceContainerHigh: Color = this.surfaceContainerHigh,
onSurface: Color = this.onSurface,
onSurfaceVariant: Color = this.onSurfaceVariant,
outline: Color = this.outline,
@@ -135,35 +138,40 @@
background: Color = this.background,
onBackground: Color = this.onBackground,
error: Color = this.error,
- onError: Color = this.onError
- ): ColorScheme = ColorScheme(
- primary = primary,
- primaryDim = primaryDim,
- primaryContainer = primaryContainer,
- onPrimary = onPrimary,
- onPrimaryContainer = onPrimaryContainer,
- secondary = secondary,
- secondaryDim = secondaryDim,
- secondaryContainer = secondaryContainer,
- onSecondary = onSecondary,
- onSecondaryContainer = onSecondaryContainer,
- tertiary = tertiary,
- tertiaryDim = tertiaryDim,
- tertiaryContainer = tertiaryContainer,
- onTertiary = onTertiary,
- onTertiaryContainer = onTertiaryContainer,
- surfaceDim = surfaceDim,
- surface = surface,
- surfaceBright = surfaceBright,
- onSurface = onSurface,
- onSurfaceVariant = onSurfaceVariant,
- outline = outline,
- outlineVariant = outlineVariant,
- background = background,
- onBackground = onBackground,
- error = error,
- onError = onError
- )
+ onError: Color = this.onError,
+ errorContainer: Color = this.errorContainer,
+ onErrorContainer: Color = this.onErrorContainer,
+ ): ColorScheme =
+ ColorScheme(
+ primary = primary,
+ primaryDim = primaryDim,
+ primaryContainer = primaryContainer,
+ onPrimary = onPrimary,
+ onPrimaryContainer = onPrimaryContainer,
+ secondary = secondary,
+ secondaryDim = secondaryDim,
+ secondaryContainer = secondaryContainer,
+ onSecondary = onSecondary,
+ onSecondaryContainer = onSecondaryContainer,
+ tertiary = tertiary,
+ tertiaryDim = tertiaryDim,
+ tertiaryContainer = tertiaryContainer,
+ onTertiary = onTertiary,
+ onTertiaryContainer = onTertiaryContainer,
+ surfaceContainerLow = surfaceContainerLow,
+ surfaceContainer = surfaceContainer,
+ surfaceContainerHigh = surfaceContainerHigh,
+ onSurface = onSurface,
+ onSurfaceVariant = onSurfaceVariant,
+ outline = outline,
+ outlineVariant = outlineVariant,
+ background = background,
+ onBackground = onBackground,
+ error = error,
+ onError = onError,
+ errorContainer = errorContainer,
+ onErrorContainer = onErrorContainer,
+ )
override fun toString(): String {
return "Colors(" +
@@ -182,17 +190,18 @@
"tertiaryContainer=$tertiaryContainer, " +
"onTertiary=$onTertiary, " +
"onTertiaryContainer=$onTertiaryContainer, " +
- "surfaceDim=$surfaceDim, " +
- "surface=$surface, " +
- "surfaceBright=$surfaceBright, " +
+ "surfaceContainerLow=$surfaceContainerLow, " +
+ "surfaceContainer=$surfaceContainer, " +
+ "surfaceContainerHigh=$surfaceContainerHigh, " +
"onSurface=$onSurface, " +
"onSurfaceVariant=$onSurfaceVariant, " +
"outline=$outline, " +
"outlineVariant=$outlineVariant, " +
"background=$background, " +
"onBackground=$onBackground, " +
- "error=$error, " +
- "onError=$onError" +
+ "onError=$onError," +
+ "errorContainer=$errorContainer, " +
+ "onErrorContainer=$onErrorContainer" +
")"
}
@@ -229,8 +238,8 @@
}
/**
- * The Material color system contains pairs of colors that are typically used for the background
- * and content color inside a component. For example, a Button typically uses `primary` for its
+ * The Material color system contains pairs of colors that are typically used for the background and
+ * content color inside a component. For example, a Button typically uses `primary` for its
* background, and `onPrimary` for the color of its content (usually text or iconography).
*
* This function tries to match the provided [backgroundColor] to a 'background' color in this
@@ -241,53 +250,56 @@
* [Color.Unspecified].
*
* @return the matching content color for [backgroundColor]. If [backgroundColor] is not present in
- * the theme's [ColorScheme], then returns [Color.Unspecified].
- *
+ * the theme's [ColorScheme], then returns [Color.Unspecified].
* @see contentColorFor
*/
fun ColorScheme.contentColorFor(backgroundColor: Color): Color {
return when (backgroundColor) {
- primary, primaryDim -> onPrimary
+ primary,
+ primaryDim -> onPrimary
primaryContainer -> onPrimaryContainer
- secondary, secondaryDim -> onSecondary
+ secondary,
+ secondaryDim -> onSecondary
secondaryContainer -> onSecondaryContainer
- tertiary, tertiaryDim -> onTertiary
+ tertiary,
+ tertiaryDim -> onTertiary
tertiaryContainer -> onTertiaryContainer
- surface, surfaceDim, surfaceBright -> onSurface
+ surfaceContainer,
+ surfaceContainerLow,
+ surfaceContainerHigh -> onSurface
background -> onBackground
error -> onError
+ errorContainer -> onErrorContainer
else -> Color.Unspecified
}
}
/**
- * The Material color system contains pairs of colors that are typically used for the background
- * and content color inside a component. For example, a Button typically uses `primary` for its
+ * The Material color system contains pairs of colors that are typically used for the background and
+ * content color inside a component. For example, a Button typically uses `primary` for its
* background, and `onPrimary` for the color of its content (usually text or iconography).
*
* This function tries to match the provided [backgroundColor] to a 'background' color in this
* [ColorScheme], and then will return the corresponding color used for content. For example, when
* [backgroundColor] is [ColorScheme.primary], this will return [ColorScheme.onPrimary].
*
- * If [backgroundColor] does not match a background color in the theme, this will return
- * the current value of [LocalContentColor] as a best-effort color.
+ * If [backgroundColor] does not match a background color in the theme, this will return the current
+ * value of [LocalContentColor] as a best-effort color.
*
* @return the matching content color for [backgroundColor]. If [backgroundColor] is not present in
- * the theme's [ColorScheme], then returns the current value of [LocalContentColor].
- *
+ * the theme's [ColorScheme], then returns the current value of [LocalContentColor].
* @see ColorScheme.contentColorFor
*/
@Composable
@ReadOnlyComposable
fun contentColorFor(backgroundColor: Color): Color =
- MaterialTheme.colorScheme
- .contentColorFor(backgroundColor)
- .takeOrElse { LocalContentColor.current }
+ MaterialTheme.colorScheme.contentColorFor(backgroundColor).takeOrElse {
+ LocalContentColor.current
+ }
/**
* Helper function for component color tokens. Here is an example on how to use component color
- * tokens:
- * ``MaterialTheme.colorScheme.fromToken(FilledButtonTokens.ContainerColor)``
+ * tokens: ``MaterialTheme.colorScheme.fromToken(FilledButtonTokens.ContainerColor)``
*/
internal fun ColorScheme.fromToken(value: ColorSchemeKeyTokens): Color {
return when (value) {
@@ -306,9 +318,9 @@
ColorSchemeKeyTokens.TertiaryContainer -> tertiaryContainer
ColorSchemeKeyTokens.OnTertiary -> onTertiary
ColorSchemeKeyTokens.OnTertiaryContainer -> onTertiaryContainer
- ColorSchemeKeyTokens.SurfaceDim -> surfaceDim
- ColorSchemeKeyTokens.Surface -> surface
- ColorSchemeKeyTokens.SurfaceBright -> surfaceBright
+ ColorSchemeKeyTokens.SurfaceContainerLow -> surfaceContainerLow
+ ColorSchemeKeyTokens.SurfaceContainer -> surfaceContainer
+ ColorSchemeKeyTokens.SurfaceContainerHigh -> surfaceContainerHigh
ColorSchemeKeyTokens.OnSurface -> onSurface
ColorSchemeKeyTokens.OnSurfaceVariant -> onSurfaceVariant
ColorSchemeKeyTokens.Outline -> outline
@@ -317,33 +329,33 @@
ColorSchemeKeyTokens.OnBackground -> onBackground
ColorSchemeKeyTokens.Error -> error
ColorSchemeKeyTokens.OnError -> onError
+ ColorSchemeKeyTokens.ErrorContainer -> errorContainer
+ ColorSchemeKeyTokens.OnErrorContainer -> onErrorContainer
}
}
/**
* CompositionLocal used to pass [ColorScheme] down the tree.
*
- * Setting the value here is typically done as part of [MaterialTheme].
- * To retrieve the current value of this CompositionLocal, use
- * [MaterialTheme.colorScheme].
+ * Setting the value here is typically done as part of [MaterialTheme]. To retrieve the current
+ * value of this CompositionLocal, use [MaterialTheme.colorScheme].
*/
internal val LocalColorScheme = staticCompositionLocalOf<ColorScheme> { ColorScheme() }
/**
* Convert given color to disabled color.
+ *
* @param disabledAlpha Alpha used to represent disabled colors.
*/
internal fun Color.toDisabledColor(disabledAlpha: Float = DisabledContentAlpha) =
this.copy(alpha = this.alpha * disabledAlpha)
/**
- * Converts a color token key to the local color scheme provided by the theme.
- * The color references the [LocalColorScheme].
+ * Converts a color token key to the local color scheme provided by the theme. The color references
+ * the [LocalColorScheme].
*/
internal val ColorSchemeKeyTokens.value: Color
- @ReadOnlyComposable
- @Composable
- get() = MaterialTheme.colorScheme.fromToken(this)
+ @ReadOnlyComposable @Composable get() = MaterialTheme.colorScheme.fromToken(this)
internal const val DisabledContentAlpha = 0.38f
internal const val DisabledContainerAlpha = 0.12f
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ContentColor.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ContentColor.kt
index f05bed6..0f79f2e 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ContentColor.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ContentColor.kt
@@ -21,8 +21,9 @@
/**
* CompositionLocal containing the preferred content color for a given position in the hierarchy.
- * This typically represents the `on` color for a color in [ColorScheme]. For example, if the background
- * color is [ColorScheme.surface], this color is typically set to [ColorScheme.onSurface].
+ * This typically represents the `on` color for a color in [ColorScheme]. For example, if the
+ * background color is [ColorScheme.surfaceContainer], this color is typically set to
+ * [ColorScheme.onSurface].
*
* This color should be used for any typography / iconography, to ensure that the color of these
* adjusts when the background color changes. For example, on a dark background, text should be
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt
index a02f410..133d447 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt
@@ -329,7 +329,7 @@
*/
@Composable
fun colors(
- containerColor: Color = MaterialTheme.colorScheme.surface,
+ containerColor: Color = MaterialTheme.colorScheme.surfaceContainer,
buttonIconColor: Color = MaterialTheme.colorScheme.secondary,
selectedBarColor: Color = MaterialTheme.colorScheme.primary,
unselectedBarColor: Color = MaterialTheme.colorScheme.background.copy(alpha = 0.3f),
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ToggleControls.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ToggleControls.kt
index a7c48bc..b44ce9b 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ToggleControls.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ToggleControls.kt
@@ -458,7 +458,7 @@
checkedTrackBorderColor: Color = MaterialTheme.colorScheme.primary,
uncheckedThumbColor: Color = MaterialTheme.colorScheme.outline,
uncheckedThumbIconColor: Color = Color.Transparent,
- uncheckedTrackColor: Color = MaterialTheme.colorScheme.surface,
+ uncheckedTrackColor: Color = MaterialTheme.colorScheme.surfaceContainer,
uncheckedTrackBorderColor: Color = MaterialTheme.colorScheme.outline,
disabledCheckedThumbColor: Color = MaterialTheme.colorScheme.background.toDisabledColor(),
disabledCheckedThumbIconColor: Color =
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/CardTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/CardTokens.kt
index 93778f2..f0cc446 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/CardTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/CardTokens.kt
@@ -25,7 +25,7 @@
val AppImageSize = 16.0.dp
val AppNameColor = ColorSchemeKeyTokens.OnSurfaceVariant
val AppNameTypography = TypographyKeyTokens.LabelSmall
- val ContainerColor = ColorSchemeKeyTokens.Surface
+ val ContainerColor = ColorSchemeKeyTokens.SurfaceContainer
val ContentColor = ColorSchemeKeyTokens.OnSurfaceVariant
val ContentTypography = TypographyKeyTokens.BodyLarge
val Shape = ShapeKeyTokens.CornerLarge
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ColorSchemeKeyTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ColorSchemeKeyTokens.kt
index 9bbfe57..3dbbc0c 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ColorSchemeKeyTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ColorSchemeKeyTokens.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-// VERSION: v0_7
+// VERSION: v0_42
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.wear.compose.material3.tokens
@@ -22,8 +22,10 @@
internal enum class ColorSchemeKeyTokens {
Background,
Error,
+ ErrorContainer,
OnBackground,
OnError,
+ OnErrorContainer,
OnPrimary,
OnPrimaryContainer,
OnSecondary,
@@ -40,9 +42,9 @@
Secondary,
SecondaryContainer,
SecondaryDim,
- Surface,
- SurfaceBright,
- SurfaceDim,
+ SurfaceContainer,
+ SurfaceContainerHigh,
+ SurfaceContainerLow,
Tertiary,
TertiaryContainer,
TertiaryDim,
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ColorTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ColorTokens.kt
index e3be595..68dc43f 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ColorTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ColorTokens.kt
@@ -14,16 +14,18 @@
* limitations under the License.
*/
-// VERSION: v0_8
+// VERSION: v0_42
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.wear.compose.material3.tokens
internal object ColorTokens {
val Background = PaletteTokens.Neutral0
- val Error = PaletteTokens.Error65
+ val Error = PaletteTokens.Error80
+ val ErrorContainer = PaletteTokens.Error70
val OnBackground = PaletteTokens.Neutral100
- val OnError = PaletteTokens.Error10
+ val OnError = PaletteTokens.Error20
+ val OnErrorContainer = PaletteTokens.Error10
val OnPrimary = PaletteTokens.Primary10
val OnPrimaryContainer = PaletteTokens.Primary90
val OnSecondary = PaletteTokens.Secondary10
@@ -40,9 +42,9 @@
val Secondary = PaletteTokens.Secondary90
val SecondaryContainer = PaletteTokens.Secondary30
val SecondaryDim = PaletteTokens.Secondary80
- val Surface = PaletteTokens.Neutral20
- val SurfaceBright = PaletteTokens.Neutral30
- val SurfaceDim = PaletteTokens.Neutral15
+ val SurfaceContainer = PaletteTokens.Neutral20
+ val SurfaceContainerHigh = PaletteTokens.Neutral30
+ val SurfaceContainerLow = PaletteTokens.Neutral15
val Tertiary = PaletteTokens.Tertiary90
val TertiaryContainer = PaletteTokens.Tertiary30
val TertiaryDim = PaletteTokens.Tertiary80
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalButtonTokens.kt
index 1341a0b..9275864 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalButtonTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalButtonTokens.kt
@@ -22,7 +22,7 @@
import androidx.compose.ui.unit.dp
internal object FilledTonalButtonTokens {
- val ContainerColor = ColorSchemeKeyTokens.Surface
+ val ContainerColor = ColorSchemeKeyTokens.SurfaceContainer
val ContainerHeight = 52.0.dp
val ContainerShape = ShapeKeyTokens.CornerLarge
val DisabledContainerColor = ColorSchemeKeyTokens.OnSurface
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalIconButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalIconButtonTokens.kt
index e2c42f5..f4dc76f 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalIconButtonTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalIconButtonTokens.kt
@@ -20,10 +20,10 @@
package androidx.wear.compose.material3.tokens
internal object FilledTonalIconButtonTokens {
- val ContainerColor = ColorSchemeKeyTokens.Surface
- val ContentColor = ColorSchemeKeyTokens.OnSurfaceVariant
- val DisabledContainerColor = ColorSchemeKeyTokens.OnSurface
- val DisabledContainerOpacity = 0.12f
- val DisabledContentColor = ColorSchemeKeyTokens.OnSurface
- val DisabledContentOpacity = 0.38f
+ val ContainerColor = ColorSchemeKeyTokens.SurfaceContainer
+ val ContentColor = ColorSchemeKeyTokens.OnSurfaceVariant
+ val DisabledContainerColor = ColorSchemeKeyTokens.OnSurface
+ val DisabledContainerOpacity = 0.12f
+ val DisabledContentColor = ColorSchemeKeyTokens.OnSurface
+ val DisabledContentOpacity = 0.38f
}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalTextButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalTextButtonTokens.kt
index a77dd0a..8459b18 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalTextButtonTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalTextButtonTokens.kt
@@ -20,7 +20,7 @@
package androidx.wear.compose.material3.tokens
internal object FilledTonalTextButtonTokens {
- val ContainerColor = ColorSchemeKeyTokens.Surface
+ val ContainerColor = ColorSchemeKeyTokens.SurfaceContainer
val ContainerShape = ShapeKeyTokens.CornerFull
val ContentColor = ColorSchemeKeyTokens.OnSurface
val ContentFont = TypographyKeyTokens.LabelMedium
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/IconToggleButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/IconToggleButtonTokens.kt
index b6ae374..c626ab9 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/IconToggleButtonTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/IconToggleButtonTokens.kt
@@ -26,10 +26,10 @@
val DisabledCheckedContainerOpacity = 0.38f
val DisabledCheckedContentColor = ColorSchemeKeyTokens.OnPrimary
val DisabledCheckedContentOpacity = 0.38f
- val DisabledUncheckedContainerColor = ColorSchemeKeyTokens.Surface
+ val DisabledUncheckedContainerColor = ColorSchemeKeyTokens.SurfaceContainer
val DisabledUncheckedContainerOpacity = 0.38f
val DisabledUncheckedContentColor = ColorSchemeKeyTokens.OnSurfaceVariant
val DisabledUncheckedContentOpacity = 0.38f
- val UncheckedContainerColor = ColorSchemeKeyTokens.Surface
+ val UncheckedContainerColor = ColorSchemeKeyTokens.SurfaceContainer
val UncheckedContentColor = ColorSchemeKeyTokens.OnSurfaceVariant
}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ImageButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ImageButtonTokens.kt
index 1f8188e..51a8342 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ImageButtonTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ImageButtonTokens.kt
@@ -20,7 +20,7 @@
package androidx.wear.compose.material3.tokens
internal object ImageButtonTokens {
- val BackgroundImageGradientColor = ColorSchemeKeyTokens.Surface
+ val BackgroundImageGradientColor = ColorSchemeKeyTokens.SurfaceContainer
val ContentColor = ColorSchemeKeyTokens.OnSurface
val DisabledContentOpacity = 0.38f
val DisabledContentColor = ColorSchemeKeyTokens.OnSurface
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/PaletteTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/PaletteTokens.kt
index 65e3d32..6c79d71 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/PaletteTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/PaletteTokens.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-// VERSION: v0_8
+// VERSION: v0_44
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.wear.compose.material3.tokens
@@ -23,78 +23,79 @@
internal object PaletteTokens {
val Error0 = Color(red = 0, green = 0, blue = 0)
- val Error10 = Color(red = 65, green = 0, blue = 2)
+ val Error10 = Color(red = 65, green = 14, blue = 11)
val Error100 = Color(red = 255, green = 255, blue = 255)
- val Error20 = Color(red = 104, green = 1, blue = 7)
- val Error30 = Color(red = 137, green = 29, blue = 26)
- val Error40 = Color(red = 170, green = 53, blue = 47)
- val Error50 = Color(red = 203, green = 77, blue = 69)
- val Error60 = Color(red = 236, green = 102, blue = 91)
- val Error65 = Color(red = 253, green = 114, blue = 103)
- val Error70 = Color(red = 255, green = 137, blue = 126)
- val Error80 = Color(red = 255, green = 180, blue = 172)
- val Error85 = Color(red = 255, green = 199, blue = 193)
- val Error90 = Color(red = 255, green = 218, blue = 214)
- val Error95 = Color(red = 255, green = 237, blue = 234)
+ val Error20 = Color(red = 96, green = 20, blue = 16)
+ val Error30 = Color(red = 140, green = 29, blue = 24)
+ val Error40 = Color(red = 179, green = 38, blue = 30)
+ val Error5 = Color(red = 33, green = 7, blue = 6)
+ val Error50 = Color(red = 220, green = 54, blue = 46)
+ val Error60 = Color(red = 228, green = 105, blue = 98)
+ val Error65 = Color(red = 232, green = 126, blue = 120)
+ val Error70 = Color(red = 236, green = 146, blue = 142)
+ val Error80 = Color(red = 242, green = 184, blue = 181)
+ val Error85 = Color(red = 245, green = 203, blue = 200)
+ val Error90 = Color(red = 249, green = 222, blue = 220)
+ val Error95 = Color(red = 252, green = 238, blue = 238)
val Neutral0 = Color(red = 0, green = 0, blue = 0)
- val Neutral10 = Color(red = 31, green = 31, blue = 31)
+ val Neutral10 = Color(red = 29, green = 26, blue = 38)
val Neutral100 = Color(red = 255, green = 255, blue = 255)
- val Neutral15 = Color(red = 37, green = 38, blue = 38)
- val Neutral20 = Color(red = 48, green = 48, blue = 48)
- val Neutral30 = Color(red = 71, green = 71, blue = 71)
- val Neutral40 = Color(red = 94, green = 94, blue = 94)
- val Neutral50 = Color(red = 117, green = 117, blue = 117)
- val Neutral60 = Color(red = 143, green = 143, blue = 143)
- val Neutral70 = Color(red = 171, green = 171, blue = 171)
- val Neutral80 = Color(red = 199, green = 199, blue = 199)
- val Neutral90 = Color(red = 227, green = 227, blue = 227)
- val Neutral95 = Color(red = 242, green = 242, blue = 242)
+ val Neutral15 = Color(red = 39, green = 36, blue = 48)
+ val Neutral20 = Color(red = 51, green = 46, blue = 60)
+ val Neutral30 = Color(red = 73, green = 68, blue = 83)
+ val Neutral40 = Color(red = 97, green = 92, blue = 107)
+ val Neutral50 = Color(red = 122, green = 116, blue = 132)
+ val Neutral60 = Color(red = 148, green = 142, blue = 159)
+ val Neutral70 = Color(red = 175, green = 168, blue = 185)
+ val Neutral80 = Color(red = 203, green = 195, blue = 213)
+ val Neutral90 = Color(red = 232, green = 223, blue = 242)
+ val Neutral95 = Color(red = 246, green = 237, blue = 255)
val NeutralVariant0 = Color(red = 0, green = 0, blue = 0)
- val NeutralVariant10 = Color(red = 25, green = 29, blue = 28)
+ val NeutralVariant10 = Color(red = 29, green = 26, blue = 35)
val NeutralVariant100 = Color(red = 255, green = 255, blue = 255)
- val NeutralVariant20 = Color(red = 45, green = 49, blue = 47)
- val NeutralVariant30 = Color(red = 68, green = 71, blue = 70)
- val NeutralVariant40 = Color(red = 92, green = 95, blue = 94)
- val NeutralVariant50 = Color(red = 116, green = 119, blue = 117)
- val NeutralVariant60 = Color(red = 142, green = 145, blue = 143)
- val NeutralVariant70 = Color(red = 169, green = 172, blue = 170)
- val NeutralVariant80 = Color(red = 196, green = 199, blue = 197)
- val NeutralVariant90 = Color(red = 225, green = 227, blue = 225)
- val NeutralVariant95 = Color(red = 239, green = 242, blue = 239)
+ val NeutralVariant20 = Color(red = 50, green = 47, blue = 56)
+ val NeutralVariant30 = Color(red = 73, green = 69, blue = 79)
+ val NeutralVariant40 = Color(red = 97, green = 93, blue = 103)
+ val NeutralVariant50 = Color(red = 122, green = 117, blue = 128)
+ val NeutralVariant60 = Color(red = 148, green = 143, blue = 154)
+ val NeutralVariant70 = Color(red = 175, green = 169, blue = 181)
+ val NeutralVariant80 = Color(red = 202, green = 196, blue = 208)
+ val NeutralVariant90 = Color(red = 231, green = 224, blue = 236)
+ val NeutralVariant95 = Color(red = 245, green = 238, blue = 251)
val Primary0 = Color(red = 0, green = 0, blue = 0)
- val Primary10 = Color(red = 4, green = 30, blue = 73)
+ val Primary10 = Color(red = 33, green = 15, blue = 72)
val Primary100 = Color(red = 255, green = 255, blue = 255)
- val Primary20 = Color(red = 4, green = 30, blue = 73)
- val Primary30 = Color(red = 8, green = 66, blue = 160)
- val Primary40 = Color(red = 11, green = 87, blue = 208)
- val Primary50 = Color(red = 27, green = 110, blue = 243)
- val Primary60 = Color(red = 76, green = 141, blue = 246)
- val Primary70 = Color(red = 124, green = 172, blue = 248)
- val Primary80 = Color(red = 168, green = 199, blue = 250)
- val Primary90 = Color(red = 211, green = 227, blue = 253)
- val Primary95 = Color(red = 236, green = 243, blue = 254)
+ val Primary20 = Color(red = 55, green = 38, blue = 94)
+ val Primary30 = Color(red = 77, green = 61, blue = 118)
+ val Primary40 = Color(red = 102, green = 85, blue = 144)
+ val Primary50 = Color(red = 127, green = 109, blue = 170)
+ val Primary60 = Color(red = 153, green = 135, blue = 198)
+ val Primary70 = Color(red = 180, green = 161, blue = 226)
+ val Primary80 = Color(red = 208, green = 188, blue = 255)
+ val Primary90 = Color(red = 233, green = 221, blue = 255)
+ val Primary95 = Color(red = 246, green = 237, blue = 255)
val Secondary0 = Color(red = 0, green = 0, blue = 0)
- val Secondary10 = Color(red = 0, green = 29, blue = 53)
+ val Secondary10 = Color(red = 30, green = 24, blue = 46)
val Secondary100 = Color(red = 255, green = 255, blue = 255)
- val Secondary20 = Color(red = 0, green = 51, blue = 85)
- val Secondary30 = Color(red = 0, green = 51, blue = 85)
- val Secondary40 = Color(red = 0, green = 99, blue = 155)
- val Secondary50 = Color(red = 4, green = 125, blue = 183)
- val Secondary60 = Color(red = 57, green = 152, blue = 211)
- val Secondary70 = Color(red = 90, green = 179, blue = 240)
- val Secondary80 = Color(red = 127, green = 207, blue = 255)
- val Secondary90 = Color(red = 194, green = 231, blue = 255)
- val Secondary95 = Color(red = 223, green = 243, blue = 255)
+ val Secondary20 = Color(red = 51, green = 45, blue = 68)
+ val Secondary30 = Color(red = 74, green = 67, blue = 91)
+ val Secondary40 = Color(red = 98, green = 90, blue = 116)
+ val Secondary50 = Color(red = 123, green = 115, blue = 141)
+ val Secondary60 = Color(red = 150, green = 140, blue = 168)
+ val Secondary70 = Color(red = 177, green = 167, blue = 195)
+ val Secondary80 = Color(red = 204, green = 194, blue = 223)
+ val Secondary90 = Color(red = 233, green = 222, blue = 252)
+ val Secondary95 = Color(red = 246, green = 237, blue = 255)
val Tertiary0 = Color(red = 0, green = 0, blue = 0)
- val Tertiary10 = Color(red = 7, green = 39, blue = 17)
+ val Tertiary10 = Color(red = 46, green = 21, blue = 0)
val Tertiary100 = Color(red = 255, green = 255, blue = 255)
- val Tertiary20 = Color(red = 7, green = 39, blue = 17)
- val Tertiary30 = Color(red = 15, green = 82, blue = 35)
- val Tertiary40 = Color(red = 20, green = 108, blue = 46)
- val Tertiary50 = Color(red = 25, green = 134, blue = 57)
- val Tertiary60 = Color(red = 30, green = 164, blue = 70)
- val Tertiary70 = Color(red = 55, green = 190, blue = 95)
- val Tertiary80 = Color(red = 109, green = 213, blue = 140)
- val Tertiary90 = Color(red = 196, green = 238, blue = 208)
- val Tertiary95 = Color(red = 231, green = 248, blue = 237)
+ val Tertiary20 = Color(red = 76, green = 39, blue = 0)
+ val Tertiary30 = Color(red = 108, green = 58, blue = 3)
+ val Tertiary40 = Color(red = 136, green = 81, blue = 27)
+ val Tertiary50 = Color(red = 165, green = 105, blue = 49)
+ val Tertiary60 = Color(red = 195, green = 130, blue = 72)
+ val Tertiary70 = Color(red = 226, green = 156, blue = 95)
+ val Tertiary80 = Color(red = 255, green = 183, blue = 122)
+ val Tertiary90 = Color(red = 255, green = 220, blue = 194)
+ val Tertiary95 = Color(red = 255, green = 238, blue = 226)
}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/RadioButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/RadioButtonTokens.kt
index 85d5654..71802e6 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/RadioButtonTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/RadioButtonTokens.kt
@@ -25,7 +25,7 @@
val DisabledSelectedIconColor = ColorSchemeKeyTokens.OnPrimaryContainer
val DisabledSelectedSecondaryLabelColor = ColorSchemeKeyTokens.OnPrimaryContainer
val DisabledSelectedSecondaryLabelOpacity = 0.8f
- val DisabledUnselectedContainerColor = ColorSchemeKeyTokens.Surface
+ val DisabledUnselectedContainerColor = ColorSchemeKeyTokens.SurfaceContainer
val DisabledUnselectedContentColor = ColorSchemeKeyTokens.OnSurface
val DisabledUnselectedIconColor = ColorSchemeKeyTokens.Primary
val DisabledUnselectedSecondaryLabelColor = ColorSchemeKeyTokens.OnSurfaceVariant
@@ -37,7 +37,7 @@
val SelectedSecondaryLabelColor = ColorSchemeKeyTokens.OnPrimaryContainer
val SelectedSecondaryLabelOpacity = 0.8f
val Shape = ShapeKeyTokens.CornerLarge
- val UnselectedContainerColor = ColorSchemeKeyTokens.Surface
+ val UnselectedContainerColor = ColorSchemeKeyTokens.SurfaceContainer
val UnselectedContentColor = ColorSchemeKeyTokens.OnSurface
val UnselectedIconColor = ColorSchemeKeyTokens.Primary
val UnselectedSecondaryLabelColor = ColorSchemeKeyTokens.OnSurfaceVariant
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ShapeTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ShapeTokens.kt
index 7a65a84..f9c5b20 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ShapeTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ShapeTokens.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-// VERSION: v0_7
+// VERSION: v0_45
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.wear.compose.material3.tokens
@@ -29,7 +29,7 @@
val CornerExtraSmall = RoundedCornerShape(4.0.dp)
val CornerFull = CircleShape
val CornerLarge = RoundedCornerShape(26.0.dp)
- val CornerMedium = RoundedCornerShape(16.0.dp)
+ val CornerMedium = RoundedCornerShape(18.0.dp)
val CornerNone = RectangleShape
val CornerSmall = RoundedCornerShape(8.0.dp)
}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/SplitRadioButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/SplitRadioButtonTokens.kt
index 91b1135..afa6d71 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/SplitRadioButtonTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/SplitRadioButtonTokens.kt
@@ -26,10 +26,10 @@
val DisabledSelectedSecondaryLabelOpacity = 0.8f
val DisabledSelectedSplitContainerColor = ColorSchemeKeyTokens.Primary
val DisabledSelectedSplitContainerOpacity = 0.15f
- val DisabledUnselectedContainerColor = ColorSchemeKeyTokens.Surface
+ val DisabledUnselectedContainerColor = ColorSchemeKeyTokens.SurfaceContainer
val DisabledUnselectedContentColor = ColorSchemeKeyTokens.OnSurface
val DisabledUnselectedSecondaryLabelColor = ColorSchemeKeyTokens.OnSurfaceVariant
- val DisabledUnselectedSplitContainerColor = ColorSchemeKeyTokens.SurfaceBright
+ val DisabledUnselectedSplitContainerColor = ColorSchemeKeyTokens.SurfaceContainerHigh
val LabelFont = TypographyKeyTokens.LabelMedium
val SecondaryLabelFont = TypographyKeyTokens.LabelSmall
val SelectedContainerColor = ColorSchemeKeyTokens.PrimaryContainer
@@ -39,8 +39,8 @@
val SelectedSplitContainerColor = ColorSchemeKeyTokens.Primary
val SelectedSplitContainerOpacity = 0.15f
val Shape = ShapeKeyTokens.CornerLarge
- val UnselectedContainerColor = ColorSchemeKeyTokens.Surface
+ val UnselectedContainerColor = ColorSchemeKeyTokens.SurfaceContainer
val UnselectedContentColor = ColorSchemeKeyTokens.OnSurface
val UnselectedSecondaryLabelColor = ColorSchemeKeyTokens.OnSurfaceVariant
- val UnselectedSplitContainerColor = ColorSchemeKeyTokens.SurfaceBright
+ val UnselectedSplitContainerColor = ColorSchemeKeyTokens.SurfaceContainerHigh
}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/SplitToggleButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/SplitToggleButtonTokens.kt
index 6241bb6..73a0847 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/SplitToggleButtonTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/SplitToggleButtonTokens.kt
@@ -34,14 +34,14 @@
val DisabledCheckedSplitContainerColor = ColorSchemeKeyTokens.Primary
val DisabledCheckedSplitContainerOpacity = 0.15f
val DisabledOpacity = 0.38f
- val DisabledUncheckedContainerColor = ColorSchemeKeyTokens.Surface
+ val DisabledUncheckedContainerColor = ColorSchemeKeyTokens.SurfaceContainer
val DisabledUncheckedContentColor = ColorSchemeKeyTokens.OnSurface
val DisabledUncheckedSecondaryLabelColor = ColorSchemeKeyTokens.OnSurfaceVariant
- val DisabledUncheckedSplitContainerColor = ColorSchemeKeyTokens.SurfaceBright
+ val DisabledUncheckedSplitContainerColor = ColorSchemeKeyTokens.SurfaceContainerHigh
val LabelFont = TypographyKeyTokens.LabelMedium
val SecondaryLabelFont = TypographyKeyTokens.LabelSmall
- val UncheckedContainerColor = ColorSchemeKeyTokens.Surface
+ val UncheckedContainerColor = ColorSchemeKeyTokens.SurfaceContainer
val UncheckedContentColor = ColorSchemeKeyTokens.OnSurface
val UncheckedSecondaryLabelColor = ColorSchemeKeyTokens.OnSurfaceVariant
- val UncheckedSplitContainerColor = ColorSchemeKeyTokens.SurfaceBright
+ val UncheckedSplitContainerColor = ColorSchemeKeyTokens.SurfaceContainerHigh
}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TextToggleButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TextToggleButtonTokens.kt
index 3e04ba6..fb0b3c2 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TextToggleButtonTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TextToggleButtonTokens.kt
@@ -28,10 +28,10 @@
val DisabledCheckedContainerOpacity = 0.38f
val DisabledCheckedContentColor = ColorSchemeKeyTokens.OnPrimary
val DisabledCheckedContentOpacity = 0.38f
- val DisabledUncheckedContainerColor = ColorSchemeKeyTokens.Surface
+ val DisabledUncheckedContainerColor = ColorSchemeKeyTokens.SurfaceContainer
val DisabledUncheckedContainerOpacity = 0.38f
val DisabledUncheckedContentColor = ColorSchemeKeyTokens.OnSurfaceVariant
val DisabledUncheckedContentOpacity = 0.38f
- val UncheckedContainerColor = ColorSchemeKeyTokens.Surface
+ val UncheckedContainerColor = ColorSchemeKeyTokens.SurfaceContainer
val UncheckedContentColor = ColorSchemeKeyTokens.OnSurfaceVariant
}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ToggleButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ToggleButtonTokens.kt
index c211e57..f4c0eef 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ToggleButtonTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ToggleButtonTokens.kt
@@ -32,13 +32,13 @@
val DisabledCheckedSecondaryLabelColor = ColorSchemeKeyTokens.OnPrimaryContainer
val DisabledCheckedSecondaryLabelOpacity = 0.8f
val DisabledOpacity = 0.38f
- val DisabledUncheckedContainerColor = ColorSchemeKeyTokens.Surface
+ val DisabledUncheckedContainerColor = ColorSchemeKeyTokens.SurfaceContainer
val DisabledUncheckedContentColor = ColorSchemeKeyTokens.OnSurface
val DisabledUncheckedIconColor = ColorSchemeKeyTokens.Primary
val DisabledUncheckedSecondaryLabelColor = ColorSchemeKeyTokens.OnSurfaceVariant
val LabelFont = TypographyKeyTokens.LabelMedium
val SecondaryLabelFont = TypographyKeyTokens.LabelSmall
- val UncheckedContainerColor = ColorSchemeKeyTokens.Surface
+ val UncheckedContainerColor = ColorSchemeKeyTokens.SurfaceContainer
val UncheckedContentColor = ColorSchemeKeyTokens.OnSurface
val UncheckedIconColor = ColorSchemeKeyTokens.Primary
val UncheckedSecondaryLabelColor = ColorSchemeKeyTokens.OnSurfaceVariant
diff --git a/wear/compose/compose-navigation/build.gradle b/wear/compose/compose-navigation/build.gradle
index 4ba30f4..60d6009 100644
--- a/wear/compose/compose-navigation/build.gradle
+++ b/wear/compose/compose-navigation/build.gradle
@@ -68,7 +68,7 @@
androidx {
name = "Android Wear Compose Navigation"
- type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2021"
description = "WearOS Compose Navigation Library. This library makes it easier for developers" +
"to write Jetpack Compose applications for Wearable devices by providing " +
diff --git a/wear/compose/compose-ui-tooling/build.gradle b/wear/compose/compose-ui-tooling/build.gradle
index ee2ece8..367cb5e 100644
--- a/wear/compose/compose-ui-tooling/build.gradle
+++ b/wear/compose/compose-ui-tooling/build.gradle
@@ -44,7 +44,7 @@
androidx {
name = "Wear Compose Tools"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2023"
description = "Tools for Wear Composable"
metalavaK2UastEnabled = true
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 12b9de3..2276907 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
@@ -40,6 +40,7 @@
import androidx.wear.compose.foundation.samples.SimpleScalingLazyColumnWithSnap
import androidx.wear.compose.foundation.samples.SimpleSwipeToDismissBox
import androidx.wear.compose.foundation.samples.StatefulSwipeToDismissBox
+import androidx.wear.compose.integration.demos.common.Centralize
import androidx.wear.compose.integration.demos.common.ComposableDemo
import androidx.wear.compose.integration.demos.common.DemoCategory
import androidx.wear.compose.material.samples.SwipeToRevealCardSample
@@ -165,18 +166,31 @@
"Samples",
listOf(
ComposableDemo("Material S2R Chip") { params ->
- SwipeToRevealChipSample(params.swipeToDismissBoxState)
+ Centralize {
+ SwipeToRevealChipSample(params.swipeToDismissBoxState)
+ }
},
ComposableDemo("Material S2R Card") { params ->
- SwipeToRevealCardSample(params.swipeToDismissBoxState)
+ Centralize {
+ SwipeToRevealCardSample(params.swipeToDismissBoxState)
+ }
},
)
),
DemoCategory(
"Demos",
listOf(
- ComposableDemo("S2R Chip") { params ->
- SwipeToRevealChips(params.swipeToDismissBoxState)
+ ComposableDemo("S2R Chip, 2 actions") { params ->
+ SwipeToRevealChips(
+ params.swipeToDismissBoxState,
+ includeSecondaryAction = true
+ )
+ },
+ ComposableDemo("S2R Chip, 1 action") { params ->
+ SwipeToRevealChips(
+ params.swipeToDismissBoxState,
+ includeSecondaryAction = false
+ )
},
ComposableDemo("S2R Card") { params ->
SwipeToRevealCards(params.swipeToDismissBoxState)
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SwipeToRevealDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SwipeToRevealDemo.kt
index 6bb6e21..aaa66be 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SwipeToRevealDemo.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SwipeToRevealDemo.kt
@@ -42,6 +42,7 @@
import androidx.wear.compose.foundation.ExpandableState
import androidx.wear.compose.foundation.ExperimentalWearFoundationApi
import androidx.wear.compose.foundation.RevealActionType
+import androidx.wear.compose.foundation.RevealState
import androidx.wear.compose.foundation.RevealValue
import androidx.wear.compose.foundation.SwipeToDismissBoxState
import androidx.wear.compose.foundation.edgeSwipeToDismiss
@@ -67,9 +68,12 @@
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
-@OptIn(ExperimentalWearFoundationApi::class, ExperimentalWearMaterialApi::class)
+@OptIn(ExperimentalWearFoundationApi::class)
@Composable
-fun SwipeToRevealChips(swipeToDismissBoxState: SwipeToDismissBoxState) {
+fun SwipeToRevealChips(
+ swipeToDismissBoxState: SwipeToDismissBoxState,
+ includeSecondaryAction: Boolean
+) {
val expandableStateMapping = rememberExpandableStateMapping<Int>(
initiallyExpanded = { true }
)
@@ -90,7 +94,7 @@
var undoActionEnabled by remember { mutableStateOf(true) }
val revealState = rememberRevealState()
val coroutineScope = rememberCoroutineScope()
- val deleteItem = {
+ val deleteItem: () -> Unit = {
coroutineScope.launch {
revealState.animateTo(RevealValue.Revealed)
@@ -103,7 +107,7 @@
}
}
}
- val addItem = {
+ val addItem: () -> Unit = {
coroutineScope.launch {
revealState.animateTo(RevealValue.Revealed)
itemCount++
@@ -116,85 +120,33 @@
}
}
}
- if (expanded) {
- SwipeToRevealChip(
- modifier = Modifier
- .edgeSwipeToDismiss(swipeToDismissBoxState)
- .semantics {
- customActions = listOf(
- CustomAccessibilityAction("Delete") {
- deleteItem()
- true
- },
- CustomAccessibilityAction("Duplicate") {
- addItem()
- true
- }
- )
- },
- revealState = revealState,
- onFullSwipe = { deleteItem() },
- primaryAction = {
- SwipeToRevealPrimaryAction(
- revealState = revealState,
- icon = {
- Icon(
- SwipeToRevealDefaults.Delete,
- contentDescription = "Delete"
- )
- },
- label = { Text(text = "Delete") },
- onClick = { deleteItem() },
- )
- },
- secondaryAction = {
- SwipeToRevealSecondaryAction(
- revealState = revealState,
- content = {
- Icon(Icons.Outlined.Add, contentDescription = "Duplicate")
- },
- onClick = { addItem() }
- )
- },
- undoPrimaryAction = {
- SwipeToRevealUndoAction(
- revealState = revealState,
- label = { Text("Undo Primary Action") },
- onClick = {
- if (undoActionEnabled) {
- coroutineScope.launch {
- // reset the state when undo is clicked
- revealState.animateTo(RevealValue.Covered)
- revealState.lastActionType = RevealActionType.None
- }
- }
- }
- )
- },
- undoSecondaryAction = {
- SwipeToRevealUndoAction(
- revealState = revealState,
- label = { Text("Undo Secondary Action") },
- onClick = {
- coroutineScope.launch {
- itemCount--
- // reset the state when undo is clicked
- revealState.animateTo(RevealValue.Covered)
- revealState.lastActionType = RevealActionType.None
- }
- }
- )
+ val undoDeleteItem: () -> Unit = {
+ if (undoActionEnabled) {
+ coroutineScope.launch {
+ // reset the state when undo is clicked
+ revealState.animateTo(RevealValue.Covered)
+ revealState.lastActionType = RevealActionType.None
}
- ) {
- Chip(
- onClick = { /*TODO*/ },
- colors = ChipDefaults.secondaryChipColors(),
- modifier = Modifier.fillMaxWidth(),
- label = {
- Text("Chip #$it")
- }
- )
}
+ }
+ val undoAddItem: () -> Unit = {
+ coroutineScope.launch {
+ itemCount--
+ // reset the state when undo is clicked
+ revealState.animateTo(RevealValue.Covered)
+ revealState.lastActionType = RevealActionType.None
+ }
+ }
+ if (expanded) {
+ SwipeToRevealChipExpandable(
+ modifier = Modifier.edgeSwipeToDismiss(swipeToDismissBoxState),
+ text = "Chip #$it",
+ revealState = revealState,
+ onDeleteAction = deleteItem,
+ onUndoDelete = undoDeleteItem,
+ onDuplicateAction = addItem.takeIf { includeSecondaryAction },
+ onUndoDuplicate = undoAddItem.takeIf { includeSecondaryAction }
+ )
} else {
Spacer(modifier = Modifier.width(200.dp))
}
@@ -203,6 +155,86 @@
}
}
+@OptIn(ExperimentalWearFoundationApi::class, ExperimentalWearMaterialApi::class)
+@Composable
+private fun SwipeToRevealChipExpandable(
+ modifier: Modifier = Modifier,
+ text: String,
+ revealState: RevealState,
+ onDeleteAction: () -> Unit,
+ onUndoDelete: () -> Unit,
+ onDuplicateAction: (() -> Unit)?,
+ onUndoDuplicate: (() -> Unit)?
+) {
+ SwipeToRevealChip(
+ modifier = modifier.semantics {
+ customActions = listOfNotNull(
+ CustomAccessibilityAction("Delete") {
+ onDeleteAction()
+ true
+ },
+ onDuplicateAction?.let {
+ CustomAccessibilityAction("Duplicate") {
+ onDuplicateAction()
+ true
+ }
+ }
+ )
+ },
+ revealState = revealState,
+ onFullSwipe = onDeleteAction,
+ primaryAction = {
+ SwipeToRevealPrimaryAction(
+ revealState = revealState,
+ icon = {
+ Icon(
+ SwipeToRevealDefaults.Delete,
+ contentDescription = "Delete"
+ )
+ },
+ label = { Text(text = "Delete") },
+ onClick = onDeleteAction,
+ )
+ },
+ secondaryAction = onDuplicateAction?.let {
+ {
+ SwipeToRevealSecondaryAction(
+ revealState = revealState,
+ content = {
+ Icon(Icons.Outlined.Add, contentDescription = "Duplicate")
+ },
+ onClick = onDuplicateAction
+ )
+ }
+ },
+ undoPrimaryAction = {
+ SwipeToRevealUndoAction(
+ revealState = revealState,
+ label = { Text("Undo Delete") },
+ onClick = onUndoDelete
+ )
+ },
+ undoSecondaryAction = onUndoDuplicate?.let {
+ {
+ SwipeToRevealUndoAction(
+ revealState = revealState,
+ label = { Text("Undo Duplicate") },
+ onClick = onUndoDuplicate
+ )
+ }
+ }
+ ) {
+ Chip(
+ onClick = { /*TODO*/ },
+ colors = ChipDefaults.secondaryChipColors(),
+ modifier = Modifier.fillMaxWidth(),
+ label = {
+ Text(text)
+ }
+ )
+ }
+}
+
@Composable
fun SwipeToRevealCards(swipeToDismissBoxState: SwipeToDismissBoxState) {
val emailMap = mutableMapOf(
diff --git a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/TestWatchFaceServices.kt b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/TestWatchFaceServices.kt
index 704f5fd..821d610 100644
--- a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/TestWatchFaceServices.kt
+++ b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/TestWatchFaceServices.kt
@@ -526,6 +526,7 @@
testContext: Context,
private var surfaceHolderOverride: SurfaceHolder
) : WatchFaceService() {
+ var lastComplicationType: ComplicationType? = null
init {
attachBaseContext(testContext)
@@ -605,7 +606,10 @@
CanvasType.HARDWARE,
16
) {
- override fun render(canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime) {}
+ override fun render(canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime) {
+ lastComplicationType =
+ complicationSlotsManager[123]!!.complicationData.value.type
+ }
override fun renderHighlightLayer(
canvas: Canvas,
diff --git a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
index 2afa5d96..b77902c 100644
--- a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
+++ b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
@@ -1344,6 +1344,73 @@
assertTrue(ObservableServiceC.awaitForServiceToBeBound(UPDATE_TIMEOUT_MILLIS))
}
+
+ @Test
+ @RequiresApi(Build.VERSION_CODES.O_MR1)
+ fun overrideComplicationData() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1) {
+ return
+ }
+ val wallpaperService =
+ TestComplicationProviderDefaultsWatchFaceService(context, surfaceHolder)
+ val interactiveInstance = getOrCreateTestSubject(wallpaperService)
+ interactiveInstance.updateComplicationData(
+ mapOf(123 to rangedValueComplicationBuilder().build())
+ )
+
+ interactiveInstance.overrideComplicationData(
+ mapOf(
+ 123 to
+ ShortTextComplicationData.Builder(
+ PlainComplicationText.Builder("TEST").build(),
+ ComplicationText.EMPTY
+ )
+ .build()
+ )
+ )
+
+ interactiveInstance.renderWatchFaceToBitmap(
+ RenderParameters(DrawMode.INTERACTIVE, WatchFaceLayer.ALL_WATCH_FACE_LAYERS, null),
+ Instant.ofEpochMilli(1234567),
+ null,
+ null
+ )
+ assertThat(wallpaperService.lastComplicationType).isEqualTo(ComplicationType.SHORT_TEXT)
+ }
+
+ @Test
+ @RequiresApi(Build.VERSION_CODES.O_MR1)
+ fun clearComplicationDataOverride() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1) {
+ return
+ }
+ val wallpaperService =
+ TestComplicationProviderDefaultsWatchFaceService(context, surfaceHolder)
+ val interactiveInstance = getOrCreateTestSubject(wallpaperService)
+ interactiveInstance.updateComplicationData(
+ mapOf(123 to rangedValueComplicationBuilder().build())
+ )
+ interactiveInstance.overrideComplicationData(
+ mapOf(
+ 123 to
+ ShortTextComplicationData.Builder(
+ PlainComplicationText.Builder("TEST").build(),
+ ComplicationText.EMPTY
+ )
+ .build(),
+ )
+ )
+
+ interactiveInstance.clearComplicationDataOverride()
+
+ interactiveInstance.renderWatchFaceToBitmap(
+ RenderParameters(DrawMode.INTERACTIVE, WatchFaceLayer.ALL_WATCH_FACE_LAYERS, null),
+ Instant.ofEpochMilli(1234567),
+ null,
+ null
+ )
+ assertThat(wallpaperService.lastComplicationType).isEqualTo(ComplicationType.RANGED_VALUE)
+ }
}
@RunWith(AndroidJUnit4::class)
diff --git a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceClient.kt b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceClient.kt
index 6112bfe..44bd7b7 100644
--- a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceClient.kt
+++ b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceClient.kt
@@ -141,6 +141,23 @@
public fun updateComplicationData(slotIdToComplicationData: Map<Int, ComplicationData>)
/**
+ * Sets override complications which are displayed until [clearComplicationDataOverride] is
+ * called. For editors this is more efficient than repeatedly calling [renderWatchFaceToBitmap]
+ * with complication data.
+ *
+ * While there are overrides [updateComplicationData] has no effect until
+ * [clearComplicationDataOverride] is called.
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public fun overrideComplicationData(slotIdToComplicationData: Map<Int, ComplicationData>) {}
+
+ /**
+ * Clears any overrides set by [overrideComplicationData].
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public fun clearComplicationDataOverride() {}
+
+ /**
* Renders the watchface to a shared memory backed [Bitmap] with the given settings. Note this
* will be fairly slow since either software canvas or glReadPixels will be invoked.
*
@@ -433,6 +450,7 @@
private var lastWatchFaceColors: WatchFaceColors? = null
private var disconnectReason: Int? = null
private var closed = false
+ private var overrideSlotIdToComplicationData = HashMap<Int, ComplicationData>()
private val iWatchFaceListener =
object : IWatchfaceListener.Stub() {
@@ -505,6 +523,31 @@
)
}
+ override fun overrideComplicationData(slotIdToComplicationData: Map<Int, ComplicationData>) {
+ if (iInteractiveWatchFace.apiVersion >= 11) {
+ iInteractiveWatchFace.overrideComplicationData(
+ slotIdToComplicationData.map {
+ IdAndComplicationDataWireFormat(
+ it.key,
+ it.value.asWireComplicationData()
+ )
+ }
+ )
+ } else {
+ for ((id, complicationData) in slotIdToComplicationData) {
+ overrideSlotIdToComplicationData[id] = complicationData
+ }
+ }
+ }
+
+ override fun clearComplicationDataOverride() {
+ if (iInteractiveWatchFace.apiVersion >= 11) {
+ iInteractiveWatchFace.clearComplicationDataOverride()
+ } else {
+ overrideSlotIdToComplicationData.clear()
+ }
+ }
+
@RequiresApi(27)
override fun renderWatchFaceToBitmap(
renderParameters: RenderParameters,
@@ -519,7 +562,11 @@
renderParameters.toWireFormat(),
instant.toEpochMilli(),
userStyle?.toWireFormat(),
- idAndComplicationData?.map {
+ if (iInteractiveWatchFace.apiVersion >= 11) {
+ idAndComplicationData
+ } else {
+ mergeWithOverrideComplicationData(idAndComplicationData)
+ }?.map {
IdAndComplicationDataWireFormat(
it.key,
it.value.asWireComplicationData()
@@ -530,6 +577,27 @@
)
}
+ private fun mergeWithOverrideComplicationData(
+ idAndComplicationData: Map<Int, ComplicationData>?
+ ): Map<Int, ComplicationData>? {
+ if (overrideSlotIdToComplicationData.isEmpty()) {
+ return idAndComplicationData
+ }
+
+ if (idAndComplicationData.isNullOrEmpty()) {
+ return overrideSlotIdToComplicationData
+ }
+
+ val merged = HashMap(overrideSlotIdToComplicationData)
+ for ((id, complicationData) in idAndComplicationData) {
+ if (merged.contains(id)) {
+ continue
+ }
+ merged[id] = complicationData
+ }
+ return merged
+ }
+
override val isRemoteWatchFaceViewHostSupported = iInteractiveWatchFace.apiVersion >= 9
@RequiresApi(Build.VERSION_CODES.R)
diff --git a/wear/watchface/watchface-complications-data-source-ktx/build.gradle b/wear/watchface/watchface-complications-data-source-ktx/build.gradle
index 6d0a95c..dcd380b 100644
--- a/wear/watchface/watchface-complications-data-source-ktx/build.gradle
+++ b/wear/watchface/watchface-complications-data-source-ktx/build.gradle
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import androidx.build.Publish
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -45,7 +45,7 @@
androidx {
name = "Android Wear Complications Data Source Ktx"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2021"
description = "Kotlin suspend wrapper for Android Wear Complications Data Source"
metalavaK2UastEnabled = true
diff --git a/wear/watchface/watchface-data/src/main/aidl/androidx/wear/watchface/control/IInteractiveWatchFace.aidl b/wear/watchface/watchface-data/src/main/aidl/androidx/wear/watchface/control/IInteractiveWatchFace.aidl
index c06fdab8..7e61727 100644
--- a/wear/watchface/watchface-data/src/main/aidl/androidx/wear/watchface/control/IInteractiveWatchFace.aidl
+++ b/wear/watchface/watchface-data/src/main/aidl/androidx/wear/watchface/control/IInteractiveWatchFace.aidl
@@ -37,12 +37,12 @@
interface IInteractiveWatchFace {
// IMPORTANT NOTE: All methods must be given an explicit transaction id that must never change
// in the future to remain binary backwards compatible.
- // Next Id: 25
+ // Next Id: 28
/**
* API version number. This should be incremented every time a new method is added.
*/
- const int API_VERSION = 10;
+ const int API_VERSION = 11;
/** Indicates a "down" touch event on the watch face. */
const int TAP_TYPE_DOWN = 0;
@@ -243,4 +243,22 @@
* @since API version 10.
*/
UserStyleFlavorsWireFormat getUserStyleFlavors() = 25;
+
+ /**
+ * Send override ComplicationData to be used until clearComplicationDataOverride is called.
+ * While overrides, any calls to updateComplicationData are deferred until
+ * clearComplicationDataOverride is called.
+ *
+ * @since API version 11.
+ */
+ oneway void overrideComplicationData(
+ in List<IdAndComplicationDataWireFormat> complicationData) = 26;
+
+ /**
+ * Clears any complicaton data set by overrideComplicationData, and activates any complications
+ * set by updateComplicationData.
+ *
+ * @since API version 11.
+ */
+ oneway void clearComplicationDataOverride() = 27;
}
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt
index 2f02cf3..aec9b5c 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt
@@ -92,10 +92,7 @@
object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
- Intent.ACTION_BATTERY_LOW -> observer.onActionBatteryLow()
- Intent.ACTION_BATTERY_OKAY -> observer.onActionBatteryOkay()
- Intent.ACTION_POWER_CONNECTED -> observer.onActionPowerConnected()
- Intent.ACTION_POWER_DISCONNECTED -> observer.onActionPowerDisconnected()
+ Intent.ACTION_BATTERY_CHANGED -> processBatteryStatus(intent)
Intent.ACTION_TIME_CHANGED -> observer.onActionTimeChanged()
Intent.ACTION_TIME_TICK -> observer.onActionTimeTick()
Intent.ACTION_TIMEZONE_CHANGED -> observer.onActionTimeZoneChanged()
@@ -112,15 +109,11 @@
init {
context.registerReceiver(
receiver,
- IntentFilter(Intent.ACTION_SCREEN_OFF).apply {
- addAction(Intent.ACTION_SCREEN_ON)
+ IntentFilter(Intent.ACTION_BATTERY_CHANGED).apply {
+ addAction(Intent.ACTION_TIME_CHANGED)
addAction(Intent.ACTION_TIME_TICK)
addAction(Intent.ACTION_TIMEZONE_CHANGED)
- addAction(Intent.ACTION_TIME_CHANGED)
- addAction(Intent.ACTION_BATTERY_LOW)
- addAction(Intent.ACTION_BATTERY_OKAY)
- addAction(Intent.ACTION_POWER_CONNECTED)
- addAction(Intent.ACTION_POWER_DISCONNECTED)
+ addAction(Intent.ACTION_SCREEN_OFF)
addAction(Intent.ACTION_USER_PRESENT)
addAction(WatchFaceImpl.MOCK_TIME_INTENT)
addAction(ACTION_AMBIENT_STARTED)
@@ -137,7 +130,6 @@
)
}
- /** Called to send observers initial battery state in advance of receiving any broadcasts. */
internal fun processBatteryStatus(batteryStatus: Intent?) {
val status = batteryStatus?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) ?: -1
if (
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt
index 3c7ec5c..7cd7f68 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt
@@ -24,6 +24,7 @@
import androidx.annotation.RequiresApi
import androidx.wear.watchface.TapEvent
import androidx.wear.watchface.WatchFaceService
+import androidx.wear.watchface.complications.data.toApiComplicationData
import androidx.wear.watchface.control.data.WatchFaceRenderParams
import androidx.wear.watchface.data.IdAndComplicationDataWireFormat
import androidx.wear.watchface.data.IdAndComplicationStateWireFormat
@@ -292,6 +293,22 @@
}
}
+ override fun overrideComplicationData(
+ complicationDatumWireFormats: List<IdAndComplicationDataWireFormat>
+ ): Unit = aidlMethod(TAG, "overrideComplicationData") {
+ engine?.overrideComplications(
+ complicationDatumWireFormats.associateBy(
+ { it.id },
+ { it.complicationData.toApiComplicationData() }
+ )
+ )
+ }
+
+ override fun clearComplicationDataOverride(): Unit =
+ aidlMethod(TAG, "overrideComplicationData") {
+ engine?.removeAnyComplicationOverrides()
+ }
+
fun onDestroy() {
// Note this is almost certainly called on the ui thread, from release() above.
runBlocking {
diff --git a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
index cf39775..470a7e1 100644
--- a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
+++ b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
@@ -1426,14 +1426,35 @@
// The delay should change when battery is low.
watchFaceImpl.broadcastsReceiver!!
.receiver
- .onReceive(context, Intent(Intent.ACTION_BATTERY_LOW))
+ .onReceive(
+ context,
+ Intent(Intent.ACTION_BATTERY_CHANGED).apply {
+ putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_DISCHARGING)
+ putExtra(
+ BatteryManager.EXTRA_LEVEL,
+ (BroadcastsReceiver.INITIAL_LOW_BATTERY_THRESHOLD - 1).toInt()
+ )
+ putExtra(BatteryManager.EXTRA_SCALE, 100)
+ }
+ )
+
assertThat(watchFaceImpl.computeDelayTillNextFrame(0, 0, Instant.EPOCH))
.isEqualTo(WatchFaceImpl.MAX_LOW_POWER_INTERACTIVE_UPDATE_RATE_MS)
// And go back to normal when battery is OK.
watchFaceImpl.broadcastsReceiver!!
.receiver
- .onReceive(context, Intent(Intent.ACTION_BATTERY_OKAY))
+ .onReceive(
+ context,
+ Intent(Intent.ACTION_BATTERY_CHANGED).apply {
+ putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING)
+ putExtra(
+ BatteryManager.EXTRA_LEVEL,
+ (BroadcastsReceiver.INITIAL_LOW_BATTERY_THRESHOLD + 1).toInt()
+ )
+ putExtra(BatteryManager.EXTRA_SCALE, 100)
+ }
+ )
assertThat(watchFaceImpl.computeDelayTillNextFrame(0, 0, Instant.EPOCH))
.isEqualTo(INTERACTIVE_UPDATE_RATE_MS)
}
@@ -1453,14 +1474,34 @@
// The delay should change when battery is low.
watchFaceImpl.broadcastsReceiver!!
.receiver
- .onReceive(context, Intent(Intent.ACTION_BATTERY_LOW))
+ .onReceive(
+ context,
+ Intent(Intent.ACTION_BATTERY_CHANGED).apply {
+ putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_DISCHARGING)
+ putExtra(
+ BatteryManager.EXTRA_LEVEL,
+ (BroadcastsReceiver.INITIAL_LOW_BATTERY_THRESHOLD - 1).toInt()
+ )
+ putExtra(BatteryManager.EXTRA_SCALE, 100)
+ }
+ )
assertThat(watchFaceImpl.computeDelayTillNextFrame(0, 0, Instant.EPOCH))
.isEqualTo(WatchFaceImpl.MAX_LOW_POWER_INTERACTIVE_UPDATE_RATE_MS)
// And go back to normal when power is connected.
watchFaceImpl.broadcastsReceiver!!
.receiver
- .onReceive(context, Intent(Intent.ACTION_POWER_CONNECTED))
+ .onReceive(
+ context,
+ Intent(Intent.ACTION_BATTERY_CHANGED).apply {
+ putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING)
+ putExtra(
+ BatteryManager.EXTRA_LEVEL,
+ (BroadcastsReceiver.INITIAL_LOW_BATTERY_THRESHOLD - 1).toInt()
+ )
+ putExtra(BatteryManager.EXTRA_SCALE, 100)
+ }
+ )
assertThat(watchFaceImpl.computeDelayTillNextFrame(0, 0, Instant.EPOCH))
.isEqualTo(INTERACTIVE_UPDATE_RATE_MS)
}
diff --git a/window/window/proguard-rules.pro b/window/window/proguard-rules.pro
index b8cf236..b5faff0 100644
--- a/window/window/proguard-rules.pro
+++ b/window/window/proguard-rules.pro
@@ -24,4 +24,8 @@
public *** onWindowLayoutChanged(android.os.IBinder, androidx.window.sidecar.SidecarWindowLayoutInfo);
}
# Required for window area API reflection guard
--keep interface androidx.window.area.reflectionguard.* {*;}
\ No newline at end of file
+-keep interface androidx.window.area.reflectionguard.* {*;}
+# Required to support kotlin-reflect
+-keep,allowshrinking class androidx.window.layout.adapter.extensions.MulticastConsumer {
+ void accept(androidx.window.extensions.layout.WindowLayoutInfo);
+}
\ No newline at end of file
diff --git a/window/window/src/main/java/androidx/window/core/ConsumerAdapter.kt b/window/window/src/main/java/androidx/window/core/ConsumerAdapter.kt
index dae0489..faf3228 100644
--- a/window/window/src/main/java/androidx/window/core/ConsumerAdapter.kt
+++ b/window/window/src/main/java/androidx/window/core/ConsumerAdapter.kt
@@ -157,6 +157,10 @@
consumer.hashCode()
}
method.isToString(parameters) -> {
+ // MulticastConsumer#accept must not be obfuscated by proguard if kotlin-reflect
+ // is included. Otherwise, invocation of consumer#toString (e.g. by the library
+ // or by the on-device implementation) will crash due to kotlin-reflect not
+ // finding MulticastConsumer#accept.
consumer.toString()
}
else -> {
diff --git a/work/work-runtime-ktx/build.gradle b/work/work-runtime-ktx/build.gradle
index ffc1117..8a60f47 100644
--- a/work/work-runtime-ktx/build.gradle
+++ b/work/work-runtime-ktx/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -35,7 +35,7 @@
androidx {
name = "WorkManager Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2018"
description = "Android WorkManager Kotlin Extensions"
metalavaK2UastEnabled = true