summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Daniel Akinola <dakinola@google.com> 2025-02-18 15:44:05 -0800
committer Android (Google) Code Review <android-gerrit@google.com> 2025-02-18 15:44:05 -0800
commit07fa60dbd228db213ab6acfbfbb11aea1057afc1 (patch)
treeb5f5c0f0c665b9b85a60e224f14dbf5dd31b6721
parent4e42aebcc916d9b9c16fbaad472e773f8df9c22d (diff)
parentfda55e5c6fe0681e2c8900070e25376f5ad411d4 (diff)
Merge "Linter for shade dialog creation" into main
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt26
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDialogDetector.kt124
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt1
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt28
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDialogDetectorTest.kt541
5 files changed, 680 insertions, 40 deletions
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt
index 2a27a3033cf9..6eafb9f60b9e 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt
@@ -29,6 +29,14 @@ import com.intellij.psi.PsiParameter
import org.jetbrains.uast.UClass
import org.jetbrains.uast.getContainingUFile
+/**
+ * Lint check to ensure that when including a Context or Context-dependent argument in
+ * shade-relevant packages, the argument has the @ShadeDisplayAware annotation.
+ *
+ * This is to ensure that Context-dependent components correctly handle Configuration changes when
+ * the shade is moved to a different display. @ShadeDisplayAware-annotated components will update
+ * accordingly to reflect the new display.
+ */
class ShadeDisplayAwareDetector : Detector(), SourceCodeScanner {
override fun getApplicableUastTypes() = listOf(UClass::class.java)
@@ -38,8 +46,8 @@ class ShadeDisplayAwareDetector : Detector(), SourceCodeScanner {
for (constructor in node.constructors) {
// Visit all injected constructors in shade-relevant packages
if (!constructor.hasAnnotation(INJECT_ANNOTATION)) continue
- if (!isInRelevantShadePackage(node)) continue
- if (IGNORED_PACKAGES.contains(node.qualifiedName)) continue
+ if (!isInRelevantShadePackage(node.getContainingUFile()?.packageName)) continue
+ if (IGNORED_CLASSES.contains(node.qualifiedName)) continue
for (parameter in constructor.parameterList.parameters) {
if (parameter.shouldReport()) {
@@ -84,24 +92,19 @@ class ShadeDisplayAwareDetector : Detector(), SourceCodeScanner {
CONFIG_INTERACTOR,
)
- private val CONFIG_CLASSES = setOf(CONFIG_STATE, CONFIG_CONTROLLER, CONFIG_INTERACTOR)
-
private val SHADE_WINDOW_PACKAGES =
listOf(
"com.android.systemui.biometrics",
"com.android.systemui.bouncer",
"com.android.systemui.keyboard.docking.ui.viewmodel",
+ "com.android.systemui.media.controls.ui.controller",
"com.android.systemui.qs",
"com.android.systemui.shade",
- "com.android.systemui.statusbar.notification",
+ "com.android.systemui.statusbar.lockscreen",
"com.android.systemui.unfold.domain.interactor",
)
- private val IGNORED_PACKAGES =
- setOf(
- "com.android.systemui.biometrics.UdfpsController",
- "com.android.systemui.qs.customize.TileAdapter",
- )
+ private val IGNORED_CLASSES = setOf("com.android.systemui.statusbar.phone.SystemUIDialog")
private fun PsiParameter.shouldReport(): Boolean {
val className = type.canonicalText
@@ -116,8 +119,7 @@ class ShadeDisplayAwareDetector : Detector(), SourceCodeScanner {
return true
}
- private fun isInRelevantShadePackage(node: UClass): Boolean {
- val packageName = node.getContainingUFile()?.packageName
+ fun isInRelevantShadePackage(packageName: String?): Boolean {
if (packageName.isNullOrBlank()) return false
return SHADE_WINDOW_PACKAGES.any { relevantPackage ->
packageName.startsWith(relevantPackage)
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDialogDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDialogDetector.kt
new file mode 100644
index 000000000000..4cd5d89ea919
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDialogDetector.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2025 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.internal.systemui.lint.ShadeDisplayAwareDetector.Companion.isInRelevantShadePackage
+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.android.tools.lint.detector.api.UastLintUtils.Companion.tryResolveUDeclaration
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.getContainingUClass
+import org.jetbrains.uast.getContainingUFile
+
+/**
+ * Lint check to ensure that when creating dialogs shade-relevant packages, the correct Context is
+ * provided.
+ *
+ * This is to ensure that the dialog is created with the correct context when the shade is moved to
+ * a different display. When the shade is moved, the configuration might change, and only
+ * `@ShadeDisplayAware`-annotated components will update accordingly to reflect the new display.
+ *
+ * Example:
+ * ```kotlin
+ * class ExampleClass
+ * @Inject
+ * constructor(private val contextInteractor: ShadeDialogContextInteractor) {
+ *
+ * fun showDialog() {
+ * val dialog = systemUIDialogFactory.create(delegate, contextInteractor.context)
+ * dialog.show()
+ * }
+ * }
+ * ```
+ */
+// TODO: b/396066687 - update linter after refactoring to use ShadeDialogFactory
+class ShadeDisplayAwareDialogDetector : Detector(), SourceCodeScanner {
+ override fun getApplicableMethodNames(): List<String> = listOf(CREATE_METHOD)
+
+ override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+ if (!isInRelevantShadePackage(node.getContainingUFile()?.packageName)) return
+ if (!context.evaluator.isMemberInClass(method, SYSUI_DIALOG_FACTORY)) return
+ val contextArg =
+ node.valueArguments.find {
+ it.getExpressionType()?.canonicalText == "android.content.Context"
+ }
+ if (contextArg == null) {
+ context.report(
+ issue = ISSUE,
+ scope = node,
+ location = context.getNameLocation(node),
+ message =
+ "SystemUIDialog.Factory#create requires a Context that accounts for the " +
+ "shade's display. Use create(shadeDialogContextInteractor.getContext()) " +
+ "or create(shadeDialogContextInteractor.context) to provide the correct Context.",
+ )
+ } else {
+ val isProvidedByContextInteractor =
+ contextArg.tryResolveUDeclaration()?.getContainingUClass()?.qualifiedName ==
+ SHADE_DIALOG_CONTEXT_INTERACTOR
+
+ if (!isProvidedByContextInteractor) {
+ context.report(
+ issue = ISSUE,
+ scope = contextArg,
+ location = context.getNameLocation(contextArg),
+ message =
+ "In shade-relevant packages, SystemUIDialog.Factory#create must be called " +
+ "with the Context directly from ShadeDialogContextInteractor " +
+ "(ShadeDialogContextInteractor.context or getContext()). " +
+ "Avoid intermediate variables or function calls. This direct usage " +
+ "is required to ensure proper shade display handling.",
+ )
+ }
+ }
+ }
+
+ companion object {
+ private const val CREATE_METHOD = "create"
+ private const val SYSUI_DIALOG_FACTORY =
+ "com.android.systemui.statusbar.phone.SystemUIDialog.Factory"
+ private const val SHADE_DIALOG_CONTEXT_INTERACTOR =
+ "com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor"
+
+ @JvmField
+ val ISSUE: Issue =
+ Issue.create(
+ id = "ShadeDisplayAwareDialogChecker",
+ briefDescription = "Checking for shade display aware context when creating dialogs",
+ explanation =
+ """
+ Dialogs created by the notification shade must use a special Context to appear
+ on the correct display, especially when the shade is not on the default display.
+ """
+ .trimIndent(),
+ category = Category.CORRECTNESS,
+ priority = 8,
+ severity = Severity.WARNING,
+ implementation =
+ Implementation(
+ ShadeDisplayAwareDialogDetector::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 6d18f9377806..adb311610587 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
@@ -47,6 +47,7 @@ class SystemUIIssueRegistry : IssueRegistry() {
TestFunctionNameViolationDetector.ISSUE,
MissingApacheLicenseDetector.ISSUE,
ShadeDisplayAwareDetector.ISSUE,
+ ShadeDisplayAwareDialogDetector.ISSUE,
RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING,
RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR,
)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt
index 638d7cb7ee58..f8f8b40c4c01 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt
@@ -410,34 +410,6 @@ class ShadeDisplayAwareDetectorTest : SystemUILintDetectorTest() {
.expectClean()
}
- @Test
- fun injectedConstructor_inExemptPackage_withRelevantParameter_withoutAnnotation() {
- lint()
- .files(
- TestFiles.java(
- """
- package com.android.systemui.qs.customize;
-
- import javax.inject.Inject;
- import com.android.systemui.qs.dagger.QSThemedContext;
- import android.content.Context;
-
- public class TileAdapter {
- @Inject
- public TileAdapter(@QSThemedContext Context context) {}
- }
- """
- .trimIndent()
- ),
- *androidStubs,
- *otherStubs,
- )
- .issues(ShadeDisplayAwareDetector.ISSUE)
- .testModes(TestMode.DEFAULT)
- .run()
- .expectClean()
- }
-
private fun errorMsgString(lineNumber: Int, className: String) =
"src/com/android/systemui/shade/example/ExampleClass.kt:$lineNumber: Error: UI elements of " +
"the shade window should use ShadeDisplayAware-annotated $className, as the shade " +
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDialogDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDialogDetectorTest.kt
new file mode 100644
index 000000000000..86cdcdcf9644
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDialogDetectorTest.kt
@@ -0,0 +1,541 @@
+/*
+ * Copyright (C) 2025 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.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestMode
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class ShadeDisplayAwareDialogDetectorTest : SystemUILintDetectorTest() {
+ override fun getDetector(): Detector = ShadeDisplayAwareDialogDetector()
+
+ override fun getIssues(): List<Issue> = listOf(ShadeDisplayAwareDialogDetector.ISSUE)
+
+ private val injectStub: TestFile =
+ kotlin(
+ """
+ package javax.inject
+ @Retention(AnnotationRetention.RUNTIME) annotation class Inject
+ """
+ )
+ .indented()
+ private val shadeDisplayAwareStub: TestFile =
+ kotlin(
+ """
+ package com.android.systemui.shade
+ @Retention(AnnotationRetention.RUNTIME) annotation class ShadeDisplayAware
+ """
+ )
+ .indented()
+ private val mainStub: TestFile =
+ kotlin(
+ """
+ package com.android.systemui.dagger.qualifiers
+
+ @Retention(AnnotationRetention.RUNTIME) annotation class Main
+ """
+ )
+ .indented()
+ private val dialogContextStub: TestFile =
+ kotlin(
+ """
+ package com.android.systemui.shade.domain.interactor
+
+ import android.content.Context
+
+ interface ShadeDialogContextInteractor {
+ val context: Context
+ }
+ """
+ )
+ .indented()
+ private val delegateStub: TestFile =
+ java(
+ """
+ package com.android.systemui.statusbar.phone;
+
+ public interface Delegate { }
+ """
+ .trimIndent()
+ )
+ private val sysuiDialogStub: TestFile =
+ java(
+ """
+ package com.android.systemui.statusbar.phone;
+
+ import android.content.Context;
+ public class SystemUIDialog {
+ public SystemUIDialog(int id) { }
+
+ public static class Factory {
+ public SystemUIDialog create() {
+ return new SystemUIDialog();
+ }
+
+ public SystemUIDialog create(Context context) {
+ return new SystemUIDialog();
+ }
+
+ public SystemUIDialog create(Delegate delegate, Context context) {
+ return new SystemUIDialog();
+ }
+
+ public SystemUIDialog create(Delegate delegate, Context context,
+ boolean shouldAcsdDismissDialog) {
+ return new SystemUIDialog();
+ }
+
+ public SystemUIDialog create(Delegate delegate, Context context, int theme) {
+ return new SystemUIDialog();
+ }
+
+ public SystemUIDialog create(Delegate delegate) {
+ return new SystemUIDialog();
+ }
+ }
+ }
+ """
+ )
+ .indented()
+
+ private val otherStubs =
+ arrayOf(
+ injectStub,
+ shadeDisplayAwareStub,
+ mainStub,
+ delegateStub,
+ sysuiDialogStub,
+ dialogContextStub,
+ )
+
+ @Test
+ fun create_noArguments() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package com.android.systemui.shade.example
+ import javax.inject.Inject
+ import android.content.Context
+ import com.android.systemui.statusbar.phone.SystemUIDialog
+
+ class ExampleClass
+ @Inject
+ constructor(private val systemUIDialogFactory: SystemUIDialog.Factory) {
+
+ fun showDialog() {
+ val dialog = systemUIDialogFactory.create()
+ dialog.show()
+ }
+ }
+ """
+ .trimIndent()
+ ),
+ *androidStubs,
+ *otherStubs,
+ )
+ .issues(ShadeDisplayAwareDialogDetector.ISSUE)
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(
+ "SystemUIDialog.Factory#create requires a Context that accounts for the " +
+ "shade's display. Use create(shadeDialogContextInteractor.getContext()) " +
+ "or create(shadeDialogContextInteractor.context) to provide the correct Context."
+ )
+ .expectContains("[ShadeDisplayAwareDialogChecker]")
+ .expectContains("0 errors, 1 warning")
+ }
+
+ @Test
+ fun create_UnannotatedContext() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package com.android.systemui.shade.example
+ import javax.inject.Inject
+ import android.content.Context
+ import com.android.systemui.statusbar.phone.SystemUIDialog
+ class ExampleClass
+ @Inject
+ constructor(
+ private val context: Context,
+ private val systemUIDialogFactory: SystemUIDialog.Factory
+ ) {
+
+ fun showDialog() {
+ val dialog = systemUIDialogFactory.create(context)
+ dialog.show()
+ }
+ }
+ """
+ .trimIndent()
+ ),
+ *androidStubs,
+ *otherStubs,
+ )
+ .issues(ShadeDisplayAwareDialogDetector.ISSUE)
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(
+ "In shade-relevant packages, SystemUIDialog.Factory#create must be called " +
+ "with the Context directly from ShadeDialogContextInteractor " +
+ "(ShadeDialogContextInteractor.context or getContext()). " +
+ "Avoid intermediate variables or function calls. This direct usage " +
+ "is required to ensure proper shade display handling."
+ )
+ .expectContains("[ShadeDisplayAwareDialogChecker]")
+ .expectContains("0 errors, 1 warning")
+ }
+
+ @Test
+ fun create_annotatedContext() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package com.android.systemui.shade.example
+ import javax.inject.Inject
+ import android.content.Context
+ import com.android.systemui.statusbar.phone.SystemUIDialog
+ import com.android.systemui.shade.ShadeDisplayAware
+ class ExampleClass
+ @Inject
+ constructor(
+ @ShadeDisplayAware private val context: Context,
+ private val systemUIDialogFactory: SystemUIDialog.Factory
+ ) {
+
+ fun showDialog() {
+ val dialog = systemUIDialogFactory.create(context)
+ dialog.show()
+ }
+ }
+ """
+ .trimIndent()
+ ),
+ *androidStubs,
+ *otherStubs,
+ )
+ .issues(ShadeDisplayAwareDialogDetector.ISSUE)
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(
+ "In shade-relevant packages, SystemUIDialog.Factory#create must be called " +
+ "with the Context directly from ShadeDialogContextInteractor " +
+ "(ShadeDialogContextInteractor.context or getContext()). " +
+ "Avoid intermediate variables or function calls. This direct usage " +
+ "is required to ensure proper shade display handling."
+ )
+ .expectContains("[ShadeDisplayAwareDialogChecker]")
+ .expectContains("0 errors, 1 warning")
+ }
+
+ @Test
+ fun create_LocalVariable() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package com.android.systemui.shade.example
+ import javax.inject.Inject
+ import com.android.systemui.statusbar.phone.SystemUIDialog
+ import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
+
+ class ExampleClass
+ @Inject
+ constructor(
+ private val contextInteractor: ShadeDialogContextInteractor,
+ private val systemUIDialogFactory: SystemUIDialog.Factory
+ ) {
+
+ fun showDialog() {
+ val context2 = contextInteractor.context
+ val dialog = systemUIDialogFactory.create(context2)
+ dialog.show()
+ }
+ }
+ """
+ .trimIndent()
+ ),
+ *androidStubs,
+ *otherStubs,
+ )
+ .issues(ShadeDisplayAwareDialogDetector.ISSUE)
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(
+ "In shade-relevant packages, SystemUIDialog.Factory#create must be called " +
+ "with the Context directly from ShadeDialogContextInteractor " +
+ "(ShadeDialogContextInteractor.context or getContext()). " +
+ "Avoid intermediate variables or function calls. This direct usage " +
+ "is required to ensure proper shade display handling."
+ )
+ .expectContains("[ShadeDisplayAwareDialogChecker]")
+ .expectContains("0 errors, 1 warning")
+ }
+
+ @Test
+ fun create_delegate_UnannotatedContext() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package com.android.systemui.shade.example
+ import javax.inject.Inject
+ import android.content.Context
+ import com.android.systemui.statusbar.phone.SystemUIDialog
+ import com.android.systemui.statusbar.phone.Delegate
+ class ExampleClass
+ @Inject
+ constructor(
+ private val context: Context,
+ private val delegate: Delegate,
+ private val systemUIDialogFactory: SystemUIDialog.Factory
+ ) {
+
+ fun showDialog() {
+ val dialog = systemUIDialogFactory.create(delegate, context)
+ dialog.show()
+ }
+ }
+ """
+ .trimIndent()
+ ),
+ *androidStubs,
+ *otherStubs,
+ )
+ .issues(ShadeDisplayAwareDialogDetector.ISSUE)
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(
+ "In shade-relevant packages, SystemUIDialog.Factory#create must be called " +
+ "with the Context directly from ShadeDialogContextInteractor " +
+ "(ShadeDialogContextInteractor.context or getContext()). " +
+ "Avoid intermediate variables or function calls. This direct usage " +
+ "is required to ensure proper shade display handling."
+ )
+ .expectContains("[ShadeDisplayAwareDialogChecker]")
+ .expectContains("0 errors, 1 warning")
+ }
+
+ @Test
+ fun create_delegate_Context() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package com.android.systemui.shade.example
+ import javax.inject.Inject
+ import android.content.Context
+ import com.android.systemui.statusbar.phone.SystemUIDialog
+ import com.android.systemui.statusbar.phone.Delegate
+ import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
+
+ class ExampleClass
+ @Inject
+ constructor(
+ private val delegate: Delegate,
+ private val contextInteractor: ShadeDialogContextInteractor,
+ private val systemUIDialogFactory: SystemUIDialog.Factory
+ ) {
+
+ fun showDialog() {
+ val dialog = systemUIDialogFactory.create(delegate, contextInteractor.context)
+ dialog.show()
+ }
+ }
+ """
+ .trimIndent()
+ ),
+ *androidStubs,
+ *otherStubs,
+ )
+ .issues(ShadeDisplayAwareDialogDetector.ISSUE)
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun create_Delegate_Context_Boolean() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package com.android.systemui.shade.example
+ import javax.inject.Inject
+ import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
+ import com.android.systemui.statusbar.phone.SystemUIDialog
+ import com.android.systemui.statusbar.phone.Delegate
+
+ class ExampleClass
+ @Inject
+ constructor(
+ private val delegate: Delegate,
+ private val contextInteractor: ShadeDialogContextInteractor,
+ private val systemUIDialogFactory: SystemUIDialog.Factory
+ ) {
+
+ fun showDialog() {
+ val dialog = systemUIDialogFactory.create(delegate, contextInteractor.context, true)
+ dialog.show()
+ }
+ }
+ """
+ .trimIndent()
+ ),
+ *androidStubs,
+ *otherStubs,
+ )
+ .issues(ShadeDisplayAwareDialogDetector.ISSUE)
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun create_Delegate_UnannotatedContext_Int() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package com.android.systemui.shade.example
+ import javax.inject.Inject
+ import android.content.Context
+ import com.android.systemui.statusbar.phone.SystemUIDialog
+ import com.android.systemui.statusbar.phone.Delegate
+ class ExampleClass
+ @Inject
+ constructor(
+ private val context: Context,
+ private val delegate: Delegate,
+ private val systemUIDialogFactory: SystemUIDialog.Factory
+ ) {
+
+ fun showDialog() {
+ val dialog = systemUIDialogFactory.create(delegate, context, 0)
+ dialog.show()
+ }
+ }
+ """
+ .trimIndent()
+ ),
+ *androidStubs,
+ *otherStubs,
+ )
+ .issues(ShadeDisplayAwareDialogDetector.ISSUE)
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(
+ "In shade-relevant packages, SystemUIDialog.Factory#create must be called " +
+ "with the Context directly from ShadeDialogContextInteractor " +
+ "(ShadeDialogContextInteractor.context or getContext()). " +
+ "Avoid intermediate variables or function calls. This direct usage " +
+ "is required to ensure proper shade display handling."
+ )
+ .expectContains("[ShadeDisplayAwareDialogChecker]")
+ .expectContains("0 errors, 1 warning")
+ }
+
+ @Test
+ fun create_Delegate_Context_Int() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package com.android.systemui.shade.example
+ import javax.inject.Inject
+ import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
+ import com.android.systemui.statusbar.phone.SystemUIDialog
+ import com.android.systemui.statusbar.phone.Delegate
+
+ class ExampleClass
+ @Inject
+ constructor(
+ private val delegate: Delegate,
+ private val contextInteractor: ShadeDialogContextInteractor,
+ private val systemUIDialogFactory: SystemUIDialog.Factory
+ ) {
+
+ fun showDialog() {
+ val dialog = systemUIDialogFactory.create(delegate, contextInteractor.context, 0)
+ dialog.show()
+ }
+ }
+ """
+ .trimIndent()
+ ),
+ *androidStubs,
+ *otherStubs,
+ )
+ .issues(ShadeDisplayAwareDialogDetector.ISSUE)
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun create_Delegate() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package com.android.systemui.shade.example
+ import javax.inject.Inject
+ import com.android.systemui.statusbar.phone.Delegate
+ import com.android.systemui.statusbar.phone.SystemUIDialog
+
+ class ExampleClass
+ @Inject
+ constructor(
+ private val delegate: Delegate,
+ private val systemUIDialogFactory: SystemUIDialog.Factory
+ ) {
+
+ fun showDialog() {
+ val dialog = systemUIDialogFactory.create(delegate)
+ dialog.show()
+ }
+ }
+ """
+ .trimIndent()
+ ),
+ *androidStubs,
+ *otherStubs,
+ )
+ .issues(ShadeDisplayAwareDialogDetector.ISSUE)
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(
+ "SystemUIDialog.Factory#create requires a Context that accounts for the " +
+ "shade's display. Use create(shadeDialogContextInteractor.getContext()) " +
+ "or create(shadeDialogContextInteractor.context) to provide the correct Context."
+ )
+ .expectContains("[ShadeDisplayAwareDialogChecker]")
+ .expectContains("0 errors, 1 warning")
+ }
+}