diff options
| author | 2025-02-18 15:44:05 -0800 | |
|---|---|---|
| committer | 2025-02-18 15:44:05 -0800 | |
| commit | 07fa60dbd228db213ab6acfbfbb11aea1057afc1 (patch) | |
| tree | b5f5c0f0c665b9b85a60e224f14dbf5dd31b6721 | |
| parent | 4e42aebcc916d9b9c16fbaad472e773f8df9c22d (diff) | |
| parent | fda55e5c6fe0681e2c8900070e25376f5ad411d4 (diff) | |
Merge "Linter for shade dialog creation" into main
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") + } +} |