Add a lint rule which ensures that the default WorkManagerInitializer is removed when using on-demand init.

Test: Added unit tests.
Change-Id: Iebbdda6bda43e4bbe7274a3fd9120cb5cf0e2918
diff --git a/work/workmanager-lint/src/main/java/androidx/work/lint/RemoveWorkManagerInitializerDetector.kt b/work/workmanager-lint/src/main/java/androidx/work/lint/RemoveWorkManagerInitializerDetector.kt
new file mode 100644
index 0000000..a300883
--- /dev/null
+++ b/work/workmanager-lint/src/main/java/androidx/work/lint/RemoveWorkManagerInitializerDetector.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.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.work.lint
+
+import com.android.SdkConstants.ANDROID_URI
+import com.android.SdkConstants.ATTR_NAME
+import com.android.SdkConstants.TOOLS_URI
+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 com.android.tools.lint.detector.api.XmlContext
+import com.android.tools.lint.detector.api.XmlScanner
+import org.jetbrains.uast.UClass
+import org.w3c.dom.Element
+import org.w3c.dom.Node
+import org.w3c.dom.NodeList
+import java.util.EnumSet
+
+class RemoveWorkManagerInitializerDetector : Detector(), SourceCodeScanner, XmlScanner {
+    private var removedDefaultInitializer = false
+    private var location: Location? = null
+    private var applicationImplementsConfigurationProvider = false
+
+    companion object {
+
+        private const val DESCRIPTION = "Remove androidx.work.impl.WorkManagerInitializer from " +
+                "your AndroidManifest.xml when using on-demand initialization."
+
+        val ISSUE = Issue.create(
+            id = "RemoveWorkManagerInitializer",
+            briefDescription = DESCRIPTION,
+            explanation = """
+                If an `android.app.Application` implements `androidx.work.Configuration.Provider`,
+                the default `androidx.work.impl.WorkManagerInitializer` needs to be removed from the
+                AndroidManifest.xml file.
+            """,
+            androidSpecific = true,
+            category = Category.CORRECTNESS,
+            severity = Severity.FATAL,
+            implementation = Implementation(
+                RemoveWorkManagerInitializerDetector::class.java,
+                EnumSet.of(Scope.JAVA_FILE, Scope.MANIFEST)
+            )
+        )
+
+        private const val ATTR_NODE = "node"
+
+        fun NodeList?.find(fn: (node: Node) -> Boolean): Node? {
+            if (this == null) {
+                return null
+            } else {
+                for (i in 0 until this.length) {
+                    val node = this.item(i)
+                    if (fn(node)) {
+                        return node
+                    }
+                }
+                return null
+            }
+        }
+    }
+
+    override fun getApplicableElements() = listOf("application")
+
+    override fun applicableSuperClasses() = listOf("androidx.work.Configuration.Provider")
+
+    override fun visitElement(context: XmlContext, element: Element) {
+        val providers = element.getElementsByTagName("provider")
+        val provider = providers.find { node ->
+            val name = node.attributes.getNamedItemNS(ANDROID_URI, ATTR_NAME)?.textContent
+            name == "androidx.work.impl.WorkManagerInitializer"
+        }
+        if (provider != null) {
+            location = context.getLocation(provider)
+            val remove = provider.attributes.getNamedItemNS(TOOLS_URI, ATTR_NODE)
+            if (remove?.textContent == "remove") {
+                removedDefaultInitializer = true
+            }
+        }
+    }
+
+    override fun visitClass(context: JavaContext, declaration: UClass) {
+        if (context.evaluator.inheritsFrom(
+                declaration.javaPsi,
+                "android.app.Application",
+                false
+            )
+        ) {
+            applicationImplementsConfigurationProvider = true
+        }
+    }
+
+    override fun afterCheckRootProject(context: Context) {
+        val location = location ?: Location.create(context.file)
+        if (applicationImplementsConfigurationProvider) {
+            if (!removedDefaultInitializer) {
+                context.report(
+                    issue = ISSUE,
+                    location = location,
+                    message = DESCRIPTION
+                )
+            }
+        }
+    }
+}
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
index 8e69bab..fb3e9e5 100644
--- a/work/workmanager-lint/src/main/java/androidx/work/lint/WorkManagerIssueRegistry.kt
+++ b/work/workmanager-lint/src/main/java/androidx/work/lint/WorkManagerIssueRegistry.kt
@@ -26,6 +26,7 @@
     override val minApi: Int = CURRENT_API
     override val issues: List<Issue> = listOf(
         BadConfigurationProviderIssueDetector.ISSUE,
-        PeriodicEnqueueIssueDetector.ISSUE
+        PeriodicEnqueueIssueDetector.ISSUE,
+        RemoveWorkManagerInitializerDetector.ISSUE
     )
 }
