1 /*
2  * Copyright (C) 2022 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 package com.android.internal.systemui.lint
18 
19 import com.android.SdkConstants.CLASS_CONTEXT
20 import com.android.tools.lint.detector.api.Category
21 import com.android.tools.lint.detector.api.Detector
22 import com.android.tools.lint.detector.api.Implementation
23 import com.android.tools.lint.detector.api.Issue
24 import com.android.tools.lint.detector.api.JavaContext
25 import com.android.tools.lint.detector.api.Scope
26 import com.android.tools.lint.detector.api.Severity
27 import com.android.tools.lint.detector.api.SourceCodeScanner
28 import com.intellij.psi.PsiMethod
29 import org.jetbrains.uast.UCallExpression
30 import org.jetbrains.uast.UClass
31 import org.jetbrains.uast.getParentOfType
32 
33 /**
34  * Checks if anyone is calling sendBroadcast / sendBroadcastAsUser on a Context (or subclasses) and
35  * directs them to using com.android.systemui.broadcast.BroadcastSender instead.
36  */
37 @Suppress("UnstableApiUsage")
38 class BroadcastSentViaContextDetector : Detector(), SourceCodeScanner {
39 
40     override fun getApplicableMethodNames(): List<String> {
41         return listOf("sendBroadcast", "sendBroadcastAsUser")
42     }
43 
44     override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
45         if (node.getParentOfType(UClass::class.java)?.qualifiedName ==
46                 "com.android.systemui.broadcast.BroadcastSender"
47         ) {
48             // Don't warn for class we want the developers to use.
49             return
50         }
51 
52         val evaluator = context.evaluator
53         if (evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) {
54             context.report(
55                     issue = ISSUE,
56                     location = context.getNameLocation(node),
57                     message = "`Context.${method.name}()` should be replaced with " +
58                     "`BroadcastSender.${method.name}()`"
59             )
60         }
61     }
62 
63     companion object {
64         @JvmField
65         val ISSUE: Issue =
66             Issue.create(
67                 id = "BroadcastSentViaContext",
68                 briefDescription = "Broadcast sent via `Context` instead of `BroadcastSender`",
69                 // lint trims indents and converts \ to line continuations
70                 explanation = """
71                         Broadcasts sent via `Context.sendBroadcast()` or \
72                         `Context.sendBroadcastAsUser()` will block the main thread and may cause \
73                         missed frames. Instead, use `BroadcastSender.sendBroadcast()` or \
74                         `BroadcastSender.sendBroadcastAsUser()` which will schedule and dispatch \
75                         broadcasts on a background worker thread.""",
76                 category = Category.PERFORMANCE,
77                 priority = 8,
78                 severity = Severity.WARNING,
79                 implementation =
80                 Implementation(BroadcastSentViaContextDetector::class.java, Scope.JAVA_FILE_SCOPE)
81             )
82     }
83 }
84