Merge "Ensure ActivityResult lint works with newer lint versions" into androidx-master-dev
diff --git a/activity/activity-lint/src/main/java/androidx/activity/lint/ActivityResultFragmentVersionDetector.kt b/activity/activity-lint/src/main/java/androidx/activity/lint/ActivityResultFragmentVersionDetector.kt
index 003ed3c..582084e 100644
--- a/activity/activity-lint/src/main/java/androidx/activity/lint/ActivityResultFragmentVersionDetector.kt
+++ b/activity/activity-lint/src/main/java/androidx/activity/lint/ActivityResultFragmentVersionDetector.kt
@@ -27,11 +27,15 @@
 import com.android.tools.lint.detector.api.Issue
 import com.android.tools.lint.detector.api.JavaContext
 import com.android.tools.lint.detector.api.Location
+import com.android.tools.lint.detector.api.Project
 import com.android.tools.lint.detector.api.Scope
 import com.android.tools.lint.detector.api.Severity
 import org.jetbrains.uast.UCallExpression
 import org.jetbrains.uast.UElement
 import java.util.EnumSet
+import kotlin.reflect.full.memberFunctions
+import kotlin.reflect.full.memberProperties
+import kotlin.reflect.jvm.isAccessible
 
 class ActivityResultFragmentVersionDetector : Detector(), UastScanner, GradleScanner {
     companion object {
@@ -39,7 +43,7 @@
 
         val ISSUE = Issue.create(
             id = "InvalidFragmentVersionForActivityResult",
-            briefDescription = "Update to $FRAGMENT_VERSION to use ActivityResult APIs",
+            briefDescription = "Update to Fragment $FRAGMENT_VERSION to use ActivityResult APIs",
             explanation = """In order to use the ActivityResult APIs you must upgrade your \
                 Fragment version to $FRAGMENT_VERSION. Previous versions of FragmentActivity \
                 failed to call super.onRequestPermissionsResult() and used invalid request codes""",
@@ -93,24 +97,79 @@
             // always check api dependencies
             reportIssue(value, context)
         } else if (!checkedImplementationDependencies) {
-            val explicitLibraries =
-                context.project.currentVariant.mainArtifact.dependencies.libraries
-
-            // collect all of the library dependencies
-            val allLibraries = HashSet<AndroidLibrary>()
-            addIndirectAndroidLibraries(explicitLibraries, allLibraries)
-            // check all of the dependencies
-            allLibraries.forEach {
-                val resolvedCoords = it.resolvedCoordinates
-                val groupId = resolvedCoords.groupId
-                val artifactId = resolvedCoords.artifactId
-                val version = resolvedCoords.version
-                reportIssue("$groupId:$artifactId:$version", context, false)
+            val project = context.project
+            if (useNewLintVersion(project)) {
+                checkWithNewLintVersion(project, context)
+            } else {
+                checkWithOldLintVersion(project, context)
             }
-            checkedImplementationDependencies = true
         }
     }
 
+    private fun useNewLintVersion(project: Project): Boolean {
+        project::class.memberFunctions.forEach { function ->
+            if (function.name == "getBuildVariant") {
+                return true
+            }
+        }
+        return false
+    }
+
+    private fun checkWithNewLintVersion(project: Project, context: GradleContext) {
+        val buildVariant = callFunctionWithReflection(project, "getBuildVariant")
+        val mainArtifact = getMemberWithReflection(buildVariant, "mainArtifact")
+        val dependencies = getMemberWithReflection(mainArtifact, "dependencies")
+        val all = callFunctionWithReflection(dependencies, "getAll")
+        (all as ArrayList<*>).forEach { lmLibrary ->
+            lmLibrary::class.memberProperties.forEach { libraryMembers ->
+                if (libraryMembers.name == "resolvedCoordinates") {
+                    reportIssue(libraryMembers.call(lmLibrary).toString(), context, false)
+                }
+            }
+        }
+    }
+
+    private fun checkWithOldLintVersion(project: Project, context: GradleContext) {
+        lateinit var explicitLibraries: Collection<AndroidLibrary>
+        val currentVariant = callFunctionWithReflection(project, "getCurrentVariant")
+        val mainArtifact = callFunctionWithReflection(currentVariant, "getMainArtifact")
+        val dependencies = callFunctionWithReflection(mainArtifact, "getDependencies")
+        @Suppress("UNCHECKED_CAST")
+        explicitLibraries =
+            callFunctionWithReflection(dependencies, "getLibraries") as Collection<AndroidLibrary>
+
+        // collect all of the library dependencies
+        val allLibraries = HashSet<AndroidLibrary>()
+        addIndirectAndroidLibraries(explicitLibraries, allLibraries)
+        // check all of the dependencies
+        allLibraries.forEach {
+            val resolvedCoords = it.resolvedCoordinates
+            val groupId = resolvedCoords.groupId
+            val artifactId = resolvedCoords.artifactId
+            val version = resolvedCoords.version
+            reportIssue("$groupId:$artifactId:$version", context, false)
+        }
+    }
+
+    private fun callFunctionWithReflection(caller: Any, functionName: String): Any {
+        caller::class.memberFunctions.forEach { function ->
+            if (function.name == functionName) {
+                function.isAccessible = true
+                return function.call(caller)!!
+            }
+        }
+        return Unit
+    }
+
+    private fun getMemberWithReflection(caller: Any, memberName: String): Any {
+        caller::class.memberProperties.forEach { member ->
+            if (member.name == memberName) {
+                return member.getter.call(caller)!!
+            }
+        }
+        return Unit
+    }
+
     private fun addIndirectAndroidLibraries(
         libraries: Collection<AndroidLibrary>,
         result: MutableSet<AndroidLibrary>
diff --git a/activity/activity-lint/src/test/java/androidx/activity/lint/ActivityResultFragmentVersionDetectorTest.kt b/activity/activity-lint/src/test/java/androidx/activity/lint/ActivityResultFragmentVersionDetectorTest.kt
index 88eb0dc..5726acc 100644
--- a/activity/activity-lint/src/test/java/androidx/activity/lint/ActivityResultFragmentVersionDetectorTest.kt
+++ b/activity/activity-lint/src/test/java/androidx/activity/lint/ActivityResultFragmentVersionDetectorTest.kt
@@ -146,4 +146,36 @@
             """.trimIndent()
             )
     }
+
+    @Test
+    fun expectFailTransitiveDependency() {
+        val projectFragment = project(kotlin(
+                """
+                package com.example
+
+                import androidx.activity.result.ActivityResultCaller
+                import androidx.activity.result.contract.ActivityResultContract
+
+                val launcher = ActivityResultCaller().registerForActivityResult(ActivityResultContract())
+            """
+            ), gradle(
+                "build.gradle", """
+                dependencies {
+                    implementation("androidx.fragment:fragment-ktx:1.3.0-alpha05")
+                }
+            """).indented()).withDependencyGraph("""
+                +--- androidx.fragment:fragment-ktx:1.3.0-alpha05
+                     \--- androidx.fragment:fragment:1.3.0-alpha05
+            """.trimIndent()
+        )
+
+        lint().projects(projectFragment).run().expect(
+            """
+                src/main/kotlin/com/example/test.kt:7: Error: Upgrade Fragment version to at least $FRAGMENT_VERSION. [InvalidFragmentVersionForActivityResult]
+                                val launcher = ActivityResultCaller().registerForActivityResult(ActivityResultContract())
+                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                1 errors, 0 warnings
+            """.trimIndent()
+        )
+    }
 }