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