1 /* 2 * Copyright 2020 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.tools.lint.detector.api.Category 24 import com.android.tools.lint.detector.api.Detector 25 import com.android.tools.lint.detector.api.Implementation 26 import com.android.tools.lint.detector.api.Issue 27 import com.android.tools.lint.detector.api.JavaContext 28 import com.android.tools.lint.detector.api.Scope 29 import com.android.tools.lint.detector.api.Severity 30 import com.android.tools.lint.detector.api.SourceCodeScanner 31 import com.android.tools.lint.detector.api.XmlContext 32 import com.android.tools.lint.detector.api.XmlScanner 33 import com.intellij.psi.PsiMethod 34 import java.util.EnumSet 35 import org.jetbrains.uast.UCallExpression 36 import org.w3c.dom.Element 37 38 /** 39 * Detects usage of `ForegroundInfo` with the `foregroundServiceType` and ensure that the service 40 * type is defined in the `AndroidManifest.xml`. 41 */ 42 class SpecifyForegroundServiceTypeIssueDetector : Detector(), SourceCodeScanner, XmlScanner { 43 44 private val knownServiceTypes = mutableSetOf<String>() 45 46 companion object { 47 val ISSUE = 48 Issue.create( 49 id = "SpecifyForegroundServiceType", 50 briefDescription = "Specify foreground service type", 51 explanation = 52 """ 53 When using the setForegroundAsync() API, the application must override <service /> \ 54 entry for `SystemForegroundService` to include the foreground service type in the \ 55 `AndroidManifest.xml` file. 56 """, 57 androidSpecific = true, 58 category = Category.CORRECTNESS, 59 severity = Severity.FATAL, 60 implementation = 61 Implementation( 62 SpecifyForegroundServiceTypeIssueDetector::class.java, 63 EnumSet.of(Scope.JAVA_FILE, Scope.MANIFEST) 64 ) 65 ) 66 67 private val SERVICE_TYPE_MAPPING = 68 mapOf( 69 1 to "dataSync", 70 2 to "mediaPlayback", 71 4 to "phoneCall", 72 8 to "location", 73 16 to "connectedDevice", 74 32 to "mediaProjection" 75 ) 76 } 77 getApplicableConstructorTypesnull78 override fun getApplicableConstructorTypes() = listOf("androidx.work.ForegroundInfo") 79 80 override fun getApplicableElements() = listOf("service") 81 82 override fun visitElement(context: XmlContext, element: Element) { 83 val name = element.getAttributeNS(ANDROID_URI, ATTR_NAME) 84 if ("androidx.work.impl.foreground.SystemForegroundService" == name) { 85 val serviceTypes = element.getAttributeNS(ANDROID_URI, "foregroundServiceType") 86 if (serviceTypes != null) { 87 knownServiceTypes += serviceTypes.split("|") 88 } 89 } 90 } 91 visitConstructornull92 override fun visitConstructor( 93 context: JavaContext, 94 node: UCallExpression, 95 constructor: PsiMethod 96 ) { 97 if (node.valueArgumentCount > 2) { 98 val type = node.valueArguments[2].evaluate() 99 if (type != null && type is Int && type > 0) { 100 for ((mask, name) in SERVICE_TYPE_MAPPING) { 101 if (type and mask > 0) { 102 if (name !in knownServiceTypes) { 103 context.report( 104 issue = ISSUE, 105 location = context.getLocation(node), 106 message = 107 "Missing $name foregroundServiceType in " + 108 "the AndroidManifest.xml" 109 ) 110 } 111 } 112 } 113 } 114 } 115 } 116 } 117