1 /* <lambda>null2 * 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 com.intellij.psi.PsiModifierListOwner 30 import org.jetbrains.uast.UCallExpression 31 import org.jetbrains.uast.UClass 32 import org.jetbrains.uast.UMethod 33 import org.jetbrains.uast.getParentOfType 34 35 /** 36 * Warns if {@code Context.bindService}, {@code Context.bindServiceAsUser}, or {@code 37 * Context.unbindService} is not called on a {@code WorkerThread} 38 */ 39 @Suppress("UnstableApiUsage") 40 class BindServiceOnMainThreadDetector : Detector(), SourceCodeScanner { 41 42 override fun getApplicableMethodNames(): List<String> { 43 return listOf("bindService", "bindServiceAsUser", "unbindService") 44 } 45 46 private fun hasWorkerThreadAnnotation( 47 context: JavaContext, 48 annotated: PsiModifierListOwner? 49 ): Boolean { 50 return context.evaluator.getAnnotations(annotated, inHierarchy = true).any { uAnnotation -> 51 uAnnotation.qualifiedName == "androidx.annotation.WorkerThread" 52 } 53 } 54 55 override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { 56 if (context.evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) { 57 if ( 58 !hasWorkerThreadAnnotation(context, node.getParentOfType(UMethod::class.java)) && 59 !hasWorkerThreadAnnotation(context, node.getParentOfType(UClass::class.java)) 60 ) { 61 context.report( 62 issue = ISSUE, 63 location = context.getLocation(node), 64 message = 65 "This method should be annotated with `@WorkerThread` because " + 66 "it calls ${method.name}", 67 ) 68 } 69 } 70 } 71 72 companion object { 73 @JvmField 74 val ISSUE: Issue = 75 Issue.create( 76 id = "BindServiceOnMainThread", 77 briefDescription = "Service bound or unbound on main thread", 78 explanation = 79 """ 80 Binding and unbinding services are synchronous calls to `ActivityManager`. \ 81 They usually take multiple milliseconds to complete. If called on the main \ 82 thread, it will likely cause missed frames. To fix it, use a `@Background \ 83 Executor` and annotate the calling method with `@WorkerThread`. 84 """, 85 category = Category.PERFORMANCE, 86 priority = 8, 87 severity = Severity.WARNING, 88 implementation = 89 Implementation( 90 BindServiceOnMainThreadDetector::class.java, 91 Scope.JAVA_FILE_SCOPE 92 ) 93 ) 94 } 95 } 96