diff options
3 files changed, 202 insertions, 0 deletions
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 new file mode 100644 index 000000000000..a629eeeb0102 --- /dev/null +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt @@ -0,0 +1,66 @@ +/* + * 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) + ) + } +} 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 a96850a4c00c..78c6d7267dba 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 @@ -29,6 +29,7 @@ class SystemUIIssueRegistry : IssueRegistry() { override val issues: List<Issue> get() = listOf(BindServiceViaContextDetector.ISSUE, BroadcastSentViaContextDetector.ISSUE, + GetMainLooperViaContextDetector.ISSUE, RegisterReceiverViaContextDetector.ISSUE ) diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt new file mode 100644 index 000000000000..ec761cd7660d --- /dev/null +++ b/packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt @@ -0,0 +1,135 @@ +/* + * 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.TestFile +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 + +class GetMainLooperViaContextDetectorTest : LintDetectorTest() { + + override fun getDetector(): Detector = GetMainLooperViaContextDetector() + 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." + + @Test + fun testGetMainThreadHandler() { + lint().files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + import android.os.Handler; + + public class TestClass1 { + public void test(Context context) { + Handler mainThreadHandler = context.getMainThreadHandler(); + } + } + """ + ).indented(), + *stubs) + .issues(GetMainLooperViaContextDetector.ISSUE) + .run() + .expectWarningCount(1) + .expectContains(explanation) + } + + @Test + fun testGetMainLooper() { + lint().files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + import android.os.Looper; + + public class TestClass1 { + public void test(Context context) { + Looper mainLooper = context.getMainLooper(); + } + } + """ + ).indented(), + *stubs) + .issues(GetMainLooperViaContextDetector.ISSUE) + .run() + .expectWarningCount(1) + .expectContains(explanation) + } + + @Test + fun testGetMainExecutor() { + lint().files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + import java.util.concurrent.Executor; + + public class TestClass1 { + public void test(Context context) { + Executor mainExecutor = context.getMainExecutor(); + } + } + """ + ).indented(), + *stubs) + .issues(GetMainLooperViaContextDetector.ISSUE) + .run() + .expectWarningCount(1) + .expectContains(explanation) + } + + private val contextStub: TestFile = java( + """ + package android.content; + import android.os.Handler;import android.os.Looper;import java.util.concurrent.Executor; + + public class Context { + public Looper getMainLooper() { return null; }; + public Executor getMainExecutor() { return null; }; + public Handler getMainThreadHandler() { return null; }; + } + """ + ) + + private val looperStub: TestFile = java( + """ + package android.os; + + public class Looper {} + """ + ) + + private val handlerStub: TestFile = java( + """ + package android.os; + + public class Handler {} + """ + ) + + private val stubs = arrayOf(contextStub, looperStub, handlerStub) +} |