Merge "Compose beta bump" into androidx-platform-dev
diff --git a/annotation/annotation-experimental/build.gradle b/annotation/annotation-experimental/build.gradle
index 7608693..0b75e9a 100644
--- a/annotation/annotation-experimental/build.gradle
+++ b/annotation/annotation-experimental/build.gradle
@@ -31,7 +31,6 @@
name = "Experimental annotation"
publish = Publish.SNAPSHOT_AND_RELEASE
mavenVersion = LibraryVersions.ANNOTATION_EXPERIMENTAL
- mavenMultiplatformVersion = LibraryVersions.ANNOTATION_EXPERIMENTAL_KMP
inceptionYear = "2019"
description = "Java annotation for use on unstable Android API surfaces. When used in " +
"conjunction with the Experimental annotation lint checks, this annotation provides " +
diff --git a/annotation/annotation/build.gradle b/annotation/annotation/build.gradle
index 769ac65..21e996b 100644
--- a/annotation/annotation/build.gradle
+++ b/annotation/annotation/build.gradle
@@ -69,7 +69,6 @@
name = "Annotation"
type = LibraryType.PUBLISHED_LIBRARY
mavenVersion = LibraryVersions.ANNOTATION
- mavenMultiplatformVersion = LibraryVersions.ANNOTATION_KMP
inceptionYear = "2013"
description = "Provides source annotations for tooling and readability."
}
diff --git a/buildSrc-tests/src/test/kotlin/androidx/build/LibraryVersionsServiceTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/LibraryVersionsServiceTest.kt
index 484ecac..75b061a 100644
--- a/buildSrc-tests/src/test/kotlin/androidx/build/LibraryVersionsServiceTest.kt
+++ b/buildSrc-tests/src/test/kotlin/androidx/build/LibraryVersionsServiceTest.kt
@@ -78,35 +78,6 @@
}
@Test
- fun withMultiplatformVersion() {
- val toml = """
- [versions]
- V1 = "1.2.3"
- V1_KMP = "1.2.3-dev05"
- [groups]
- G1 = { group = "g.g1", atomicGroupVersion = "versions.V1", multiplatformGroupVersion = "versions.V1_KMP"}
- """.trimIndent()
- val dontUseKmpVersions = createLibraryVersionsService(toml)
- assertThat(
- dontUseKmpVersions.libraryGroups["G1"]
- ).isEqualTo(
- LibraryGroup(
- group = "g.g1", atomicGroupVersion = Version("1.2.3")
- )
- )
-
- val useKmpVersions =
- createLibraryVersionsService(toml, useMultiplatformGroupVersions = true)
- assertThat(
- useKmpVersions.libraryGroups["G1"]
- ).isEqualTo(
- LibraryGroup(
- group = "g.g1", atomicGroupVersion = Version("1.2.3-dev05")
- )
- )
- }
-
- @Test
fun customComposeVersions() {
val toml = """
[versions]
@@ -175,68 +146,6 @@
}
@Test
- fun missingVersionReference_multiplatform() {
- val service = createLibraryVersionsService(
- """
- [versions]
- V1 = "1.2.3"
- [groups]
- G1 = { group = "g.g1", atomicGroupVersion = "versions.V1", multiplatformGroupVersion = "versions.doesNotExist2" }
- """.trimIndent()
- )
- val result = runCatching {
- service.libraryGroups["G1"]
- }
- assertThat(
- result.exceptionOrNull()
- ).hasMessageThat().contains(
- "Group entry g.g1 specifies doesNotExist2, but such version doesn't exist"
- )
- }
-
- @Test
- fun malformedVersionReference_multiplatform() {
- val service = createLibraryVersionsService(
- """
- [versions]
- V1 = "1.2.3"
- [groups]
- G1 = { group = "g.g1", atomicGroupVersion = "versions.V1", multiplatformGroupVersion = "V1" }
- """.trimIndent()
- )
- val result = runCatching {
- service.libraryGroups["G1"]
- }
- assertThat(
- result.exceptionOrNull()
- ).hasMessageThat().contains(
- "Group entry multiplatformGroupVersion is expected to start with versions"
- )
- }
-
- @Test
- fun atomicMultiplatformGroupWithoutAtomicGroup() {
- val service = createLibraryVersionsService(
- """
- [versions]
- V1 = "1.2.3"
- [groups]
- G1 = { group = "g.g1", multiplatformGroupVersion = "versions.V1" }
- """.trimIndent()
- )
- val result = runCatching {
- service.libraryGroups["G1"]
- }
- assertThat(
- result.exceptionOrNull()
- ).hasMessageThat().contains(
- """
- Cannot specify multiplatformGroupVersion for G1 without specifying an atomicGroupVersion
- """.trimIndent()
- )
- }
-
- @Test
fun overrideInclude() {
val service = createLibraryVersionsService(
"""
@@ -273,7 +182,8 @@
assertThrows<Exception> {
service.libraryGroupsByGroupId["g.g1"]
}.hasMessageThat().contains(
- "Duplicate library group g.g1 defined in G2 does not set overrideInclude. Declarations beyond the first can only have an effect if they set overrideInclude"
+ "Duplicate library group g.g1 defined in G2 does not set overrideInclude. " +
+ "Declarations beyond the first can only have an effect if they set overrideInclude"
)
}
@@ -298,118 +208,6 @@
)
}
- @Test
- fun androidxExtension_noAtomicGroup() {
- runAndroidExtensionTest(
- projectPath = "myGroup:project1",
- tomlFile = """
- [versions]
- [groups]
- G1 = { group = "androidx.myGroup" }
- """.trimIndent(),
- validateWithKmp = { extension ->
- extension.mavenVersion = Version("1.0.0")
- extension.mavenMultiplatformVersion = Version("1.0.0-dev01")
- assertThat(
- extension.mavenGroup
- ).isEqualTo(
- LibraryGroup("androidx.myGroup", atomicGroupVersion = null)
- )
- assertThat(
- extension.project.version
- ).isEqualTo(Version("1.0.0-dev01"))
- extension.validateMavenVersion()
- },
- validateWithoutKmp = { extension ->
- extension.mavenVersion = Version("1.0.0")
- extension.mavenMultiplatformVersion = Version("1.0.0-dev01")
- assertThat(
- extension.mavenGroup
- ).isEqualTo(
- LibraryGroup("androidx.myGroup", atomicGroupVersion = null)
- )
- assertThat(
- extension.project.version
- ).isEqualTo(Version("1.0.0"))
- extension.validateMavenVersion()
- }
- )
- }
-
- @Test
- fun androidxExtension_noAtomicGroup_setKmpVersionFirst() {
- runAndroidExtensionTest(
- projectPath = "myGroup:project1",
- tomlFile = """
- [versions]
- [groups]
- G1 = { group = "androidx.myGroup" }
- """.trimIndent(),
- validateWithKmp = { extension ->
- extension.mavenMultiplatformVersion = Version("1.0.0-dev01")
- extension.mavenVersion = Version("1.0.0")
- assertThat(
- extension.mavenGroup
- ).isEqualTo(
- LibraryGroup("androidx.myGroup", atomicGroupVersion = null)
- )
- assertThat(
- extension.project.version
- ).isEqualTo(Version("1.0.0-dev01"))
- extension.validateMavenVersion()
- },
- validateWithoutKmp = { extension ->
- extension.mavenMultiplatformVersion = Version("1.0.0-dev01")
- extension.mavenVersion = Version("1.0.0")
- assertThat(
- extension.mavenGroup
- ).isEqualTo(
- LibraryGroup("androidx.myGroup", atomicGroupVersion = null)
- )
- assertThat(
- extension.project.version
- ).isEqualTo(Version("1.0.0"))
- extension.validateMavenVersion()
- }
- )
- }
-
- @Test
- fun androidxExtension_withAtomicGroup() {
- runAndroidExtensionTest(
- projectPath = "myGroup:project1",
- tomlFile = """
- [versions]
- V1 = "1.0.0"
- V1_KMP = "1.0.0-dev01"
- [groups]
- G1 = { group = "androidx.myGroup", atomicGroupVersion = "versions.V1", multiplatformGroupVersion = "versions.V1_KMP" }
- """.trimIndent(),
- validateWithKmp = { extension ->
- assertThat(
- extension.mavenGroup
- ).isEqualTo(
- LibraryGroup("androidx.myGroup", atomicGroupVersion = Version("1.0.0-dev01"))
- )
- assertThat(
- extension.project.version
- ).isEqualTo(Version("1.0.0-dev01"))
- extension.validateMavenVersion()
- },
- validateWithoutKmp = { extension ->
- assertThat(
- extension.mavenGroup
- ).isEqualTo(
- LibraryGroup("androidx.myGroup", atomicGroupVersion = Version("1.0.0"))
- )
- assertThat(
- extension.project.version
- ).isEqualTo(Version("1.0.0"))
- extension.validateMavenVersion()
- }
- )
- }
-
private fun runAndroidExtensionTest(
projectPath: String,
tomlFile: String,
@@ -430,7 +228,6 @@
createLibraryVersionsService(
tomlFileContents = tomlFile,
project = rootProject,
- useMultiplatformGroupVersions = useKmpVersions
)
// needed for AndroidXExtension initialization
rootProject.setSupportRootFolder(rootProjectDir)
@@ -450,7 +247,6 @@
tomlFileName: String = "libraryversions.toml",
composeCustomVersion: String? = null,
composeCustomGroup: String? = null,
- useMultiplatformGroupVersions: Boolean = false,
project: Project = ProjectBuilder.builder().withProjectDir(tempDir.newFolder()).build()
): LibraryVersionsService {
val serviceProvider = project.gradle.sharedServices.registerIfAbsent(
@@ -466,9 +262,6 @@
spec.parameters.composeCustomGroup = project.provider {
composeCustomGroup
}
- spec.parameters.useMultiplatformGroupVersions = project.provider {
- useMultiplatformGroupVersions
- }
}
return serviceProvider.get()
}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
index 242b964..f0b5bb6 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
@@ -70,9 +70,6 @@
spec.parameters.tomlFileContents = toml
spec.parameters.composeCustomVersion = composeCustomVersion
spec.parameters.composeCustomGroup = composeCustomGroup
- spec.parameters.useMultiplatformGroupVersions = project.provider {
- Multiplatform.isKotlinNativeEnabled(project)
- }
}.get()
AllLibraryGroups = versionService.libraryGroups.values.toList()
LibraryVersions = versionService.libraryVersions
@@ -101,32 +98,12 @@
* Maven version of the library.
*
* Note that, setting this is an error if the library group sets an atomic version.
- * If the build is a multiplatform build, this value will be overridden by
- * the [mavenMultiplatformVersion] property when it is provided.
- *
- * @see mavenMultiplatformVersion
*/
var mavenVersion: Version? = null
set(value) {
field = value
chooseProjectVersion()
}
- get() = if (versionService.useMultiplatformGroupVersions) {
- mavenMultiplatformVersion ?: field
- } else {
- field
- }
-
- /**
- * If set, this will override the [mavenVersion] property in multiplatform builds.
- *
- * @see mavenVersion
- */
- var mavenMultiplatformVersion: Version? = null
- set(value) {
- field = value
- chooseProjectVersion()
- }
internal var projectDirectlySpecifiesMavenVersion: Boolean = false
@@ -176,12 +153,10 @@
explanationBuilder: MutableList<String>? = null
): LibraryGroup? {
val overridden = overrideLibraryGroupsByProjectPath.get(projectPath)
- if (explanationBuilder != null) {
- explanationBuilder.add(
- "Library group (in libraryversions.toml) having" +
+ explanationBuilder?.add(
+ "Library group (in libraryversions.toml) having" +
" overrideInclude=[\"$projectPath\"] is $overridden"
- )
- }
+ )
if (overridden != null)
return overridden
@@ -206,8 +181,7 @@
val parentPath = substringBeforeLastColon(projectPath)
if (parentPath == "") {
- if (explanationBuilder != null)
- explanationBuilder.add("Parent path for $projectPath is empty")
+ explanationBuilder?.add("Parent path for $projectPath is empty")
return null
}
// convert parent project path to groupId
@@ -218,12 +192,10 @@
}
// get the library group having that text
- val result = libraryGroupsByGroupId.get(groupIdText)
- if (explanationBuilder != null) {
- explanationBuilder.add(
- "Library group (in libraryversions.toml) having group=\"$groupIdText\" is $result"
- )
- }
+ val result = libraryGroupsByGroupId[groupIdText]
+ explanationBuilder?.add(
+ "Library group (in libraryversions.toml) having group=\"$groupIdText\" is $result"
+ )
return result
}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index bb7940d..f4339cd 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -40,8 +40,6 @@
import androidx.build.testConfiguration.addAppApkToTestConfigGeneration
import androidx.build.testConfiguration.configureTestConfigGeneration
import com.android.build.api.artifact.SingleArtifact
-import com.android.build.api.dsl.ManagedVirtualDevice
-import com.android.build.api.dsl.TestOptions
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.android.build.api.variant.HasAndroidTest
import com.android.build.api.variant.LibraryAndroidComponentsExtension
@@ -100,8 +98,9 @@
* This plugin reacts to other plugins being added and adds required and optional functionality.
*/
-class AndroidXImplPlugin @Inject constructor(val componentFactory: SoftwareComponentFactory) :
- Plugin<Project> {
+class AndroidXImplPlugin @Inject constructor(
+ private val componentFactory: SoftwareComponentFactory
+) : Plugin<Project> {
override fun apply(project: Project) {
if (project.isRoot)
throw Exception("Root project should use AndroidXRootImplPlugin instead")
@@ -333,7 +332,6 @@
}
}
- @Suppress("UnstableApiUsage") // AGP DSL APIs
private fun configureWithAppPlugin(project: Project, androidXExtension: AndroidXExtension) {
project.extensions.getByType<AppExtension>().apply {
configureAndroidBaseOptions(project, androidXExtension)
@@ -374,6 +372,7 @@
excludeVersionFilesFromTestApks()
}
+ @Suppress("UnstableApiUsage") // usage of experimentalProperties
private fun Variant.artRewritingWorkaround() {
// b/279234807
experimentalProperties.put(
@@ -404,7 +403,7 @@
}
}
- fun Project.configureKotlinStdlibVersion() {
+ private fun Project.configureKotlinStdlibVersion() {
project.configurations.all { configuration ->
configuration.resolutionStrategy { strategy ->
strategy.eachDependency { details ->
@@ -418,7 +417,7 @@
}
}
- @Suppress("UnstableApiUsage", "DEPRECATION") // AGP DSL APIs
+ @Suppress("DEPRECATION") // AGP DSL APIs
private fun configureWithLibraryPlugin(
project: Project,
androidXExtension: AndroidXExtension
@@ -600,25 +599,6 @@
}
}
- @Suppress("UnstableApiUsage") // Usage of ManagedVirtualDevice
- private fun TestOptions.configureVirtualDevices() {
- managedDevices.devices.register<ManagedVirtualDevice>("pixel2api29") {
- device = "Pixel 2"
- apiLevel = 29
- systemImageSource = "aosp"
- }
- managedDevices.devices.register<ManagedVirtualDevice>("pixel2api30") {
- device = "Pixel 2"
- apiLevel = 30
- systemImageSource = "aosp"
- }
- managedDevices.devices.register<ManagedVirtualDevice>("pixel2api31") {
- device = "Pixel 2"
- apiLevel = 31
- systemImageSource = "aosp"
- }
- }
-
private fun BaseExtension.configureAndroidBaseOptions(
project: Project,
androidXExtension: AndroidXExtension
@@ -647,7 +627,6 @@
// Robolectric 1.7 increased heap size requirements, see b/207169653.
task.maxHeapSize = "3g"
}
- testOptions.configureVirtualDevices()
// Include resources in Robolectric tests as a workaround for b/184641296 and
// ensure the build directory exists as a workaround for b/187970292.
@@ -869,13 +848,9 @@
}
// make sure that the project has a group
- val projectGroup = extension.mavenGroup
- if (projectGroup == null)
- return@afterEvaluate
+ val projectGroup = extension.mavenGroup ?: return@afterEvaluate
// make sure that this group is configured to use a single version
- val requiredVersion = projectGroup.atomicGroupVersion
- if (requiredVersion == null)
- return@afterEvaluate
+ projectGroup.atomicGroupVersion ?: return@afterEvaluate
// We don't want to emit the same constraint into our .module file more than once,
// and we don't want to try to apply a constraint to a configuration that doesn't accept them,
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
index db8882a..417e2d5 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
@@ -20,6 +20,7 @@
import org.gradle.api.Action
import org.gradle.api.NamedDomainObjectCollection
import org.gradle.api.Project
+import org.gradle.api.provider.Provider
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
@@ -220,6 +221,6 @@
* Returns a provider that is set to true if and only if this project has at least 1 kotlin native
* target (mac, linux, ios).
*/
-internal fun Project.hasKotlinNativeTarget() = project.provider {
+internal fun Project.hasKotlinNativeTarget(): Provider<Boolean> = project.provider {
project.extensions.getByType(AndroidXMultiplatformExtension::class.java).hasNativeTarget()
-}
\ No newline at end of file
+}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
index 1a36ae6..fd28686 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
@@ -47,7 +47,6 @@
import org.gradle.kotlin.dsl.extra
abstract class AndroidXRootImplPlugin : Plugin<Project> {
- @Suppress("UnstableApiUsage")
@get:javax.inject.Inject
abstract val registry: BuildEventsListenerRegistry
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
index 68fb77c..3ccfbcb 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
@@ -71,6 +71,7 @@
var annotationArgs: MapProperty<String, String>? = null
val extension = extensions.findByType(AndroidComponentsExtension::class.java)
extension?.onVariants { variant ->
+ @Suppress("UnstableApiUsage")
annotationArgs = variant.javaCompilation.annotationProcessor.arguments
}
val errorProneConfiguration = createErrorProneConfiguration()
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/Ktlint.kt b/buildSrc/private/src/main/kotlin/androidx/build/Ktlint.kt
index d1319ad..e7c6e12 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/Ktlint.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/Ktlint.kt
@@ -178,7 +178,7 @@
overrideDirectory?.let {
val subdirectories = overrideSubdirectories
- if (subdirectories == null || subdirectories.isEmpty()) return@let
+ if (subdirectories.isNullOrEmpty()) return@let
subdirectories.map { arguments.add("$it/$InputDir/$IncludedFiles") }
} ?: arguments.add("$InputDir/$IncludedFiles")
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt b/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt
index e0b4aa8..d886eca 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt
@@ -33,23 +33,19 @@
var tomlFileContents: Provider<String>
var composeCustomVersion: Provider<String>
var composeCustomGroup: Provider<String>
- var useMultiplatformGroupVersions: Provider<Boolean>
}
private val parsedTomlFile: TomlParseResult by lazy {
val result = Toml.parse(parameters.tomlFileContents.get())
if (result.hasErrors()) {
- val issues = result.errors().map {
+ val issues = result.errors().joinToString(separator = "\n") {
"${parameters.tomlFileName}:${it.position()}: ${it.message}"
- }.joinToString(separator = "\n")
+ }
throw Exception("${parameters.tomlFileName} file has issues.\n$issues")
}
result
}
- val useMultiplatformGroupVersions
- get() = parameters.useMultiplatformGroupVersions.get()
-
private fun getTable(key: String): TomlTable {
return parsedTomlFile.getTable(key)
?: throw GradleException("Library versions toml file is missing [$key] table")
@@ -78,7 +74,7 @@
val libraryGroups: Map<String, LibraryGroup> by lazy {
val result = mutableMapOf<String, LibraryGroup>()
for (association in libraryGroupAssociations) {
- result.put(association.declarationName, association.libraryGroup)
+ result[association.declarationName] = association.libraryGroup
}
result
}
@@ -89,9 +85,9 @@
for (association in libraryGroupAssociations) {
// Check for duplicate groups
val groupId = association.libraryGroup.group
- val existingAssociation = result.get(groupId)
+ val existingAssociation = result[groupId]
if (existingAssociation != null) {
- if (association.overrideIncludeInProjectPaths.size < 1) {
+ if (association.overrideIncludeInProjectPaths.isEmpty()) {
throw GradleException(
"Duplicate library group $groupId defined in " +
"${association.declarationName} does not set overrideInclude. " +
@@ -99,7 +95,7 @@
"overrideInclude")
}
} else {
- result.put(groupId, association.libraryGroup)
+ result[groupId] = association.libraryGroup
}
}
result
@@ -110,7 +106,7 @@
val result = mutableMapOf<String, LibraryGroup>()
for (association in libraryGroupAssociations) {
for (overridePath in association.overrideIncludeInProjectPaths) {
- result.put(overridePath, association.libraryGroup)
+ result[overridePath] = association.libraryGroup
}
}
result
@@ -118,8 +114,6 @@
private val libraryGroupAssociations: List<LibraryGroupAssociation> by lazy {
val groups = getTable("groups")
- val useMultiplatformGroupVersion =
- parameters.useMultiplatformGroupVersions.orElse(false).get()
fun readGroupVersion(groupDefinition: TomlTable, groupName: String, key: String): Version? {
val versionRef = groupDefinition.getString(key) ?: return null
@@ -154,27 +148,11 @@
groupName = groupName,
key = AtomicGroupVersion
)
- val multiplatformGroupVersion = readGroupVersion(
- groupDefinition = groupDefinition,
- groupName = groupName,
- key = MultiplatformGroupVersion
- )
- check(
- multiplatformGroupVersion == null || atomicGroupVersion != null
- ) {
- "Cannot specify $MultiplatformGroupVersion for $name without specifying an " +
- AtomicGroupVersion
- }
- val groupVersion = when {
- useMultiplatformGroupVersion -> multiplatformGroupVersion ?: atomicGroupVersion
- else -> atomicGroupVersion
- }
-
val overrideApplyToProjects = (
groupDefinition.getArray("overrideInclude")?.toList() ?: listOf()
- ).map({ it -> it as String })
+ ).map { it as String }
- val group = LibraryGroup(finalGroupName, groupVersion)
+ val group = LibraryGroup(finalGroupName, atomicGroupVersion)
val association = LibraryGroupAssociation(name, group, overrideApplyToProjects)
result.add(association)
}
@@ -194,4 +172,3 @@
private const val VersionReferencePrefix = "versions."
private const val AtomicGroupVersion = "atomicGroupVersion"
-private const val MultiplatformGroupVersion = "multiplatformGroupVersion"
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/ListAndroidXPropertiesTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/ListAndroidXPropertiesTask.kt
index 11abb36..08bb1d7 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/ListAndroidXPropertiesTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/ListAndroidXPropertiesTask.kt
@@ -24,7 +24,7 @@
* Lists recognized properties whose names start with "androidx"
*/
@DisableCachingByDefault(because = "Too many inputs to cache, and runs quickly anyway")
-abstract class ListAndroidXPropertiesTask() : DefaultTask() {
+abstract class ListAndroidXPropertiesTask : DefaultTask() {
init {
group = "Help"
description = "Lists AndroidX-specific properties (specifiable via -Pandroidx.*)"
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/ListTaskOutputsTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/ListTaskOutputsTask.kt
index 49a1ff3..27076c2 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/ListTaskOutputsTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/ListTaskOutputsTask.kt
@@ -46,7 +46,7 @@
group = "Help"
// compute the output text when the taskgraph is ready so that the output text can be
// saved in the configuration cache and not generate a configuration cache violation
- project.gradle.taskGraph.whenReady({ outputText.toString() })
+ project.gradle.taskGraph.whenReady { outputText }
}
fun setOutput(f: File) {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/PrintProjectCoordinatesTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/PrintProjectCoordinatesTask.kt
index 91ea675..03d56e2 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/PrintProjectCoordinatesTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/PrintProjectCoordinatesTask.kt
@@ -54,10 +54,10 @@
var projectPath: String? = null
@TaskAction
- public fun printInformation() {
+ fun printInformation() {
val projectGroup = projectGroup
val versionFrom =
- if (projectGroup == null || projectGroup.atomicGroupVersion == null) {
+ if (projectGroup?.atomicGroupVersion == null) {
"build.gradle: mavenVersion"
} else {
"group.atomicGroupVersion"
@@ -70,9 +70,9 @@
// put each component of the explanation on its own line
groupExplanation.forEachIndexed { i, component ->
if (i == 0)
- lines.add(listOf("group : ${projectGroup?.group} ", "$component"))
+ lines.add(listOf("group : ${projectGroup?.group} ", component))
else
- lines.add(listOf("", "$component"))
+ lines.add(listOf("", component))
}
lines.add(listOf("artifact: $projectName ", "(from project name)"))
lines.add(listOf("version : $version ", "(from $versionFrom)"))
@@ -88,36 +88,32 @@
private fun formatRow(line: List<String>, columnSizes: List<Int>): String {
var result = ""
- for (i in 0..(line.size - 1)) {
- val word = line.get(i)
- val columnSize = columnSizes.get(i)
+ for (i in line.indices) {
+ val word = line[i]
+ val columnSize = columnSizes[i]
// only have to pad columns before the last column
- if (i != line.size - 1)
- result += word.padEnd(columnSize)
+ result += if (i != line.size - 1)
+ word.padEnd(columnSize)
else
- result += word
+ word
}
return result
}
private fun getColumnSizes(lines: List<List<String>>): List<Int> {
- var maxLengths = mutableListOf<Int>()
+ val maxLengths = mutableListOf<Int>()
for (line in lines) {
- for (i in 0..(line.size - 1)) {
- val word = line.get(i)
+ for (i in line.indices) {
+ val word = line[i]
if (maxLengths.size <= i)
maxLengths.add(0)
- if (maxLengths.get(i) < word.length)
- maxLengths.set(i, word.length)
+ if (maxLengths[i] < word.length)
+ maxLengths[i] = word.length
}
}
return maxLengths
}
- private fun printRow(prefix: String, suffix: String) {
- println(formatTableLine(prefix, suffix))
- }
-
private fun formatTableLine(prefix: String, suffix: String): String {
return prefix.padEnd(10) + suffix
}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/ProjectParser.kt b/buildSrc/private/src/main/kotlin/androidx/build/ProjectParser.kt
index 9bf82ea..836d7f0 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/ProjectParser.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/ProjectParser.kt
@@ -38,7 +38,7 @@
private fun parseProject(fileLines: List<String>): ParsedProject {
var libraryType: String? = null
var publish: String? = null
- var specifiesVersion: Boolean = false
+ var specifiesVersion = false
fileLines.forEach { line ->
if (libraryType == null)
libraryType = line.extractVariableValue(" type = LibraryType.")
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/ProjectResolver.kt b/buildSrc/private/src/main/kotlin/androidx/build/ProjectResolver.kt
index 65f0428..a28ad23 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/ProjectResolver.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/ProjectResolver.kt
@@ -21,7 +21,7 @@
// Resolves the given project, and if it is not found,
// throws an exception that mentions the active project subset, if any (MAIN, COMPOSE, ...)
-public fun Project.resolveProject(projectSpecification: String): Project {
+fun Project.resolveProject(projectSpecification: String): Project {
try {
return project.project(projectSpecification)
} catch (e: UnknownProjectException) {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/Release.kt b/buildSrc/private/src/main/kotlin/androidx/build/Release.kt
index d6e9baf..24d1dae 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/Release.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/Release.kt
@@ -244,7 +244,7 @@
zipTask.dependsOn(verifyInputs)
zipTask.finalizedBy(verifyOutputs)
val verifyOutputsTask = verifyOutputs.get()
- verifyOutputsTask.addFile(zipTask.archiveFile.get().getAsFile())
+ verifyOutputsTask.addFile(zipTask.archiveFile.get().asFile)
}
}
@@ -371,21 +371,19 @@
private fun getVerifyProjectZipInputsTask(
project: Project
): TaskProvider<VerifyGMavenZipTask> {
- val taskProvider = project.tasks.register(
- "verifyInputs" + PROJECT_ARCHIVE_ZIP_TASK_NAME,
+ return project.tasks.register(
+ "verifyInputs$PROJECT_ARCHIVE_ZIP_TASK_NAME",
VerifyGMavenZipTask::class.java
)
- return taskProvider
}
private fun getVerifyProjectZipOutputsTask(
project: Project
): TaskProvider<VerifyGMavenZipTask> {
- val taskProvider = project.tasks.register(
- "verifyOutputs" + PROJECT_ARCHIVE_ZIP_TASK_NAME,
+ return project.tasks.register(
+ "verifyOutputs$PROJECT_ARCHIVE_ZIP_TASK_NAME",
VerifyGMavenZipTask::class.java
)
- return taskProvider
}
}
@@ -417,7 +415,7 @@
val projectSubdir = File("$groupSubdir/${artifact.projectName}")
val androidxRepoOut = project.getRepositoryDirectory()
val fromDir = project.file("$androidxRepoOut/$projectSubdir")
- addFile(File(fromDir, "${artifact.version}"))
+ addFile(File(fromDir, artifact.version))
}
@TaskAction
@@ -426,7 +424,7 @@
verifyFiles()
}
- fun verifySettings() {
+ private fun verifySettings() {
if (!shouldAddGroupConstraints.get() && !isSnapshotBuild()) {
throw GradleException(
"""
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
index 21b7fdd..6470c1b 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
@@ -432,7 +432,6 @@
GenerateMetadataTask::class.java
) { task ->
- @Suppress("UnstableApiUsage") // getResolvedArtifacts() is marked @Incubating
val artifacts = docsConfiguration.incoming.artifacts.resolvedArtifacts
task.getArtifactIds().set(
artifacts.map { result -> result.map { it.id } }
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/metalava/UpdateApiTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/metalava/UpdateApiTask.kt
index b900386..635f7df 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/metalava/UpdateApiTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/metalava/UpdateApiTask.kt
@@ -181,7 +181,6 @@
throw GradleException(message)
}
if (source.exists()) {
- @Suppress("UnstableApiUsage")
Files.copy(source, dest)
logger?.lifecycle("Copied $source to $dest")
} else {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
index 3d4d026..ac5aefff 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-@file:Suppress("UnstableApiUsage") // Incubating AGP APIs
-
package androidx.build.testConfiguration
import androidx.build.AndroidXExtension
@@ -237,6 +235,7 @@
// Recreate the same configuration existing for test modules to pull the artifact
// from the application module specified in the deviceTests extension.
+ @Suppress("UnstableApiUsage") // Incubating dependencyFactory APIs
val configuration = configurations.create("${variant.name}TestedApks") { config ->
config.isCanBeResolved = true
config.isCanBeConsumed = false
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt b/buildSrc/private/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt
index 5d3287f..f91049f 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt
@@ -167,7 +167,6 @@
"androidx.build.license.CheckExternalDependencyLicensesTask_Decorated",
)
-@Suppress("UnstableApiUsage") // usage of BuildService that's incubating
abstract class TaskUpToDateValidator :
BuildService<TaskUpToDateValidator.Parameters>, OperationCompletionListener {
interface Parameters : BuildServiceParameters {
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/SingleFileCopy.kt b/buildSrc/public/src/main/kotlin/androidx/build/SingleFileCopy.kt
index 6064dd2..e2121d7 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/SingleFileCopy.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/SingleFileCopy.kt
@@ -36,7 +36,6 @@
@TaskAction
fun copyFile() {
- @Suppress("UnstableApiUsage")
Files.copy(sourceFile, destinationFile)
}
}
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceEffect.kt b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceEffect.kt
index ed428d7..4f5c8ae7 100644
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceEffect.kt
+++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceEffect.kt
@@ -17,16 +17,19 @@
package androidx.camera.integration.view
import androidx.camera.core.CameraEffect
-import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
/**
* A tone mapping effect for Preview/VideoCapture UseCase.
*/
-internal class ToneMappingSurfaceEffect : CameraEffect(
- PREVIEW or VIDEO_CAPTURE, mainThreadExecutor(), ToneMappingSurfaceProcessor(), {}
-) {
+internal class ToneMappingSurfaceEffect(
+ targets: Int = PREVIEW or VIDEO_CAPTURE,
+ private val processor: ToneMappingSurfaceProcessor = ToneMappingSurfaceProcessor()
+) :
+ CameraEffect(
+ targets, processor.getGlExecutor(), processor, {}
+ ) {
fun release() {
- (surfaceProcessor as? ToneMappingSurfaceProcessor)?.release()
+ processor.release()
}
}
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceProcessor.kt b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceProcessor.kt
index 9dc5199..33ac70c 100644
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceProcessor.kt
+++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceProcessor.kt
@@ -19,16 +19,17 @@
import android.graphics.SurfaceTexture
import android.graphics.SurfaceTexture.OnFrameAvailableListener
import android.os.Handler
-import android.os.Looper
+import android.os.HandlerThread
import android.view.Surface
import androidx.annotation.VisibleForTesting
import androidx.camera.core.SurfaceOutput
import androidx.camera.core.SurfaceProcessor
import androidx.camera.core.SurfaceRequest
-import androidx.camera.core.impl.utils.Threads.checkMainThread
-import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
+import androidx.camera.core.impl.utils.executor.CameraXExecutors.newHandlerExecutor
import androidx.camera.core.processing.OpenGlRenderer
import androidx.camera.core.processing.ShaderProvider
+import androidx.core.util.Preconditions.checkState
+import java.util.concurrent.Executor
/**
* A processor that applies tone mapping on camera output.
@@ -57,28 +58,36 @@
"""
}
}
+
+ private const val GL_THREAD_NAME = "ToneMappingSurfaceProcessor"
}
- private val mainThreadHandler: Handler = Handler(Looper.getMainLooper())
+ private val glThread: HandlerThread = HandlerThread(GL_THREAD_NAME)
+ private var glHandler: Handler
+ private var glExecutor: Executor
+
+ // Members below are only accessed on GL thread.
private val glRenderer: OpenGlRenderer = OpenGlRenderer()
private val outputSurfaces: MutableMap<SurfaceOutput, Surface> = mutableMapOf()
private val textureTransform: FloatArray = FloatArray(16)
private val surfaceTransform: FloatArray = FloatArray(16)
private var isReleased = false
- // For testing.
+ // For testing only
private var surfaceRequested = false
- // For testing.
private var outputSurfaceProvided = false
init {
- mainThreadExecutor().execute {
+ glThread.start()
+ glHandler = Handler(glThread.looper)
+ glExecutor = newHandlerExecutor(glHandler)
+ glExecutor.execute {
glRenderer.init(TONE_MAPPING_SHADER_PROVIDER)
}
}
override fun onInputSurface(surfaceRequest: SurfaceRequest) {
- checkMainThread()
+ checkGlThread()
if (isReleased) {
surfaceRequest.willNotProvideSurface()
return
@@ -89,22 +98,22 @@
surfaceRequest.resolution.width, surfaceRequest.resolution.height
)
val surface = Surface(surfaceTexture)
- surfaceRequest.provideSurface(surface, mainThreadExecutor()) {
+ surfaceRequest.provideSurface(surface, glExecutor) {
surfaceTexture.setOnFrameAvailableListener(null)
surfaceTexture.release()
surface.release()
}
- surfaceTexture.setOnFrameAvailableListener(this, mainThreadHandler)
+ surfaceTexture.setOnFrameAvailableListener(this, glHandler)
}
override fun onOutputSurface(surfaceOutput: SurfaceOutput) {
- checkMainThread()
+ checkGlThread()
outputSurfaceProvided = true
if (isReleased) {
surfaceOutput.close()
return
}
- val surface = surfaceOutput.getSurface(mainThreadExecutor()) {
+ val surface = surfaceOutput.getSurface(glExecutor) {
surfaceOutput.close()
outputSurfaces.remove(surfaceOutput)?.let { removedSurface ->
glRenderer.unregisterOutputSurface(removedSurface)
@@ -120,22 +129,35 @@
}
fun release() {
- checkMainThread()
- if (isReleased) {
- return
+ glExecutor.execute {
+ releaseInternal()
}
+ }
- // Once release is called, we can stop sending frame to output surfaces.
- for (surfaceOutput in outputSurfaces.keys) {
- surfaceOutput.close()
+ private fun releaseInternal() {
+ checkGlThread()
+ if (!isReleased) {
+ // Once release is called, we can stop sending frame to output surfaces.
+ for (surfaceOutput in outputSurfaces.keys) {
+ surfaceOutput.close()
+ }
+ outputSurfaces.clear()
+ glRenderer.release()
+ glThread.quitSafely()
+ isReleased = true
}
- outputSurfaces.clear()
- glRenderer.release()
- isReleased = true
+ }
+
+ private fun checkGlThread() {
+ checkState(GL_THREAD_NAME == Thread.currentThread().name)
+ }
+
+ fun getGlExecutor(): Executor {
+ return glExecutor
}
override fun onFrameAvailable(surfaceTexture: SurfaceTexture) {
- checkMainThread()
+ checkGlThread()
if (isReleased) {
return
}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegateTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegateTest.kt
index e601a24..ae4ae34 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegateTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegateTest.kt
@@ -16,6 +16,7 @@
package androidx.compose.foundation.text.selection
+import android.os.Build
import androidx.compose.foundation.text.InternalFoundationTextApi
import androidx.compose.foundation.text.TEST_FONT_FAMILY
import androidx.compose.foundation.text.TextDelegate
@@ -513,8 +514,6 @@
)
}
- // TODO(b/270441925); Returns a different result below API 26.
- @SdkSuppress(minSdkVersion = 26)
@Test
fun getHandlePosition_EndHandle_not_cross_ltr_overflowed() {
val text = "hello\nworld"
@@ -618,8 +617,6 @@
)
}
- // TODO(b/270441925); Returns a different result below API 26.
- @SdkSuppress(minSdkVersion = 26)
@Test
fun getHandlePosition_EndHandle_cross_ltr_overflowed() {
val text = "hello\nworld"
@@ -774,8 +771,6 @@
)
}
- // TODO(b/270441925); Returns a different result below API 26.
- @SdkSuppress(minSdkVersion = 26)
@Test
fun getHandlePosition_EndHandle_not_cross_rtl_overflowed() {
val text = "\u05D0\u05D1\u05D2\n\u05D3\u05D4\u05D5"
@@ -826,8 +821,6 @@
assertThat(coordinates).isEqualTo(Offset(0f, fontSizeInPx))
}
- // TODO(b/270441925); Returns a different result below API 26.
- @SdkSuppress(minSdkVersion = 26)
@Test
fun getHandlePosition_EndHandle_cross_rtl_overflowed() {
val text = "\u05D0\u05D1\u05D2\n\u05D3\u05D4\u05D5"
@@ -1322,8 +1315,6 @@
assertThat(lineRange).isEqualTo(TextRange(6, 6))
}
- // TODO(b/270441925); Returns a different result below API 26.
- @SdkSuppress(minSdkVersion = 26)
@Test
fun getRangeOfLineContaining_overflowed_returnsLastVisibleLine() {
val text = "hello\nworld"
@@ -1425,8 +1416,6 @@
// start = maxLines 1
// start = clip
// start = enabled soft wrap
- // TODO(b/270441925); Returns a different result below API 26.
- @SdkSuppress(minSdkVersion = 26)
@Test
fun getLastVisibleOffset_maxLines1_clip_enabledSoftwrap_multiLineContent() {
val text = "hello\nworld"
@@ -1452,8 +1441,6 @@
assertThat(lastVisibleOffset).isEqualTo(5)
}
- // TODO(b/270441925); Returns a different result below API 26.
- @SdkSuppress(minSdkVersion = 26)
@Test
fun getLastVisibleOffset_maxLines1_clip_enabledSoftwrap_singleLineContent() {
val text = "hello world"
@@ -1481,8 +1468,6 @@
}
// start = disabled soft wrap
- // TODO(b/270441925); Returns a different result below API 26.
- @SdkSuppress(minSdkVersion = 26)
@Test
fun getLastVisibleOffset_maxLines1_clip_disabledSoftwrap_multiLineContent() {
val text = "hello\nworld"
@@ -1531,7 +1516,7 @@
val lastVisibleOffset = selectable.getLastVisibleOffset()
- assertThat(lastVisibleOffset).isEqualTo(text.length)
+ assertThat(lastVisibleOffset).isEqualTo(text.length - 1) // ignore last whitespace
}
// start = ellipsis
@@ -1618,8 +1603,6 @@
assertThat(lastVisibleOffset).isEqualTo(4)
}
- // TODO(b/270441925); Returns a different result below API 26.
- @SdkSuppress(minSdkVersion = 26)
@Test
fun getLastVisibleOffset_maxLines1_ellipsis_disabledSoftwrap_singleLineContent() {
val text = "hello world ".repeat(10)
@@ -1643,7 +1626,13 @@
val lastVisibleOffset = selectable.getLastVisibleOffset()
- assertThat(lastVisibleOffset).isEqualTo(19)
+ // the way text layout is calculated with ellipsis is vastly different before and
+ // after API 23. Last visible offset logic cannot be unified below API 23.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ assertThat(lastVisibleOffset).isEqualTo(19)
+ } else {
+ assertThat(lastVisibleOffset).isEqualTo(17)
+ }
}
// start = height constrained
@@ -1749,7 +1738,7 @@
val lastVisibleOffset = selectable.getLastVisibleOffset()
- assertThat(lastVisibleOffset).isEqualTo(text.length)
+ assertThat(lastVisibleOffset).isEqualTo(text.length - 1) // ignores last whitespace
}
// start = ellipsis
@@ -1780,8 +1769,6 @@
assertThat(lastVisibleOffset).isEqualTo(11)
}
- // TODO(b/270441925); Returns a different result below API 26.
- @SdkSuppress(minSdkVersion = 26)
@Test
fun getLastVisibleOffset_limitHeight_ellipsis_enabledSoftwrap_singleLineContent() {
val text = "hello world ".repeat(10)
@@ -1805,7 +1792,13 @@
val lastVisibleOffset = selectable.getLastVisibleOffset()
- assertThat(lastVisibleOffset).isEqualTo(9)
+ // the way text layout is calculated with ellipsis is vastly different before and
+ // after API 23. Last visible offset logic cannot be unified below API 23.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ assertThat(lastVisibleOffset).isEqualTo(9)
+ } else {
+ assertThat(lastVisibleOffset).isEqualTo(5)
+ }
}
// start = disabled soft wrap
@@ -1835,8 +1828,6 @@
assertThat(lastVisibleOffset).isEqualTo(5)
}
- // TODO(b/270441925); Returns a different result below API 26.
- @SdkSuppress(minSdkVersion = 26)
@Test
fun getLastVisibleOffset_limitHeight_ellipsis_disabledSoftwrap_singleLineContent() {
val text = "hello world ".repeat(10)
@@ -1860,7 +1851,13 @@
val lastVisibleOffset = selectable.getLastVisibleOffset()
- assertThat(lastVisibleOffset).isEqualTo(19)
+ // the way text layout is calculated with ellipsis is vastly different before and
+ // after API 23. Last visible offset logic cannot be unified below API 23.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ assertThat(lastVisibleOffset).isEqualTo(19)
+ } else {
+ assertThat(lastVisibleOffset).isEqualTo(17)
+ }
}
@Test
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt
index e697e3c..cbb944d 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt
@@ -63,8 +63,6 @@
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.toPixelMap
-import androidx.compose.ui.input.key.KeyEvent
-import androidx.compose.ui.input.key.NativeKeyEvent
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
@@ -92,11 +90,9 @@
import androidx.compose.ui.test.isNotFocused
import androidx.compose.ui.test.junit4.StateRestorationTester
import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.longClick
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performImeAction
-import androidx.compose.ui.test.performKeyPress
import androidx.compose.ui.test.performSemanticsAction
import androidx.compose.ui.test.performTextClearance
import androidx.compose.ui.test.performTextInput
@@ -1244,7 +1240,6 @@
)
}
val textNode = rule.onNodeWithTag(Tag)
- textNode.performTouchInput { longClick() }
textNode.performTextInputSelection(TextRange(0, 4))
textFieldValue.value = ""
@@ -1271,7 +1266,6 @@
)
}
val textNode = rule.onNodeWithTag(Tag)
- textNode.performTouchInput { longClick() }
textNode.performTextInputSelection(TextRange(2, 8))
textFieldValue.value = "Hello"
@@ -1299,7 +1293,6 @@
)
}
val textNode = rule.onNodeWithTag(Tag)
- textNode.performTouchInput { longClick() }
textNode.performTextInputSelection(TextRange(0, 4))
rule.waitForIdle()
@@ -1333,7 +1326,6 @@
)
}
val textNode = rule.onNodeWithTag(Tag)
- textNode.performTouchInput { longClick() }
textNode.performTextInputSelection(TextRange(0, 4))
rule.waitForIdle()
@@ -1351,7 +1343,6 @@
assertThat(actual).isEqualTo(expected)
}
- @OptIn(ExperimentalTestApi::class)
@Test
fun whenSelectedTextIsRemovedByIME_SelectionDoesNotRevert() {
// hard to find a descriptive name. Take a look at
@@ -1370,24 +1361,7 @@
)
}
val textNode = rule.onNodeWithTag(Tag)
- textNode.performSemanticsAction(SemanticsActions.RequestFocus)
- textNode.performTextInputSelection(TextRange(0, 5))
- textNode.performKeyPress(
- KeyEvent(
- NativeKeyEvent(
- NativeKeyEvent.ACTION_DOWN,
- NativeKeyEvent.KEYCODE_DEL
- )
- )
- )
- textNode.performKeyPress(
- KeyEvent(
- NativeKeyEvent(
- NativeKeyEvent.ACTION_UP,
- NativeKeyEvent.KEYCODE_DEL
- )
- )
- )
+ textNode.performTextClearance()
rule.waitForIdle()
textNode.assertTextEquals("")
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt
index c95216d..0bae435 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt
@@ -133,14 +133,24 @@
var width = widthParameter
set(value) {
- field = value
- drawWithCacheModifierNode.invalidateDrawCache()
+ if (field != value) {
+ field = value
+ drawWithCacheModifierNode.invalidateDrawCache()
+ }
}
var brush = brushParameter
+ set(value) {
+ if (field != value) {
+ field = value
+ drawWithCacheModifierNode.invalidateDrawCache()
+ }
+ }
var shape = shapeParameter
set(value) {
- field = value
- drawWithCacheModifierNode.invalidateDrawCache()
+ if (field != value) {
+ field = value
+ drawWithCacheModifierNode.invalidateDrawCache()
+ }
}
private val drawWithCacheModifierNode = delegate(
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 e29b20b..25557de 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
@@ -867,12 +867,7 @@
// If the text hasn't been laid out yet, don't show the modifier.
val offsetCenter = layoutResult.getBoundingBox(textOffset).center
- val containerCoordinates = manager.state?.layoutCoordinates ?: return Offset.Unspecified
- val fieldCoordinates =
- manager.state?.layoutResult?.innerTextFieldCoordinates ?: return Offset.Unspecified
- val localDragPosition = manager.currentDragPosition?.let {
- fieldCoordinates.localPositionOf(containerCoordinates, it)
- } ?: return Offset.Unspecified
+ val localDragPosition = manager.currentDragPosition ?: return Offset.Unspecified
val dragX = localDragPosition.x
val line = layoutResult.getLineForOffset(textOffset)
val lineStartOffset = layoutResult.getLineStart(line)
@@ -899,8 +894,5 @@
return Offset.Unspecified
}
- return containerCoordinates.localPositionOf(
- fieldCoordinates,
- Offset(centerX, offsetCenter.y)
- )
+ return Offset(centerX, offsetCenter.y)
}
\ No newline at end of file
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index 614cfda..31c0540 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -209,6 +209,10 @@
property public abstract long size;
}
+ public sealed interface CacheDrawModifierNode extends androidx.compose.ui.node.DrawModifierNode {
+ method public void invalidateDrawCache();
+ }
+
public final class CacheDrawScope implements androidx.compose.ui.unit.Density {
method public float getDensity();
method public float getFontScale();
@@ -236,7 +240,7 @@
}
public final class DrawModifierKt {
- method public static androidx.compose.ui.node.CacheDrawModifierNode CacheDrawModifierNode(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draw.CacheDrawScope,androidx.compose.ui.draw.DrawResult> onBuildDrawCache);
+ method public static androidx.compose.ui.draw.CacheDrawModifierNode CacheDrawModifierNode(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draw.CacheDrawScope,androidx.compose.ui.draw.DrawResult> onBuildDrawCache);
method public static androidx.compose.ui.Modifier drawBehind(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.drawscope.DrawScope,kotlin.Unit> onDraw);
method public static androidx.compose.ui.Modifier drawWithCache(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.draw.CacheDrawScope,androidx.compose.ui.draw.DrawResult> onBuildDrawCache);
method public static androidx.compose.ui.Modifier drawWithContent(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.drawscope.ContentDrawScope,kotlin.Unit> onDraw);
@@ -2261,10 +2265,6 @@
package androidx.compose.ui.node {
- public interface CacheDrawModifierNode extends androidx.compose.ui.node.DrawModifierNode {
- method public void invalidateDrawCache();
- }
-
public interface CompositionLocalConsumerModifierNode extends androidx.compose.ui.node.DelegatableNode {
}
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index 434d4ce..cb0e90b 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -291,6 +291,10 @@
property public abstract long size;
}
+ public sealed interface CacheDrawModifierNode extends androidx.compose.ui.node.DrawModifierNode {
+ method public void invalidateDrawCache();
+ }
+
public final class CacheDrawScope implements androidx.compose.ui.unit.Density {
method public float getDensity();
method public float getFontScale();
@@ -318,7 +322,7 @@
}
public final class DrawModifierKt {
- method public static androidx.compose.ui.node.CacheDrawModifierNode CacheDrawModifierNode(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draw.CacheDrawScope,androidx.compose.ui.draw.DrawResult> onBuildDrawCache);
+ method public static androidx.compose.ui.draw.CacheDrawModifierNode CacheDrawModifierNode(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draw.CacheDrawScope,androidx.compose.ui.draw.DrawResult> onBuildDrawCache);
method public static androidx.compose.ui.Modifier drawBehind(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.drawscope.DrawScope,kotlin.Unit> onDraw);
method public static androidx.compose.ui.Modifier drawWithCache(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.draw.CacheDrawScope,androidx.compose.ui.draw.DrawResult> onBuildDrawCache);
method public static androidx.compose.ui.Modifier drawWithContent(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.drawscope.ContentDrawScope,kotlin.Unit> onDraw);
@@ -2472,10 +2476,6 @@
package androidx.compose.ui.node {
- public interface CacheDrawModifierNode extends androidx.compose.ui.node.DrawModifierNode {
- method public void invalidateDrawCache();
- }
-
public interface CompositionLocalConsumerModifierNode extends androidx.compose.ui.node.DelegatableNode {
}
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 72c9aaa..1903c49 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -209,6 +209,10 @@
property public abstract long size;
}
+ public sealed interface CacheDrawModifierNode extends androidx.compose.ui.node.DrawModifierNode {
+ method public void invalidateDrawCache();
+ }
+
public final class CacheDrawScope implements androidx.compose.ui.unit.Density {
method public float getDensity();
method public float getFontScale();
@@ -236,7 +240,7 @@
}
public final class DrawModifierKt {
- method public static androidx.compose.ui.node.CacheDrawModifierNode CacheDrawModifierNode(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draw.CacheDrawScope,androidx.compose.ui.draw.DrawResult> onBuildDrawCache);
+ method public static androidx.compose.ui.draw.CacheDrawModifierNode CacheDrawModifierNode(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draw.CacheDrawScope,androidx.compose.ui.draw.DrawResult> onBuildDrawCache);
method public static androidx.compose.ui.Modifier drawBehind(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.drawscope.DrawScope,kotlin.Unit> onDraw);
method public static androidx.compose.ui.Modifier drawWithCache(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.draw.CacheDrawScope,androidx.compose.ui.draw.DrawResult> onBuildDrawCache);
method public static androidx.compose.ui.Modifier drawWithContent(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.drawscope.ContentDrawScope,kotlin.Unit> onDraw);
@@ -2268,10 +2272,6 @@
package androidx.compose.ui.node {
- public interface CacheDrawModifierNode extends androidx.compose.ui.node.DrawModifierNode {
- method public void invalidateDrawCache();
- }
-
@kotlin.PublishedApi internal interface ComposeUiNode {
method public androidx.compose.runtime.CompositionLocalMap getCompositionLocalMap();
method public androidx.compose.ui.unit.Density getDensity();
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/DrawModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/DrawModifier.kt
index 0408729..3c02269 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/DrawModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/DrawModifier.kt
@@ -22,7 +22,6 @@
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.internal.JvmDefaultWithCompatibility
-import androidx.compose.ui.node.CacheDrawModifierNode
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.Nodes
@@ -161,6 +160,15 @@
return CacheDrawModifierNodeImpl(CacheDrawScope(), onBuildDrawCache)
}
+/**
+ * Expands on the [androidx.compose.ui.node.DrawModifierNode] by adding the ability to invalidate
+ * the draw cache for changes in things like shapes and bitmaps (see Modifier.border for a usage
+ * examples).
+ */
+sealed interface CacheDrawModifierNode : DrawModifierNode {
+ fun invalidateDrawCache()
+}
+
private class CacheDrawModifierNodeImpl(
private val cacheDrawScope: CacheDrawScope,
block: CacheDrawScope.() -> DrawResult
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DrawModifierNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DrawModifierNode.kt
index 05fb28e..99cd8b2 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DrawModifierNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DrawModifierNode.kt
@@ -33,15 +33,6 @@
}
/**
- * Expands on the [androidx.compose.ui.node.DrawModifierNode] by adding the ability to invalidate
- * the draw cache for changes in things like shapes and bitmaps (see Modifier.border for a usage
- * examples).
- */
-interface CacheDrawModifierNode : DrawModifierNode {
- fun invalidateDrawCache()
-}
-
-/**
* Invalidates this modifier's draw layer, ensuring that a draw pass will
* be run on the next frame.
*/
diff --git a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/PermissionControllerUpsideDownTest.kt b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/PermissionControllerUpsideDownTest.kt
index 3fd857b..e364fd6 100644
--- a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/PermissionControllerUpsideDownTest.kt
+++ b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/PermissionControllerUpsideDownTest.kt
@@ -20,7 +20,6 @@
import android.health.connect.HealthPermissions
import android.os.Build
import androidx.health.connect.client.PermissionController
-import androidx.health.connect.client.impl.platform.time.SystemDefaultTimeSource
import androidx.health.connect.client.permission.HealthPermission.Companion.PERMISSION_PREFIX
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -60,11 +59,10 @@
fun revokeAllPermissions_revokesHealthPermissions() = runTest {
val revokedPermissions: MutableList<String> = mutableListOf()
val permissionController: PermissionController =
- HealthConnectClientUpsideDownImpl(
- ApplicationProvider.getApplicationContext(), SystemDefaultTimeSource) {
- permissionsToRevoke ->
- revokedPermissions.addAll(permissionsToRevoke)
- }
+ HealthConnectClientUpsideDownImpl(ApplicationProvider.getApplicationContext()) {
+ permissionsToRevoke ->
+ revokedPermissions.addAll(permissionsToRevoke)
+ }
permissionController.revokeAllPermissions()
assertThat(revokedPermissions.all { it.startsWith(PERMISSION_PREFIX) }).isTrue()
}
diff --git a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/RequestConvertersTest.kt b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/RequestConvertersTest.kt
index ddba2fe..4dcd780 100644
--- a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/RequestConvertersTest.kt
+++ b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/RequestConvertersTest.kt
@@ -25,8 +25,6 @@
import android.health.connect.datatypes.StepsRecord as PlatformStepsRecord
import android.health.connect.datatypes.WheelchairPushesRecord as PlatformWheelchairPushesRecord
import android.os.Build
-import androidx.health.connect.client.impl.platform.time.FakeTimeSource
-import androidx.health.connect.client.impl.platform.time.SystemDefaultTimeSource
import androidx.health.connect.client.records.HeartRateRecord
import androidx.health.connect.client.records.NutritionRecord
import androidx.health.connect.client.records.StepsRecord
@@ -67,7 +65,7 @@
setOf(DataOrigin("package1"), DataOrigin("package2"))
)
- with(sdkRequest.toPlatformRequest(SystemDefaultTimeSource)) {
+ with(sdkRequest.toPlatformRequest()) {
assertThat(recordType).isAssignableTo(PlatformStepsRecord::class.java)
assertThat(isAscending).isTrue() // Default Order
assertThat(dataOrigins)
@@ -89,7 +87,7 @@
pageToken = "123"
)
- with(sdkRequest.toPlatformRequest(SystemDefaultTimeSource)) {
+ with(sdkRequest.toPlatformRequest()) {
assertThat(recordType).isAssignableTo(PlatformStepsRecord::class.java)
assertThat(pageToken).isEqualTo(123)
assertThat(dataOrigins)
@@ -105,9 +103,7 @@
val sdkFilter =
TimeRangeFilter.between(Instant.ofEpochMilli(123L), Instant.ofEpochMilli(456L))
- with(
- sdkFilter.toPlatformTimeRangeFilter(SystemDefaultTimeSource) as TimeInstantRangeFilter
- ) {
+ with(sdkFilter.toPlatformTimeRangeFilter() as TimeInstantRangeFilter) {
assertThat(endTime).isEqualTo(Instant.ofEpochMilli(456L))
}
}
@@ -116,7 +112,7 @@
fun timeRangeFilter_localDateTime_fromSdkToPlatform() {
val sdkFilter = TimeRangeFilter.before(LocalDateTime.of(2023, Month.MARCH, 10, 17, 30))
- with(sdkFilter.toPlatformTimeRangeFilter(SystemDefaultTimeSource) as LocalTimeRangeFilter) {
+ with(sdkFilter.toPlatformTimeRangeFilter() as LocalTimeRangeFilter) {
assertThat(endTime).isEqualTo(LocalDateTime.of(2023, Month.MARCH, 10, 17, 30))
}
}
@@ -125,12 +121,9 @@
fun timeRangeFilter_fromSdkToPlatform_none() {
val sdkFilter = TimeRangeFilter.none()
- val fakeTimeSource = FakeTimeSource()
- fakeTimeSource.now = Instant.ofEpochMilli(123L)
- with(sdkFilter.toPlatformTimeRangeFilter(fakeTimeSource) as TimeInstantRangeFilter) {
+ with(sdkFilter.toPlatformTimeRangeFilter() as TimeInstantRangeFilter) {
assertThat(startTime).isEqualTo(Instant.EPOCH)
- assertThat(endTime).isEqualTo(fakeTimeSource.now)
}
}
@@ -165,7 +158,7 @@
setOf(DataOrigin("package1"))
)
- with(sdkRequest.toPlatformRequest(SystemDefaultTimeSource)) {
+ with(sdkRequest.toPlatformRequest()) {
with(timeRangeFilter as TimeInstantRangeFilter) {
assertThat(startTime).isEqualTo(Instant.ofEpochMilli(123L))
assertThat(endTime).isEqualTo(Instant.ofEpochMilli(456L))
@@ -190,7 +183,7 @@
setOf(DataOrigin("package1"), DataOrigin("package2"))
)
- with(sdkRequest.toPlatformRequest(SystemDefaultTimeSource)) {
+ with(sdkRequest.toPlatformRequest()) {
with(timeRangeFilter as TimeInstantRangeFilter) {
assertThat(startTime).isEqualTo(Instant.ofEpochMilli(123L))
assertThat(endTime).isEqualTo(Instant.ofEpochMilli(456L))
@@ -214,7 +207,7 @@
setOf(DataOrigin("package1"), DataOrigin("package2"), DataOrigin("package3"))
)
- with(sdkRequest.toPlatformRequest(SystemDefaultTimeSource)) {
+ with(sdkRequest.toPlatformRequest()) {
with(timeRangeFilter as TimeInstantRangeFilter) {
assertThat(startTime).isEqualTo(Instant.ofEpochMilli(123L))
assertThat(endTime).isEqualTo(Instant.ofEpochMilli(456L))
diff --git a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/time/FakeTimeSource.kt b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/time/FakeTimeSource.kt
deleted file mode 100644
index 270eaf4..0000000
--- a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/time/FakeTimeSource.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2022 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.health.connect.client.impl.platform.time
-
-import android.os.Build
-import androidx.annotation.RequiresApi
-import java.time.Instant
-
-@RequiresApi(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
-class FakeTimeSource : TimeSource {
- override lateinit var now: Instant
-}
\ No newline at end of file
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImpl.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImpl.kt
index 2086d04..35edb69 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImpl.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImpl.kt
@@ -43,8 +43,6 @@
import androidx.health.connect.client.impl.platform.records.toSdkRecord
import androidx.health.connect.client.impl.platform.records.toSdkResponse
import androidx.health.connect.client.impl.platform.response.toKtResponse
-import androidx.health.connect.client.impl.platform.time.SystemDefaultTimeSource
-import androidx.health.connect.client.impl.platform.time.TimeSource
import androidx.health.connect.client.impl.platform.toKtException
import androidx.health.connect.client.permission.HealthPermission.Companion.PERMISSION_PREFIX
import androidx.health.connect.client.records.Record
@@ -74,22 +72,17 @@
private val executor = Dispatchers.Default.asExecutor()
private val context: Context
- private val timeSource: TimeSource
private val healthConnectManager: HealthConnectManager
private val revokePermissionsFunction: (Collection<String>) -> Unit
- constructor(
- context: Context
- ) : this(context, SystemDefaultTimeSource, context::revokeSelfPermissionsOnKill)
+ constructor(context: Context) : this(context, context::revokeSelfPermissionsOnKill)
@VisibleForTesting
internal constructor(
context: Context,
- timeSource: TimeSource,
revokePermissionsFunction: (Collection<String>) -> Unit
) {
this.context = context
- this.timeSource = timeSource
this.healthConnectManager =
context.getSystemService(Context.HEALTHCONNECT_SERVICE) as HealthConnectManager
this.revokePermissionsFunction = revokePermissionsFunction
@@ -159,7 +152,7 @@
suspendCancellableCoroutine { continuation ->
healthConnectManager.deleteRecords(
recordType.toPlatformRecordClass(),
- timeRangeFilter.toPlatformTimeRangeFilter(timeSource),
+ timeRangeFilter.toPlatformTimeRangeFilter(),
executor,
continuation.asOutcomeReceiver()
)
@@ -196,7 +189,7 @@
val response = wrapPlatformException {
suspendCancellableCoroutine { continuation ->
healthConnectManager.readRecords(
- request.toPlatformRequest(timeSource),
+ request.toPlatformRequest(),
executor,
continuation.asOutcomeReceiver()
)
@@ -212,7 +205,7 @@
return wrapPlatformException {
suspendCancellableCoroutine { continuation ->
healthConnectManager.aggregate(
- request.toPlatformRequest(timeSource),
+ request.toPlatformRequest(),
executor,
continuation.asOutcomeReceiver()
)
@@ -227,7 +220,7 @@
return wrapPlatformException {
suspendCancellableCoroutine { continuation ->
healthConnectManager.aggregateGroupByDuration(
- request.toPlatformRequest(timeSource),
+ request.toPlatformRequest(),
request.timeRangeSlicer,
executor,
continuation.asOutcomeReceiver()
@@ -243,7 +236,7 @@
return wrapPlatformException {
suspendCancellableCoroutine { continuation ->
healthConnectManager.aggregateGroupByPeriod(
- request.toPlatformRequest(timeSource),
+ request.toPlatformRequest(),
request.timeRangeSlicer,
executor,
continuation.asOutcomeReceiver()
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/RequestConverters.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/RequestConverters.kt
index 5c7896d..cce4e13 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/RequestConverters.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/RequestConverters.kt
@@ -30,7 +30,6 @@
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
import androidx.health.connect.client.aggregate.AggregateMetric
-import androidx.health.connect.client.impl.platform.time.TimeSource
import androidx.health.connect.client.records.Record
import androidx.health.connect.client.request.AggregateGroupByDurationRequest
import androidx.health.connect.client.request.AggregateGroupByPeriodRequest
@@ -39,14 +38,11 @@
import androidx.health.connect.client.request.ReadRecordsRequest
import androidx.health.connect.client.time.TimeRangeFilter
import java.time.Instant
-import java.time.LocalDateTime
-import java.time.ZoneOffset
-fun ReadRecordsRequest<out Record>.toPlatformRequest(
- timeSource: TimeSource
-): ReadRecordsRequestUsingFilters<out PlatformRecord> {
+fun ReadRecordsRequest<out Record>.toPlatformRequest():
+ ReadRecordsRequestUsingFilters<out PlatformRecord> {
return ReadRecordsRequestUsingFilters.Builder(recordType.toPlatformRecordClass())
- .setTimeRangeFilter(timeRangeFilter.toPlatformTimeRangeFilter(timeSource))
+ .setTimeRangeFilter(timeRangeFilter.toPlatformTimeRangeFilter())
.setPageSize(pageSize)
.apply {
dataOriginFilter.forEach { addDataOrigins(it.toPlatformDataOrigin()) }
@@ -59,24 +55,14 @@
.build()
}
-fun TimeRangeFilter.toPlatformTimeRangeFilter(timeSource: TimeSource): PlatformTimeRangeFilter {
- // TODO(b/272760519): Remove handling for nullable fields in the first two branches. Needed as
- // the values used in the underlining implementation cause long overflow
+fun TimeRangeFilter.toPlatformTimeRangeFilter(): PlatformTimeRangeFilter {
return if (startTime != null || endTime != null) {
- TimeInstantRangeFilter.Builder()
- .setStartTime(startTime ?: Instant.EPOCH)
- .setEndTime(endTime ?: timeSource.now)
- .build()
+ TimeInstantRangeFilter.Builder().setStartTime(startTime).setEndTime(endTime).build()
} else if (localStartTime != null || localEndTime != null) {
- LocalTimeRangeFilter.Builder()
- .setStartTime(localStartTime ?: LocalDateTime.ofEpochSecond(0, 0, ZoneOffset.MIN))
- .setEndTime(localEndTime ?: LocalDateTime.ofInstant(timeSource.now, ZoneOffset.MAX))
- .build()
+ LocalTimeRangeFilter.Builder().setStartTime(localStartTime).setEndTime(localEndTime).build()
} else {
- TimeInstantRangeFilter.Builder()
- .setStartTime(Instant.EPOCH)
- .setEndTime(timeSource.now)
- .build()
+ // Platform doesn't allow both startTime and endTime to be null
+ TimeInstantRangeFilter.Builder().setStartTime(Instant.EPOCH).build()
}
}
@@ -89,10 +75,8 @@
.build()
}
-fun AggregateRequest.toPlatformRequest(timeSource: TimeSource): AggregateRecordsRequest<Any> {
- return AggregateRecordsRequest.Builder<Any>(
- timeRangeFilter.toPlatformTimeRangeFilter(timeSource)
- )
+fun AggregateRequest.toPlatformRequest(): AggregateRecordsRequest<Any> {
+ return AggregateRecordsRequest.Builder<Any>(timeRangeFilter.toPlatformTimeRangeFilter())
.apply {
dataOriginFilter.forEach { addDataOriginsFilter(it.toPlatformDataOrigin()) }
metrics.forEach { addAggregationType(it.toAggregationType()) }
@@ -100,12 +84,8 @@
.build()
}
-fun AggregateGroupByDurationRequest.toPlatformRequest(
- timeSource: TimeSource
-): AggregateRecordsRequest<Any> {
- return AggregateRecordsRequest.Builder<Any>(
- timeRangeFilter.toPlatformTimeRangeFilter(timeSource)
- )
+fun AggregateGroupByDurationRequest.toPlatformRequest(): AggregateRecordsRequest<Any> {
+ return AggregateRecordsRequest.Builder<Any>(timeRangeFilter.toPlatformTimeRangeFilter())
.apply {
dataOriginFilter.forEach { addDataOriginsFilter(it.toPlatformDataOrigin()) }
metrics.forEach { addAggregationType(it.toAggregationType()) }
@@ -113,12 +93,8 @@
.build()
}
-fun AggregateGroupByPeriodRequest.toPlatformRequest(
- timeSource: TimeSource
-): AggregateRecordsRequest<Any> {
- return AggregateRecordsRequest.Builder<Any>(
- timeRangeFilter.toPlatformTimeRangeFilter(timeSource)
- )
+fun AggregateGroupByPeriodRequest.toPlatformRequest(): AggregateRecordsRequest<Any> {
+ return AggregateRecordsRequest.Builder<Any>(timeRangeFilter.toPlatformTimeRangeFilter())
.apply {
dataOriginFilter.forEach { addDataOriginsFilter(it.toPlatformDataOrigin()) }
metrics.forEach { addAggregationType(it.toAggregationType()) }
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/time/TimeSource.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/time/TimeSource.kt
deleted file mode 100644
index fb3561c..0000000
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/time/TimeSource.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2022 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:RestrictTo(RestrictTo.Scope.LIBRARY)
-@file:RequiresApi(api = 34)
-
-package androidx.health.connect.client.impl.platform.time
-
-import androidx.annotation.RequiresApi
-import androidx.annotation.RestrictTo
-import java.time.Instant
-
-interface TimeSource {
- val now: Instant
-}
-
-object SystemDefaultTimeSource : TimeSource {
- override val now: Instant
- get() = Instant.now()
-}
diff --git a/mediarouter/mediarouter/api/current.txt b/mediarouter/mediarouter/api/current.txt
index 36663f8..00e0b0ae6 100644
--- a/mediarouter/mediarouter/api/current.txt
+++ b/mediarouter/mediarouter/api/current.txt
@@ -454,7 +454,15 @@
field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2
field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1
field public static final int CONNECTION_STATE_DISCONNECTED = 0; // 0x0
+ field public static final int DEVICE_TYPE_AUDIO_VIDEO_RECEIVER = 4; // 0x4
+ field public static final int DEVICE_TYPE_CAR = 9; // 0x9
+ field public static final int DEVICE_TYPE_COMPUTER = 7; // 0x7
+ field public static final int DEVICE_TYPE_GAME_CONSOLE = 8; // 0x8
+ field public static final int DEVICE_TYPE_GROUP = 1000; // 0x3e8
+ field public static final int DEVICE_TYPE_SMARTWATCH = 10; // 0xa
field public static final int DEVICE_TYPE_SPEAKER = 2; // 0x2
+ field public static final int DEVICE_TYPE_TABLET = 5; // 0x5
+ field public static final int DEVICE_TYPE_TABLET_DOCKED = 6; // 0x6
field public static final int DEVICE_TYPE_TV = 1; // 0x1
field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0
field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1
diff --git a/mediarouter/mediarouter/api/public_plus_experimental_current.txt b/mediarouter/mediarouter/api/public_plus_experimental_current.txt
index 36663f8..00e0b0ae6 100644
--- a/mediarouter/mediarouter/api/public_plus_experimental_current.txt
+++ b/mediarouter/mediarouter/api/public_plus_experimental_current.txt
@@ -454,7 +454,15 @@
field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2
field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1
field public static final int CONNECTION_STATE_DISCONNECTED = 0; // 0x0
+ field public static final int DEVICE_TYPE_AUDIO_VIDEO_RECEIVER = 4; // 0x4
+ field public static final int DEVICE_TYPE_CAR = 9; // 0x9
+ field public static final int DEVICE_TYPE_COMPUTER = 7; // 0x7
+ field public static final int DEVICE_TYPE_GAME_CONSOLE = 8; // 0x8
+ field public static final int DEVICE_TYPE_GROUP = 1000; // 0x3e8
+ field public static final int DEVICE_TYPE_SMARTWATCH = 10; // 0xa
field public static final int DEVICE_TYPE_SPEAKER = 2; // 0x2
+ field public static final int DEVICE_TYPE_TABLET = 5; // 0x5
+ field public static final int DEVICE_TYPE_TABLET_DOCKED = 6; // 0x6
field public static final int DEVICE_TYPE_TV = 1; // 0x1
field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0
field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1
diff --git a/mediarouter/mediarouter/api/restricted_current.txt b/mediarouter/mediarouter/api/restricted_current.txt
index 36663f8..00e0b0ae6 100644
--- a/mediarouter/mediarouter/api/restricted_current.txt
+++ b/mediarouter/mediarouter/api/restricted_current.txt
@@ -454,7 +454,15 @@
field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2
field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1
field public static final int CONNECTION_STATE_DISCONNECTED = 0; // 0x0
+ field public static final int DEVICE_TYPE_AUDIO_VIDEO_RECEIVER = 4; // 0x4
+ field public static final int DEVICE_TYPE_CAR = 9; // 0x9
+ field public static final int DEVICE_TYPE_COMPUTER = 7; // 0x7
+ field public static final int DEVICE_TYPE_GAME_CONSOLE = 8; // 0x8
+ field public static final int DEVICE_TYPE_GROUP = 1000; // 0x3e8
+ field public static final int DEVICE_TYPE_SMARTWATCH = 10; // 0xa
field public static final int DEVICE_TYPE_SPEAKER = 2; // 0x2
+ field public static final int DEVICE_TYPE_TABLET = 5; // 0x5
+ field public static final int DEVICE_TYPE_TABLET_DOCKED = 6; // 0x6
field public static final int DEVICE_TYPE_TV = 1; // 0x1
field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0
field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteDescriptor.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteDescriptor.java
index a1abac9..e053d4f 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteDescriptor.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteDescriptor.java
@@ -254,10 +254,9 @@
/**
* Gets the type of the receiver device associated with this route.
*
- * @return The type of the receiver device associated with this route:
- * {@link MediaRouter.RouteInfo#DEVICE_TYPE_TV} or
- * {@link MediaRouter.RouteInfo#DEVICE_TYPE_SPEAKER}.
+ * @return The type of the receiver device associated with this route.
*/
+ @MediaRouter.RouteInfo.DeviceType
public int getDeviceType() {
return mBundle.getInt(KEY_DEVICE_TYPE);
}
@@ -730,12 +729,10 @@
/**
* Sets the route's receiver device type.
*
- * @param deviceType The receive device type of the route:
- * {@link MediaRouter.RouteInfo#DEVICE_TYPE_TV} or
- * {@link MediaRouter.RouteInfo#DEVICE_TYPE_SPEAKER}.
+ * @param deviceType The type of the receiver device.
*/
@NonNull
- public Builder setDeviceType(int deviceType) {
+ public Builder setDeviceType(@MediaRouter.RouteInfo.DeviceType int deviceType) {
mBundle.putInt(KEY_DEVICE_TYPE, deviceType);
return this;
}
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
index aa575786..1e96d25 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
@@ -1184,9 +1184,23 @@
*/
public static final int PLAYBACK_TYPE_REMOTE = 1;
- @IntDef({DEVICE_TYPE_UNKNOWN, DEVICE_TYPE_TV, DEVICE_TYPE_SPEAKER, DEVICE_TYPE_BLUETOOTH})
+ @RestrictTo(LIBRARY)
+ @IntDef({
+ DEVICE_TYPE_UNKNOWN,
+ DEVICE_TYPE_TV,
+ DEVICE_TYPE_SPEAKER,
+ DEVICE_TYPE_BLUETOOTH,
+ DEVICE_TYPE_AUDIO_VIDEO_RECEIVER,
+ DEVICE_TYPE_TABLET,
+ DEVICE_TYPE_TABLET_DOCKED,
+ DEVICE_TYPE_COMPUTER,
+ DEVICE_TYPE_GAME_CONSOLE,
+ DEVICE_TYPE_CAR,
+ DEVICE_TYPE_SMARTWATCH,
+ DEVICE_TYPE_GROUP
+ })
@Retention(RetentionPolicy.SOURCE)
- private @interface DeviceType {}
+ public @interface DeviceType {}
/**
* The default receiver device type of the route indicating the type is unknown.
@@ -1221,6 +1235,63 @@
@RestrictTo(LIBRARY)
public static final int DEVICE_TYPE_BLUETOOTH = 3;
+ /**
+ * A receiver device type indicating that the presentation of the media is happening on an
+ * Audio/Video receiver (AVR).
+ *
+ * @see #getDeviceType
+ */
+ public static final int DEVICE_TYPE_AUDIO_VIDEO_RECEIVER = 4;
+ /**
+ * A receiver device type indicating that the presentation of the media is happening on a
+ * tablet.
+ *
+ * @see #getDeviceType
+ */
+ public static final int DEVICE_TYPE_TABLET = 5;
+ /**
+ * A receiver device type indicating that the presentation of the media is happening on a
+ * docked tablet.
+ *
+ * @see #getDeviceType
+ */
+ public static final int DEVICE_TYPE_TABLET_DOCKED = 6;
+ /**
+ * A receiver device type indicating that the presentation of the media is happening on a
+ * computer.
+ *
+ * @see #getDeviceType
+ */
+ public static final int DEVICE_TYPE_COMPUTER = 7;
+ /**
+ * A receiver device type indicating that the presentation of the media is happening on a
+ * gaming console.
+ *
+ * @see #getDeviceType
+ */
+ public static final int DEVICE_TYPE_GAME_CONSOLE = 8;
+ /**
+ * A receiver device type indicating that the presentation of the media is happening on a
+ * car.
+ *
+ * @see #getDeviceType
+ */
+ public static final int DEVICE_TYPE_CAR = 9;
+ /**
+ * A receiver device type indicating that the presentation of the media is happening on a
+ * smartwatch.
+ *
+ * @see #getDeviceType
+ */
+ public static final int DEVICE_TYPE_SMARTWATCH = 10;
+ /**
+ * A receiver device type indicating that the presentation of the media is happening on a
+ * group of devices.
+ *
+ * @see #getDeviceType
+ */
+ public static final int DEVICE_TYPE_GROUP = 1000;
+
@IntDef({PLAYBACK_VOLUME_FIXED,PLAYBACK_VOLUME_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
private @interface PlaybackVolume {}
@@ -1622,9 +1693,9 @@
/**
* Gets the type of the receiver device associated with this route.
*
- * @return The type of the receiver device associated with this route:
- * {@link #DEVICE_TYPE_TV} or {@link #DEVICE_TYPE_SPEAKER}.
+ * @return The type of the receiver device associated with this route.
*/
+ @DeviceType
public int getDeviceType() {
return mDeviceType;
}
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/SystemMediaRouteProvider.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/SystemMediaRouteProvider.java
index 2648406..74c12ed 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/SystemMediaRouteProvider.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/SystemMediaRouteProvider.java
@@ -16,6 +16,7 @@
package androidx.mediarouter.media;
+import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -862,6 +863,7 @@
super(context, syncCallback);
}
+ @SuppressLint("WrongConstant") // False positive. See b/283059575.
@Override
@DoNotInline
protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
diff --git a/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt b/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
index 68778460..9670896 100644
--- a/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
+++ b/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
@@ -95,6 +95,11 @@
var isSessionOpened = false
var internalClient: SandboxedUiAdapter.SessionClient? = null
var testSession: TestSession? = null
+ var isZOrderOnTop = true
+
+ // When set to true, the onSessionOpened callback will only be invoked when specified
+ // by the test. This is to test race conditions when the session is being loaded.
+ var delayOpenSessionCallback = false
override fun openSession(
context: Context,
@@ -107,11 +112,18 @@
internalClient = client
testSession =
TestSession(context, initialWidth, initialHeight, resizeLatch, configChangedLatch)
- client.onSessionOpened(testSession!!)
+ if (!delayOpenSessionCallback) {
+ client.onSessionOpened(testSession!!)
+ }
isSessionOpened = true
+ this.isZOrderOnTop = isZOrderOnTop
openSessionLatch?.countDown()
}
+ internal fun sendOnSessionOpened() {
+ internalClient?.onSessionOpened(testSession!!)
+ }
+
inner class TestSession(
context: Context,
initialWidth: Int,
@@ -120,7 +132,7 @@
private var configChangedLatch: CountDownLatch?
) : SandboxedUiAdapter.Session {
- var isZOrderChanged = false
+ var zOrderChangedLatch: CountDownLatch = CountDownLatch(1)
override val view: View = View(context)
@@ -137,7 +149,8 @@
}
override fun notifyZOrderChanged(isZOrderOnTop: Boolean) {
- isZOrderChanged = true
+ [email protected] = isZOrderOnTop
+ zOrderChangedLatch.countDown()
}
override fun notifyConfigurationChanged(configuration: Configuration) {
@@ -225,11 +238,7 @@
@Test
fun onAttachedToWindowTest() {
- activity.runOnUiThread(Runnable {
- activity.findViewById<LinearLayout>(
- R.id.mainlayout
- ).addView(view)
- })
+ addViewToLayout()
openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)
assertTrue(testSandboxedUiAdapter.isSessionOpened)
assertTrue(view.childCount == 1)
@@ -240,11 +249,7 @@
fun childViewRemovedOnErrorTest() {
assertTrue(view.childCount == 0)
- activity.runOnUiThread(Runnable {
- activity.findViewById<LinearLayout>(
- R.id.mainlayout
- ).addView(view)
- })
+ addViewToLayout()
openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)
assertTrue(openSessionLatch.count == 0.toLong())
@@ -259,32 +264,78 @@
@Test
fun onZOrderChangedTest() {
- val layout = activity.findViewById<LinearLayout>(
- R.id.mainlayout
- )
+ addViewToLayout()
- view.setZOrderOnTopAndEnableUserInteraction(true)
- resizeLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)
- testSandboxedUiAdapter.testSession?.let { assertFalse(it.isZOrderChanged) }
+ // When session is opened, the provider should not receive a Z-order notification.
+ assertThat(openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+ val session = testSandboxedUiAdapter.testSession!!
+ val adapter = testSandboxedUiAdapter
+ assertThat(session.zOrderChangedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isFalse()
+ assertThat(adapter.isZOrderOnTop).isTrue()
- activity.runOnUiThread(Runnable {
- layout.addView(view)
- })
+ // When state changes to false, the provider should be notified.
view.setZOrderOnTopAndEnableUserInteraction(false)
+ assertThat(session.zOrderChangedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(adapter.isZOrderOnTop).isFalse()
- resizeLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)
- testSandboxedUiAdapter.testSession?.let { assertTrue(it.isZOrderChanged) }
- assertTrue(resizeLatch.count == 0.toLong())
+ // When state changes back to true, the provider should be notified.
+ session.zOrderChangedLatch = CountDownLatch(1)
+ view.setZOrderOnTopAndEnableUserInteraction(true)
+ assertThat(session.zOrderChangedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(adapter.isZOrderOnTop).isTrue()
+ }
+
+ @Test
+ fun onZOrderUnchangedTest() {
+ addViewToLayout()
+
+ // When session is opened, the provider should not receive a Z-order notification.
+ assertThat(openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+ val session = testSandboxedUiAdapter.testSession!!
+ val adapter = testSandboxedUiAdapter
+ assertThat(adapter.isZOrderOnTop).isTrue()
+
+ // When Z-order state is unchanged, the provider should not be notified.
+ session.zOrderChangedLatch = CountDownLatch(1)
+ view.setZOrderOnTopAndEnableUserInteraction(true)
+ assertThat(session.zOrderChangedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isFalse()
+ assertThat(adapter.isZOrderOnTop).isTrue()
+ }
+
+ @Test
+ fun setZOrderNotOnTopBeforeOpeningSession() {
+ view.setZOrderOnTopAndEnableUserInteraction(false)
+ addViewToLayout()
+ assertThat(openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+ val session = testSandboxedUiAdapter.testSession!!
+
+ // The initial Z-order state is passed to the session, but notifyZOrderChanged is not called
+ assertThat(session.zOrderChangedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isFalse()
+ assertThat(testSandboxedUiAdapter.isZOrderOnTop).isFalse()
+ }
+
+ @Test
+ fun setZOrderNotOnTopWhileSessionLoading() {
+ testSandboxedUiAdapter.delayOpenSessionCallback = true
+ addViewToLayout()
+ assertThat(openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+ view.setZOrderOnTopAndEnableUserInteraction(false)
+ val session = testSandboxedUiAdapter.testSession!!
+ assertThat(session.zOrderChangedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isFalse()
+ activity.runOnUiThread {
+ testSandboxedUiAdapter.sendOnSessionOpened()
+ }
+
+ // After session has opened, the pending Z order changed made while loading is notified
+ // th the session.
+ assertThat(session.zOrderChangedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(testSandboxedUiAdapter.isZOrderOnTop).isFalse()
}
@Test
@Ignore("b/272324246")
fun onConfigurationChangedTest() {
- val layout = activity.findViewById<LinearLayout>(R.id.mainlayout)
-
- activity.runOnUiThread(Runnable {
- layout.addView(view)
- })
+ addViewToLayout()
openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)
assertTrue(openSessionLatch.count == 0.toLong())
@@ -297,16 +348,12 @@
@Test
fun onSizeChangedTest() {
- val layout = activity.findViewById<LinearLayout>(
- R.id.mainlayout
- )
-
- activity.runOnUiThread(Runnable {
- layout.addView(view)
+ addViewToLayout()
+ assertThat(openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+ activity.runOnUiThread {
view.layoutParams = LinearLayout.LayoutParams(100, 200)
- })
- resizeLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)
- assertTrue(resizeLatch.count == 0.toLong())
+ }
+ assertThat(resizeLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
assertTrue(view.width == 100 && view.height == 200)
}
@@ -356,4 +403,12 @@
"XML overrides SandboxedSdkView.isTransitionGroup", view.isTransitionGroup
)
}
+
+ private fun addViewToLayout() {
+ activity.runOnUiThread {
+ activity.findViewById<LinearLayout>(
+ R.id.mainlayout
+ ).addView(view)
+ }
+ }
}
diff --git a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt
index 8cb8485..7842b0a 100644
--- a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt
+++ b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt
@@ -124,11 +124,14 @@
checkClientOpenSession()
}
+ /**
+ * Sets the Z-ordering of the [SandboxedSdkView]'s surface, relative to its window.
+ */
fun setZOrderOnTopAndEnableUserInteraction(setOnTop: Boolean) {
if (setOnTop == isZOrderOnTop) return
- this.isZOrderOnTop = setOnTop
- checkClientOpenSession()
client?.notifyZOrderChanged(setOnTop)
+ isZOrderOnTop = setOnTop
+ checkClientOpenSession()
}
private fun checkClientOpenSession() {
@@ -251,16 +254,16 @@
oldHeight: Int
) {
super.onSizeChanged(width, height, oldWidth, oldHeight)
- checkClientOpenSession()
client?.notifyResized(width, height)
+ checkClientOpenSession()
}
// TODO(b/270971893) Compare to old configuration before notifying of configuration change.
override fun onConfigurationChanged(config: Configuration?) {
requireNotNull(config) { "Config cannot be null" }
super.onConfigurationChanged(config)
- checkClientOpenSession()
client?.notifyConfigurationChanged(config)
+ checkClientOpenSession()
}
/**
@@ -330,9 +333,7 @@
private var pendingWidth: Int? = null
private var pendingHeight: Int? = null
- // pendingZOrderOnTop ensures visible and interactive provider UI as long as the UI is
- // unobstructed to the user.
- private var pendingZOrderOnTop: Boolean? = true
+ private var pendingZOrderOnTop: Boolean? = null
private var pendingConfiguration: Configuration? = null
fun notifyConfigurationChanged(configuration: Configuration) {
@@ -390,6 +391,7 @@
pendingZOrderOnTop?.let {
session.notifyZOrderChanged(it)
}
+ pendingZOrderOnTop = null
}
override fun onSessionError(throwable: Throwable) {
diff --git a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/providers/SampleDynamicGroupMediaRouteProvider.java b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/providers/SampleDynamicGroupMediaRouteProvider.java
index 3ca2448..47ffed2 100644
--- a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/providers/SampleDynamicGroupMediaRouteProvider.java
+++ b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/providers/SampleDynamicGroupMediaRouteProvider.java
@@ -345,7 +345,7 @@
mDynamicRouteDescriptors.put(routeId, builder.build());
- if (routeDescriptor.getDeviceType() == MediaRouter.RouteInfo.DEVICE_TYPE_TV) {
+ if (routeDescriptor.getDeviceType() == RouteInfo.DEVICE_TYPE_TV) {
mTvSelectedCount++;
}
return true;
@@ -365,7 +365,7 @@
MediaRouteDescriptor routeDescriptor =
mRouteDescriptors.get(dynamicDescriptor.getRouteDescriptor().getId());
- if (routeDescriptor.getDeviceType() == MediaRouter.RouteInfo.DEVICE_TYPE_TV) {
+ if (routeDescriptor.getDeviceType() == RouteInfo.DEVICE_TYPE_TV) {
mTvSelectedCount--;
}
return true;
@@ -532,7 +532,7 @@
private int countTvFromRoute(MediaRouteDescriptor routeDescriptor) {
if (routeDescriptor.getGroupMemberIds().isEmpty()) {
- return (routeDescriptor.getDeviceType() == MediaRouter.RouteInfo.DEVICE_TYPE_TV)
+ return (routeDescriptor.getDeviceType() == RouteInfo.DEVICE_TYPE_TV)
? 1 : 0;
}
int count = 0;
diff --git a/text/text/src/androidTest/java/androidx/compose/ui/text/android/TextLayoutLineVisibleEndTest.kt b/text/text/src/androidTest/java/androidx/compose/ui/text/android/TextLayoutLineVisibleEndTest.kt
new file mode 100644
index 0000000..f80b15a
--- /dev/null
+++ b/text/text/src/androidTest/java/androidx/compose/ui/text/android/TextLayoutLineVisibleEndTest.kt
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.text.android
+
+import android.graphics.Typeface
+import android.os.Build
+import android.text.TextPaint
+import android.text.TextUtils
+import androidx.core.content.res.ResourcesCompat
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.testutils.fonts.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(InternalPlatformTextApi::class)
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class TextLayoutLineVisibleEndTest {
+ lateinit var sampleTypeface: Typeface
+ @Before
+ fun setup() {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ // This sample font provides the following features:
+ // 1. The width of most of visible characters equals to font size.
+ // 2. The LTR/RTL characters are rendered as ▶/◀.
+ // 3. The fontMetrics passed to TextPaint has descend - ascend equal to 1.2 * fontSize.
+ // 4. The fontMetrics passed to TextPaint has ascend equal to fontSize.
+ sampleTypeface = ResourcesCompat.getFont(instrumentation.context, R.font.sample_font)!!
+ }
+
+ @Test
+ fun excludesLineBreak_whenMaxLinesPresent_withoutEllipsis() {
+ val text = "abc\ndef"
+ val textSize = 20.0f
+
+ val layout = simpleLayout(
+ text = text,
+ textSize = textSize,
+ layoutWidth = textSize * 10,
+ maxLines = 1
+ )
+
+ assertThat(layout.getLineVisibleEnd(0)).isEqualTo(3)
+ }
+
+ @Test
+ fun excludesLineBreak_whenMaxLinesPresent_withEllipsisEnd() {
+ val text = "abc\ndef"
+ val textSize = 20.0f
+
+ val layout = simpleLayout(
+ text = text,
+ textSize = textSize,
+ layoutWidth = textSize * 10,
+ maxLines = 1,
+ ellipsize = TextUtils.TruncateAt.END
+ )
+
+ assertThat(layout.getLineVisibleEnd(0)).isEqualTo(3)
+ }
+
+ @Test
+ fun excludesWhitespace_singleLineContent_withEllipsis() {
+ val text = "abc def ghi"
+ val textSize = 20.0f
+ val layoutWidth = textSize * 10
+
+ val layout = simpleLayout(
+ text = text,
+ textSize = textSize,
+ layoutWidth = layoutWidth,
+ ellipsize = TextUtils.TruncateAt.END
+ )
+
+ assertThat(layout.getLineVisibleEnd(0)).isEqualTo(7)
+ }
+
+ @Test
+ fun excludesWhitespace_multiLineContent_withoutEllipsis() {
+ val text = "abc def \nghi"
+ val textSize = 20.0f
+ val layoutWidth = textSize * 10
+
+ val layout = simpleLayout(
+ text = text,
+ textSize = textSize,
+ layoutWidth = layoutWidth
+ )
+
+ // the way overflown text layout is calculated with ellipsis is vastly different before and
+ // after API 23. Line visible end logic cannot be unified below API 23.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ assertThat(layout.getLineVisibleEnd(0)).isEqualTo(7)
+ } else {
+ assertThat(layout.getLineVisibleEnd(0)).isEqualTo(3)
+ }
+ }
+
+ @Test
+ fun excludesWhitespace_multiLineContent_withEllipsis() {
+ val text = "abc def \nghi"
+ val textSize = 20.0f
+ val layoutWidth = textSize * 10
+
+ val layout = simpleLayout(
+ text = text,
+ textSize = textSize,
+ layoutWidth = layoutWidth,
+ ellipsize = TextUtils.TruncateAt.END
+ )
+
+ // the way overflown text layout is calculated with ellipsis is vastly different before and
+ // after API 23. Line visible end logic cannot be unified below API 23.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ assertThat(layout.getLineVisibleEnd(0)).isEqualTo(7)
+ } else {
+ assertThat(layout.getLineVisibleEnd(0)).isEqualTo(3)
+ }
+ }
+
+ @Test
+ fun noVisibleContent_multiLine_withoutEllipsis() {
+ val text = "\n\nabc"
+ val textSize = 20.0f
+ val layoutWidth = textSize * 10
+
+ val layout = simpleLayout(
+ text = text,
+ textSize = textSize,
+ layoutWidth = layoutWidth,
+ maxLines = 2
+ )
+
+ assertThat(layout.getLineVisibleEnd(0)).isEqualTo(0)
+ assertThat(layout.getLineVisibleEnd(1)).isEqualTo(1)
+ }
+
+ @Test
+ fun noVisibleContent_multiLine_withEllipsis() {
+ val text = "\n\nabc"
+ val textSize = 20.0f
+ val layoutWidth = textSize * 10
+
+ val layout = simpleLayout(
+ text = text,
+ textSize = textSize,
+ layoutWidth = layoutWidth,
+ maxLines = 2,
+ ellipsize = TextUtils.TruncateAt.END
+ )
+
+ assertThat(layout.getLineVisibleEnd(0)).isEqualTo(0)
+ assertThat(layout.getLineVisibleEnd(1)).isEqualTo(2) // ellipsis character
+ }
+
+ private fun simpleLayout(
+ text: CharSequence,
+ textSize: Float = Float.NaN,
+ layoutWidth: Float = textSize * text.length,
+ ellipsize: TextUtils.TruncateAt? = null,
+ maxLines: Int = Int.MAX_VALUE
+ ): TextLayout {
+ val textPaint = TextPaint()
+ textPaint.typeface = sampleTypeface
+ textPaint.textSize = if (!textSize.isNaN()) textSize else 14f
+
+ return TextLayout(
+ charSequence = text,
+ width = layoutWidth,
+ textPaint = textPaint,
+ maxLines = maxLines,
+ ellipsize = ellipsize
+ )
+ }
+}
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/LayoutHelper.kt b/text/text/src/main/java/androidx/compose/ui/text/android/LayoutHelper.kt
index 62e7978..4deeab9 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/LayoutHelper.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/LayoutHelper.kt
@@ -247,7 +247,7 @@
// Use line visible end for creating bidi object since invisible whitespaces should not be
// considered for location retrieval.
- val lineVisibleEnd = lineEndToVisibleEnd(lineEnd)
+ val lineVisibleEnd = lineEndToVisibleEnd(lineEnd, lineStart)
val paragraphStart = getParagraphStart(paraNo)
val bidiStart = lineStart - paragraphStart
val bidiEnd = lineVisibleEnd - paragraphStart
@@ -314,7 +314,7 @@
// out of bounds for the runs in this Bidi. We are adjusting the requested offset
// to the visible end of line.
val lineEndAdjustedOffset = if (offset > lineVisibleEnd) {
- lineEndToVisibleEnd(offset)
+ lineEndToVisibleEnd(offset, lineStart)
} else {
offset
}
@@ -349,6 +349,14 @@
}
}
+ /**
+ * Return the text offset after the last visible character on the specified line. For example
+ * whitespaces are not counted as visible characters.
+ */
+ fun getLineVisibleEnd(lineIndex: Int): Int {
+ return lineEndToVisibleEnd(layout.getLineEnd(lineIndex), layout.getLineStart(lineIndex))
+ }
+
private fun getDownstreamHorizontal(offset: Int, primary: Boolean): Float {
val lineNo = layout.getLineForOffset(offset)
val lineEnd = layout.getLineEnd(lineNo)
@@ -370,10 +378,13 @@
private data class BidiRun(val start: Int, val end: Int, val isRtl: Boolean)
- // Convert line end offset to the offset that is the last visible character.
- private fun lineEndToVisibleEnd(lineEnd: Int): Int {
+ /**
+ * Convert line end offset to the offset that is the last visible character. Last visible
+ * character on this line cannot be before line start.
+ */
+ private fun lineEndToVisibleEnd(lineEnd: Int, lineStart: Int): Int {
var visibleEnd = lineEnd
- while (visibleEnd > 0) {
+ while (visibleEnd > lineStart) {
if (isLineEndSpace(layout.text[visibleEnd - 1 /* visibleEnd is exclusive */])) {
visibleEnd--
} else {
@@ -385,6 +396,7 @@
// The spaces that will not be rendered if they are placed at the line end. In most case, it is
// whitespace or line feed character, hence checking linearly should be enough.
+ @Suppress("ConvertTwoComparisonsToRangeCheck")
fun isLineEndSpace(c: Char) = c == ' ' || c == '\n' || c == '\u1680' ||
- (c in '\u2000'..'\u200A' && c != '\u2007') || c == '\u205F' || c == '\u3000'
+ (c >= '\u2000' && c <= '\u200A' && c != '\u2007') || c == '\u205F' || c == '\u3000'
}
\ No newline at end of file
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.kt b/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.kt
index 3944f11..1f88bd0 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.kt
@@ -462,7 +462,7 @@
*/
fun getLineVisibleEnd(lineIndex: Int): Int =
if (layout.getEllipsisStart(lineIndex) == 0) { // no ellipsis
- layout.getLineVisibleEnd(lineIndex)
+ layoutHelper.getLineVisibleEnd(lineIndex)
} else {
layout.getLineStart(lineIndex) + layout.getEllipsisStart(lineIndex)
}
diff --git a/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/FeaturedCarousel.kt b/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/FeaturedCarousel.kt
index 855ac02..369392a 100644
--- a/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/FeaturedCarousel.kt
+++ b/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/FeaturedCarousel.kt
@@ -16,6 +16,8 @@
package androidx.tv.integration.playground
+import android.content.Context
+import android.view.accessibility.AccessibilityManager
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
@@ -25,6 +27,7 @@
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -45,9 +48,15 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.semantics.CollectionItemInfo
+import androidx.compose.ui.semantics.collectionItemInfo
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
@@ -134,7 +143,7 @@
modifier = Modifier
.background(backgrounds[itemIndex])
.fillMaxSize()
- .semantics { contentDescription = "Featured Content" }
+ .carouselItemSemantics(itemIndex, contentDescription = "Featured Content")
) {
Column(
modifier = Modifier
@@ -155,6 +164,41 @@
}
}
+@OptIn(ExperimentalComposeUiApi::class)
+@Suppress("ComposableModifierFactory")
+@Composable
+internal fun Modifier.carouselItemSemantics(
+ itemIndex: Int,
+ contentDescription: String?
+): Modifier {
+ val focusManager = LocalFocusManager.current
+ val context = LocalContext.current
+ val accessibilityManager = remember {
+ context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
+ }
+
+ return this
+ .semantics(mergeDescendants = true) {
+ contentDescription?.let {
+ this.contentDescription = it
+ }
+ collectionItemInfo =
+ CollectionItemInfo(
+ rowIndex = 0,
+ rowSpan = 1,
+ columnIndex = itemIndex,
+ columnSpan = 1
+ )
+ }
+ .then(
+ if (accessibilityManager.isEnabled) {
+ Modifier.clickable {
+ focusManager.moveFocus(FocusDirection.Enter)
+ }
+ } else Modifier
+ )
+}
+
@Composable
private fun OverlayButton(modifier: Modifier = Modifier, text: String = "Play") {
var isFocused by remember { mutableStateOf(false) }
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/SwitchTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/SwitchTest.kt
index fbe4bb2..8d2efaa 100644
--- a/tv/tv-material/src/androidTest/java/androidx/tv/material3/SwitchTest.kt
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/SwitchTest.kt
@@ -221,17 +221,17 @@
}
rule.onNodeWithTag("spacer", useUnmergedTree = true)
- .assertLeftPositionInRootIsEqualTo(8.dp)
+ .assertLeftPositionInRootIsEqualTo(4.dp)
rule.runOnIdle { checked = true }
rule.onNodeWithTag("spacer", useUnmergedTree = true)
- .assertLeftPositionInRootIsEqualTo(28.dp)
+ .assertLeftPositionInRootIsEqualTo(20.dp)
rule.runOnIdle { checked = false }
rule.onNodeWithTag("spacer", useUnmergedTree = true)
- .assertLeftPositionInRootIsEqualTo(8.dp)
+ .assertLeftPositionInRootIsEqualTo(4.dp)
}
@Test
@@ -258,7 +258,7 @@
}
rule.onNodeWithTag("spacer", useUnmergedTree = true)
- .assertLeftPositionInRootIsEqualTo(8.dp)
+ .assertLeftPositionInRootIsEqualTo(4.dp)
}
// regression test for b/191375128
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt
index 5fbfc80c..dc403a6 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt
@@ -16,6 +16,8 @@
package androidx.tv.material3
+import android.content.Context
+import android.view.accessibility.AccessibilityManager
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.AnimatedVisibilityScope
@@ -62,10 +64,14 @@
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onKeyEvent
import androidx.compose.ui.input.key.type
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.semantics.CollectionInfo
+import androidx.compose.ui.semantics.ScrollAxisRange
import androidx.compose.ui.semantics.collectionInfo
+import androidx.compose.ui.semantics.horizontalScrollAxisRange
+import androidx.compose.ui.semantics.scrollBy
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
@@ -123,18 +129,20 @@
val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr
val carouselOuterBoxFocusRequester = remember { FocusRequester() }
var isAutoScrollActive by remember { mutableStateOf(false) }
+ val context = LocalContext.current
+ val accessibilityManager = remember {
+ context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
+ }
AutoScrollSideEffect(
autoScrollDurationMillis = autoScrollDurationMillis,
itemCount = itemCount,
carouselState = carouselState,
- doAutoScroll = shouldPerformAutoScroll(focusState),
+ doAutoScroll = shouldPerformAutoScroll(focusState, accessibilityManager),
onAutoScrollChange = { isAutoScrollActive = it })
Box(modifier = modifier
- .semantics {
- collectionInfo = CollectionInfo(rowCount = 1, columnCount = itemCount)
- }
+ .carouselSemantics(itemCount = itemCount, state = carouselState)
.bringIntoViewIfChildrenAreFocused()
.focusRequester(carouselOuterBoxFocusRequester)
.onFocusChanged {
@@ -165,6 +173,9 @@
label = "CarouselAnimation"
) { activeItemIndex ->
LaunchedEffect(Unit) {
+ if (accessibilityManager.isEnabled) {
+ carouselOuterBoxFocusRequester.requestFocus()
+ }
[email protected] {
// Outer box is focused
if (!isAutoScrollActive && focusState?.isFocused == true) {
@@ -186,10 +197,15 @@
}
@Composable
-private fun shouldPerformAutoScroll(focusState: FocusState?): Boolean {
+private fun shouldPerformAutoScroll(
+ focusState: FocusState?,
+ accessibilityManager: AccessibilityManager
+): Boolean {
val carouselIsFocused = focusState?.isFocused ?: false
val carouselHasFocus = focusState?.hasFocus ?: false
- return !(carouselIsFocused || carouselHasFocus)
+
+ // Disable auto scroll when accessibility mode is enabled or the carousel is focused
+ return !accessibilityManager.isEnabled && !(carouselIsFocused || carouselHasFocus)
}
@Suppress("IllegalExperimentalApiUsage")
@@ -487,3 +503,52 @@
}
}
}
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Suppress("ComposableModifierFactory")
+@Composable
+internal fun Modifier.carouselSemantics(
+ itemCount: Int,
+ state: CarouselState
+): Modifier {
+ return this.then(
+ remember(
+ state,
+ itemCount
+ ) {
+ val accessibilityScrollState = ScrollAxisRange(
+ value = {
+ // Active slide index represents the current position
+ state.activeItemIndex.toFloat()
+ },
+ maxValue = {
+ // Last slide index represents the max. value
+ (itemCount - 1).toFloat()
+ },
+ reverseScrolling = false
+ )
+
+ val scrollByAction: ((x: Float, y: Float) -> Boolean) =
+ { x, _ ->
+ when {
+ // Positive value of x represents forward scrolling
+ x > 0f -> state.moveToNextItem(itemCount)
+
+ // Negative value of x represents backward scrolling
+ x < 0f -> state.moveToPreviousItem(itemCount)
+ }
+
+ // Return false for non-horizontal scrolling (x==0)
+ x != 0f
+ }
+
+ Modifier.semantics {
+ horizontalScrollAxisRange = accessibilityScrollState
+
+ scrollBy(action = scrollByAction)
+
+ collectionInfo = CollectionInfo(rowCount = 1, columnCount = itemCount)
+ }
+ }
+ )
+}
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Switch.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Switch.kt
index d71168a..61d51b2 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/Switch.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Switch.kt
@@ -24,7 +24,6 @@
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.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.height
@@ -178,27 +177,13 @@
maxBound: Dp,
) {
val trackColor by 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 thumbSizeDp = uncheckedThumbDiameter + (ThumbDiameter - uncheckedThumbDiameter) *
+ ((thumbValueDp - minBound) / (maxBound - minBound))
+
+ val thumbOffset = thumbValue.value
val trackShape = SwitchTokens.TrackShape.toShape()
val modifier = Modifier
@@ -328,7 +313,7 @@
/**
* Icon size to use for `thumbContent`
*/
- val IconSize = 16.dp
+ val IconSize = 12.dp
}
/**
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/tokens/SwitchTokens.kt b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/SwitchTokens.kt
index 498364f..bb29efd 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/tokens/SwitchTokens.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/SwitchTokens.kt
@@ -34,48 +34,48 @@
val DisabledUnselectedTrackColor = ColorSchemeKeyTokens.SurfaceVariant
val DisabledUnselectedTrackOutlineColor = ColorSchemeKeyTokens.OnSurface
val HandleShape = ShapeKeyTokens.CornerFull
- val PressedHandleHeight = 28.0.dp
- val PressedHandleWidth = 28.0.dp
+ val PressedHandleHeight = 18.0.dp
+ val PressedHandleWidth = 18.0.dp
val SelectedFocusHandleColor = ColorSchemeKeyTokens.PrimaryContainer
val SelectedFocusIconColor = ColorSchemeKeyTokens.OnPrimaryContainer
val SelectedFocusTrackColor = ColorSchemeKeyTokens.Primary
val SelectedHandleColor = ColorSchemeKeyTokens.OnPrimary
- val SelectedHandleHeight = 24.0.dp
- val SelectedHandleWidth = 24.0.dp
+ val SelectedHandleHeight = 18.0.dp
+ val SelectedHandleWidth = 18.0.dp
val SelectedHoverHandleColor = ColorSchemeKeyTokens.PrimaryContainer
val SelectedHoverIconColor = ColorSchemeKeyTokens.OnPrimaryContainer
val SelectedHoverTrackColor = ColorSchemeKeyTokens.Primary
val SelectedIconColor = ColorSchemeKeyTokens.OnPrimaryContainer
- val SelectedIconSize = 16.0.dp
+ val SelectedIconSize = 12.0.dp
val SelectedPressedHandleColor = ColorSchemeKeyTokens.PrimaryContainer
val SelectedPressedIconColor = ColorSchemeKeyTokens.OnPrimaryContainer
val SelectedPressedTrackColor = ColorSchemeKeyTokens.Primary
val SelectedTrackColor = ColorSchemeKeyTokens.Primary
val StateLayerShape = ShapeKeyTokens.CornerFull
val StateLayerSize = 40.0.dp
- val TrackHeight = 32.0.dp
- val TrackOutlineWidth = 2.0.dp
+ val TrackHeight = 24.0.dp
+ val TrackOutlineWidth = 1.5.dp
val TrackShape = ShapeKeyTokens.CornerFull
- val TrackWidth = 52.0.dp
+ val TrackWidth = 40.0.dp
val UnselectedFocusHandleColor = ColorSchemeKeyTokens.OnSurfaceVariant
val UnselectedFocusIconColor = ColorSchemeKeyTokens.SurfaceVariant
val UnselectedFocusTrackColor = ColorSchemeKeyTokens.SurfaceVariant
val UnselectedFocusTrackOutlineColor = ColorSchemeKeyTokens.Border
val UnselectedHandleColor = ColorSchemeKeyTokens.Border
- val UnselectedHandleHeight = 16.0.dp
- val UnselectedHandleWidth = 16.0.dp
+ val UnselectedHandleHeight = 12.0.dp
+ val UnselectedHandleWidth = 12.0.dp
val UnselectedHoverHandleColor = ColorSchemeKeyTokens.OnSurfaceVariant
val UnselectedHoverIconColor = ColorSchemeKeyTokens.SurfaceVariant
val UnselectedHoverTrackColor = ColorSchemeKeyTokens.SurfaceVariant
val UnselectedHoverTrackOutlineColor = ColorSchemeKeyTokens.Border
val UnselectedIconColor = ColorSchemeKeyTokens.SurfaceVariant
- val UnselectedIconSize = 16.0.dp
+ val UnselectedIconSize = 12.0.dp
val UnselectedPressedHandleColor = ColorSchemeKeyTokens.OnSurfaceVariant
val UnselectedPressedIconColor = ColorSchemeKeyTokens.SurfaceVariant
val UnselectedPressedTrackColor = ColorSchemeKeyTokens.SurfaceVariant
val UnselectedPressedTrackOutlineColor = ColorSchemeKeyTokens.Border
val UnselectedTrackColor = ColorSchemeKeyTokens.SurfaceVariant
val UnselectedTrackOutlineColor = ColorSchemeKeyTokens.Border
- val IconHandleHeight = 24.0.dp
- val IconHandleWidth = 24.0.dp
+ val IconHandleHeight = 18.0.dp
+ val IconHandleWidth = 18.0.dp
}
\ No newline at end of file