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(&quot;param&quot;);"
-        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()
     }