Launcher3: Introduce lint checks for Launcher3.

As part of the initial check in, add a test to ensure
  that custom Dialogs are not utilized inside of the codebase.

Bug: 389709580
Test: DialogDetectorTest
Flag: NONE Lint checks
Change-Id: I7e3f98c729cdbf4d062419c53a209d12a23b1806
This commit is contained in:
Adnan Begovic
2025-01-28 14:51:25 -08:00
parent b3bb1f1c4a
commit a610285dfa
6 changed files with 297 additions and 0 deletions

View File

@@ -452,6 +452,7 @@ android_app {
"AndroidManifest-common.xml",
],
lint: {
extra_check_modules: ["Launcher3LintChecker"],
baseline_filename: "lint-baseline.xml",
},
}

46
checks/Android.bp Normal file
View File

@@ -0,0 +1,46 @@
// 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 {
default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
default_applicable_licenses: ["Android-Apache-2.0"],
}
java_library_host {
name: "Launcher3LintChecker",
srcs: ["src/**/*.kt"],
plugins: ["auto_service_plugin"],
libs: [
"auto_service_annotations",
"lint_api",
],
kotlincflags: ["-Xjvm-default=all"],
}
java_test_host {
name: "Launcher3LintCheckerTest",
defaults: ["AndroidLintCheckerTestDefaults"],
srcs: ["tests/**/*.kt"],
data: [
":androidx.annotation_annotation",
":dagger2",
":kotlinx-coroutines-core",
],
device_common_data: [
":framework",
],
static_libs: [
"Launcher3LintChecker",
],
}

View File

@@ -0,0 +1,68 @@
/*
* 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.
*/
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 org.jetbrains.uast.UClass
/** Detector to identify custom usage of Android's Dialog within the Launcher3 codebase. */
class CustomDialogDetector : Detector(), SourceCodeScanner {
override fun applicableSuperClasses(): List<String> {
return listOf(DIALOG_CLASS_NAME)
}
override fun visitClass(context: JavaContext, declaration: UClass) {
val superTypeClassNames = declaration.superTypes.mapNotNull { it.resolve()?.qualifiedName }
if (superTypeClassNames.contains(DIALOG_CLASS_NAME)) {
context.report(
ISSUE,
declaration,
context.getNameLocation(declaration),
"Class implements Dialog",
)
}
}
companion object {
private const val DIALOG_CLASS_NAME = "android.app.Dialog"
@JvmField
val ISSUE =
Issue.create(
id = "IllegalUseOfCustomDialog",
briefDescription = "dialogs should not be used in Launcher",
explanation =
"""
Don't use custom Dialogs within the launcher code base, instead consider utilizing
AbstractFloatingView to display content that should float above the launcher where
it can be correctly managed for dismissal.
"""
.trimIndent(),
category = Category.CORRECTNESS,
priority = 10,
severity = Severity.ERROR,
implementation =
Implementation(CustomDialogDetector::class.java, Scope.JAVA_FILE_SCOPE),
)
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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.launcher3.lint
import CustomDialogDetector
import com.android.tools.lint.client.api.IssueRegistry
import com.android.tools.lint.client.api.Vendor
import com.android.tools.lint.detector.api.CURRENT_API
import com.android.tools.lint.detector.api.Issue
import com.google.auto.service.AutoService
@AutoService(IssueRegistry::class)
@Suppress("UnstableApiUsage")
class Launcher3IssueRegistry : IssueRegistry() {
override val issues: List<Issue>
get() = listOf(CustomDialogDetector.ISSUE)
override val api: Int
get() = CURRENT_API
override val minApi: Int
get() = 8
override val vendor: Vendor =
Vendor(
vendorName = "Android",
feedbackUrl = "http://b/issues/new?component=78010",
contact = "abegovic@google.com",
)
}

View File

@@ -0,0 +1,78 @@
/*
* 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.launcher3.lint
import CustomDialogDetector
import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
/** Test for [CustomDialogDetector]. */
class CustomDialogDetectorTest : Launcher3LintDetectorTest() {
override fun getDetector(): Detector = CustomDialogDetector()
override fun getIssues(): List<Issue> = listOf(CustomDialogDetector.ISSUE)
@Test
fun classDoesNotExtendDialog_noViolation() {
lint()
.files(
TestFiles.kotlin(
"""
package test.pkg
class SomeClass
"""
.trimIndent()
),
*androidStubs,
)
.issues(CustomDialogDetector.ISSUE)
.run()
.expectClean()
}
@Test
fun classDoesExtendDialog_violation() {
lint()
.files(
TestFiles.kotlin(
"""
package test.pkg
import android.app.Dialog
class SomeClass(context: Context) : Dialog(context)
"""
.trimIndent()
),
*androidStubs,
)
.issues(CustomDialogDetector.ISSUE)
.run()
.expect(
("""
src/test/pkg/SomeClass.kt:5: Error: Class implements Dialog [IllegalUseOfCustomDialog]
class SomeClass(context: Context) : Dialog(context)
~~~~~~~~~
1 errors, 0 warnings
""")
.trimIndent()
)
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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.launcher3.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 java.io.File
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
/**
* Abstract class that should be used by any test for launcher 3 lint detectors.
*
* When you write your test, ensure that you pass [androidStubs] as part of your [TestFiles]
* definition.
*/
@RunWith(JUnit4::class)
abstract class Launcher3LintDetectorTest : LintDetectorTest() {
/**
* Customize the lint task to disable SDK usage completely. This ensures that running the tests
* in Android Studio has the same result as running the tests in atest
*/
override fun lint(): TestLintTask =
super.lint().allowMissingSdk(true).sdkHome(File("/dev/null"))
companion object {
private val libraryNames =
arrayOf(
"androidx.annotation_annotation.jar",
"dagger2.jar",
"framework.jar",
"kotlinx-coroutines-core.jar",
)
/**
* 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.
*/
val androidStubs =
libraryNames
.map { TestFiles.LibraryReferenceTestFile(File(it).canonicalFile) }
.toTypedArray()
}
}