1 /*
<lambda>null2  * Copyright 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 @file:Suppress("UnstableApiUsage")
18 
19 package androidx.work.lint
20 
21 import com.android.SdkConstants.ANDROID_URI
22 import com.android.SdkConstants.ATTR_NAME
23 import com.android.SdkConstants.TOOLS_URI
24 import com.android.tools.lint.detector.api.Category
25 import com.android.tools.lint.detector.api.Context
26 import com.android.tools.lint.detector.api.Detector
27 import com.android.tools.lint.detector.api.Implementation
28 import com.android.tools.lint.detector.api.Issue
29 import com.android.tools.lint.detector.api.JavaContext
30 import com.android.tools.lint.detector.api.Location
31 import com.android.tools.lint.detector.api.Scope
32 import com.android.tools.lint.detector.api.Severity
33 import com.android.tools.lint.detector.api.SourceCodeScanner
34 import com.android.tools.lint.detector.api.XmlContext
35 import com.android.tools.lint.detector.api.XmlScanner
36 import java.util.EnumSet
37 import org.jetbrains.uast.UClass
38 import org.w3c.dom.Element
39 import org.w3c.dom.Node
40 import org.w3c.dom.NodeList
41 
42 class RemoveWorkManagerInitializerDetector : Detector(), SourceCodeScanner, XmlScanner {
43     private var removedDefaultInitializer = false
44     private var location: Location? = null
45 
46     private var applicationImplementsConfigurationProvider = false
47 
48     companion object {
49 
50         private const val DESCRIPTION =
51             "Remove androidx.work.WorkManagerInitializer from " +
52                 "your AndroidManifest.xml when using on-demand initialization."
53 
54         val ISSUE =
55             Issue.create(
56                 id = "RemoveWorkManagerInitializer",
57                 briefDescription = DESCRIPTION,
58                 explanation =
59                     """
60                 If an `android.app.Application` implements `androidx.work.Configuration.Provider`,
61                 the default `androidx.startup.InitializationProvider` needs to be removed from the
62                 AndroidManifest.xml file.
63             """,
64                 androidSpecific = true,
65                 category = Category.CORRECTNESS,
66                 severity = Severity.FATAL,
67                 implementation =
68                     Implementation(
69                         RemoveWorkManagerInitializerDetector::class.java,
70                         EnumSet.of(Scope.JAVA_FILE, Scope.MANIFEST)
71                     )
72             )
73 
74         private const val ATTR_NODE = "node"
75 
76         fun NodeList?.find(fn: (node: Node) -> Boolean): Node? {
77             if (this == null) {
78                 return null
79             } else {
80                 for (i in 0 until this.length) {
81                     val node = this.item(i)
82                     if (fn(node)) {
83                         return node
84                     }
85                 }
86                 return null
87             }
88         }
89     }
90 
91     override fun getApplicableElements() = listOf("application")
92 
93     override fun applicableSuperClasses() = listOf("android.app.Application")
94 
95     override fun visitElement(context: XmlContext, element: Element) {
96         // Use the application tag as the location, unless we find something better.
97         if (location == null) {
98             location = context.getLocation(element)
99         }
100         // Check providers
101         val providers = element.getElementsByTagName("provider")
102         val provider =
103             providers.find { node ->
104                 val name = node.attributes.getNamedItemNS(ANDROID_URI, ATTR_NAME)?.textContent
105                 name == "androidx.startup.InitializationProvider"
106             }
107         if (provider != null) {
108             location = context.getLocation(provider)
109             val remove = provider.attributes.getNamedItemNS(TOOLS_URI, ATTR_NODE)
110             if (remove?.textContent == "remove") {
111                 removedDefaultInitializer = true
112             }
113         }
114         // Check metadata
115         val metadataElements = element.getElementsByTagName("meta-data")
116         val metadata =
117             metadataElements.find { node ->
118                 val name = node.attributes.getNamedItemNS(ANDROID_URI, ATTR_NAME)?.textContent
119                 name == "androidx.work.WorkManagerInitializer"
120             }
121         if (metadata != null && !removedDefaultInitializer) {
122             location = context.getLocation(metadata)
123             val remove = metadata.attributes.getNamedItemNS(TOOLS_URI, ATTR_NODE)
124             if (remove?.textContent == "remove") {
125                 removedDefaultInitializer = true
126             }
127         }
128     }
129 
130     override fun visitClass(context: JavaContext, declaration: UClass) {
131         if (
132             !context.evaluator.inheritsFrom(declaration.javaPsi, "android.app.Application", false)
133         ) {
134             return
135         }
136 
137         // Ignore abstract classes.
138         if (context.evaluator.isAbstract(declaration)) {
139             return
140         }
141 
142         // Allow suppression on the class itself.
143         if (context.driver.isSuppressed(context, ISSUE, declaration.javaPsi)) {
144             return
145         }
146 
147         if (
148             context.evaluator.implementsInterface(
149                 declaration.javaPsi,
150                 "androidx.work.Configuration.Provider",
151                 false
152             )
153         ) {
154             applicationImplementsConfigurationProvider = true
155         }
156     }
157 
158     override fun afterCheckRootProject(context: Context) {
159         val location = location ?: return
160         if (applicationImplementsConfigurationProvider) {
161             if (!removedDefaultInitializer) {
162                 context.report(issue = ISSUE, location = location, message = DESCRIPTION)
163             }
164         }
165     }
166 }
167