Lint rules for WorkManager

* This lint rule catches improper implementations of Configuration.Provider

Test: Added unit tests.
Change-Id: I2ae376d99080cdf187b8a1ec3bd1b14e139008ee
diff --git a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
index 123d247..c10b3b3 100644
--- a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
@@ -143,7 +143,7 @@
             .addStubs("wear/wear_stubs/com.google.android.wearable-stubs.jar")
     prebuilts(LibraryGroups.WEBKIT, "1.1.0")
     ignore(LibraryGroups.WORK.group, "work-gcm")
-    ignore(LibraryGroups.WORK.group, "work-foreground")
+    ignore(LibraryGroups.WORK.group, "work-runtime-lint")
     prebuilts(LibraryGroups.WORK, "2.3.0-alpha03")
     default(Ignore)
 }
diff --git a/settings.gradle b/settings.gradle
index b7a1a00..0a90373 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -265,6 +265,7 @@
 includeProject(":work:work-runtime-ktx", "work/workmanager-ktx")
 includeProject(":work:work-rxjava2", "work/workmanager-rxjava2")
 includeProject(":work:work-testing", "work/workmanager-testing")
+includeProject(":work:work-runtime-lint", "work/workmanager-lint")
 includeProject(":work:work-benchmark", "work/workmanager-benchmark")
 includeProject(":work:integration-tests:testapp", "work/integration-tests/testapp")
 
