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 */
+ }
+}