Add utility lint for metrics on @EnforcePermission

Bug: 298285238
Test: lint_fix --print --no-fix --check AnnotatedAidlCounter --lint-module AndroidUtilsLintChecker services.autofill
Test: atest --host AndroidUtilsLintCheckerTest
Change-Id: I7876e44cabc006de9b996f84477e15071cb95203
diff --git a/tools/lint/common/Android.bp b/tools/lint/common/Android.bp
index 898f88b..8bfbfe5 100644
--- a/tools/lint/common/Android.bp
+++ b/tools/lint/common/Android.bp
@@ -27,3 +27,30 @@
     libs: ["lint_api"],
     kotlincflags: ["-Xjvm-default=all"],
 }
+
+java_defaults {
+    name: "AndroidLintCheckerTestDefaults",
+    srcs: ["checks/src/test/java/**/*.kt"],
+    static_libs: [
+        "junit",
+        "lint",
+        "lint_tests",
+    ],
+    test_options: {
+        unit_test: true,
+        tradefed_options: [
+            {
+                // lint bundles in some classes that were built with older versions
+                // of libraries, and no longer load. Since tradefed tries to load
+                // all classes in the jar to look for tests, it crashes loading them.
+                // Exclude these classes from tradefed's search.
+                name: "exclude-paths",
+                value: "org/apache",
+            },
+            {
+                name: "exclude-paths",
+                value: "META-INF",
+            },
+        ],
+    },
+}
diff --git a/tools/lint/framework/Android.bp b/tools/lint/framework/Android.bp
index 30a6daa..5acdf43 100644
--- a/tools/lint/framework/Android.bp
+++ b/tools/lint/framework/Android.bp
@@ -37,28 +37,9 @@
 
 java_test_host {
     name: "AndroidFrameworkLintCheckerTest",
+    defaults: ["AndroidLintCheckerTestDefaults"],
     srcs: ["checks/src/test/java/**/*.kt"],
     static_libs: [
         "AndroidFrameworkLintChecker",
-        "junit",
-        "lint",
-        "lint_tests",
     ],
-    test_options: {
-        unit_test: true,
-        tradefed_options: [
-            {
-                // lint bundles in some classes that were built with older versions
-                // of libraries, and no longer load. Since tradefed tries to load
-                // all classes in the jar to look for tests, it crashes loading them.
-                // Exclude these classes from tradefed's search.
-                name: "exclude-paths",
-                value: "org/apache",
-            },
-            {
-                name: "exclude-paths",
-                value: "META-INF",
-            },
-        ],
-    },
 }
diff --git a/tools/lint/global/Android.bp b/tools/lint/global/Android.bp
index bedb7bd..3e74171 100644
--- a/tools/lint/global/Android.bp
+++ b/tools/lint/global/Android.bp
@@ -38,28 +38,9 @@
 
 java_test_host {
     name: "AndroidGlobalLintCheckerTest",
+    defaults: ["AndroidLintCheckerTestDefaults"],
     srcs: ["checks/src/test/java/**/*.kt"],
     static_libs: [
         "AndroidGlobalLintChecker",
-        "junit",
-        "lint",
-        "lint_tests",
     ],
-    test_options: {
-        unit_test: true,
-        tradefed_options: [
-            {
-                // lint bundles in some classes that were built with older versions
-                // of libraries, and no longer load. Since tradefed tries to load
-                // all classes in the jar to look for tests, it crashes loading them.
-                // Exclude these classes from tradefed's search.
-                name: "exclude-paths",
-                value: "org/apache",
-            },
-            {
-                name: "exclude-paths",
-                value: "META-INF",
-            },
-        ],
-    },
 }