diff --git a/work/workmanager-lint/src/test/java/androidx/work/lint/RemoveWorkManagerInitializerDetectorTest.kt b/work/workmanager-lint/src/test/java/androidx/work/lint/RemoveWorkManagerInitializerDetectorTest.kt
new file mode 100644
index 0000000..99f532a
--- /dev/null
+++ b/work/workmanager-lint/src/test/java/androidx/work/lint/RemoveWorkManagerInitializerDetectorTest.kt
@@ -0,0 +1,224 @@
+/*
+ * 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.TestFiles.manifest
+import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
+import org.junit.Test
+
+class RemoveWorkManagerInitializerDetectorTest {
+    @Test
+    fun testNoWarningsWhenDefaultInitializerIsRemoved() {
+        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")
+
+        val manifestWithNoInitializer = manifest(
+            """
+               <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:tools="http://schemas.android.com/tools"
+                  package="com.example">
+                  <application>
+                        <provider
+                          android:name="androidx.work.impl.WorkManagerInitializer"
+                          android:authorities="com.example.workmanager-init"
+                          tools:node="remove"/>
+
+                  </application>
+                </manifest>
+        """
+        ).indented()
+
+        lint().files(
+            // Manifest file
+            manifestWithNoInitializer,
+            // Source files
+            ANDROID_APPLICATION,
+            WORK_MANAGER_CONFIGURATION_PROVIDER,
+            customApplication
+        )
+            .issues(RemoveWorkManagerInitializerDetector.ISSUE)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testNoWarningsWhenNotUsingOnDemandInitialization() {
+        val customApplication = kotlin(
+            "com/example/App.kt",
+            """
+            package com.example
+
+            import android.app.Application
+            import androidx.work.Configuration
+
+            class App: Application() {
+                override fun onCreate() {
+
+                }
+            }
+            """
+        ).indented().within("src")
+
+        val manifestWithInitializer = manifest(
+            """
+               <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:tools="http://schemas.android.com/tools"
+                  package="com.example">
+                  <application>
+                        <provider
+                          android:name="androidx.work.impl.WorkManagerInitializer"
+                          android:authorities="com.example.workmanager-init"/>
+
+                  </application>
+                </manifest>
+        """
+        ).indented()
+
+        lint().files(
+            // Manifest file
+            manifestWithInitializer,
+            // Source files
+            ANDROID_APPLICATION,
+            WORK_MANAGER_CONFIGURATION_PROVIDER,
+            customApplication
+        )
+            .issues(RemoveWorkManagerInitializerDetector.ISSUE)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun failWhenUsingDefaultManifestMergeStrategy() {
+        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")
+
+        val emptyManifest = manifest(
+            """
+               <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:tools="http://schemas.android.com/tools"
+                  package="com.example">
+                  <application />
+                </manifest>
+        """
+        ).indented()
+
+        /* ktlint-disable max-line-length */
+        lint().files(
+            // Manifest file
+            emptyManifest,
+            // Source files
+            ANDROID_APPLICATION,
+            WORK_MANAGER_CONFIGURATION_PROVIDER,
+            customApplication
+        )
+            .issues(RemoveWorkManagerInitializerDetector.ISSUE)
+            .run()
+            .expect("""
+                project0: Error: Remove androidx.work.impl.WorkManagerInitializer from your AndroidManifest.xml when using on-demand initialization. [RemoveWorkManagerInitializer]
+                1 errors, 0 warnings
+            """.trimIndent())
+        /* ktlint-enable max-line-length */
+    }
+
+    @Test
+    fun failWhenManifestHasDefaultInitializer() {
+        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")
+
+        val manifestWithInitializer = manifest(
+            """
+               <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:tools="http://schemas.android.com/tools"
+                  package="com.example">
+                  <application>
+                        <provider
+                          android:name="androidx.work.impl.WorkManagerInitializer"
+                          android:authorities="com.example.workmanager-init"/>
+
+                  </application>
+                </manifest>
+        """
+        ).indented()
+
+        /* ktlint-disable max-line-length */
+        lint().files(
+            // Manifest file
+            manifestWithInitializer,
+            // Source files
+            ANDROID_APPLICATION,
+            WORK_MANAGER_CONFIGURATION_PROVIDER,
+            customApplication
+        )
+            .issues(RemoveWorkManagerInitializerDetector.ISSUE)
+            .run()
+            .expect("""
+                AndroidManifest.xml:5: Error: Remove androidx.work.impl.WorkManagerInitializer from your AndroidManifest.xml when using on-demand initialization. [RemoveWorkManagerInitializer]
+                         <provider
+                         ^
+                1 errors, 0 warnings
+            """.trimIndent())
+        /* ktlint-enable max-line-length */
+    }
+}