diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt new file mode 100644 index 0000000000000..1d808ba7ee168 --- /dev/null +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.systemui.lint + +import com.android.SdkConstants.CLASS_CONTEXT +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import com.android.tools.lint.detector.api.SourceCodeScanner +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiModifierListOwner +import org.jetbrains.uast.UCallExpression +import org.jetbrains.uast.UClass +import org.jetbrains.uast.UMethod +import org.jetbrains.uast.getParentOfType + +/** + * Warns if {@code Context.bindService}, {@code Context.bindServiceAsUser}, or {@code + * Context.unbindService} is not called on a {@code WorkerThread} + */ +@Suppress("UnstableApiUsage") +class BindServiceOnMainThreadDetector : Detector(), SourceCodeScanner { + + override fun getApplicableMethodNames(): List { + return listOf("bindService", "bindServiceAsUser", "unbindService") + } + + private fun hasWorkerThreadAnnotation( + context: JavaContext, + annotated: PsiModifierListOwner? + ): Boolean { + return context.evaluator.getAnnotations(annotated, inHierarchy = true).any { uAnnotation -> + uAnnotation.qualifiedName == "androidx.annotation.WorkerThread" + } + } + + override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { + if (context.evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) { + if ( + !hasWorkerThreadAnnotation(context, node.getParentOfType(UMethod::class.java)) && + !hasWorkerThreadAnnotation(context, node.getParentOfType(UClass::class.java)) + ) { + context.report( + ISSUE, + method, + context.getLocation(node), + "This method should be annotated with `@WorkerThread` because " + + "it calls ${method.name}", + ) + } + } + } + + companion object { + @JvmField + val ISSUE: Issue = + Issue.create( + id = "BindServiceOnMainThread", + briefDescription = "Service bound or unbound on main thread", + explanation = + """ + Binding and unbinding services are synchronous calls to `ActivityManager`. \ + They usually take multiple milliseconds to complete. If called on the main \ + thread, it will likely cause missed frames. To fix it, use a `@Background \ + Executor` and annotate the calling method with `@WorkerThread`. + """, + category = Category.PERFORMANCE, + priority = 8, + severity = Severity.WARNING, + implementation = + Implementation( + BindServiceOnMainThreadDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) + ) + } +} diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt index 8d48f0957be45..1129929136619 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt @@ -16,6 +16,7 @@ package com.android.internal.systemui.lint +import com.android.SdkConstants.CLASS_CONTEXT import com.android.tools.lint.detector.api.Category import com.android.tools.lint.detector.api.Detector import com.android.tools.lint.detector.api.Implementation @@ -48,14 +49,14 @@ class BroadcastSentViaContextDetector : Detector(), SourceCodeScanner { return } - val evaulator = context.evaluator - if (evaulator.isMemberInSubClassOf(method, "android.content.Context")) { + val evaluator = context.evaluator + if (evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) { context.report( ISSUE, method, context.getNameLocation(node), - "Please don't call sendBroadcast/sendBroadcastAsUser directly on " + - "Context, use com.android.systemui.broadcast.BroadcastSender instead." + "`Context.${method.name}()` should be replaced with " + + "`BroadcastSender.${method.name}()`" ) } } @@ -65,14 +66,14 @@ class BroadcastSentViaContextDetector : Detector(), SourceCodeScanner { val ISSUE: Issue = Issue.create( id = "BroadcastSentViaContext", - briefDescription = "Broadcast sent via Context instead of BroadcastSender.", - explanation = - "Broadcast was sent via " + - "Context.sendBroadcast/Context.sendBroadcastAsUser. Please use " + - "BroadcastSender.sendBroadcast/BroadcastSender.sendBroadcastAsUser " + - "which will schedule dispatch of broadcasts on background thread. " + - "Sending broadcasts on main thread causes jank due to synchronous " + - "Binder calls.", + briefDescription = "Broadcast sent via `Context` instead of `BroadcastSender`", + // lint trims indents and converts \ to line continuations + explanation = """ + Broadcasts sent via `Context.sendBroadcast()` or \ + `Context.sendBroadcastAsUser()` will block the main thread and may cause \ + missed frames. Instead, use `BroadcastSender.sendBroadcast()` or \ + `BroadcastSender.sendBroadcastAsUser()` which will schedule and dispatch \ + broadcasts on a background worker thread.""", category = Category.PERFORMANCE, priority = 8, severity = Severity.WARNING, diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt deleted file mode 100644 index a629eeeb0102f..0000000000000 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.systemui.lint - -import com.android.tools.lint.detector.api.Category -import com.android.tools.lint.detector.api.Detector -import com.android.tools.lint.detector.api.Implementation -import com.android.tools.lint.detector.api.Issue -import com.android.tools.lint.detector.api.JavaContext -import com.android.tools.lint.detector.api.Scope -import com.android.tools.lint.detector.api.Severity -import com.android.tools.lint.detector.api.SourceCodeScanner -import com.intellij.psi.PsiMethod -import org.jetbrains.uast.UCallExpression - -@Suppress("UnstableApiUsage") -class GetMainLooperViaContextDetector : Detector(), SourceCodeScanner { - - override fun getApplicableMethodNames(): List { - return listOf("getMainThreadHandler", "getMainLooper", "getMainExecutor") - } - - override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { - if (context.evaluator.isMemberInSubClassOf(method, "android.content.Context")) { - context.report( - ISSUE, - method, - context.getNameLocation(node), - "Please inject a @Main Executor instead." - ) - } - } - - companion object { - @JvmField - val ISSUE: Issue = - Issue.create( - id = "GetMainLooperViaContextDetector", - briefDescription = "Please use idiomatic SystemUI executors, injecting " + - "them via Dagger.", - explanation = "Injecting the @Main Executor is preferred in order to make" + - "dependencies explicit and increase testability. It's much " + - "easier to pass a FakeExecutor on your test ctor than to " + - "deal with loopers in unit tests.", - category = Category.LINT, - priority = 8, - severity = Severity.WARNING, - implementation = Implementation(GetMainLooperViaContextDetector::class.java, - Scope.JAVA_FILE_SCOPE) - ) - } -} diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt similarity index 58% rename from packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.kt rename to packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt index 925fae0ebfb4d..bab76ab4bce2b 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt @@ -16,6 +16,7 @@ package com.android.internal.systemui.lint +import com.android.SdkConstants.CLASS_CONTEXT import com.android.tools.lint.detector.api.Category import com.android.tools.lint.detector.api.Detector import com.android.tools.lint.detector.api.Implementation @@ -28,20 +29,19 @@ import com.intellij.psi.PsiMethod import org.jetbrains.uast.UCallExpression @Suppress("UnstableApiUsage") -class BindServiceViaContextDetector : Detector(), SourceCodeScanner { +class NonInjectedMainThreadDetector : Detector(), SourceCodeScanner { override fun getApplicableMethodNames(): List { - return listOf("bindService", "bindServiceAsUser", "unbindService") + return listOf("getMainThreadHandler", "getMainLooper", "getMainExecutor") } override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { - if (context.evaluator.isMemberInSubClassOf(method, "android.content.Context")) { + if (context.evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) { context.report( - ISSUE, - method, - context.getNameLocation(node), - "Binding or unbinding services are synchronous calls, please make " + - "sure you're on a @Background Executor." + ISSUE, + method, + context.getNameLocation(node), + "Replace with injected `@Main Executor`." ) } } @@ -50,18 +50,20 @@ class BindServiceViaContextDetector : Detector(), SourceCodeScanner { @JvmField val ISSUE: Issue = Issue.create( - id = "BindServiceViaContextDetector", - briefDescription = "Service bound/unbound via Context, please make sure " + - "you're on a background thread.", + id = "NonInjectedMainThread", + briefDescription = "Main thread usage without dependency injection", explanation = - "Binding or unbinding services are synchronous calls to ActivityManager, " + - "they usually take multiple milliseconds to complete and will make" + - "the caller drop frames. Make sure you're on a @Background Executor.", - category = Category.PERFORMANCE, + """ + Main thread should be injected using the `@Main Executor` instead \ + of using the accessors in `Context`. This is to make the \ + dependencies explicit and increase testability. It's much easier \ + to pass a `FakeExecutor` on test constructors than it is to deal \ + with loopers in unit tests.""", + category = Category.LINT, priority = 8, severity = Severity.WARNING, implementation = - Implementation(BindServiceViaContextDetector::class.java, Scope.JAVA_FILE_SCOPE) + Implementation(NonInjectedMainThreadDetector::class.java, Scope.JAVA_FILE_SCOPE) ) } } diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt index 4eb7c7dd0d7e8..b62290025437e 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt @@ -16,6 +16,7 @@ package com.android.internal.systemui.lint +import com.android.SdkConstants.CLASS_CONTEXT import com.android.tools.lint.detector.api.Category import com.android.tools.lint.detector.api.Detector import com.android.tools.lint.detector.api.Implementation @@ -32,7 +33,7 @@ import org.jetbrains.uast.UCallExpression class NonInjectedServiceDetector : Detector(), SourceCodeScanner { override fun getApplicableMethodNames(): List { - return listOf("getSystemService") + return listOf("getSystemService", "get") } override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { @@ -40,14 +41,25 @@ class NonInjectedServiceDetector : Detector(), SourceCodeScanner { if ( !evaluator.isStatic(method) && method.name == "getSystemService" && - method.containingClass?.qualifiedName == "android.content.Context" + method.containingClass?.qualifiedName == CLASS_CONTEXT ) { context.report( ISSUE, method, context.getNameLocation(node), - "Use @Inject to get the handle to a system-level services instead of using " + - "Context.getSystemService()" + "Use `@Inject` to get system-level service handles instead of " + + "`Context.getSystemService()`" + ) + } else if ( + evaluator.isStatic(method) && + method.name == "get" && + method.containingClass?.qualifiedName == "android.accounts.AccountManager" + ) { + context.report( + ISSUE, + method, + context.getNameLocation(node), + "Replace `AccountManager.get()` with an injected instance of `AccountManager`" ) } } @@ -57,14 +69,14 @@ class NonInjectedServiceDetector : Detector(), SourceCodeScanner { val ISSUE: Issue = Issue.create( id = "NonInjectedService", - briefDescription = - "System-level services should be retrieved using " + - "@Inject instead of Context.getSystemService().", + briefDescription = "System service not injected", explanation = - "Context.getSystemService() should be avoided because it makes testing " + - "difficult. Instead, use an injected service. For example, " + - "instead of calling Context.getSystemService(UserManager.class), " + - "use @Inject and add UserManager to the constructor", + """ + `Context.getSystemService()` should be avoided because it makes testing \ + difficult. Instead, use an injected service. For example, instead of calling \ + `Context.getSystemService(UserManager.class)` in a class, annotate the class' \ + constructor with `@Inject` and add `UserManager` to the parameters. + """, category = Category.CORRECTNESS, priority = 8, severity = Severity.WARNING, diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt index eb71d32b2d8be..4ba3afc7f7e2c 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt @@ -16,6 +16,7 @@ package com.android.internal.systemui.lint +import com.android.SdkConstants.CLASS_CONTEXT import com.android.tools.lint.detector.api.Category import com.android.tools.lint.detector.api.Detector import com.android.tools.lint.detector.api.Implementation @@ -35,12 +36,12 @@ class RegisterReceiverViaContextDetector : Detector(), SourceCodeScanner { } override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { - if (context.evaluator.isMemberInSubClassOf(method, "android.content.Context")) { + if (context.evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) { context.report( ISSUE, method, context.getNameLocation(node), - "BroadcastReceivers should be registered via BroadcastDispatcher." + "Register `BroadcastReceiver` using `BroadcastDispatcher` instead of `Context`" ) } } @@ -49,14 +50,16 @@ class RegisterReceiverViaContextDetector : Detector(), SourceCodeScanner { @JvmField val ISSUE: Issue = Issue.create( - id = "RegisterReceiverViaContextDetector", - briefDescription = "Broadcast registrations via Context are blocking " + - "calls. Please use BroadcastDispatcher.", - explanation = - "Context#registerReceiver is a blocking call to the system server, " + - "making it very likely that you'll drop a frame. Please use " + - "BroadcastDispatcher instead (or move this call to a " + - "@Background Executor.)", + id = "RegisterReceiverViaContext", + briefDescription = "Blocking broadcast registration", + // lint trims indents and converts \ to line continuations + explanation = """ + `Context.registerReceiver()` is a blocking call to the system server, \ + making it very likely that you'll drop a frame. Please use \ + `BroadcastDispatcher` instead, which registers the receiver on a \ + background thread. `BroadcastDispatcher` also improves our visibility \ + into ANRs.""", + moreInfo = "go/identifying-broadcast-threads", category = Category.PERFORMANCE, priority = 8, severity = Severity.WARNING, diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt index b00661575c140..7be21a512f892 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt @@ -49,8 +49,7 @@ class SlowUserQueryDetector : Detector(), SourceCodeScanner { ISSUE_SLOW_USER_ID_QUERY, method, context.getNameLocation(node), - "ActivityManager.getCurrentUser() is slow. " + - "Use UserTracker.getUserId() instead." + "Use `UserTracker.getUserId()` instead of `ActivityManager.getCurrentUser()`" ) } if ( @@ -62,7 +61,7 @@ class SlowUserQueryDetector : Detector(), SourceCodeScanner { ISSUE_SLOW_USER_INFO_QUERY, method, context.getNameLocation(node), - "UserManager.getUserInfo() is slow. " + "Use UserTracker.getUserInfo() instead." + "Use `UserTracker.getUserInfo()` instead of `UserManager.getUserInfo()`" ) } } @@ -72,11 +71,13 @@ class SlowUserQueryDetector : Detector(), SourceCodeScanner { val ISSUE_SLOW_USER_ID_QUERY: Issue = Issue.create( id = "SlowUserIdQuery", - briefDescription = "User ID queried using ActivityManager instead of UserTracker.", + briefDescription = "User ID queried using ActivityManager", explanation = - "ActivityManager.getCurrentUser() makes a binder call and is slow. " + - "Instead, inject a UserTracker and call UserTracker.getUserId(). For " + - "more info, see: http://go/multi-user-in-systemui-slides", + """ + `ActivityManager.getCurrentUser()` uses a blocking binder call and is slow. \ + Instead, inject a `UserTracker` and call `UserTracker.getUserId()`. + """, + moreInfo = "http://go/multi-user-in-systemui-slides", category = Category.PERFORMANCE, priority = 8, severity = Severity.WARNING, @@ -88,11 +89,13 @@ class SlowUserQueryDetector : Detector(), SourceCodeScanner { val ISSUE_SLOW_USER_INFO_QUERY: Issue = Issue.create( id = "SlowUserInfoQuery", - briefDescription = "User info queried using UserManager instead of UserTracker.", + briefDescription = "User info queried using UserManager", explanation = - "UserManager.getUserInfo() makes a binder call and is slow. " + - "Instead, inject a UserTracker and call UserTracker.getUserInfo(). For " + - "more info, see: http://go/multi-user-in-systemui-slides", + """ + `UserManager.getUserInfo()` uses a blocking binder call and is slow. \ + Instead, inject a `UserTracker` and call `UserTracker.getUserInfo()`. + """, + moreInfo = "http://go/multi-user-in-systemui-slides", category = Category.PERFORMANCE, priority = 8, severity = Severity.WARNING, diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt index a584894fed71e..4eeeb850292ae 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt @@ -47,7 +47,7 @@ class SoftwareBitmapDetector : Detector(), SourceCodeScanner { ISSUE, referenced, context.getNameLocation(referenced), - "Usage of Config.HARDWARE is highly encouraged." + "Replace software bitmap with `Config.HARDWARE`" ) } } @@ -56,12 +56,12 @@ class SoftwareBitmapDetector : Detector(), SourceCodeScanner { @JvmField val ISSUE: Issue = Issue.create( - id = "SoftwareBitmapDetector", - briefDescription = "Software bitmap detected. Please use Config.HARDWARE instead.", - explanation = - "Software bitmaps occupy twice as much memory, when compared to Config.HARDWARE. " + - "In case you need to manipulate the pixels, please consider to either use" + - "a shader (encouraged), or a short lived software bitmap.", + id = "SoftwareBitmap", + briefDescription = "Software bitmap", + explanation = """ + Software bitmaps occupy twice as much memory as `Config.HARDWARE` bitmaps \ + do. However, hardware bitmaps are read-only. If you need to manipulate the \ + pixels, use a shader (preferably) or a short lived software bitmap.""", category = Category.PERFORMANCE, priority = 8, severity = Severity.WARNING, diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt index 312810ba46336..cf7c1b5e44a2c 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt @@ -28,11 +28,11 @@ class SystemUIIssueRegistry : IssueRegistry() { override val issues: List get() = listOf( - BindServiceViaContextDetector.ISSUE, + BindServiceOnMainThreadDetector.ISSUE, BroadcastSentViaContextDetector.ISSUE, SlowUserQueryDetector.ISSUE_SLOW_USER_ID_QUERY, SlowUserQueryDetector.ISSUE_SLOW_USER_INFO_QUERY, - GetMainLooperViaContextDetector.ISSUE, + NonInjectedMainThreadDetector.ISSUE, RegisterReceiverViaContextDetector.ISSUE, SoftwareBitmapDetector.ISSUE, NonInjectedServiceDetector.ISSUE, diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt index 26bd8d0a6ff4c..486af9dd5d982 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt @@ -16,16 +16,21 @@ package com.android.internal.systemui.lint +import com.android.annotations.NonNull import com.android.tools.lint.checks.infrastructure.LintDetectorTest.java +import org.intellij.lang.annotations.Language + +@Suppress("UnstableApiUsage") +@NonNull +private fun indentedJava(@NonNull @Language("JAVA") source: String) = java(source).indented() /* * This file contains stubs of framework APIs and System UI classes for testing purposes only. The * stubs are not used in the lint detectors themselves. */ -@Suppress("UnstableApiUsage") internal val androidStubs = arrayOf( - java( + indentedJava( """ package android.app; @@ -34,7 +39,16 @@ public class ActivityManager { } """ ), - java( + indentedJava( + """ +package android.accounts; + +public class AccountManager { + public static AccountManager get(Context context) { return null; } +} +""" + ), + indentedJava( """ package android.os; import android.content.pm.UserInfo; @@ -45,39 +59,39 @@ public class UserManager { } """ ), - java(""" + indentedJava(""" package android.annotation; public @interface UserIdInt {} """), - java(""" + indentedJava(""" package android.content.pm; public class UserInfo {} """), - java(""" + indentedJava(""" package android.os; public class Looper {} """), - java(""" + indentedJava(""" package android.os; public class Handler {} """), - java(""" + indentedJava(""" package android.content; public class ServiceConnection {} """), - java(""" + indentedJava(""" package android.os; public enum UserHandle { ALL } """), - java( + indentedJava( """ package android.content; import android.os.UserHandle; @@ -108,7 +122,7 @@ public class Context { } """ ), - java( + indentedJava( """ package android.app; import android.content.Context; @@ -116,7 +130,7 @@ import android.content.Context; public class Activity extends Context {} """ ), - java( + indentedJava( """ package android.graphics; @@ -132,17 +146,17 @@ public class Bitmap { } """ ), - java(""" + indentedJava(""" package android.content; public class BroadcastReceiver {} """), - java(""" + indentedJava(""" package android.content; public class IntentFilter {} """), - java( + indentedJava( """ package com.android.systemui.settings; import android.content.pm.UserInfo; @@ -151,6 +165,25 @@ public interface UserTracker { int getUserId(); UserInfo getUserInfo(); } +""" + ), + indentedJava( + """ +package androidx.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +@Retention(SOURCE) +@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER}) +public @interface WorkerThread { +} """ ), ) diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt new file mode 100644 index 0000000000000..6ae8fd3f25a11 --- /dev/null +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.systemui.lint + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest +import com.android.tools.lint.checks.infrastructure.TestFiles +import com.android.tools.lint.checks.infrastructure.TestLintTask +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue +import org.junit.Test + +@Suppress("UnstableApiUsage") +class BindServiceOnMainThreadDetectorTest : LintDetectorTest() { + + override fun getDetector(): Detector = BindServiceOnMainThreadDetector() + override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) + + override fun getIssues(): List = listOf(BindServiceOnMainThreadDetector.ISSUE) + + @Test + fun testBindService() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + + public class TestClass { + public void bind(Context context) { + Intent intent = new Intent(Intent.ACTION_VIEW); + context.bindService(intent, null, 0); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(BindServiceOnMainThreadDetector.ISSUE) + .run() + .expect( + """ + src/test/pkg/TestClass.java:7: Warning: This method should be annotated with @WorkerThread because it calls bindService [BindServiceOnMainThread] + context.bindService(intent, null, 0); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + } + + @Test + fun testBindServiceAsUser() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + import android.os.UserHandle; + + public class TestClass { + public void bind(Context context) { + Intent intent = new Intent(Intent.ACTION_VIEW); + context.bindServiceAsUser(intent, null, 0, UserHandle.ALL); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(BindServiceOnMainThreadDetector.ISSUE) + .run() + .expect( + """ + src/test/pkg/TestClass.java:8: Warning: This method should be annotated with @WorkerThread because it calls bindServiceAsUser [BindServiceOnMainThread] + context.bindServiceAsUser(intent, null, 0, UserHandle.ALL); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + } + + @Test + fun testUnbindService() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + import android.content.ServiceConnection; + + public class TestClass { + public void unbind(Context context, ServiceConnection connection) { + context.unbindService(connection); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(BindServiceOnMainThreadDetector.ISSUE) + .run() + .expect( + """ + src/test/pkg/TestClass.java:7: Warning: This method should be annotated with @WorkerThread because it calls unbindService [BindServiceOnMainThread] + context.unbindService(connection); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + } + + @Test + fun testWorkerMethod() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + import android.content.ServiceConnection; + import androidx.annotation.WorkerThread; + + public class TestClass { + @WorkerThread + public void unbind(Context context, ServiceConnection connection) { + context.unbindService(connection); + } + } + + public class ChildTestClass extends TestClass { + @Override + public void unbind(Context context, ServiceConnection connection) { + context.unbindService(connection); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(BindServiceOnMainThreadDetector.ISSUE) + .run() + .expectClean() + } + + @Test + fun testWorkerClass() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + import android.content.ServiceConnection; + import androidx.annotation.WorkerThread; + + @WorkerThread + public class TestClass { + public void unbind(Context context, ServiceConnection connection) { + context.unbindService(connection); + } + } + + public class ChildTestClass extends TestClass { + @Override + public void unbind(Context context, ServiceConnection connection) { + context.unbindService(connection); + } + + public void bind(Context context, ServiceConnection connection) { + context.bind(connection); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(BindServiceOnMainThreadDetector.ISSUE) + .run() + .expectClean() + } + + private val stubs = androidStubs +} diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceViaContextDetectorTest.kt deleted file mode 100644 index 564afcb773fdb..0000000000000 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceViaContextDetectorTest.kt +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.systemui.lint - -import com.android.tools.lint.checks.infrastructure.LintDetectorTest -import com.android.tools.lint.checks.infrastructure.TestFiles -import com.android.tools.lint.checks.infrastructure.TestLintTask -import com.android.tools.lint.detector.api.Detector -import com.android.tools.lint.detector.api.Issue -import org.junit.Test - -@Suppress("UnstableApiUsage") -class BindServiceViaContextDetectorTest : LintDetectorTest() { - - override fun getDetector(): Detector = BindServiceViaContextDetector() - override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) - - override fun getIssues(): List = listOf(BindServiceViaContextDetector.ISSUE) - - private val explanation = "Binding or unbinding services are synchronous calls" - - @Test - fun testBindService() { - lint() - .files( - TestFiles.java( - """ - package test.pkg; - import android.content.Context; - - public class TestClass1 { - public void bind(Context context) { - Intent intent = new Intent(Intent.ACTION_VIEW); - context.bindService(intent, null, 0); - } - } - """ - ) - .indented(), - *stubs - ) - .issues(BindServiceViaContextDetector.ISSUE) - .run() - .expectWarningCount(1) - .expectContains(explanation) - } - - @Test - fun testBindServiceAsUser() { - lint() - .files( - TestFiles.java( - """ - package test.pkg; - import android.content.Context; - import android.os.UserHandle; - - public class TestClass1 { - public void bind(Context context) { - Intent intent = new Intent(Intent.ACTION_VIEW); - context.bindServiceAsUser(intent, null, 0, UserHandle.ALL); - } - } - """ - ) - .indented(), - *stubs - ) - .issues(BindServiceViaContextDetector.ISSUE) - .run() - .expectWarningCount(1) - .expectContains(explanation) - } - - @Test - fun testUnbindService() { - lint() - .files( - TestFiles.java( - """ - package test.pkg; - import android.content.Context; - import android.content.ServiceConnection; - - public class TestClass1 { - public void unbind(Context context, ServiceConnection connection) { - context.unbindService(connection); - } - } - """ - ) - .indented(), - *stubs - ) - .issues(BindServiceViaContextDetector.ISSUE) - .run() - .expectWarningCount(1) - .expectContains(explanation) - } - - private val stubs = androidStubs -} diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt index 06aee8e358984..7d422807ae080 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt @@ -41,7 +41,7 @@ class BroadcastSentViaContextDetectorTest : LintDetectorTest() { package test.pkg; import android.content.Context; - public class TestClass1 { + public class TestClass { public void send(Context context) { Intent intent = new Intent(Intent.ACTION_VIEW); context.sendBroadcast(intent); @@ -54,10 +54,13 @@ class BroadcastSentViaContextDetectorTest : LintDetectorTest() { ) .issues(BroadcastSentViaContextDetector.ISSUE) .run() - .expectWarningCount(1) - .expectContains( - "Please don't call sendBroadcast/sendBroadcastAsUser directly on " + - "Context, use com.android.systemui.broadcast.BroadcastSender instead." + .expect( + """ + src/test/pkg/TestClass.java:7: Warning: Context.sendBroadcast() should be replaced with BroadcastSender.sendBroadcast() [BroadcastSentViaContext] + context.sendBroadcast(intent); + ~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ ) } @@ -71,7 +74,7 @@ class BroadcastSentViaContextDetectorTest : LintDetectorTest() { import android.content.Context; import android.os.UserHandle; - public class TestClass1 { + public class TestClass { public void send(Context context) { Intent intent = new Intent(Intent.ACTION_VIEW); context.sendBroadcastAsUser(intent, UserHandle.ALL, "permission"); @@ -84,10 +87,13 @@ class BroadcastSentViaContextDetectorTest : LintDetectorTest() { ) .issues(BroadcastSentViaContextDetector.ISSUE) .run() - .expectWarningCount(1) - .expectContains( - "Please don't call sendBroadcast/sendBroadcastAsUser directly on " + - "Context, use com.android.systemui.broadcast.BroadcastSender instead." + .expect( + """ + src/test/pkg/TestClass.java:8: Warning: Context.sendBroadcastAsUser() should be replaced with BroadcastSender.sendBroadcastAsUser() [BroadcastSentViaContext] + context.sendBroadcastAsUser(intent, UserHandle.ALL, "permission"); + ~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ ) } @@ -101,7 +107,7 @@ class BroadcastSentViaContextDetectorTest : LintDetectorTest() { import android.app.Activity; import android.os.UserHandle; - public class TestClass1 { + public class TestClass { public void send(Activity activity) { Intent intent = new Intent(Intent.ACTION_VIEW); activity.sendBroadcastAsUser(intent, UserHandle.ALL, "permission"); @@ -115,13 +121,43 @@ class BroadcastSentViaContextDetectorTest : LintDetectorTest() { ) .issues(BroadcastSentViaContextDetector.ISSUE) .run() - .expectWarningCount(1) - .expectContains( - "Please don't call sendBroadcast/sendBroadcastAsUser directly on " + - "Context, use com.android.systemui.broadcast.BroadcastSender instead." + .expect( + """ + src/test/pkg/TestClass.java:8: Warning: Context.sendBroadcastAsUser() should be replaced with BroadcastSender.sendBroadcastAsUser() [BroadcastSentViaContext] + activity.sendBroadcastAsUser(intent, UserHandle.ALL, "permission"); + ~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ ) } + @Test + fun testSendBroadcastInBroadcastSender() { + lint() + .files( + TestFiles.java( + """ + package com.android.systemui.broadcast; + import android.app.Activity; + import android.os.UserHandle; + + public class BroadcastSender { + public void send(Activity activity) { + Intent intent = new Intent(Intent.ACTION_VIEW); + activity.sendBroadcastAsUser(intent, UserHandle.ALL, "permission"); + } + + } + """ + ) + .indented(), + *stubs + ) + .issues(BroadcastSentViaContextDetector.ISSUE) + .run() + .expectClean() + } + @Test fun testNoopIfNoCall() { lint() @@ -131,7 +167,7 @@ class BroadcastSentViaContextDetectorTest : LintDetectorTest() { package test.pkg; import android.content.Context; - public class TestClass1 { + public class TestClass { public void sendBroadcast() { Intent intent = new Intent(Intent.ACTION_VIEW); context.startActivity(intent); diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/GetMainLooperViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt similarity index 63% rename from packages/SystemUI/checks/tests/com/android/internal/systemui/lint/GetMainLooperViaContextDetectorTest.kt rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt index c55f3995f1027..c468af8d09e01 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/GetMainLooperViaContextDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt @@ -24,14 +24,12 @@ import com.android.tools.lint.detector.api.Issue import org.junit.Test @Suppress("UnstableApiUsage") -class GetMainLooperViaContextDetectorTest : LintDetectorTest() { +class NonInjectedMainThreadDetectorTest : LintDetectorTest() { - override fun getDetector(): Detector = GetMainLooperViaContextDetector() + override fun getDetector(): Detector = NonInjectedMainThreadDetector() override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) - override fun getIssues(): List = listOf(GetMainLooperViaContextDetector.ISSUE) - - private val explanation = "Please inject a @Main Executor instead." + override fun getIssues(): List = listOf(NonInjectedMainThreadDetector.ISSUE) @Test fun testGetMainThreadHandler() { @@ -43,7 +41,7 @@ class GetMainLooperViaContextDetectorTest : LintDetectorTest() { import android.content.Context; import android.os.Handler; - public class TestClass1 { + public class TestClass { public void test(Context context) { Handler mainThreadHandler = context.getMainThreadHandler(); } @@ -53,10 +51,16 @@ class GetMainLooperViaContextDetectorTest : LintDetectorTest() { .indented(), *stubs ) - .issues(GetMainLooperViaContextDetector.ISSUE) + .issues(NonInjectedMainThreadDetector.ISSUE) .run() - .expectWarningCount(1) - .expectContains(explanation) + .expect( + """ + src/test/pkg/TestClass.java:7: Warning: Replace with injected @Main Executor. [NonInjectedMainThread] + Handler mainThreadHandler = context.getMainThreadHandler(); + ~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) } @Test @@ -69,7 +73,7 @@ class GetMainLooperViaContextDetectorTest : LintDetectorTest() { import android.content.Context; import android.os.Looper; - public class TestClass1 { + public class TestClass { public void test(Context context) { Looper mainLooper = context.getMainLooper(); } @@ -79,10 +83,16 @@ class GetMainLooperViaContextDetectorTest : LintDetectorTest() { .indented(), *stubs ) - .issues(GetMainLooperViaContextDetector.ISSUE) + .issues(NonInjectedMainThreadDetector.ISSUE) .run() - .expectWarningCount(1) - .expectContains(explanation) + .expect( + """ + src/test/pkg/TestClass.java:7: Warning: Replace with injected @Main Executor. [NonInjectedMainThread] + Looper mainLooper = context.getMainLooper(); + ~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) } @Test @@ -95,7 +105,7 @@ class GetMainLooperViaContextDetectorTest : LintDetectorTest() { import android.content.Context; import java.util.concurrent.Executor; - public class TestClass1 { + public class TestClass { public void test(Context context) { Executor mainExecutor = context.getMainExecutor(); } @@ -105,10 +115,16 @@ class GetMainLooperViaContextDetectorTest : LintDetectorTest() { .indented(), *stubs ) - .issues(GetMainLooperViaContextDetector.ISSUE) + .issues(NonInjectedMainThreadDetector.ISSUE) .run() - .expectWarningCount(1) - .expectContains(explanation) + .expect( + """ + src/test/pkg/TestClass.java:7: Warning: Replace with injected @Main Executor. [NonInjectedMainThread] + Executor mainExecutor = context.getMainExecutor(); + ~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) } private val stubs = androidStubs diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt index 6b9f88fedbdd2..c83a35b46ca6c 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt @@ -39,7 +39,7 @@ class NonInjectedServiceDetectorTest : LintDetectorTest() { package test.pkg; import android.content.Context; - public class TestClass1 { + public class TestClass { public void getSystemServiceWithoutDagger(Context context) { context.getSystemService("user"); } @@ -51,8 +51,14 @@ class NonInjectedServiceDetectorTest : LintDetectorTest() { ) .issues(NonInjectedServiceDetector.ISSUE) .run() - .expectWarningCount(1) - .expectContains("Use @Inject to get the handle") + .expect( + """ + src/test/pkg/TestClass.java:6: Warning: Use @Inject to get system-level service handles instead of Context.getSystemService() [NonInjectedService] + context.getSystemService("user"); + ~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) } @Test @@ -65,7 +71,7 @@ class NonInjectedServiceDetectorTest : LintDetectorTest() { import android.content.Context; import android.os.UserManager; - public class TestClass2 { + public class TestClass { public void getSystemServiceWithoutDagger(Context context) { context.getSystemService(UserManager.class); } @@ -77,8 +83,46 @@ class NonInjectedServiceDetectorTest : LintDetectorTest() { ) .issues(NonInjectedServiceDetector.ISSUE) .run() - .expectWarningCount(1) - .expectContains("Use @Inject to get the handle") + .expect( + """ + src/test/pkg/TestClass.java:7: Warning: Use @Inject to get system-level service handles instead of Context.getSystemService() [NonInjectedService] + context.getSystemService(UserManager.class); + ~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + } + + @Test + fun testGetAccountManager() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + import android.accounts.AccountManager; + + public class TestClass { + public void getSystemServiceWithoutDagger(Context context) { + AccountManager.get(context); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(NonInjectedServiceDetector.ISSUE) + .run() + .expect( + """ + src/test/pkg/TestClass.java:7: Warning: Replace AccountManager.get() with an injected instance of AccountManager [NonInjectedService] + AccountManager.get(context); + ~~~ + 0 errors, 1 warnings + """ + ) } private val stubs = androidStubs diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt index 802ceba4196cb..ebcddebfbc282 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt @@ -31,8 +31,6 @@ class RegisterReceiverViaContextDetectorTest : LintDetectorTest() { override fun getIssues(): List = listOf(RegisterReceiverViaContextDetector.ISSUE) - private val explanation = "BroadcastReceivers should be registered via BroadcastDispatcher." - @Test fun testRegisterReceiver() { lint() @@ -44,7 +42,7 @@ class RegisterReceiverViaContextDetectorTest : LintDetectorTest() { import android.content.Context; import android.content.IntentFilter; - public class TestClass1 { + public class TestClass { public void bind(Context context, BroadcastReceiver receiver, IntentFilter filter) { context.registerReceiver(receiver, filter, 0); @@ -57,8 +55,14 @@ class RegisterReceiverViaContextDetectorTest : LintDetectorTest() { ) .issues(RegisterReceiverViaContextDetector.ISSUE) .run() - .expectWarningCount(1) - .expectContains(explanation) + .expect( + """ + src/test/pkg/TestClass.java:9: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext] + context.registerReceiver(receiver, filter, 0); + ~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) } @Test @@ -74,7 +78,7 @@ class RegisterReceiverViaContextDetectorTest : LintDetectorTest() { import android.os.Handler; import android.os.UserHandle; - public class TestClass1 { + public class TestClass { public void bind(Context context, BroadcastReceiver receiver, IntentFilter filter, Handler handler) { context.registerReceiverAsUser(receiver, UserHandle.ALL, filter, @@ -88,8 +92,14 @@ class RegisterReceiverViaContextDetectorTest : LintDetectorTest() { ) .issues(RegisterReceiverViaContextDetector.ISSUE) .run() - .expectWarningCount(1) - .expectContains(explanation) + .expect( + """ + src/test/pkg/TestClass.java:11: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext] + context.registerReceiverAsUser(receiver, UserHandle.ALL, filter, + ~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) } @Test @@ -105,7 +115,7 @@ class RegisterReceiverViaContextDetectorTest : LintDetectorTest() { import android.os.Handler; import android.os.UserHandle; - public class TestClass1 { + public class TestClass { public void bind(Context context, BroadcastReceiver receiver, IntentFilter filter, Handler handler) { context.registerReceiverForAllUsers(receiver, filter, "permission", @@ -119,8 +129,14 @@ class RegisterReceiverViaContextDetectorTest : LintDetectorTest() { ) .issues(RegisterReceiverViaContextDetector.ISSUE) .run() - .expectWarningCount(1) - .expectContains(explanation) + .expect( + """ + src/test/pkg/TestClass.java:11: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext] + context.registerReceiverForAllUsers(receiver, filter, "permission", + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) } private val stubs = androidStubs diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt index e26583793e205..b03a11c4f02f9 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt @@ -44,7 +44,7 @@ class SlowUserQueryDetectorTest : LintDetectorTest() { package test.pkg; import android.app.ActivityManager; - public class TestClass1 { + public class TestClass { public void slewlyGetCurrentUser() { ActivityManager.getCurrentUser(); } @@ -59,10 +59,13 @@ class SlowUserQueryDetectorTest : LintDetectorTest() { SlowUserQueryDetector.ISSUE_SLOW_USER_INFO_QUERY ) .run() - .expectWarningCount(1) - .expectContains( - "ActivityManager.getCurrentUser() is slow. " + - "Use UserTracker.getUserId() instead." + .expect( + """ + src/test/pkg/TestClass.java:6: Warning: Use UserTracker.getUserId() instead of ActivityManager.getCurrentUser() [SlowUserIdQuery] + ActivityManager.getCurrentUser(); + ~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ ) } @@ -75,7 +78,7 @@ class SlowUserQueryDetectorTest : LintDetectorTest() { package test.pkg; import android.os.UserManager; - public class TestClass2 { + public class TestClass { public void slewlyGetUserInfo(UserManager userManager) { userManager.getUserInfo(); } @@ -90,9 +93,13 @@ class SlowUserQueryDetectorTest : LintDetectorTest() { SlowUserQueryDetector.ISSUE_SLOW_USER_INFO_QUERY ) .run() - .expectWarningCount(1) - .expectContains( - "UserManager.getUserInfo() is slow. " + "Use UserTracker.getUserInfo() instead." + .expect( + """ + src/test/pkg/TestClass.java:6: Warning: Use UserTracker.getUserInfo() instead of UserManager.getUserInfo() [SlowUserInfoQuery] + userManager.getUserInfo(); + ~~~~~~~~~~~ + 0 errors, 1 warnings + """ ) } @@ -105,7 +112,7 @@ class SlowUserQueryDetectorTest : LintDetectorTest() { package test.pkg; import com.android.systemui.settings.UserTracker; - public class TestClass3 { + public class TestClass { public void quicklyGetUserId(UserTracker userTracker) { userTracker.getUserId(); } @@ -132,7 +139,7 @@ class SlowUserQueryDetectorTest : LintDetectorTest() { package test.pkg; import com.android.systemui.settings.UserTracker; - public class TestClass4 { + public class TestClass { public void quicklyGetUserId(UserTracker userTracker) { userTracker.getUserInfo(); } diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt index fd6ab09a2ccde..fb6537e92d15f 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt @@ -31,8 +31,6 @@ class SoftwareBitmapDetectorTest : LintDetectorTest() { override fun getIssues(): List = listOf(SoftwareBitmapDetector.ISSUE) - private val explanation = "Usage of Config.HARDWARE is highly encouraged." - @Test fun testSoftwareBitmap() { lint() @@ -41,7 +39,7 @@ class SoftwareBitmapDetectorTest : LintDetectorTest() { """ import android.graphics.Bitmap; - public class TestClass1 { + public class TestClass { public void test() { Bitmap.createBitmap(300, 300, Bitmap.Config.RGB_565); Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888); @@ -54,8 +52,17 @@ class SoftwareBitmapDetectorTest : LintDetectorTest() { ) .issues(SoftwareBitmapDetector.ISSUE) .run() - .expectWarningCount(2) - .expectContains(explanation) + .expect( + """ + src/android/graphics/Bitmap.java:5: Warning: Replace software bitmap with Config.HARDWARE [SoftwareBitmap] + ARGB_8888, + ~~~~~~~~~ + src/android/graphics/Bitmap.java:6: Warning: Replace software bitmap with Config.HARDWARE [SoftwareBitmap] + RGB_565, + ~~~~~~~ + 0 errors, 2 warnings + """ + ) } @Test @@ -66,7 +73,7 @@ class SoftwareBitmapDetectorTest : LintDetectorTest() { """ import android.graphics.Bitmap; - public class TestClass1 { + public class TestClass { public void test() { Bitmap.createBitmap(300, 300, Bitmap.Config.HARDWARE); } @@ -78,7 +85,7 @@ class SoftwareBitmapDetectorTest : LintDetectorTest() { ) .issues(SoftwareBitmapDetector.ISSUE) .run() - .expectWarningCount(0) + .expectClean() } private val stubs = androidStubs diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java index 3e445ddfc2a18..d393680124875 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java @@ -36,6 +36,7 @@ import android.util.ArraySet; import android.util.Log; import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Main; @@ -182,6 +183,10 @@ public class TileLifecycleManager extends BroadcastReceiver implements setBindService(true); } + /** + * Binds or unbinds to IQSService + */ + @WorkerThread public void setBindService(boolean bind) { if (mBound && mUnbindImmediate) { // If we are already bound and expecting to unbind, this means we should stay bound