Adding linter for checking license (and copyright)  header in java/kotlin files

Fixes: 302679200
Test: MissingApacheLicenseDetectorTest
Test: run `m SYSTEM_UI_CORE_PATH/lint-report.html` and see legit license warnings/errors
Change-Id: I8c79e74f6a171964b30b93e029eeef0ef72a045c
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index ee05f2d..0284117 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -212,6 +212,7 @@
 
     lint: {
         extra_check_modules: ["SystemUILintChecker"],
+        warning_checks: ["MissingApacheLicenseDetector"],
     },
 }
 
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/MissingApacheLicenseDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/MissingApacheLicenseDetector.kt
new file mode 100644
index 0000000..46125be
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/MissingApacheLicenseDetector.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 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.ide.common.blame.SourcePosition
+import com.android.tools.lint.client.api.UElementHandler
+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.Location
+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 java.time.Year
+import org.jetbrains.uast.UComment
+import org.jetbrains.uast.UFile
+
+/**
+ * Checks if every AOSP Java/Kotlin source code file is starting with Apache license information.
+ */
+class MissingApacheLicenseDetector : Detector(), SourceCodeScanner {
+
+    override fun getApplicableUastTypes() = listOf(UFile::class.java)
+
+    override fun createUastHandler(context: JavaContext): UElementHandler? {
+        return object : UElementHandler() {
+            override fun visitFile(node: UFile) {
+                val firstComment = node.allCommentsInFile.firstOrNull()
+                // Normally we don't need to explicitly handle suppressing case and just return
+                // error as usual with indicating node and lint will ignore it for us. But here
+                // suppressing will be applied on top of comment that doesn't exist so we don't have
+                // node to return - it's a bit of corner case
+                if (firstComment != null && firstComment.isSuppressingComment()) {
+                    return
+                }
+                if (firstComment == null || !firstComment.isLicenseComment()) {
+                    val firstLineOfFile =
+                        Location.create(
+                            context.file,
+                            SourcePosition(/* lineNumber= */ 1, /* column= */ 1, /* offset= */ 0)
+                        )
+                    context.report(
+                        issue = ISSUE,
+                        location = firstLineOfFile,
+                        message =
+                            "License header is missing\n" +
+                                "Please add the following copyright and license header to the" +
+                                " beginning of the file:\n\n" +
+                                copyrightHeader
+                    )
+                }
+            }
+        }
+    }
+
+    private fun UComment.isSuppressingComment(): Boolean {
+        val suppressingComment =
+            "//noinspection ${MissingApacheLicenseDetector::class.java.simpleName}"
+        return text.contains(suppressingComment)
+    }
+
+    private fun UComment.isLicenseComment(): Boolean {
+        // We probably don't want to compare full copyright header in case there are some small
+        // discrepancies in already existing files, e.g. year. We could do regexp but it should be
+        // good enough if this detector deals with missing
+        // license header instead of incorrect license header
+        return text.contains("Apache License")
+    }
+
+    private val copyrightHeader: String
+        get() =
+            """
+            /*
+             * Copyright (C) ${Year.now().value} 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.
+             */
+            """
+                .trimIndent()
+                .trim()
+
+    companion object {
+        @JvmField
+        val ISSUE: Issue =
+            Issue.create(
+                id = "MissingApacheLicenseDetector",
+                briefDescription = "File is missing Apache license information",
+                explanation =
+                    """
+                        Every source code file should have copyright and license information \
+                        attached at the beginning.""",
+                category = Category.COMPLIANCE,
+                priority = 8,
+                // ignored by default and then explicitly overridden in SysUI's soong configuration
+                severity = Severity.IGNORE,
+                implementation =
+                    Implementation(MissingApacheLicenseDetector::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 520c888..e09aa42 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
@@ -42,6 +42,7 @@
                 StaticSettingsProviderDetector.ISSUE,
                 DemotingTestWithoutBugDetector.ISSUE,
                 TestFunctionNameViolationDetector.ISSUE,
+                MissingApacheLicenseDetector.ISSUE,
             )
 
     override val api: Int
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/MissingApacheLicenseDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/MissingApacheLicenseDetectorTest.kt
new file mode 100644
index 0000000..78e133f
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/MissingApacheLicenseDetectorTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 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.TestMode
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import java.time.Year
+import org.junit.Test
+
+class MissingApacheLicenseDetectorTest : SystemUILintDetectorTest() {
+    override fun getDetector(): Detector {
+        return MissingApacheLicenseDetector()
+    }
+
+    override fun getIssues(): List<Issue> {
+        return listOf(
+            MissingApacheLicenseDetector.ISSUE,
+        )
+    }
+
+    @Test
+    fun testHasCopyright() {
+        lint()
+            .files(
+                kotlin(
+                    """
+                    /*
+                     * Copyright (C) ${Year.now().value} 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 test.pkg.name
+
+                    class MyTest
+                    """
+                        .trimIndent()
+                )
+            )
+            .issues(MissingApacheLicenseDetector.ISSUE)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testDoesntHaveCopyright() {
+        lint()
+            .files(
+                kotlin(
+                    """
+                    package test.pkg.name
+
+                    class MyTest
+                    """
+                        .trimIndent()
+                )
+            )
+            // skipping mode SUPPRESSIBLE because lint tries to add @Suppress to class which
+            // probably doesn't make much sense for license header (which is far above it) and for
+            // kotlin files that can have several classes. If someone really wants to omit header
+            // they can do it with //noinspection
+            .skipTestModes(TestMode.SUPPRESSIBLE)
+            .issues(MissingApacheLicenseDetector.ISSUE)
+            .run()
+            .expectContains("License header is missing")
+    }
+}