diff --git a/work/workmanager-lint/build.gradle b/work/workmanager-lint/build.gradle
new file mode 100644
index 0000000..7abb2b9
--- /dev/null
+++ b/work/workmanager-lint/build.gradle
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2019 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.
+ */
+
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.AndroidXExtension
+import androidx.build.CompilationTarget
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.SdkHelperKt
+import androidx.build.Publish
+
+plugins {
+    id("AndroidXPlugin")
+    id("kotlin")
+}
+
+ext.generatedResources = "$buildDir/generated/sdkResourcesForTest"
+
+sourceSets {
+    test.resources.srcDirs += generatedResources
+}
+
+task generateSdkResource() {
+    outputs.dir(generatedResources)
+    doLast {
+        new File(generatedResources, "sdk.prop").withWriter('UTF-8') { writer ->
+            writer.write("sdk.dir=${SdkHelperKt.getSdkPath(project.rootDir)}")
+        }
+    }
+}
+
+tasks["compileTestJava"].dependsOn generateSdkResource
+
+dependencies {
+    // compileOnly because we use lintChecks and it doesn't allow other types of deps
+    // this ugly hack exists because of b/63873667
+    if (rootProject.hasProperty("android.injected.invoked.from.ide")) {
+        compileOnly LINT_API_LATEST
+    } else {
+        compileOnly LINT_API_MIN
+    }
+    compileOnly KOTLIN_STDLIB
+
+    testImplementation KOTLIN_STDLIB
+    testImplementation LINT_CORE
+    testImplementation LINT_TESTS
+}
+
+androidx {
+    name = "Android WorkManager Runtime Lint Checks"
+    toolingProject = true
+    publish = Publish.NONE
+    mavenVersion = LibraryVersions.WORK
+    mavenGroup = LibraryGroups.WORK
+    inceptionYear = "2019"
+    description = "Android WorkManager Runtime Lint Checks"
+    url = AndroidXExtension.ARCHITECTURE_URL
+    compilationTarget = CompilationTarget.HOST
+}
diff --git a/work/workmanager-lint/src/main/java/androidx/work/lint/BadConfigurationProviderIssueDetector.kt b/work/workmanager-lint/src/main/java/androidx/work/lint/BadConfigurationProviderIssueDetector.kt
new file mode 100644
index 0000000..c8475ef
--- /dev/null
+++ b/work/workmanager-lint/src/main/java/androidx/work/lint/BadConfigurationProviderIssueDetector.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.work.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Context
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+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.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import org.jetbrains.uast.UClass
+
+class BadConfigurationProviderIssueDetector : Detector(), SourceCodeScanner {
+    companion object {
+        val ISSUE = Issue.create(
+            id = "BadConfigurationProvider",
+            briefDescription = "Invalid WorkManager Configuration Provider",
+            explanation = """
+                An `android.app.Application` must implement `androidx.work.Configuration.Provider`
+                for on-demand initialization.
+            """,
+            androidSpecific = true,
+            category = Category.CORRECTNESS,
+            severity = Severity.FATAL,
+            implementation = Implementation(
+                BadConfigurationProviderIssueDetector::class.java, Scope.JAVA_FILE_SCOPE
+            )
+        )
+    }
+
+    private var hasApplicableTypes = false
+    private var correct = false
+    private var location: Location? = null
+
+    override fun applicableSuperClasses() = listOf(
+        "android.app.Application",
+        "androidx.work.Configuration.Provider"
+    )
+
+    override fun visitClass(context: JavaContext, declaration: UClass) {
+        if (correct) {
+            // Bail out early
+            return
+        }
+
+        val name = declaration.qualifiedName
+        if (name == "androidx.work.Configuration.Provider" || name == "android.app.Application") {
+            // Exempt base types from analysis
+            return
+        }
+
+        val isApplication = context.evaluator.inheritsFrom(
+            declaration.javaPsi, "android.app.Application", true
+        )
+
+        val isProvider = context.evaluator.inheritsFrom(
+            declaration.javaPsi, "androidx.work.Configuration.Provider", true
+        )
+
+        if (isApplication) {
+            location = Location.create(context.file)
+        }
+
+        if (isProvider) {
+            hasApplicableTypes = true
+        }
+
+        if (isApplication && isProvider) {
+            correct = true
+        }
+    }
+
+    override fun afterCheckRootProject(context: Context) {
+        if (hasApplicableTypes && !correct) {
+            context.report(
+                issue = ISSUE,
+                location = location ?: Location.create(context.file),
+                message = "Expected Application subtype to implement Configuration.Provider"
+            )
+        }
+    }
+}
diff --git a/work/workmanager-lint/src/main/java/androidx/work/lint/WorkManagerIssueRegistry.kt b/work/workmanager-lint/src/main/java/androidx/work/lint/WorkManagerIssueRegistry.kt
new file mode 100644
index 0000000..8a42724
--- /dev/null
+++ b/work/workmanager-lint/src/main/java/androidx/work/lint/WorkManagerIssueRegistry.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.work.lint
+
+import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.detector.api.CURRENT_API
+import com.android.tools.lint.detector.api.Issue
+
+class WorkManagerIssueRegistry : IssueRegistry() {
+    override val minApi: Int = CURRENT_API
+    override val issues: List<Issue> = listOf(
+        BadConfigurationProviderIssueDetector.ISSUE
+    )
+}
diff --git a/work/workmanager-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry b/work/workmanager-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
new file mode 100644
index 0000000..ef57dd0
--- /dev/null
+++ b/work/workmanager-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
@@ -0,0 +1 @@
+androidx.work.lint.WorkManagerIssueRegistry
\ No newline at end of file
diff --git a/work/workmanager-lint/src/test/java/androidx/work/lint/BadConfigurationProviderTest.kt b/work/workmanager-lint/src/test/java/androidx/work/lint/BadConfigurationProviderTest.kt
new file mode 100644
index 0000000..2a1f43a
--- /dev/null
+++ b/work/workmanager-lint/src/test/java/androidx/work/lint/BadConfigurationProviderTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2019 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.work.lint
+
+import androidx.work.lint.Stubs.ANDROID_APPLICATION
+import androidx.work.lint.Stubs.WORK_MANAGER_CONFIGURATION_PROVIDER
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest.kotlin
+import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
+import org.junit.Test
+
+class BadConfigurationProviderTest {
+    @Test
+    fun testNoConfigurationProviderUsage() {
+        val customApplication = kotlin(
+            "com/example/App.kt",
+            """
+            package com.example
+
+            import android.app.Application
+
+            class App: Application() {
+                override fun onCreate() {
+
+                }
+            }
+            """
+        ).indented().within("src")
+
+        lint().files(
+            ANDROID_APPLICATION,
+            WORK_MANAGER_CONFIGURATION_PROVIDER,
+            customApplication
+        )
+            .issues(BadConfigurationProviderIssueDetector.ISSUE)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testWithInvalidConfigurationProvider() {
+        val customApplication = kotlin(
+            "com/example/App.kt",
+            """
+            package com.example
+
+            import android.app.Application
+
+            class App: Application() {
+                override fun onCreate() {
+
+                }
+            }
+            """
+        ).indented().within("src")
+
+        val invalidProvider = kotlin(
+            "com/example/CustomProvider.kt",
+            """
+            package com.example
+
+            import androidx.work.Configuration
+
+            class Provider: Configuration.Provider {
+                override fun getWorkManagerConfiguration(): Configuration = TODO()
+            }
+            """
+        ).indented().within("src")
+
+        /* ktlint-disable max-line-length */
+        lint().files(
+            ANDROID_APPLICATION,
+            WORK_MANAGER_CONFIGURATION_PROVIDER,
+            customApplication,
+            invalidProvider
+        )
+            .issues(BadConfigurationProviderIssueDetector.ISSUE)
+            .run()
+            .expect("""
+                src/com/example/App.kt: Error: Expected Application subtype to implement Configuration.Provider [BadConfigurationProvider]
+                1 errors, 0 warnings
+            """.trimIndent())
+        /* ktlint-enable max-line-length */
+    }
+
+    @Test
+    fun testWithValidConfigurationProvider() {
+        val customApplication = kotlin(
+            "com/example/App.kt",
+            """
+            package com.example
+
+            import android.app.Application
+            import androidx.work.Configuration
+
+            class App: Application(), Configuration.Provider {
+                override fun onCreate() {
+
+                }
+
+                override fun getWorkManagerConfiguration(): Configuration = TODO()
+            }
+            """
+        ).indented().within("src")
+
+        lint().files(
+            ANDROID_APPLICATION,
+            WORK_MANAGER_CONFIGURATION_PROVIDER,
+            customApplication
+        )
+            .issues(BadConfigurationProviderIssueDetector.ISSUE)
+            .run()
+            .expectClean()
+    }
+}
diff --git a/work/workmanager-lint/src/test/java/androidx/work/lint/Stubs.kt b/work/workmanager-lint/src/test/java/androidx/work/lint/Stubs.kt
new file mode 100644
index 0000000..6b158d2c
--- /dev/null
+++ b/work/workmanager-lint/src/test/java/androidx/work/lint/Stubs.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2019 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.work.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest.kotlin
+import com.android.tools.lint.checks.infrastructure.TestFile
+
+object Stubs {
+    val WORK_MANAGER_CONFIGURATION_PROVIDER: TestFile = kotlin(
+        "androidx/work/Configuration.kt",
+        """
+                 package androidx.work
+                 class Configuration {
+                   interface Provider {
+                     fun getWorkManagerConfiguration(): Configuration
+                   }
+                 }
+            """
+    )
+        .indented().within("src")
+
+    val ANDROID_APPLICATION: TestFile = kotlin(
+        "android/app/Application.kt",
+        """
+                package android.app
+                open class Application {
+                  fun onCreate() {
+
+                  }
+                }
+            """
+    )
+        .indented().within("src")
+}
\ No newline at end of file