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