diff --git a/tools/lint/utils/Android.bp b/tools/lint/utils/Android.bp
new file mode 100644
index 0000000..75e8d68
--- /dev/null
+++ b/tools/lint/utils/Android.bp
@@ -0,0 +1,45 @@
+// 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_library_host {
+    name: "AndroidUtilsLintChecker",
+    srcs: ["checks/src/main/java/**/*.kt"],
+    plugins: ["auto_service_plugin"],
+    libs: [
+        "auto_service_annotations",
+        "lint_api",
+    ],
+    static_libs: [
+        "AndroidCommonLint",
+    ],
+    kotlincflags: ["-Xjvm-default=all"],
+}
+
+java_test_host {
+    name: "AndroidUtilsLintCheckerTest",
+    defaults: ["AndroidLintCheckerTestDefaults"],
+    srcs: ["checks/src/test/java/**/*.kt"],
+    static_libs: [
+        "AndroidUtilsLintChecker",
+    ],
+}
diff --git a/tools/lint/utils/checks/src/main/java/com/google/android/lint/AndroidUtilsIssueRegistry.kt b/tools/lint/utils/checks/src/main/java/com/google/android/lint/AndroidUtilsIssueRegistry.kt
new file mode 100644
index 0000000..fa61c42
--- /dev/null
+++ b/tools/lint/utils/checks/src/main/java/com/google/android/lint/AndroidUtilsIssueRegistry.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.google.android.lint
+
+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.google.android.lint.aidl.AnnotatedAidlCounter
+import com.google.auto.service.AutoService
+
+@AutoService(IssueRegistry::class)
+@Suppress("UnstableApiUsage")
+class AndroidUtilsIssueRegistry : IssueRegistry() {
+    override val issues = listOf(
+        AnnotatedAidlCounter.ISSUE_ANNOTATED_AIDL_COUNTER,
+    )
+
+    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=315013",
+        contact = "tweek@google.com"
+    )
+}
diff --git a/tools/lint/utils/checks/src/main/java/com/google/android/lint/aidl/AnnotatedAidlCounter.kt b/tools/lint/utils/checks/src/main/java/com/google/android/lint/aidl/AnnotatedAidlCounter.kt
new file mode 100644
index 0000000..f0ec3f4
--- /dev/null
+++ b/tools/lint/utils/checks/src/main/java/com/google/android/lint/aidl/AnnotatedAidlCounter.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.google.android.lint.aidl
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Context
+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 org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UMethod
+
+import java.util.TreeMap
+
+/**
+ *  Count the number of AIDL interfaces. Reports the number of annotated and
+ *  non-annotated methods.
+ */
+@Suppress("UnstableApiUsage")
+class AnnotatedAidlCounter : AidlImplementationDetector() {
+
+    private data class Stat(
+        var unannotated: Int = 0,
+        var enforced: Int = 0,
+        var notRequired: Int = 0,
+    )
+
+    private var packagesStats: TreeMap<String, Stat> = TreeMap<String, Stat>()
+
+    override fun visitAidlMethod(
+            context: JavaContext,
+            node: UMethod,
+            interfaceName: String,
+            body: UBlockExpression
+    ) {
+        val packageName = context.uastFile?.packageName ?: "<unknown>"
+        var packageStat = packagesStats.getOrDefault(packageName, Stat())
+        when {
+            node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION) -> packageStat.enforced += 1
+            node.hasAnnotation(ANNOTATION_REQUIRES_NO_PERMISSION) -> packageStat.notRequired += 1
+            else -> packageStat.unannotated += 1
+        }
+        packagesStats.put(packageName, packageStat)
+        // context.driver.client.log(null, "%s.%s#%s".format(packageName, interfaceName, node.name))
+    }
+
+    override fun afterCheckRootProject(context: Context) {
+        var total = Stat()
+        for ((packageName, stat) in packagesStats) {
+            context.client.log(null, "package $packageName => $stat")
+            total.unannotated += stat.unannotated
+            total.enforced += stat.enforced
+            total.notRequired += stat.notRequired
+        }
+        val location = Location.create(context.project.dir)
+        context.report(
+            ISSUE_ANNOTATED_AIDL_COUNTER,
+            location,
+            "module ${context.project.name} => $total"
+        )
+    }
+
+    companion object {
+
+        @JvmField
+        val ISSUE_ANNOTATED_AIDL_COUNTER = Issue.create(
+                id = "AnnotatedAidlCounter",
+                briefDescription = "Statistics on the number of annotated AIDL methods.",
+                explanation = "",
+                category = Category.SECURITY,
+                priority = 5,
+                severity = Severity.INFORMATIONAL,
+                implementation = Implementation(
+                        AnnotatedAidlCounter::class.java,
+                        Scope.JAVA_FILE_SCOPE
+                ),
+        )
+    }
+}
diff --git a/tools/lint/utils/checks/src/test/java/com/google/android/lint/aidl/AnnotatedAidlCounterTest.kt b/tools/lint/utils/checks/src/test/java/com/google/android/lint/aidl/AnnotatedAidlCounterTest.kt
new file mode 100644
index 0000000..692b7da
--- /dev/null
+++ b/tools/lint/utils/checks/src/test/java/com/google/android/lint/aidl/AnnotatedAidlCounterTest.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+@Suppress("UnstableApiUsage")
+class AnnotatedAidlCounterTest : LintDetectorTest() {
+    override fun getDetector(): Detector = AnnotatedAidlCounter()
+
+    override fun getIssues(): List<Issue> = listOf(
+        AnnotatedAidlCounter.ISSUE_ANNOTATED_AIDL_COUNTER,
+    )
+
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+    /** No issue scenario */
+
+    fun testDoesNotDetectIssuesCorrectAnnotationOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            import android.annotation.EnforcePermission;
+            public class TestClass2 extends IFooMethod.Stub {
+                @Override
+                @EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+                public void testMethod() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expect("""
+        app: Information: module app => Stat(unannotated=0, enforced=1, notRequired=0) [AnnotatedAidlCounter]
+        0 errors, 0 warnings
+        """)
+    }
+
+    // A service with permission annotation on the method.
+    private val interfaceIFooMethodStub: TestFile = java(
+        """
+        public interface IFooMethod extends android.os.IInterface {
+         public static abstract class Stub extends android.os.Binder implements IFooMethod {}
+          @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+          public void testMethod();
+        }
+        """
+    ).indented()
+
+    // A service without any permission annotation.
+    private val interfaceIBarStub: TestFile = java(
+        """
+        public interface IBar extends android.os.IInterface {
+         public static abstract class Stub extends android.os.Binder implements IBar {
+            @Override
+            public void testMethod() {}
+          }
+          public void testMethod();
+        }
+        """
+    ).indented()
+
+    private val manifestPermissionStub: TestFile = java(
+        """
+        package android.Manifest;
+        class permission {
+          public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
+        }
+        """
+    ).indented()
+
+    private val enforcePermissionAnnotationStub: TestFile = java(
+        """
+        package android.annotation;
+        public @interface EnforcePermission {}
+        """
+    ).indented()
+
+    private val stubs = arrayOf(interfaceIFooMethodStub, interfaceIBarStub,
+            manifestPermissionStub, enforcePermissionAnnotationStub)
+}