Merge "Update linter formatting, add new tests" into tm-qpr-dev

This commit is contained in:
Lucas Dupin
2022-10-13 18:12:28 +00:00
committed by Android (Google) Code Review
19 changed files with 636 additions and 334 deletions

View File

@@ -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<String> {
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
)
)
}
}

View File

@@ -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,

View File

@@ -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<String> {
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)
)
}
}

View File

@@ -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<String> {
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)
)
}
}

View File

@@ -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<String> {
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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -28,11 +28,11 @@ class SystemUIIssueRegistry : IssueRegistry() {
override val issues: List<Issue>
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,

View File

@@ -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 {
}
"""
),
)

View File

@@ -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<Issue> = 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
}

View File

@@ -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<Issue> = 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
}

View File

@@ -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);

View File

@@ -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<Issue> = listOf(GetMainLooperViaContextDetector.ISSUE)
private val explanation = "Please inject a @Main Executor instead."
override fun getIssues(): List<Issue> = 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

View File

@@ -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

View File

@@ -31,8 +31,6 @@ class RegisterReceiverViaContextDetectorTest : LintDetectorTest() {
override fun getIssues(): List<Issue> = 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

View File

@@ -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();
}

View File

@@ -31,8 +31,6 @@ class SoftwareBitmapDetectorTest : LintDetectorTest() {
override fun getIssues(): List<Issue> = 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

View File

@@ -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