Merge "Add a `maxSpan` property to `LazyGridLayoutInfo` to provide the maximum number of spans that an item can occupy on a line." into androidx-main
diff --git a/OWNERS b/OWNERS
index 433a63a..0bbe0a2 100644
--- a/OWNERS
+++ b/OWNERS
@@ -15,6 +15,7 @@
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
diff --git a/annotation/annotation-experimental-lint/build.gradle b/annotation/annotation-experimental-lint/build.gradle
index d10e6d1..929dc78 100644
--- a/annotation/annotation-experimental-lint/build.gradle
+++ b/annotation/annotation-experimental-lint/build.gradle
@@ -36,7 +36,7 @@
}
dependencies {
- compileOnly(libs.androidLintMinApi)
+ compileOnly(libs.androidLintApi)
compileOnly(libs.kotlinStdlib)
testImplementation(libs.kotlinStdlib)
diff --git a/annotation/annotation-experimental-lint/integration-tests/lint-baseline.xml b/annotation/annotation-experimental-lint/integration-tests/lint-baseline.xml
index efad4c2..7a339b7 100644
--- a/annotation/annotation-experimental-lint/integration-tests/lint-baseline.xml
+++ b/annotation/annotation-experimental-lint/integration-tests/lint-baseline.xml
@@ -382,17 +382,8 @@
<issue
id="UnsafeOptInUsageError"
message="This declaration is opt-in and its usage should be marked with `@sample.optin.ExperimentalJavaAnnotation` or `@OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class)`"
- errorLine1=" return param;"
- errorLine2=" ~~~~~">
- <location
- file="src/main/java/sample/optin/RegressionTestJava313686921.java"/>
- </issue>
-
- <issue
- id="UnsafeOptInUsageError"
- message="This declaration is opt-in and its usage should be marked with `@sample.optin.ExperimentalJavaAnnotation` or `@OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class)`"
- errorLine1=" unsafeAnnotatedAnnotationUsageOnMethod("param");"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ errorLine1=" public void unsafeAnnotatedAnnotationUsageOnParam(@AnnotatedJavaAnnotation Object param) {"
+ errorLine2=" ~~~~~">
<location
file="src/main/java/sample/optin/RegressionTestJava313686921.java"/>
</issue>
@@ -408,6 +399,15 @@
<issue
id="UnsafeOptInUsageError"
+ message="This declaration is opt-in and its usage should be marked with `@sample.optin.ExperimentalJavaAnnotation` or `@OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class)`"
+ errorLine1="fun unsafeAnnotatedAnnotationUsage(@AnnotatedKotlinAnnotation param: Any): Any {"
+ errorLine2=" ~~~~~">
+ <location
+ file="src/main/java/sample/optin/RegressionTestKotlin313686921.kt"/>
+ </issue>
+
+ <issue
+ id="UnsafeOptInUsageError"
message="This declaration is opt-in and its usage should be marked with `@sample.kotlin.ExperimentalJavaAnnotation` or `@OptIn(markerClass = sample.kotlin.ExperimentalJavaAnnotation.class)`"
errorLine1=" AnnotatedJavaClass experimentalObject = new AnnotatedJavaClass();"
errorLine2=" ~~~~~~~~~~~~~~~~~~">
diff --git a/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/ExperimentalDetector.kt b/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/ExperimentalDetector.kt
index 635afda..84b8c49 100644
--- a/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/ExperimentalDetector.kt
+++ b/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/ExperimentalDetector.kt
@@ -19,8 +19,12 @@
package androidx.annotation.experimental.lint
import com.android.tools.lint.client.api.JavaEvaluator
+import com.android.tools.lint.detector.api.AnnotationInfo
+import com.android.tools.lint.detector.api.AnnotationOrigin
+import com.android.tools.lint.detector.api.AnnotationUsageInfo
import com.android.tools.lint.detector.api.AnnotationUsageType
import com.android.tools.lint.detector.api.AnnotationUsageType.ASSIGNMENT_RHS
+import com.android.tools.lint.detector.api.AnnotationUsageType.DEFINITION
import com.android.tools.lint.detector.api.AnnotationUsageType.FIELD_REFERENCE
import com.android.tools.lint.detector.api.AnnotationUsageType.METHOD_CALL_PARAMETER
import com.android.tools.lint.detector.api.Category
@@ -33,7 +37,6 @@
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
import com.android.tools.lint.detector.api.isKotlin
-import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiAnnotation
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiClassType
@@ -46,7 +49,6 @@
import com.intellij.psi.PsiPackage
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.impl.source.PsiClassReferenceType
-import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.PsiTypesUtil
import org.jetbrains.kotlin.psi.KtProperty
@@ -70,7 +72,6 @@
import org.jetbrains.uast.UReferenceExpression
import org.jetbrains.uast.USimpleNameReferenceExpression
import org.jetbrains.uast.UVariable
-import org.jetbrains.uast.UastFacade
import org.jetbrains.uast.getContainingUClass
import org.jetbrains.uast.getContainingUMethod
import org.jetbrains.uast.toUElement
@@ -90,6 +91,13 @@
override fun applicableSuperClasses(): List<String> = listOf("java.lang.Object")
+ override fun isApplicableAnnotationUsage(type: AnnotationUsageType): Boolean {
+ return when (type) {
+ DEFINITION -> true
+ else -> super.isApplicableAnnotationUsage(type)
+ }
+ }
+
override fun visitClass(
context: JavaContext,
lambda: ULambdaExpression,
@@ -130,7 +138,7 @@
superMethod: PsiMethod,
) {
val evaluator = context.evaluator
- val allAnnotations = evaluator.getAllAnnotations(superMethod, inHierarchy = true)
+ val allAnnotations = evaluator.getAnnotations(superMethod, inHierarchy = true)
val methodAnnotations = filterRelevantAnnotations(evaluator, allAnnotations)
// Look for annotations on the class as well: these trickle
@@ -172,9 +180,6 @@
method = superMethod,
referenced = superMethod,
annotations = methodAnnotations,
- allMethodAnnotations = methodAnnotations,
- allClassAnnotations = classAnnotations,
- packageAnnotations = pkgAnnotations,
annotated = superMethod,
)
}
@@ -183,13 +188,10 @@
checkAnnotations(
context,
argument = usage,
- type = AnnotationUsageType.METHOD_CALL_CLASS,
+ type = @Suppress("DEPRECATION") AnnotationUsageType.METHOD_CALL_CLASS,
method = superMethod,
referenced = superMethod,
annotations = classAnnotations,
- allMethodAnnotations = methodAnnotations,
- allClassAnnotations = classAnnotations,
- packageAnnotations = pkgAnnotations,
annotated = containingClass,
)
}
@@ -198,13 +200,10 @@
checkAnnotations(
context,
argument = usage,
- type = AnnotationUsageType.METHOD_CALL_PACKAGE,
+ type = @Suppress("DEPRECATION") AnnotationUsageType.METHOD_CALL_PACKAGE,
method = superMethod,
referenced = superMethod,
annotations = pkgAnnotations,
- allMethodAnnotations = methodAnnotations,
- allClassAnnotations = classAnnotations,
- packageAnnotations = pkgAnnotations,
annotated = null,
)
}
@@ -221,15 +220,11 @@
method: PsiMethod?,
referenced: PsiElement?,
annotations: List<UAnnotation>,
- allMethodAnnotations: List<UAnnotation> = emptyList(),
- allClassAnnotations: List<UAnnotation> = emptyList(),
- packageAnnotations: List<UAnnotation> = emptyList(),
annotated: PsiElement?
) {
for (annotation in annotations) {
val signature = annotation.qualifiedName ?: continue
var uAnnotations: List<UAnnotation>? = null
- var psiAnnotations: Array<out PsiAnnotation>? = null
// Modification: Removed loop over uastScanners list.
if (isApplicableAnnotationUsage(type)) {
@@ -284,15 +279,18 @@
if (annotated is PsiModifierListOwner) {
var found = false
- for (psiAnnotation in
- psiAnnotations
+ for (uAnnotation in
+ uAnnotations
?: run {
- val array =
- context.evaluator.getAllAnnotations(annotated, false)
- psiAnnotations = array
- array
+ val list =
+ context.evaluator.getAnnotations(
+ annotated,
+ inHierarchy = false
+ )
+ uAnnotations = list
+ list
}) {
- val qualifiedName = psiAnnotation.qualifiedName
+ val qualifiedName = uAnnotation.qualifiedName
if (qualifiedName == signature) {
found = true
break
@@ -305,19 +303,18 @@
}
}
- visitAnnotationUsage(
- context,
- argument,
- type,
- annotation,
- signature,
- method,
- referenced,
- annotations,
- allMethodAnnotations,
- allClassAnnotations,
- packageAnnotations
- )
+ val annotationInfo =
+ AnnotationInfo(
+ annotation,
+ signature,
+ method,
+ AnnotationOrigin.METHOD // since it's only invoked by doCheckMethodOverride
+ )
+
+ val usageInfo =
+ AnnotationUsageInfo(0, listOf(annotationInfo), argument, referenced, type)
+
+ visitAnnotationUsage(context, argument, annotationInfo, usageInfo)
}
}
}
@@ -336,13 +333,13 @@
val pkgAnnotations: List<UAnnotation>
if (containingClass != null) {
- val annotations = evaluator.getAllAnnotations(containingClass, inHierarchy = true)
+ val annotations = evaluator.getAnnotations(containingClass, inHierarchy = true)
classAnnotations = filterRelevantAnnotations(evaluator, annotations)
val pkg = evaluator.getPackage(containingClass)
pkgAnnotations =
if (pkg != null) {
- val annotations2 = evaluator.getAllAnnotations(pkg, inHierarchy = false)
+ val annotations2 = evaluator.getAnnotations(pkg, inHierarchy = false)
filterRelevantAnnotations(evaluator, annotations2)
} else {
emptyList()
@@ -358,7 +355,7 @@
/** Copied from Lint's `AnnotationHandler`. */
private fun filterRelevantAnnotations(
evaluator: JavaEvaluator,
- annotations: Array<PsiAnnotation>,
+ annotations: List<UAnnotation>,
): List<UAnnotation> {
var result: MutableList<UAnnotation>? = null
val length = annotations.size
@@ -377,16 +374,14 @@
}
if (relevantAnnotations.contains(signature)) {
- val uAnnotation = annotation.toUElementOfType<UAnnotation>() ?: continue
-
// Common case: there's just one annotation; no need to create a list copy
if (length == 1) {
- return listOf(uAnnotation)
+ return listOf(annotation)
}
if (result == null) {
result = ArrayList(2)
}
- result.add(uAnnotation)
+ result.add(annotation)
continue
}
@@ -396,18 +391,11 @@
// Here we want to map from @foo.bar.Baz to the corresponding int def.
// Don't need to compute this if performing @IntDef or @StringDef lookup
- val cls =
- annotation.nameReferenceElement?.resolve()
- ?: run {
- val project = annotation.project
- JavaPsiFacade.getInstance(project)
- .findClass(signature, GlobalSearchScope.projectScope(project))
- }
- ?: continue
- if (cls !is PsiClass || !cls.isAnnotationType) {
+ val cls = annotation.resolve()
+ if (cls == null || !cls.isAnnotationType) {
continue
}
- val innerAnnotations = evaluator.getAllAnnotations(cls, inHierarchy = false)
+ val innerAnnotations = evaluator.getAnnotations(cls, inHierarchy = false)
for (j in innerAnnotations.indices) {
val inner = innerAnnotations[j]
val a = inner.qualifiedName
@@ -415,10 +403,7 @@
if (result == null) {
result = ArrayList(2)
}
- val innerU =
- UastFacade.convertElement(inner, null, UAnnotation::class.java)
- as UAnnotation
- result.add(innerU)
+ result.add(inner)
}
}
}
@@ -432,17 +417,12 @@
override fun visitAnnotationUsage(
context: JavaContext,
- usage: UElement,
- type: AnnotationUsageType,
- annotation: UAnnotation,
- qualifiedName: String,
- method: PsiMethod?,
- referenced: PsiElement?,
- annotations: List<UAnnotation>,
- allMemberAnnotations: List<UAnnotation>,
- allClassAnnotations: List<UAnnotation>,
- allPackageAnnotations: List<UAnnotation>
+ element: UElement,
+ annotationInfo: AnnotationInfo,
+ usageInfo: AnnotationUsageInfo,
) {
+ val referenced = usageInfo.referenced
+ val type = usageInfo.type
// Don't visit values assigned to annotated fields or properties, parameters passed to
// annotated methods, or annotated properties being referenced as fields. We'll visit the
// annotated fields and methods separately.
@@ -455,7 +435,7 @@
return
}
- when (qualifiedName) {
+ when (annotationInfo.qualifiedName) {
JAVA_EXPERIMENTAL_ANNOTATION,
JAVA_REQUIRES_OPT_IN_ANNOTATION -> {
// Only allow Java annotations, since the Kotlin compiler doesn't understand our
@@ -463,9 +443,8 @@
// annotation that it doesn't understand.
checkExperimentalUsage(
context,
- annotation,
- referenced,
- usage,
+ annotationInfo,
+ usageInfo,
listOf(JAVA_USE_EXPERIMENTAL_ANNOTATION, JAVA_OPT_IN_ANNOTATION),
)
}
@@ -475,12 +454,11 @@
// compiler handles that already. Allow either Java or Kotlin annotations, since
// we can enforce both and it's possible that a Kotlin-sourced experimental library
// is being used from Java without the Kotlin stdlib in the classpath.
- if (!isKotlin(usage.lang)) {
+ if (!isKotlin(usageInfo.usage.lang)) {
checkExperimentalUsage(
context,
- annotation,
- referenced,
- usage,
+ annotationInfo,
+ usageInfo,
listOf(
KOTLIN_USE_EXPERIMENTAL_ANNOTATION,
KOTLIN_OPT_IN_ANNOTATION,
@@ -494,38 +472,40 @@
}
/**
- * Check whether the given experimental API [annotation] can be referenced from [usage] call
- * site.
+ * Check whether the given experimental API [annotationInfo] can be referenced from [usageInfo]
+ * call site.
*
* @param context the lint scanning context
- * @param annotation the experimental opt-in annotation detected on the referenced element
- * @param usage the element whose usage should be checked
+ * @param annotationInfo the experimental opt-in annotation detected on the referenced element
+ * @param usageInfo the element whose usage should be checked
* @param optInFqNames fully-qualified class name for experimental opt-in annotation
*/
private fun checkExperimentalUsage(
context: JavaContext,
- annotation: UAnnotation,
- referenced: PsiElement?,
- usage: UElement,
+ annotationInfo: AnnotationInfo,
+ usageInfo: AnnotationUsageInfo,
optInFqNames: List<String>
) {
+ val annotation = annotationInfo.annotation
val annotationFqName = (annotation.uastParent as? UClass)?.qualifiedName ?: return
// This method may get called multiple times when there is more than one instance of the
// annotation in the hierarchy. We don't care which one we're looking at, but we shouldn't
// report the same usage and annotation pair multiple times.
- val visitedAnnotations = visitedUsages.getOrPut(usage) { mutableSetOf() }
+ val visitedAnnotations = visitedUsages.getOrPut(usageInfo.usage) { mutableSetOf() }
if (!visitedAnnotations.add(annotationFqName)) {
return
}
+ val referenced = usageInfo.referenced
+ val usage = usageInfo.usage
// Check whether the usage actually considered experimental.
val decl =
referenced as? UElement
?: referenced.toUElement()
?: usage.getReferencedElement()
?: return
- if (!decl.isExperimentalityRequired(context, annotationFqName)) {
+ if (!decl.isExperimentalityRequired(context, annotationFqName, usageInfo.type)) {
return
}
@@ -574,16 +554,22 @@
private fun UElement.isExperimentalityRequired(
context: JavaContext,
annotationFqName: String,
+ type: AnnotationUsageType,
): Boolean {
+ // Look up annotated annotations only for DEFINITION usage type.
+ val evaluator = context.evaluator.takeIf { type == DEFINITION }
// Is the element itself experimental?
- if (isDeclarationAnnotatedWith(annotationFqName)) {
+ if (isDeclarationAnnotatedWith(annotationFqName, evaluator)) {
return true
}
// Is a parent of the element experimental? Kotlin's implementation skips this check if
// the current element is a constructor method, but it's required when we're looking at
// the syntax tree through UAST. Unclear why.
- if ((uastParent as? UClass)?.isExperimentalityRequired(context, annotationFqName) == true) {
+ if (
+ (uastParent as? UClass)?.isExperimentalityRequired(context, annotationFqName, type) ==
+ true
+ ) {
return true
}
@@ -596,7 +582,10 @@
// which is landed on the backing field if any
if (sourcePsi is KtProperty && this is UMethod) {
val backingField = (uastParent as? UClass)?.fields?.find { it.sourcePsi == sourcePsi }
- if (backingField?.isDeclarationAnnotatedWith(annotationFqName) == true) {
+ if (
+ backingField?.isDeclarationAnnotatedWith(annotationFqName, context.evaluator) ==
+ true
+ ) {
return true
}
}
@@ -687,7 +676,7 @@
return fix()
.name("Add '$annotation' annotation to $elementLabel")
- .annotate(annotation, true)
+ .annotate(annotation, context, element, true)
.range(context.getLocation(elementForInsert))
.build()
}
@@ -897,13 +886,23 @@
}
}
-/** Returns whether the element declaration is annotated with the specified annotation */
+/**
+ * Returns whether the element declaration is annotated with the specified annotation or annotated
+ * with annotation that is annotated with the specified annotation
+ */
private fun UElement.isDeclarationAnnotatedWith(
annotationFqName: String,
+ evaluator: JavaEvaluator? = null,
): Boolean {
return (this as? UAnnotated)?.uAnnotations?.firstOrNull { uAnnotation ->
// Directly annotated
- uAnnotation.qualifiedName == annotationFqName
+ if (uAnnotation.qualifiedName == annotationFqName) return@firstOrNull true
+
+ // Annotated with an annotation that is annotated with the specified annotation
+ val cls = uAnnotation.resolve()
+ if (cls == null || !cls.isAnnotationType) return@firstOrNull false
+ val metaAnnotations = evaluator?.getAnnotations(cls, inHierarchy = false)
+ metaAnnotations?.find { it.qualifiedName == annotationFqName } != null
} != null
}
diff --git a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/ApiLintVersionsTest.kt b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/ApiLintVersionsTest.kt
index 54f4f01..cae2973 100644
--- a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/ApiLintVersionsTest.kt
+++ b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/ApiLintVersionsTest.kt
@@ -36,6 +36,6 @@
// We hardcode version registry.api to the version that is used to run tests.
assertEquals("registry.api matches version used to run tests", CURRENT_API, registry.api)
// Intentionally fails in IDE, because we use different API version in Studio and CLI.
- assertEquals("registry.minApi is set to minimum level of 10", 10, registry.minApi)
+ assertEquals("registry.minApi matches the current API", CURRENT_API, registry.minApi)
}
}
diff --git a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/RequiresOptInDetectorTest.kt b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/RequiresOptInDetectorTest.kt
index 5af3222..c2c6356 100644
--- a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/RequiresOptInDetectorTest.kt
+++ b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/RequiresOptInDetectorTest.kt
@@ -23,7 +23,6 @@
import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin
import com.android.tools.lint.checks.infrastructure.TestLintResult
import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
-import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -561,7 +560,6 @@
* experimentally-annotated annotations.
*/
@Test
- @Ignore("b/313686921")
fun regressionTestJava313686921() {
val input =
arrayOf(
@@ -571,13 +569,10 @@
val expected =
"""
-src/sample/optin/RegressionTestJava313686921.java:31: Error: This declaration is opt-in and its usage should be marked with @sample.optin.ExperimentalJavaAnnotation or @OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class) [UnsafeOptInUsageError]
- return param;
- ~~~~~
-src/sample/optin/RegressionTestJava313686921.java:43: Error: This declaration is opt-in and its usage should be marked with @sample.optin.ExperimentalJavaAnnotation or @OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class) [UnsafeOptInUsageError]
- unsafeAnnotatedAnnotationUsageOnMethod("param");
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-2 errors, 0 warnings
+src/sample/optin/RegressionTestJava313686921.java:30: Error: This declaration is opt-in and its usage should be marked with @sample.optin.ExperimentalJavaAnnotation or @OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class) [UnsafeOptInUsageError]
+ public void unsafeAnnotatedAnnotationUsageOnParam(@AnnotatedJavaAnnotation Object param) {
+ ~~~~~
+1 errors, 0 warnings
"""
.trimIndent()
@@ -589,7 +584,6 @@
* experimentally-annotated annotations.
*/
@Test
- @Ignore("b/313686921")
fun regressionTestKotlin313686921() {
val input =
arrayOf(
@@ -599,9 +593,9 @@
val expected =
"""
-src/sample/optin/AnnotatedKotlinAnnotation.kt:22: Error: This declaration is opt-in and its usage should be marked with @sample.optin.ExperimentalJavaAnnotation or @OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class) [UnsafeOptInUsageError]
- return param
- ~~~~~
+src/sample/optin/AnnotatedKotlinAnnotation.kt:21: Error: This declaration is opt-in and its usage should be marked with @sample.optin.ExperimentalJavaAnnotation or @OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class) [UnsafeOptInUsageError]
+fun unsafeAnnotatedAnnotationUsage(@AnnotatedKotlinAnnotation param: Any): Any {
+ ~~~~~
1 errors, 0 warnings
"""
.trimIndent()
@@ -611,7 +605,6 @@
/** Regression test for b/344616929 that shows where to put @OptIn: use-site! */
@Test
- @Ignore("b/313686921")
fun regressionTestJava344616929() {
val input =
arrayOf(
diff --git a/buildSrc/OWNERS b/buildSrc/OWNERS
index be64598..0233869 100644
--- a/buildSrc/OWNERS
+++ b/buildSrc/OWNERS
@@ -1,9 +1,11 @@
# Bug component: 461356
set noparent
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
per-file *AndroidXPlaygroundRootPlugin.kt = [email protected], [email protected], [email protected]
per-file *LintConfiguration.kt = [email protected], [email protected]
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt
index 799c9d2..bc6c9bb 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt
@@ -139,6 +139,12 @@
/** If true, include Jetpack library projects that live outside of `frameworks/support`. */
const val INCLUDE_OPTIONAL_PROJECTS = "androidx.includeOptionalProjects"
+/**
+ * If true, enable the ArrayNullnessMigration lint check to transition to type-use nullness
+ * annotations. Defaults to false.
+ */
+const val MIGRATE_ARRAY_ANNOTATIONS = "androidx.migrateArrayAnnotations"
+
val ALL_ANDROIDX_PROPERTIES =
setOf(
ADD_GROUP_CONSTRAINTS,
@@ -172,6 +178,7 @@
FilteredAnchorTask.PROP_TASK_NAME,
FilteredAnchorTask.PROP_PATH_PREFIX,
INCLUDE_OPTIONAL_PROJECTS,
+ MIGRATE_ARRAY_ANNOTATIONS,
) + AndroidConfigImpl.GRADLE_PROPERTIES
/**
@@ -264,6 +271,12 @@
fun Project.isCustomCompileSdkAllowed(): Boolean =
findBooleanProperty(ALLOW_CUSTOM_COMPILE_SDK) ?: true
+/**
+ * Whether to enable the ArrayNullnessMigration lint check for moving nullness annotations on arrays
+ * when switching a project to type-use nullness annotations.
+ */
+fun Project.migrateArrayAnnotations() = findBooleanProperty(MIGRATE_ARRAY_ANNOTATIONS) ?: false
+
fun Project.findBooleanProperty(propName: String) = booleanPropertyProvider(propName).get()
fun Project.booleanPropertyProvider(propName: String): Provider<Boolean> {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
index 6a9c795..e6493d0 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
@@ -348,6 +348,12 @@
disable.add("IllegalExperimentalApiUsage")
}
+ // Only allow the ArrayMigration check to be run when opted-in, since this is meant to be
+ // run once per project when switching to type-use nullness annotations.
+ if (!project.migrateArrayAnnotations()) {
+ disable.add("ArrayMigration")
+ }
+
fatal.add("UastImplementation") // go/hide-uast-impl
fatal.add("KotlincFE10") // b/239982263
diff --git a/busytown/androidx_with_metalava.sh b/busytown/androidx_with_metalava.sh
index fa03981..98623e8 100755
--- a/busytown/androidx_with_metalava.sh
+++ b/busytown/androidx_with_metalava.sh
@@ -4,7 +4,7 @@
# Use this flag to temporarily disable `checkApi`
# while landing Metalava w/ breaking API changes
-METALAVA_INTEGRATION_ENFORCED=true
+METALAVA_INTEGRATION_ENFORCED=false
# The default targets to build if no arguments
# are provided on the command line.
diff --git a/compose/material/material/api/current.txt b/compose/material/material/api/current.txt
index b5452f5..70c4afc 100644
--- a/compose/material/material/api/current.txt
+++ b/compose/material/material/api/current.txt
@@ -645,7 +645,7 @@
property @Deprecated public final float factorAtMin;
}
- @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Immutable public final class RippleConfiguration {
+ @androidx.compose.runtime.Immutable public final class RippleConfiguration {
ctor public RippleConfiguration(optional long color, optional androidx.compose.material.ripple.RippleAlpha? rippleAlpha);
method public long getColor();
method public androidx.compose.material.ripple.RippleAlpha? getRippleAlpha();
@@ -660,12 +660,10 @@
}
public final class RippleKt {
- method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material.RippleConfiguration?> getLocalRippleConfiguration();
- method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalUseFallbackRippleImplementation();
+ method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material.RippleConfiguration?> getLocalRippleConfiguration();
method @androidx.compose.runtime.Stable public static androidx.compose.foundation.IndicationNodeFactory ripple(androidx.compose.ui.graphics.ColorProducer color, optional boolean bounded, optional float radius);
method @androidx.compose.runtime.Stable public static androidx.compose.foundation.IndicationNodeFactory ripple(optional boolean bounded, optional float radius, optional long color);
- property @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material.RippleConfiguration?> LocalRippleConfiguration;
- property @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalUseFallbackRippleImplementation;
+ property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material.RippleConfiguration?> LocalRippleConfiguration;
}
public final class ScaffoldDefaults {
diff --git a/compose/material/material/api/restricted_current.txt b/compose/material/material/api/restricted_current.txt
index b5452f5..70c4afc 100644
--- a/compose/material/material/api/restricted_current.txt
+++ b/compose/material/material/api/restricted_current.txt
@@ -645,7 +645,7 @@
property @Deprecated public final float factorAtMin;
}
- @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Immutable public final class RippleConfiguration {
+ @androidx.compose.runtime.Immutable public final class RippleConfiguration {
ctor public RippleConfiguration(optional long color, optional androidx.compose.material.ripple.RippleAlpha? rippleAlpha);
method public long getColor();
method public androidx.compose.material.ripple.RippleAlpha? getRippleAlpha();
@@ -660,12 +660,10 @@
}
public final class RippleKt {
- method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material.RippleConfiguration?> getLocalRippleConfiguration();
- method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalUseFallbackRippleImplementation();
+ method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material.RippleConfiguration?> getLocalRippleConfiguration();
method @androidx.compose.runtime.Stable public static androidx.compose.foundation.IndicationNodeFactory ripple(androidx.compose.ui.graphics.ColorProducer color, optional boolean bounded, optional float radius);
method @androidx.compose.runtime.Stable public static androidx.compose.foundation.IndicationNodeFactory ripple(optional boolean bounded, optional float radius, optional long color);
- property @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material.RippleConfiguration?> LocalRippleConfiguration;
- property @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalUseFallbackRippleImplementation;
+ property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material.RippleConfiguration?> LocalRippleConfiguration;
}
public final class ScaffoldDefaults {
diff --git a/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/RippleTest.kt b/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/RippleTest.kt
index 4d30a23..7c05cfb 100644
--- a/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/RippleTest.kt
+++ b/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/RippleTest.kt
@@ -848,7 +848,6 @@
}
}
- @OptIn(ExperimentalMaterialApi::class)
@Test
fun rippleConfiguration_color_dragged() {
val interactionSource = MutableInteractionSource()
@@ -891,7 +890,6 @@
}
}
- @OptIn(ExperimentalMaterialApi::class)
@Test
fun rippleConfiguration_color_explicitColorSet_dragged() {
val interactionSource = MutableInteractionSource()
@@ -940,7 +938,6 @@
}
}
- @OptIn(ExperimentalMaterialApi::class)
@Test
fun rippleConfiguration_alpha_dragged() {
val interactionSource = MutableInteractionSource()
@@ -986,7 +983,6 @@
}
}
- @OptIn(ExperimentalMaterialApi::class)
@Test
fun rippleConfiguration_disabled_dragged() {
val interactionSource = MutableInteractionSource()
@@ -1025,7 +1021,6 @@
* color of currently active ripples unless they are being drawn on the UI thread (which should
* only happen if the target radius also changes).
*/
- @OptIn(ExperimentalMaterialApi::class)
@Test
fun rippleConfigurationChangeDuringRipple_dragged() {
val interactionSource = MutableInteractionSource()
@@ -1096,62 +1091,6 @@
}
}
- @OptIn(ExperimentalMaterialApi::class)
- @Suppress("DEPRECATION_ERROR")
- @Test
- fun fallback_customRippleTheme() {
- val interactionSource = MutableInteractionSource()
-
- val contentColor = Color.Black
-
- val rippleColor = Color.Red
- val expectedAlpha = 0.5f
- val rippleAlpha = RippleAlpha(expectedAlpha, expectedAlpha, expectedAlpha, expectedAlpha)
-
- val rippleTheme =
- object : androidx.compose.material.ripple.RippleTheme {
- @Deprecated("Super method is deprecated")
- @Composable
- override fun defaultColor() = rippleColor
-
- @Deprecated("Super method is deprecated")
- @Composable
- override fun rippleAlpha() = rippleAlpha
- }
-
- var scope: CoroutineScope? = null
-
- rule.setContent {
- scope = rememberCoroutineScope()
- MaterialTheme {
- CompositionLocalProvider(
- androidx.compose.material.ripple.LocalRippleTheme provides rippleTheme,
- LocalUseFallbackRippleImplementation provides true
- ) {
- Surface(contentColor = contentColor) {
- Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
- RippleBoxWithBackground(
- interactionSource,
- rippleOrFallbackImplementation(),
- bounded = true
- )
- }
- }
- }
- }
- }
-
- val expectedColor =
- calculateResultingRippleColor(rippleColor, rippleOpacity = expectedAlpha)
-
- assertRippleMatches(
- scope!!,
- interactionSource,
- PressInteraction.Press(Offset(10f, 10f)),
- expectedColor
- )
- }
-
/**
* Asserts that the resultant color of the ripple on screen matches [expectedCenterPixelColor].
*
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomNavigation.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomNavigation.kt
index 85f4614..4defef9 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomNavigation.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomNavigation.kt
@@ -220,7 +220,7 @@
// The color of the Ripple should always the selected color, as we want to show the color
// before the item is considered selected, and hence before the new contentColor is
// provided by BottomNavigationTransition.
- val ripple = rippleOrFallbackImplementation(bounded = false, color = selectedContentColor)
+ val ripple = ripple(bounded = false, color = selectedContentColor)
Box(
modifier
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Checkbox.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Checkbox.kt
index 21498b6..72b768c 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Checkbox.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Checkbox.kt
@@ -147,8 +147,7 @@
enabled = enabled,
role = Role.Checkbox,
interactionSource = interactionSource,
- indication =
- rippleOrFallbackImplementation(bounded = false, radius = CheckboxRippleRadius)
+ indication = ripple(bounded = false, radius = CheckboxRippleRadius)
)
} else {
Modifier
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/IconButton.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/IconButton.kt
index 90471f7..27a9b5e 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/IconButton.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/IconButton.kt
@@ -68,8 +68,7 @@
enabled = enabled,
role = Role.Button,
interactionSource = interactionSource,
- indication =
- rippleOrFallbackImplementation(bounded = false, radius = RippleRadius)
+ indication = ripple(bounded = false, radius = RippleRadius)
),
contentAlignment = Alignment.Center
) {
@@ -114,8 +113,7 @@
enabled = enabled,
role = Role.Checkbox,
interactionSource = interactionSource,
- indication =
- rippleOrFallbackImplementation(bounded = false, radius = RippleRadius)
+ indication = ripple(bounded = false, radius = RippleRadius)
),
contentAlignment = Alignment.Center
) {
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/MaterialTheme.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/MaterialTheme.kt
index 976ca50..31b6a64 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/MaterialTheme.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/MaterialTheme.kt
@@ -68,15 +68,12 @@
colors.copy()
}
.apply { updateColorsFrom(colors) }
- val rippleIndication = rippleOrFallbackImplementation()
+ val rippleIndication = ripple()
val selectionColors = rememberTextSelectionColors(rememberedColors)
- @Suppress("DEPRECATION_ERROR")
CompositionLocalProvider(
LocalColors provides rememberedColors,
LocalContentAlpha provides ContentAlpha.high,
LocalIndication provides rippleIndication,
- // TODO: b/304985887 - remove after one stable release
- androidx.compose.material.ripple.LocalRippleTheme provides CompatRippleTheme,
LocalShapes provides shapes,
LocalTextSelectionColors provides selectionColors,
LocalTypography provides typography
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Menu.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Menu.kt
index 4b67c94..2580038 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Menu.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Menu.kt
@@ -237,7 +237,7 @@
enabled = enabled,
onClick = onClick,
interactionSource = interactionSource,
- indication = rippleOrFallbackImplementation(true)
+ indication = ripple(true)
)
.fillMaxWidth()
// Preferred min and max width used during the intrinsic measurement.
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/NavigationRail.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/NavigationRail.kt
index ca98e3f..b37d040 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/NavigationRail.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/NavigationRail.kt
@@ -227,7 +227,7 @@
// The color of the Ripple should always the selected color, as we want to show the color
// before the item is considered selected, and hence before the new contentColor is
// provided by NavigationRailTransition.
- val ripple = rippleOrFallbackImplementation(bounded = false, color = selectedContentColor)
+ val ripple = ripple(bounded = false, color = selectedContentColor)
Box(
modifier
.selectable(
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/RadioButton.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/RadioButton.kt
index f355028..569f2d2 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/RadioButton.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/RadioButton.kt
@@ -94,11 +94,7 @@
enabled = enabled,
role = Role.RadioButton,
interactionSource = interactionSource,
- indication =
- rippleOrFallbackImplementation(
- bounded = false,
- radius = RadioButtonRippleRadius
- )
+ indication = ripple(bounded = false, radius = RadioButtonRippleRadius)
)
} else {
Modifier
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Ripple.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Ripple.kt
index 3f85abe..69c2169 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Ripple.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Ripple.kt
@@ -23,12 +23,10 @@
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.material.ripple.RippleAlpha
import androidx.compose.material.ripple.createRippleModifierNode
-import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf
-import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorProducer
import androidx.compose.ui.graphics.isSpecified
@@ -174,25 +172,6 @@
}
/**
- * Temporary CompositionLocal to allow configuring whether the old ripple implementation that uses
- * the deprecated [androidx.compose.material.ripple.RippleTheme] API should be used in Material
- * components and LocalIndication, instead of the new [ripple] API. This flag defaults to false, and
- * will be removed after one stable release: it should only be used to temporarily unblock
- * upgrading.
- *
- * Provide this CompositionLocal before you provide [MaterialTheme] to make sure it is correctly
- * provided through LocalIndication.
- */
-// TODO: b/304985887 - remove after one stable release
-@Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
-@get:ExperimentalMaterialApi
-@ExperimentalMaterialApi
-val LocalUseFallbackRippleImplementation: ProvidableCompositionLocal<Boolean> =
- staticCompositionLocalOf {
- false
- }
-
-/**
* CompositionLocal used for providing [RippleConfiguration] down the tree. This acts as a
* tree-local 'override' for ripples used inside components that you cannot directly control, such
* as to change the color of a specific component's ripple, or disable it entirely by providing
@@ -204,9 +183,6 @@
* own custom ripple that queries your design system theme values directly using
* [createRippleModifierNode].
*/
-@Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
-@get:ExperimentalMaterialApi
-@ExperimentalMaterialApi
val LocalRippleConfiguration: ProvidableCompositionLocal<RippleConfiguration?> =
compositionLocalOf {
RippleConfiguration()
@@ -225,7 +201,6 @@
* will be used instead.
*/
@Immutable
-@ExperimentalMaterialApi
class RippleConfiguration(
val color: Color = Color.Unspecified,
val rippleAlpha: RippleAlpha? = null
@@ -251,44 +226,6 @@
}
}
-// TODO: b/304985887 - remove after one stable release
-@Suppress("DEPRECATION_ERROR")
-@OptIn(ExperimentalMaterialApi::class)
-@Composable
-internal fun rippleOrFallbackImplementation(
- bounded: Boolean = true,
- radius: Dp = Dp.Unspecified,
- color: Color = Color.Unspecified
-): Indication {
- return if (LocalUseFallbackRippleImplementation.current) {
- androidx.compose.material.ripple.rememberRipple(bounded, radius, color)
- } else {
- ripple(bounded, radius, color)
- }
-}
-
-// TODO: b/304985887 - remove after one stable release
-@Suppress("DEPRECATION_ERROR")
-@Immutable
-internal object CompatRippleTheme : androidx.compose.material.ripple.RippleTheme {
-
- @Deprecated("Super method is deprecated")
- @Composable
- override fun defaultColor() =
- RippleDefaults.rippleColor(
- contentColor = LocalContentColor.current,
- lightTheme = MaterialTheme.colors.isLight
- )
-
- @Deprecated("Super method is deprecated")
- @Composable
- override fun rippleAlpha() =
- RippleDefaults.rippleAlpha(
- contentColor = LocalContentColor.current,
- lightTheme = MaterialTheme.colors.isLight
- )
-}
-
@Stable
private class RippleNodeFactory
private constructor(
@@ -329,7 +266,6 @@
}
}
-@OptIn(ExperimentalMaterialApi::class)
private class DelegatingThemeAwareRippleNode(
private val interactionSource: InteractionSource,
private val bounded: Boolean,
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt
index d6572c7..fd1af73 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt
@@ -714,8 +714,7 @@
.size(thumbSize, thumbSize)
.indication(
interactionSource = interactionSource,
- indication =
- rippleOrFallbackImplementation(bounded = false, radius = ThumbRippleRadius)
+ indication = ripple(bounded = false, radius = ThumbRippleRadius)
)
.hoverable(interactionSource = interactionSource)
.shadow(if (enabled) elevation else 0.dp, CircleShape, clip = false)
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Surface.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Surface.kt
index 9f35894..e8e2986 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Surface.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Surface.kt
@@ -227,7 +227,7 @@
)
.clickable(
interactionSource = interactionSource,
- indication = rippleOrFallbackImplementation(),
+ indication = ripple(),
enabled = enabled,
onClick = onClick
),
@@ -337,7 +337,7 @@
.selectable(
selected = selected,
interactionSource = interactionSource,
- indication = rippleOrFallbackImplementation(),
+ indication = ripple(),
enabled = enabled,
onClick = onClick
),
@@ -447,7 +447,7 @@
.toggleable(
value = checked,
interactionSource = interactionSource,
- indication = rippleOrFallbackImplementation(),
+ indication = ripple(),
enabled = enabled,
onValueChange = onCheckedChange
),
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Switch.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Switch.kt
index 81acb81..3f7de3bc 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Switch.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Switch.kt
@@ -261,8 +261,7 @@
.offset { IntOffset(thumbValue().roundToInt(), 0) }
.indication(
interactionSource = interactionSource,
- indication =
- rippleOrFallbackImplementation(bounded = false, radius = ThumbRippleRadius)
+ indication = ripple(bounded = false, radius = ThumbRippleRadius)
)
.requiredSize(ThumbDiameter)
.shadow(elevation, CircleShape, clip = false)
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Tab.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Tab.kt
index 884e42d..6c826a4 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Tab.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Tab.kt
@@ -163,7 +163,7 @@
// The color of the Ripple should always the be selected color, as we want to show the color
// before the item is considered selected, and hence before the new contentColor is
// provided by TabTransition.
- val ripple = rippleOrFallbackImplementation(bounded = true, color = selectedContentColor)
+ val ripple = ripple(bounded = true, color = selectedContentColor)
TabTransition(selectedContentColor, unselectedContentColor, selected) {
Row(
@@ -234,7 +234,7 @@
// The color of the Ripple should always the selected color, as we want to show the color
// before the item is considered selected, and hence before the new contentColor is
// provided by TabTransition.
- val ripple = rippleOrFallbackImplementation(bounded = true, color = selectedContentColor)
+ val ripple = ripple(bounded = true, color = selectedContentColor)
TabTransition(selectedContentColor, unselectedContentColor, selected) {
Column(
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 102d2d3..7cffff7 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -1246,7 +1246,7 @@
property public final long titleContentColor;
}
- @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable public final class RippleConfiguration {
+ @androidx.compose.runtime.Immutable public final class RippleConfiguration {
ctor public RippleConfiguration(optional long color, optional androidx.compose.material.ripple.RippleAlpha? rippleAlpha);
method public long getColor();
method public androidx.compose.material.ripple.RippleAlpha? getRippleAlpha();
@@ -1261,12 +1261,10 @@
}
public final class RippleKt {
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material3.RippleConfiguration?> getLocalRippleConfiguration();
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalUseFallbackRippleImplementation();
+ method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material3.RippleConfiguration?> getLocalRippleConfiguration();
method @androidx.compose.runtime.Stable public static androidx.compose.foundation.IndicationNodeFactory ripple(androidx.compose.ui.graphics.ColorProducer color, optional boolean bounded, optional float radius);
method @androidx.compose.runtime.Stable public static androidx.compose.foundation.IndicationNodeFactory ripple(optional boolean bounded, optional float radius, optional long color);
- property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material3.RippleConfiguration?> LocalRippleConfiguration;
- property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalUseFallbackRippleImplementation;
+ property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material3.RippleConfiguration?> LocalRippleConfiguration;
}
public final class ScaffoldDefaults {
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 102d2d3..7cffff7 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -1246,7 +1246,7 @@
property public final long titleContentColor;
}
- @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable public final class RippleConfiguration {
+ @androidx.compose.runtime.Immutable public final class RippleConfiguration {
ctor public RippleConfiguration(optional long color, optional androidx.compose.material.ripple.RippleAlpha? rippleAlpha);
method public long getColor();
method public androidx.compose.material.ripple.RippleAlpha? getRippleAlpha();
@@ -1261,12 +1261,10 @@
}
public final class RippleKt {
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material3.RippleConfiguration?> getLocalRippleConfiguration();
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalUseFallbackRippleImplementation();
+ method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material3.RippleConfiguration?> getLocalRippleConfiguration();
method @androidx.compose.runtime.Stable public static androidx.compose.foundation.IndicationNodeFactory ripple(androidx.compose.ui.graphics.ColorProducer color, optional boolean bounded, optional float radius);
method @androidx.compose.runtime.Stable public static androidx.compose.foundation.IndicationNodeFactory ripple(optional boolean bounded, optional float radius, optional long color);
- property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material3.RippleConfiguration?> LocalRippleConfiguration;
- property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalUseFallbackRippleImplementation;
+ property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material3.RippleConfiguration?> LocalRippleConfiguration;
}
public final class ScaffoldDefaults {
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ChildParentSemanticsTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ChildParentSemanticsTest.kt
new file mode 100644
index 0000000..c3119f4
--- /dev/null
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ChildParentSemanticsTest.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.material3.internal.childSemantics
+import androidx.compose.material3.internal.parentSemantics
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.semantics.SemanticsProperties.ContentDescription
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.onClick
+import androidx.compose.ui.semantics.onLongClick
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.assertContentDescriptionEquals
+import androidx.compose.ui.test.assertHasClickAction
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class ChildParentSemanticsTest {
+
+ @get:Rule val rule = createComposeRule()
+
+ @Test
+ fun childAndParent_setSemantics_mergesCorrectly() {
+ rule.setContent {
+ Box(
+ Modifier.testTag(ParentTag).parentSemantics { onLongClick("onLongClick") { true } }
+ ) {
+ Box(
+ Modifier.testTag(ChildTag)
+ .semantics {}
+ .childSemantics { onClick("onClick") { true } }
+ )
+ }
+ }
+
+ rule
+ .onNodeWithTag(ChildTag, true)
+ .assertHasClickAction()
+ .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.OnLongClick))
+
+ rule
+ .onNodeWithTag(ParentTag, true)
+ .assert(SemanticsMatcher.keyNotDefined(SemanticsActions.OnLongClick))
+ }
+
+ @Test
+ fun childAndParent_haveConflict_childWins() {
+ rule.setContent {
+ Box(Modifier.testTag(ParentTag).parentSemantics { contentDescription = "Parent" }) {
+ Box(Modifier.testTag(ChildTag).childSemantics { contentDescription = "Child" })
+ }
+ }
+
+ rule.onNodeWithTag(ChildTag, true).assertContentDescriptionEquals("Child")
+
+ rule
+ .onNodeWithTag(ParentTag, true)
+ .assert(SemanticsMatcher.keyNotDefined(ContentDescription))
+ }
+
+ @Test
+ fun parent_noChild_appliesToItself() {
+ rule.setContent {
+ Box(
+ Modifier.testTag(ParentTag).parentSemantics { onLongClick("longClick") { true } }
+ ) {}
+ }
+
+ rule
+ .onNodeWithTag(ParentTag)
+ .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.OnLongClick))
+ }
+
+ @Test
+ fun parent_withNonClickable_mergesCorrectly() {
+ rule.setContent {
+ Box(Modifier.testTag(ParentTag).parentSemantics { onLongClick("longClick") { true } }) {
+ Text(
+ modifier = Modifier.semantics { contentDescription = "text" },
+ text = "Foo Bar"
+ )
+ }
+ }
+
+ rule
+ .onNodeWithTag(ParentTag)
+ .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.OnLongClick))
+ .assertContentDescriptionEquals("text")
+ }
+}
+
+private const val ChildTag = "child"
+private const val ParentTag = "parent"
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/RippleTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/RippleTest.kt
index add61ae..99d79a0 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/RippleTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/RippleTest.kt
@@ -312,7 +312,6 @@
}
}
- @OptIn(ExperimentalMaterial3Api::class)
@Test
fun rippleConfiguration_color_dragged() {
val interactionSource = MutableInteractionSource()
@@ -355,7 +354,6 @@
}
}
- @OptIn(ExperimentalMaterial3Api::class)
@Test
fun rippleConfiguration_color_explicitColorSet_dragged() {
val interactionSource = MutableInteractionSource()
@@ -404,7 +402,6 @@
}
}
- @OptIn(ExperimentalMaterial3Api::class)
@Test
fun rippleConfiguration_alpha_dragged() {
val interactionSource = MutableInteractionSource()
@@ -450,7 +447,6 @@
}
}
- @OptIn(ExperimentalMaterial3Api::class)
@Test
fun rippleConfiguration_disabled_dragged() {
val interactionSource = MutableInteractionSource()
@@ -489,7 +485,6 @@
* color of currently active ripples unless they are being drawn on the UI thread (which should
* only happen if the target radius also changes).
*/
- @OptIn(ExperimentalMaterial3Api::class)
@Test
fun rippleConfigurationChangeDuringRipple_dragged() {
val interactionSource = MutableInteractionSource()
@@ -560,62 +555,6 @@
}
}
- @OptIn(ExperimentalMaterial3Api::class)
- @Suppress("DEPRECATION_ERROR")
- @Test
- fun fallback_customRippleTheme() {
- val interactionSource = MutableInteractionSource()
-
- val contentColor = Color.Black
-
- val rippleColor = Color.Red
- val expectedAlpha = 0.5f
- val rippleAlpha = RippleAlpha(expectedAlpha, expectedAlpha, expectedAlpha, expectedAlpha)
-
- val rippleTheme =
- object : androidx.compose.material.ripple.RippleTheme {
- @Deprecated("Super method is deprecated")
- @Composable
- override fun defaultColor() = rippleColor
-
- @Deprecated("Super method is deprecated")
- @Composable
- override fun rippleAlpha() = rippleAlpha
- }
-
- var scope: CoroutineScope? = null
-
- rule.setContent {
- scope = rememberCoroutineScope()
- MaterialTheme {
- CompositionLocalProvider(
- androidx.compose.material.ripple.LocalRippleTheme provides rippleTheme,
- LocalUseFallbackRippleImplementation provides true
- ) {
- Surface(contentColor = contentColor) {
- Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
- RippleBoxWithBackground(
- interactionSource,
- rippleOrFallbackImplementation(),
- bounded = true
- )
- }
- }
- }
- }
- }
-
- val expectedColor =
- calculateResultingRippleColor(rippleColor, rippleOpacity = expectedAlpha)
-
- assertRippleMatches(
- scope!!,
- interactionSource,
- PressInteraction.Press(Offset(10f, 10f)),
- expectedColor
- )
- }
-
/**
* Asserts that the resultant color of the ripple on screen matches [expectedCenterPixelColor].
*
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TooltipTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TooltipTest.kt
index 8c7df70e..7d228d9 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TooltipTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TooltipTest.kt
@@ -30,6 +30,10 @@
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.assertHasClickAction
import androidx.compose.ui.test.assertHeightIsEqualTo
import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
@@ -773,6 +777,31 @@
Icon(Icons.Filled.Favorite, contentDescription = null)
}
}
+
+ @Test
+ fun plainTooltip_withClickable_hasCorrectSemantics() {
+ rule.setMaterialContent(lightColorScheme()) {
+ TooltipBox(
+ positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
+ tooltip = {
+ PlainTooltip(
+ modifier = Modifier.testTag(ContainerTestTag),
+ content = { Text("Tooltip") }
+ )
+ },
+ state = rememberTooltipState()
+ ) {
+ IconButton(modifier = Modifier.testTag(AnchorTestTag), onClick = {}) {
+ Icon(Icons.Filled.Favorite, contentDescription = null)
+ }
+ }
+ }
+
+ rule
+ .onNodeWithTag(AnchorTestTag)
+ .assertHasClickAction()
+ .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.OnLongClick))
+ }
}
private const val ActionTestTag = "Action"
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/internal/BasicTooltip.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/internal/BasicTooltip.android.kt
index c853804..cbfaeb3 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/internal/BasicTooltip.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/internal/BasicTooltip.android.kt
@@ -234,7 +234,7 @@
scope: CoroutineScope
): Modifier =
if (enabled) {
- this.semantics(mergeDescendants = true) {
+ this.parentSemantics {
onLongClick(
label = label,
action = {
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Checkbox.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Checkbox.kt
index 6225b59..544d421 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Checkbox.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Checkbox.kt
@@ -156,11 +156,7 @@
enabled = enabled,
role = Role.Checkbox,
interactionSource = interactionSource,
- indication =
- rippleOrFallbackImplementation(
- bounded = false,
- radius = CheckboxTokens.StateLayerSize / 2
- )
+ indication = ripple(bounded = false, radius = CheckboxTokens.StateLayerSize / 2)
)
} else {
Modifier
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt
index 876ae34..07dfb9d 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt
@@ -23,6 +23,7 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.selection.toggleable
+import androidx.compose.material3.internal.childSemantics
import androidx.compose.material3.tokens.FilledIconButtonTokens
import androidx.compose.material3.tokens.FilledTonalIconButtonTokens
import androidx.compose.material3.tokens.IconButtonTokens
@@ -97,11 +98,9 @@
role = Role.Button,
interactionSource = interactionSource,
indication =
- rippleOrFallbackImplementation(
- bounded = false,
- radius = IconButtonTokens.StateLayerSize / 2
- )
- ),
+ ripple(bounded = false, radius = IconButtonTokens.StateLayerSize / 2)
+ )
+ .childSemantics(),
contentAlignment = Alignment.Center
) {
val contentColor = colors.contentColor(enabled)
@@ -162,10 +161,7 @@
role = Role.Checkbox,
interactionSource = interactionSource,
indication =
- rippleOrFallbackImplementation(
- bounded = false,
- radius = IconButtonTokens.StateLayerSize / 2
- )
+ ripple(bounded = false, radius = IconButtonTokens.StateLayerSize / 2)
),
contentAlignment = Alignment.Center
) {
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MaterialTheme.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MaterialTheme.kt
index 7d84cbd..56833a8 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MaterialTheme.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MaterialTheme.kt
@@ -53,14 +53,11 @@
typography: Typography = MaterialTheme.typography,
content: @Composable () -> Unit
) {
- val rippleIndication = rippleOrFallbackImplementation()
+ val rippleIndication = ripple()
val selectionColors = rememberTextSelectionColors(colorScheme)
- @Suppress("DEPRECATION_ERROR")
CompositionLocalProvider(
LocalColorScheme provides colorScheme,
LocalIndication provides rippleIndication,
- // TODO: b/304985887 - remove after one stable release
- androidx.compose.material.ripple.LocalRippleTheme provides CompatRippleTheme,
LocalShapes provides shapes,
LocalTextSelectionColors provides selectionColors,
LocalTypography provides typography,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Menu.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Menu.kt
index 186a033..14f8150 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Menu.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Menu.kt
@@ -455,7 +455,7 @@
enabled = enabled,
onClick = onClick,
interactionSource = interactionSource,
- indication = rippleOrFallbackImplementation(true)
+ indication = ripple(true)
)
.fillMaxWidth()
// Preferred min and max width used during the intrinsic measurement.
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationBar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationBar.kt
index f420178..39c18a0 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationBar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationBar.kt
@@ -258,7 +258,7 @@
Box(
Modifier.layoutId(IndicatorRippleLayoutIdTag)
.clip(NavigationBarTokens.ActiveIndicatorShape.value)
- .indication(offsetInteractionSource, rippleOrFallbackImplementation())
+ .indication(offsetInteractionSource, ripple())
)
}
val indicator =
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt
index 64947a6..5f1f067 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt
@@ -361,7 +361,7 @@
Box(
Modifier.layoutId(IndicatorRippleLayoutIdTag)
.clip(indicatorShape)
- .indication(interactionSource, rippleOrFallbackImplementation())
+ .indication(interactionSource, ripple())
)
// Create the indicator. The indicator has a width-expansion animation which interferes
// with
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationRail.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationRail.kt
index bbab588..10e79f7 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationRail.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationRail.kt
@@ -262,7 +262,7 @@
Box(
Modifier.layoutId(IndicatorRippleLayoutIdTag)
.clip(indicatorShape)
- .indication(offsetInteractionSource, rippleOrFallbackImplementation())
+ .indication(offsetInteractionSource, ripple())
)
}
val indicator =
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/RadioButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/RadioButton.kt
index 350de4b..623866a 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/RadioButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/RadioButton.kt
@@ -94,11 +94,7 @@
enabled = enabled,
role = Role.RadioButton,
interactionSource = interactionSource,
- indication =
- rippleOrFallbackImplementation(
- bounded = false,
- radius = RadioButtonTokens.StateLayerSize / 2
- )
+ indication = ripple(bounded = false, radius = RadioButtonTokens.StateLayerSize / 2)
)
} else {
Modifier
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Ripple.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Ripple.kt
index 61a9b2f..5c2d85b 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Ripple.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Ripple.kt
@@ -24,12 +24,10 @@
import androidx.compose.material.ripple.RippleAlpha
import androidx.compose.material.ripple.createRippleModifierNode
import androidx.compose.material3.tokens.StateTokens
-import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf
-import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorProducer
import androidx.compose.ui.graphics.isSpecified
@@ -143,25 +141,6 @@
}
/**
- * Temporary CompositionLocal to allow configuring whether the old ripple implementation that uses
- * the deprecated [androidx.compose.material.ripple.RippleTheme] API should be used in Material
- * components and LocalIndication, instead of the new [ripple] API. This flag defaults to false, and
- * will be removed after one stable release: it should only be used to temporarily unblock
- * upgrading.
- *
- * Provide this CompositionLocal before you provide [MaterialTheme] to make sure it is correctly
- * provided through LocalIndication.
- */
-// TODO: b/304985887 - remove after one stable release
-@Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
-@get:ExperimentalMaterial3Api
-@ExperimentalMaterial3Api
-val LocalUseFallbackRippleImplementation: ProvidableCompositionLocal<Boolean> =
- staticCompositionLocalOf {
- false
- }
-
-/**
* CompositionLocal used for providing [RippleConfiguration] down the tree. This acts as a
* tree-local 'override' for ripples used inside components that you cannot directly control, such
* as to change the color of a specific component's ripple, or disable it entirely by providing
@@ -173,9 +152,6 @@
* own custom ripple that queries your design system theme values directly using
* [createRippleModifierNode].
*/
-@Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
-@get:ExperimentalMaterial3Api
-@ExperimentalMaterial3Api
val LocalRippleConfiguration: ProvidableCompositionLocal<RippleConfiguration?> =
compositionLocalOf {
RippleConfiguration()
@@ -194,7 +170,6 @@
* will be used instead.
*/
@Immutable
-@ExperimentalMaterial3Api
class RippleConfiguration(
val color: Color = Color.Unspecified,
val rippleAlpha: RippleAlpha? = null
@@ -220,35 +195,6 @@
}
}
-// TODO: b/304985887 - remove after one stable release
-@Suppress("DEPRECATION_ERROR")
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-internal fun rippleOrFallbackImplementation(
- bounded: Boolean = true,
- radius: Dp = Dp.Unspecified,
- color: Color = Color.Unspecified
-): Indication {
- return if (LocalUseFallbackRippleImplementation.current) {
- androidx.compose.material.ripple.rememberRipple(bounded, radius, color)
- } else {
- ripple(bounded, radius, color)
- }
-}
-
-// TODO: b/304985887 - remove after one stable release
-@Suppress("DEPRECATION_ERROR")
-@Immutable
-internal object CompatRippleTheme : androidx.compose.material.ripple.RippleTheme {
- @Deprecated("Super method is deprecated")
- @Composable
- override fun defaultColor() = LocalContentColor.current
-
- @Deprecated("Super method is deprecated")
- @Composable
- override fun rippleAlpha() = RippleDefaults.RippleAlpha
-}
-
@Stable
private class RippleNodeFactory
private constructor(
@@ -289,7 +235,6 @@
}
}
-@OptIn(ExperimentalMaterial3Api::class)
private class DelegatingThemeAwareRippleNode(
private val interactionSource: InteractionSource,
private val bounded: Boolean,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt
index f204d33..e6c27fd 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt
@@ -41,7 +41,7 @@
import androidx.compose.ui.util.fastMaxBy
/**
- * <a href="https://material.io/design/layout/understanding-layout.html" class="external"
+ * <a href="https://m3.material.io/foundations/layout/understanding-layout/" class="external"
* target="_blank">Material Design layout</a>.
*
* Scaffold implements the basic material design visual layout structure.
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Surface.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Surface.kt
index ecb9e9fd..11f894c0 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Surface.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Surface.kt
@@ -25,6 +25,7 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.selection.toggleable
+import androidx.compose.material3.internal.childSemantics
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.NonRestartableComposable
@@ -218,10 +219,11 @@
)
.clickable(
interactionSource = interactionSource,
- indication = rippleOrFallbackImplementation(),
+ indication = ripple(),
enabled = enabled,
onClick = onClick
- ),
+ )
+ .childSemantics(),
propagateMinConstraints = true
) {
content()
@@ -321,10 +323,11 @@
.selectable(
selected = selected,
interactionSource = interactionSource,
- indication = rippleOrFallbackImplementation(),
+ indication = ripple(),
enabled = enabled,
onClick = onClick
- ),
+ )
+ .childSemantics(),
propagateMinConstraints = true
) {
content()
@@ -424,10 +427,11 @@
.toggleable(
value = checked,
interactionSource = interactionSource,
- indication = rippleOrFallbackImplementation(),
+ indication = ripple(),
enabled = enabled,
onValueChange = onCheckedChange
- ),
+ )
+ .childSemantics(),
propagateMinConstraints = true
) {
content()
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Switch.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Switch.kt
index 756fc5d..2bcce38 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Switch.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Switch.kt
@@ -158,10 +158,7 @@
.indication(
interactionSource = interactionSource,
indication =
- rippleOrFallbackImplementation(
- bounded = false,
- radius = SwitchTokens.StateLayerSize / 2
- )
+ ripple(bounded = false, radius = SwitchTokens.StateLayerSize / 2)
)
.background(resolvedThumbColor, thumbShape),
contentAlignment = Alignment.Center
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tab.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tab.kt
index fec18d0..c5e3fa1 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tab.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tab.kt
@@ -169,7 +169,7 @@
// The color of the Ripple should always the be selected color, as we want to show the color
// before the item is considered selected, and hence before the new contentColor is
// provided by TabTransition.
- val ripple = rippleOrFallbackImplementation(bounded = true, color = selectedContentColor)
+ val ripple = ripple(bounded = true, color = selectedContentColor)
TabTransition(selectedContentColor, unselectedContentColor, selected) {
Row(
@@ -243,7 +243,7 @@
// The color of the Ripple should always the selected color, as we want to show the color
// before the item is considered selected, and hence before the new contentColor is
// provided by TabTransition.
- val ripple = rippleOrFallbackImplementation(bounded = true, color = selectedContentColor)
+ val ripple = ripple(bounded = true, color = selectedContentColor)
TabTransition(selectedContentColor, unselectedContentColor, selected) {
Column(
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/ChildParentSemantics.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/ChildParentSemantics.kt
new file mode 100644
index 0000000..dce3887
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/ChildParentSemantics.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3.internal
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.SemanticsModifierNode
+import androidx.compose.ui.node.TraversableNode
+import androidx.compose.ui.node.invalidateSemantics
+import androidx.compose.ui.node.traverseAncestors
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.semantics.SemanticsPropertyReceiver
+
+internal fun Modifier.childSemantics(properties: SemanticsPropertyReceiver.() -> Unit = {}) =
+ this then ChildSemanticsNodeElement(properties)
+
+internal fun Modifier.parentSemantics(properties: SemanticsPropertyReceiver.() -> Unit) =
+ this then ParentSemanticsNodeElement(properties)
+
+internal data class ChildSemanticsNodeElement(
+ val properties: SemanticsPropertyReceiver.() -> Unit
+) : ModifierNodeElement<ChildSemanticsNode>() {
+ override fun create(): ChildSemanticsNode = ChildSemanticsNode(properties)
+
+ override fun update(node: ChildSemanticsNode) {
+ node.properties = properties
+ node.invalidateSemantics()
+ }
+
+ override fun InspectorInfo.inspectableProperties() {
+ name = "childSemantics"
+ properties["properties"] = properties
+ }
+}
+
+internal data class ParentSemanticsNodeElement(
+ val properties: SemanticsPropertyReceiver.() -> Unit
+) : ModifierNodeElement<ParentSemanticsNode>() {
+ override fun create(): ParentSemanticsNode = ParentSemanticsNode(properties)
+
+ override fun update(node: ParentSemanticsNode) {
+ node.properties = properties
+ node.invalidateSemantics()
+ }
+
+ override fun InspectorInfo.inspectableProperties() {
+ name = "parentSemantics"
+ [email protected]["properties"] =
+ [email protected]
+ }
+}
+
+internal class ChildSemanticsNode(var properties: SemanticsPropertyReceiver.() -> Unit) :
+ Modifier.Node(), SemanticsModifierNode {
+
+ override fun SemanticsPropertyReceiver.applySemantics() {
+ traverseAncestors(ParentSemanticsNodeKey) { node ->
+ with(node as ParentSemanticsNode) {
+ obtainSemantics()
+ false
+ }
+ }
+ properties()
+ }
+
+ override fun onDetach() {
+ super.onDetach()
+ traverseAncestors(ParentSemanticsNodeKey) { node ->
+ (node as ParentSemanticsNode)
+ node.releaseSemantics()
+ false
+ }
+ }
+}
+
+internal class ParentSemanticsNode(var properties: SemanticsPropertyReceiver.() -> Unit) :
+ Modifier.Node(), TraversableNode, SemanticsModifierNode {
+
+ private var semanticsConsumed: Boolean = false
+
+ override val shouldMergeDescendantSemantics: Boolean
+ get() = true
+
+ override val traverseKey: Any = ParentSemanticsNodeKey
+
+ override fun SemanticsPropertyReceiver.applySemantics() {
+ if (!semanticsConsumed) {
+ properties()
+ }
+ }
+
+ fun SemanticsPropertyReceiver.obtainSemantics() {
+ semanticsConsumed = true
+ properties()
+ invalidateSemantics()
+ }
+
+ fun releaseSemantics() {
+ semanticsConsumed = false
+ invalidateSemantics()
+ }
+}
+
+internal object ParentSemanticsNodeKey
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
index 8c69b20..7b42b55 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
@@ -367,7 +367,8 @@
rotationY,
rotationZ,
scaleX,
- scaleY
+ scaleY,
+ 1.0f
)
}
}
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index 2ce59c4..acd2153 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -189,6 +189,7 @@
kmpDocs(project(":graphics:graphics-shapes"))
docs(project(":gridlayout:gridlayout"))
docs(project(":health:connect:connect-client"))
+ docs(project(":health:connect:connect-testing"))
docs(project(":health:health-services-client"))
docs(project(":heifwriter:heifwriter"))
docs(project(":hilt:hilt-common"))
diff --git a/health/connect/OWNERS b/health/connect/OWNERS
new file mode 100644
index 0000000..ee53f1c
--- /dev/null
+++ b/health/connect/OWNERS
@@ -0,0 +1,7 @@
+# Bug component: 1126127
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
diff --git a/health/connect/connect-testing/api/current.txt b/health/connect/connect-testing/api/current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/health/connect/connect-testing/api/current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/health/connect/connect-testing/api/res-current.txt b/health/connect/connect-testing/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/health/connect/connect-testing/api/res-current.txt
diff --git a/health/connect/connect-testing/api/restricted_current.txt b/health/connect/connect-testing/api/restricted_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/health/connect/connect-testing/api/restricted_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/health/connect/connect-testing/build.gradle b/health/connect/connect-testing/build.gradle
new file mode 100644
index 0000000..3d2bacd
--- /dev/null
+++ b/health/connect/connect-testing/build.gradle
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This file was created using the `create_project.py` script located in the
+ * `<AndroidX root>/development/project-creator` directory.
+ *
+ * Please use that script when creating a new project, rather than copying an existing project and
+ * modifying its settings.
+ */
+import androidx.build.LibraryType
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+ api(libs.kotlinStdlib)
+ // Add dependencies here
+}
+
+android {
+ namespace "androidx.health.connect.testing"
+}
+
+androidx {
+ name = "Health Connect Testing"
+ mavenVersion = LibraryVersions.HEALTH_CONNECT_TESTING_QUARANTINE
+ type = LibraryType.PUBLISHED_TEST_LIBRARY
+ inceptionYear = "2024"
+ description = "Test HealthConnect by providing a fake HealthConnectClient. This library should be added as a test dependency when writting unit tests that call HealthConnect APIs."
+}
diff --git a/health/connect/connect-testing/src/main/java/androidx/health/connect/androidx-health-connect-connect-testing-documentation.md b/health/connect/connect-testing/src/main/java/androidx/health/connect/androidx-health-connect-connect-testing-documentation.md
new file mode 100644
index 0000000..ab99573
--- /dev/null
+++ b/health/connect/connect-testing/src/main/java/androidx/health/connect/androidx-health-connect-connect-testing-documentation.md
@@ -0,0 +1,6 @@
+# Module root
+
+HealthConnect testing client
+
+# Package androidx.health.connect.testing
+
diff --git a/libraryversions.toml b/libraryversions.toml
index caa048c..ed73f4b 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -72,6 +72,7 @@
GRAPHICS_SHAPES = "1.0.0-rc01"
GRIDLAYOUT = "1.1.0-beta02"
HEALTH_CONNECT = "1.1.0-alpha08"
+HEALTH_CONNECT_TESTING_QUARANTINE = "1.0.0-alpha01"
HEALTH_SERVICES_CLIENT = "1.1.0-alpha03"
HEIFWRITER = "1.1.0-alpha03"
HILT = "1.2.0-rc01"
diff --git a/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt b/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt
index a5d5286..319d7c5 100644
--- a/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt
@@ -85,6 +85,8 @@
RestrictToDetector.RESTRICTED,
ObsoleteCompatDetector.ISSUE,
ReplaceWithDetector.ISSUE,
+ // This issue is only enabled when `-Pandroidx.migrateArrayAnnotations=true`.
+ ArrayNullnessMigration.ISSUE,
)
}
}
diff --git a/settings.gradle b/settings.gradle
index 2c98097..b4c34df 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -712,6 +712,7 @@
includeProject(":health:connect:connect-client-proto", [BuildType.MAIN])
includeProject(":health:connect:connect-client-external-protobuf", [BuildType.MAIN])
includeProject(":health:connect:connect-client-samples", "health/connect/connect-client/samples", [BuildType.MAIN])
+includeProject(":health:connect:connect-testing", [BuildType.MAIN])
includeProject(":health:health-services-client", [BuildType.MAIN])
includeProject(":heifwriter:heifwriter", [BuildType.MAIN])
includeProject(":hilt:hilt-common", [BuildType.MAIN])
diff --git a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicBuilders.java b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicBuilders.java
index 500985d..402c6b2 100644
--- a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicBuilders.java
+++ b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicBuilders.java
@@ -1690,7 +1690,7 @@
}
/**
- * Creates a {@link DynamicFlaot} containing the result of adding a {@link DynamicFloat} to
+ * Creates a {@link DynamicFloat} containing the result of adding a {@link DynamicFloat} to
* this {@link DynamicInt32}; As an example, the following is equal to {@code
* DynamicFloat.constant(13.5f)}
*
@@ -1741,7 +1741,7 @@
}
/**
- * Creates a {@link DynamicFlaot} containing the result of adding a float to this {@link
+ * Creates a {@link DynamicFloat} containing the result of adding a float to this {@link
* DynamicInt32}; As an example, the following is equal to {@code
* DynamicFloat.constant(13.5f)}
*
@@ -4363,7 +4363,7 @@
/**
* Creates a {@link DynamicFloat} containing the result of adding another {@link
* DynamicFloat} to this {@link DynamicFloat}; As an example, the following is equal to
- * {@code DynamicFloat.constant(13f)}
+ * {@code DynamicFloat.constant(12f)}
*
* <pre>
* DynamicFloat.constant(7f).plus(DynamicFloat.constant(5f));
@@ -4389,7 +4389,7 @@
/**
* Creates a {@link DynamicFloat} containing the result of adding a float to this {@link
* DynamicFloat}; As an example, the following is equal to {@code
- * DynamicFloat.constant(13f)}
+ * DynamicFloat.constant(12f)}
*
* <pre>
* DynamicFloat.constant(7f).plus(5f);
@@ -4415,7 +4415,7 @@
/**
* Creates a {@link DynamicFloat} containing the result of adding a {@link DynamicInt32} to
* this {@link DynamicFloat}; As an example, the following is equal to {@code
- * DynamicFloat.constant(13f)}
+ * DynamicFloat.constant(12f)}
*
* <pre>
* DynamicFloat.constant(7f).plus(DynamicInt32.constant(5));
@@ -4465,7 +4465,7 @@
}
/**
- * Creates a {@link DynamicFloat} containing the result of subtracting a flaot from this
+ * Creates a {@link DynamicFloat} containing the result of subtracting a float from this
* {@link DynamicFloat}; As an example, the following is equal to {@code
* DynamicFloat.constant(2f)}
*
@@ -4544,7 +4544,7 @@
/**
* Creates a {@link DynamicFloat} containing the result of multiplying this {@link
- * DynamicFloat} by a flaot; As an example, the following is equal to {@code
+ * DynamicFloat} by a float; As an example, the following is equal to {@code
* DynamicFloat.constant(35f)}
*
* <pre>
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
index b691e7ac..10e4788 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
@@ -39,6 +39,7 @@
import static org.hamcrest.Matchers.isOneOf;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MINUTES;
@@ -142,6 +143,8 @@
public void setUp() {
mContext = ApplicationProvider.getApplicationContext();
mTracer = mock(Tracer.class);
+ // Turn on tracing so we can ensure trace sections are correctly emitted.
+ when(mTracer.isEnabled()).thenReturn(true);
mWorkerExceptionHandler = new TestWorkerExceptionHandler();
mConfiguration = new Configuration.Builder()
.setExecutor(new SynchronousExecutor())
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
index 3e9e644..11dbfcd 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
@@ -54,6 +54,7 @@
import androidx.work.DatabaseTest;
import androidx.work.Logger;
import androidx.work.OneTimeWorkRequest;
+import androidx.work.Tracer;
import androidx.work.WorkInfo;
import androidx.work.impl.Processor;
import androidx.work.impl.Scheduler;
@@ -131,6 +132,8 @@
public void setUp() {
mContext = ApplicationProvider.getApplicationContext().getApplicationContext();
Scheduler scheduler = mock(Scheduler.class);
+ Tracer tracer = mock(Tracer.class);
+ when(tracer.isEnabled()).thenReturn(true);
mWorkManager = mock(WorkManagerImpl.class);
mLatch = new CountDownLatch(1);
SystemAlarmDispatcher.CommandsCompletedListener completedListener =
@@ -142,7 +145,6 @@
};
TaskExecutor instantTaskExecutor = new TaskExecutor() {
-
@Override
@NonNull
public Executor getMainThreadExecutor() {
@@ -182,6 +184,7 @@
Logger.setLogger(new Logger.LogcatLogger(Log.DEBUG));
Configuration configuration = new Configuration.Builder()
.setExecutor(new SynchronousExecutor())
+ .setTracer(tracer)
.build();
when(mWorkManager.getWorkDatabase()).thenReturn(mDatabase);
when(mWorkManager.getConfiguration()).thenReturn(configuration);
diff --git a/work/work-runtime/src/main/java/androidx/work/Configuration.kt b/work/work-runtime/src/main/java/androidx/work/Configuration.kt
index e81d20b..f15027d 100644
--- a/work/work-runtime/src/main/java/androidx/work/Configuration.kt
+++ b/work/work-runtime/src/main/java/androidx/work/Configuration.kt
@@ -600,6 +600,18 @@
// implementation.
val tracer =
object : Tracer {
+ override fun isEnabled(): Boolean {
+ return androidx.tracing.Trace.isEnabled()
+ }
+
+ override fun beginSection(label: String) {
+ androidx.tracing.Trace.beginSection(label)
+ }
+
+ override fun endSection() {
+ androidx.tracing.Trace.endSection()
+ }
+
override fun beginAsyncSection(methodName: String, cookie: Int) {
androidx.tracing.Trace.beginAsyncSection(methodName, cookie)
}
diff --git a/work/work-runtime/src/main/java/androidx/work/Operation.kt b/work/work-runtime/src/main/java/androidx/work/Operation.kt
index 48847f3..9a0f1c7 100644
--- a/work/work-runtime/src/main/java/androidx/work/Operation.kt
+++ b/work/work-runtime/src/main/java/androidx/work/Operation.kt
@@ -35,18 +35,25 @@
*/
public suspend inline fun Operation.await(): Operation.State.SUCCESS = result.await()
-internal fun launchOperation(executor: Executor, block: () -> Unit): Operation {
+internal fun launchOperation(
+ tracer: Tracer,
+ label: String,
+ executor: Executor,
+ block: () -> Unit
+): Operation {
val liveData = MutableLiveData<Operation.State>(Operation.IN_PROGRESS)
val future =
CallbackToFutureAdapter.getFuture { completer ->
executor.execute {
- try {
- block()
- liveData.postValue(Operation.SUCCESS)
- completer.set(Operation.SUCCESS)
- } catch (t: Throwable) {
- liveData.postValue(Operation.State.FAILURE(t))
- completer.setException(t)
+ tracer.traced(label) {
+ try {
+ block()
+ liveData.postValue(Operation.SUCCESS)
+ completer.set(Operation.SUCCESS)
+ } catch (t: Throwable) {
+ liveData.postValue(Operation.State.FAILURE(t))
+ completer.setException(t)
+ }
}
}
}
diff --git a/work/work-runtime/src/main/java/androidx/work/Tracer.kt b/work/work-runtime/src/main/java/androidx/work/Tracer.kt
index beaa321..a2eff3bb 100644
--- a/work/work-runtime/src/main/java/androidx/work/Tracer.kt
+++ b/work/work-runtime/src/main/java/androidx/work/Tracer.kt
@@ -21,6 +21,17 @@
/** Sets up trace spans when a {@link WorkRequest} is setup for execution by [WorkManager]. */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
interface Tracer {
+ /** Checks whether or not tracing is currently enabled. */
+ fun isEnabled(): Boolean
+
+ /**
+ * Writes a trace message, with the provided [label] to indicate that a given section of code
+ * has begun.
+ */
+ fun beginSection(label: String)
+
+ /** Writes a trace message to indicate that a given section of code has ended. */
+ fun endSection()
/**
* Writes a trace span to indicate that a given section of code has begun.
@@ -36,3 +47,18 @@
*/
fun endAsyncSection(methodName: String, cookie: Int)
}
+
+/** A helper that can insert trace sections around a [block] of code. */
+internal inline fun <T> Tracer.traced(label: String, block: () -> T): T {
+ val enabled = isEnabled()
+ try {
+ if (enabled) {
+ beginSection(label)
+ }
+ return block()
+ } finally {
+ if (enabled) {
+ endSection()
+ }
+ }
+}
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkContinuationImpl.java b/work/work-runtime/src/main/java/androidx/work/impl/WorkContinuationImpl.java
index d1a5b24..5a93ee6 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/WorkContinuationImpl.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkContinuationImpl.java
@@ -194,6 +194,8 @@
// The runnable walks the hierarchy of the continuations
// and marks them enqueued using the markEnqueued() method, parent first.
mOperation = launchOperation(
+ mWorkManagerImpl.getConfiguration().getTracer(),
+ "EnqueueRunnable_" + getExistingWorkPolicy().name(),
mWorkManagerImpl.getWorkTaskExecutor().getSerialTaskExecutor(),
() -> {
EnqueueRunnable.enqueue(this);
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java b/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
index a8267c4..14201d2 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
@@ -51,6 +51,7 @@
import androidx.work.OneTimeWorkRequest;
import androidx.work.Operation;
import androidx.work.PeriodicWorkRequest;
+import androidx.work.TracerKt;
import androidx.work.WorkContinuation;
import androidx.work.WorkInfo;
import androidx.work.WorkManager;
@@ -76,16 +77,17 @@
import com.google.common.util.concurrent.ListenableFuture;
-import java.util.Collections;
-import java.util.List;
-import java.util.UUID;
+import kotlin.Unit;
import kotlinx.coroutines.CoroutineScope;
import kotlinx.coroutines.flow.Flow;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
/**
* A concrete implementation of {@link WorkManager}.
- *
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class WorkManagerImpl extends WorkManager {
@@ -147,6 +149,7 @@
}
/**
+ *
*/
@SuppressWarnings("deprecation")
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@@ -189,11 +192,10 @@
* you want to use a custom {@link Configuration} object and have disabled
* WorkManagerInitializer.
*
- * @param context A {@link Context} object for configuration purposes. Internally, this class
- * will call {@link Context#getApplicationContext()}, so you may safely pass in
- * any Context without risking a memory leak.
+ * @param context A {@link Context} object for configuration purposes. Internally, this
+ * class will call {@link Context#getApplicationContext()}, so you may
+ * safely pass in any Context without risking a memory leak.
* @param configuration The {@link Configuration} for used to set up WorkManager.
- *
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
@@ -463,7 +465,7 @@
@Override
public @NonNull Operation pruneWork() {
- return PruneWorkRunnableKt.pruneWork(mWorkDatabase, mWorkTaskExecutor);
+ return PruneWorkRunnableKt.pruneWork(mWorkDatabase, mConfiguration, mWorkTaskExecutor);
}
@Override
@@ -545,7 +547,7 @@
@NonNull
public ListenableFuture<List<WorkInfo>> getWorkInfosForUniqueWork(
@NonNull String uniqueWorkName) {
- return StatusRunnable.forUniqueWork(mWorkDatabase, mWorkTaskExecutor, uniqueWorkName);
+ return StatusRunnable.forUniqueWork(mWorkDatabase, mWorkTaskExecutor, uniqueWorkName);
}
@NonNull
@@ -593,6 +595,7 @@
}
/**
+ *
*/
@Nullable
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@@ -616,7 +619,7 @@
/**
* @param id The {@link WorkSpec} id to stop when running in the context of a
- * foreground service.
+ * foreground service.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public void stopForegroundWork(@NonNull WorkGenerationalId id) {
@@ -627,26 +630,31 @@
/**
* Reschedules all the eligible work. Useful for cases like, app was force stopped or
* BOOT_COMPLETED, TIMEZONE_CHANGED and TIME_SET for AlarmManager.
- *
*/
public void rescheduleEligibleWork() {
- // TODO (rahulrav@) Make every scheduler do its own cancelAll().
- if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
- SystemJobScheduler.cancelAllInAllNamespaces(getApplicationContext());
- }
+ // Delegate to the getter so mocks continue to work when testing.
+ Configuration configuration = getConfiguration();
+ TracerKt.traced(configuration.getTracer(), "ReschedulingWork", () -> {
+ // This gives us an easy way to clear persisted work state, and then reschedule work
+ // that WorkManager is aware of. Ideally, we do something similar for other
+ // persistent schedulers.
+ if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
+ SystemJobScheduler.cancelAllInAllNamespaces(getApplicationContext());
+ }
- // Reset scheduled state.
- getWorkDatabase().workSpecDao().resetScheduledState();
+ // Reset scheduled state.
+ getWorkDatabase().workSpecDao().resetScheduledState();
- // Delegate to the WorkManager's schedulers.
- // Using getters here so we can use from a mocked instance
- // of WorkManagerImpl.
- Schedulers.schedule(getConfiguration(), getWorkDatabase(), getSchedulers());
+ // Delegate to the WorkManager's schedulers.
+ // Using getters here so we can use from a mocked instance
+ // of WorkManagerImpl.
+ Schedulers.schedule(getConfiguration(), getWorkDatabase(), getSchedulers());
+ return Unit.INSTANCE;
+ });
}
/**
* A way for {@link ForceStopRunnable} to tell {@link WorkManagerImpl} that it has completed.
- *
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public void onForceStopRunnableCompleted() {
@@ -664,7 +672,6 @@
* {@link RescheduleReceiver}
* after a call to {@link BroadcastReceiver#goAsync()}. Once {@link ForceStopRunnable} is done,
* we can safely call {@link BroadcastReceiver.PendingResult#finish()}.
- *
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public void setReschedulePendingResult(
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkerUpdater.kt b/work/work-runtime/src/main/java/androidx/work/impl/WorkerUpdater.kt
index b8ad3cc..9894ad2 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/WorkerUpdater.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkerUpdater.kt
@@ -114,7 +114,11 @@
name: String,
workRequest: WorkRequest,
): Operation =
- launchOperation(workTaskExecutor.serialTaskExecutor) {
+ launchOperation(
+ configuration.tracer,
+ "enqueueUniquePeriodic_$name",
+ workTaskExecutor.serialTaskExecutor
+ ) {
val enqueueNew = {
val requests = listOf(workRequest)
val continuation = WorkContinuationImpl(this, name, ExistingWorkPolicy.KEEP, requests)
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.kt b/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.kt
index 4b228ef..f21c8f1 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.kt
@@ -127,8 +127,9 @@
}
private suspend fun runWorker(): Resolution {
+ val isTracingEnabled = configuration.tracer.isEnabled()
val traceTag = workSpec.traceTag
- if (traceTag != null) {
+ if (isTracingEnabled && traceTag != null) {
configuration.tracer.beginAsyncSection(
traceTag,
// Use hashCode() instead of a generational id given we want to allow concurrent
@@ -274,7 +275,7 @@
if (it is WorkerStoppedException) {
worker.stop(it.reason)
}
- if (traceTag != null) {
+ if (isTracingEnabled && traceTag != null) {
configuration.tracer.endAsyncSection(traceTag, workSpec.hashCode())
}
}
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.kt b/work/work-runtime/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.kt
index 5a209c3..4854c3d 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.kt
@@ -66,7 +66,11 @@
* @return A [Operation]
*/
fun forId(id: UUID, workManagerImpl: WorkManagerImpl): Operation =
- launchOperation(workManagerImpl.workTaskExecutor.serialTaskExecutor) {
+ launchOperation(
+ tracer = workManagerImpl.configuration.tracer,
+ label = "CancelWorkById",
+ workManagerImpl.workTaskExecutor.serialTaskExecutor
+ ) {
val workDatabase = workManagerImpl.workDatabase
workDatabase.runInTransaction { cancel(workManagerImpl, id.toString()) }
reschedulePendingWorkers(workManagerImpl)
@@ -80,7 +84,11 @@
* @return A [Operation]
*/
fun forTag(tag: String, workManagerImpl: WorkManagerImpl): Operation =
- launchOperation(workManagerImpl.workTaskExecutor.serialTaskExecutor) {
+ launchOperation(
+ tracer = workManagerImpl.configuration.tracer,
+ label = "CancelWorkByTag_$tag",
+ executor = workManagerImpl.workTaskExecutor.serialTaskExecutor
+ ) {
val workDatabase = workManagerImpl.workDatabase
workDatabase.runInTransaction {
val workSpecDao = workDatabase.workSpecDao()
@@ -100,7 +108,11 @@
* @return A [Operation]
*/
fun forName(name: String, workManagerImpl: WorkManagerImpl): Operation =
- launchOperation(workManagerImpl.workTaskExecutor.serialTaskExecutor) {
+ launchOperation(
+ tracer = workManagerImpl.configuration.tracer,
+ label = "CancelWorkByName_$name",
+ workManagerImpl.workTaskExecutor.serialTaskExecutor
+ ) {
forNameInline(name, workManagerImpl)
reschedulePendingWorkers(workManagerImpl)
}
@@ -123,7 +135,11 @@
* @return A [Operation] that cancels all work
*/
fun forAll(workManagerImpl: WorkManagerImpl): Operation =
- launchOperation(workManagerImpl.workTaskExecutor.serialTaskExecutor) {
+ launchOperation(
+ tracer = workManagerImpl.configuration.tracer,
+ label = "CancelAllWork",
+ workManagerImpl.workTaskExecutor.serialTaskExecutor
+ ) {
val workDatabase = workManagerImpl.workDatabase
workDatabase.runInTransaction {
val workSpecDao = workDatabase.workSpecDao()
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/PruneWorkRunnable.kt b/work/work-runtime/src/main/java/androidx/work/impl/utils/PruneWorkRunnable.kt
index 03800b1..711296a 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/PruneWorkRunnable.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/PruneWorkRunnable.kt
@@ -15,6 +15,7 @@
*/
package androidx.work.impl.utils
+import androidx.work.Configuration
import androidx.work.Operation
import androidx.work.impl.WorkDatabase
import androidx.work.impl.utils.taskexecutor.TaskExecutor
@@ -25,7 +26,14 @@
* - Is finished (succeeded, failed, or cancelled)
* - Has zero unfinished dependents
*/
-internal fun WorkDatabase.pruneWork(executor: TaskExecutor): Operation =
- launchOperation(executor.serialTaskExecutor) {
+internal fun WorkDatabase.pruneWork(
+ configuration: Configuration,
+ executor: TaskExecutor
+): Operation =
+ launchOperation(
+ tracer = configuration.tracer,
+ label = "PruneWork",
+ executor = executor.serialTaskExecutor
+ ) {
workSpecDao().pruneFinishedWorkWithZeroDependentsIgnoringKeepForAtLeast()
}