Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 93b1dadb authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add a custom lint checker FeatureAutomotiveDetector" into main

parents 083adc2e ab4fb493
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ class AndroidFrameworkIssueRegistry : IssueRegistry() {
        PermissionAnnotationDetector.ISSUE_MISSING_PERMISSION_ANNOTATION,
        PermissionMethodDetector.ISSUE_PERMISSION_METHOD_USAGE,
        PermissionMethodDetector.ISSUE_CAN_BE_PERMISSION_METHOD,
        FeatureAutomotiveDetector.ISSUE,
    )

    override val api: Int
+90 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.detector.api.Category
import com.android.tools.lint.detector.api.ConstantEvaluator
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.intellij.psi.PsiMethod
import java.util.EnumSet
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.ULiteralExpression
import org.jetbrains.uast.UReferenceExpression

/**
 * A detector to check the usage of PackageManager.hasSystemFeature("
 * android.hardware.type.automotive") in CTS tests.
 */
class FeatureAutomotiveDetector : Detector(), SourceCodeScanner {

    companion object {

        val EXPLANATION =
            """
            This class uses PackageManager.hasSystemFeature(\"android.hardware.type.automotive\") \
            or other equivalent methods. \
            If it is used to make a CTS test behave differently on AAOS, you should use \
            @RequireAutomotive or @RequireNotAutomotive instead; otherwise, please ignore this \
            warning. See https://g3doc.corp.google.com/wireless/android/partner/compatibility/\
            g3doc/dev/write-a-test/index.md#write-a-test-that-behaves-differently-on-aaos
            """

        val ISSUE: Issue =
            Issue.create(
                id = "UsingFeatureAutomotiveInCTS",
                briefDescription =
                    "PackageManager.hasSystemFeature(\"" +
                        " android.hardware.type.automotive\") is used in CTS tests",
                explanation = EXPLANATION,
                category = Category.TESTING,
                priority = 8,
                severity = Severity.WARNING,
                implementation =
                    Implementation(
                        FeatureAutomotiveDetector::class.java,
                        EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
                    )
            )
    }

    override fun getApplicableMethodNames() =
        listOf("hasSystemFeature", "hasFeature", "hasDeviceFeature", "bypassTestForFeatures")

    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
        node.valueArguments.forEach {
            val value =
                when (it) {
                    is ULiteralExpression -> it.value
                    is UReferenceExpression -> ConstantEvaluator.evaluate(context, it)
                    else -> null
                }
            if (value is String && value == "android.hardware.type.automotive") {
                context.report(
                    issue = ISSUE,
                    location = context.getNameLocation(method),
                    message = EXPLANATION
                )
            }
        }
    }
}
+366 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.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
import org.junit.Test

@Suppress("UnstableApiUsage")
class FeatureAutomotiveDetectorTest : LintDetectorTest() {
    val explanation =
        FeatureAutomotiveDetector.EXPLANATION.replace("\\", "").replace("\n            ", "") +
            " [UsingFeatureAutomotiveInCTS]"

    override fun getDetector(): Detector = FeatureAutomotiveDetector()
    override fun getIssues(): List<Issue> = listOf(FeatureAutomotiveDetector.ISSUE)
    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)

    @Test
    fun testWarning1() {
        lint()
            .files(
                java(
                        """
                import android.content.pm.PackageManager;

                public class Foo {

                    private void fun() {
                        PackageManager.getInstance().hasSystemFeature(
                            "android.hardware.type.automotive");
                    }
                }
                """
                    )
                    .indented(),
                *stubs
            )
            .issues(FeatureAutomotiveDetector.ISSUE)
            .run()
            .expect(
                """
                            src/android/content/pm/PackageManager.java:13: Warning: $explanation
                public boolean hasSystemFeature(String feature) {
                               ~~~~~~~~~~~~~~~~
    0 errors, 1 warnings
                            """
            )
    }

    @Test
    fun testWarning2() {
        lint()
            .files(
                java(
                        """
                import android.content.pm.PackageManager;

                public class Foo {

                    private void fun() {
                        String featureName = "android.hardware.type.automotive";
                        PackageManager.getInstance().hasSystemFeature(featureName);
                    }
                }
                """
                    )
                    .indented(),
                *stubs
            )
            .issues(FeatureAutomotiveDetector.ISSUE)
            .run()
            .expect(
                """
                            src/android/content/pm/PackageManager.java:13: Warning: $explanation
                public boolean hasSystemFeature(String feature) {
                               ~~~~~~~~~~~~~~~~
    0 errors, 1 warnings
                            """
            )
    }

    @Test
    fun testWarning3() {
        lint()
            .files(
                java(
                        """
                import android.content.pm.PackageManager;

                public class Foo {

                    private void fun() {
                        PackageManager.getInstance().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
                    }
                }
                """
                    )
                    .indented(),
                *stubs
            )
            .issues(FeatureAutomotiveDetector.ISSUE)
            .run()
            .expect(
                """
                            src/android/content/pm/PackageManager.java:13: Warning: $explanation
                public boolean hasSystemFeature(String feature) {
                               ~~~~~~~~~~~~~~~~
    0 errors, 1 warnings
                            """
            )
    }

    @Test
    fun testWarning4() {
        lint()
            .files(
                java(
                        """
                import android.content.pm.PackageManager;

                public class Foo {

                    private void fun() {
                        String featureName = PackageManager.FEATURE_AUTOMOTIVE;
                        PackageManager.getInstance().hasSystemFeature(featureName);
                    }
                }
                """
                    )
                    .indented(),
                *stubs
            )
            .issues(FeatureAutomotiveDetector.ISSUE)
            .run()
            .expect(
                """
                            src/android/content/pm/PackageManager.java:13: Warning: $explanation
                public boolean hasSystemFeature(String feature) {
                               ~~~~~~~~~~~~~~~~
    0 errors, 1 warnings
                            """
            )
    }

    @Test
    fun testWarning5() {
        lint()
            .files(
                java(
                        """
                import com.android.example.Utils;

                public class Foo {

                    private void fun() {
                        Utils.hasFeature("android.hardware.type.automotive");
                    }
                }
                """
                    )
                    .indented(),
                *stubs
            )
            .issues(FeatureAutomotiveDetector.ISSUE)
            .run()
            .expect(
                """
                    src/com/android/example/Utils.java:7: Warning: $explanation
                public static boolean hasFeature(String feature) {
                                      ~~~~~~~~~~
    0 errors, 1 warnings
                            """
            )
    }

    @Test
    fun testWarning6() {
        lint()
            .files(
                java(
                        """
                import com.android.example.Utils;

                public class Foo {

                    private void fun() {
                        Utils.hasDeviceFeature("android.hardware.type.automotive");
                    }
                }
                """
                    )
                    .indented(),
                *stubs
            )
            .issues(FeatureAutomotiveDetector.ISSUE)
            .run()
            .expect(
                """
                    src/com/android/example/Utils.java:11: Warning: $explanation
                public static boolean hasDeviceFeature(String feature) {
                                      ~~~~~~~~~~~~~~~~
    0 errors, 1 warnings
                            """
            )
    }

    @Test
    fun testWarning7() {
        lint()
            .files(
                java(
                        """
                import com.android.example.Utils;

                public class Foo {

                    private void fun() {
                        Utils.hasFeature(new Object(), "android.hardware.type.automotive");
                    }
                }
                """
                    )
                    .indented(),
                *stubs
            )
            .issues(FeatureAutomotiveDetector.ISSUE)
            .run()
            .expect(
                """
                    src/com/android/example/Utils.java:15: Warning: $explanation
                public static boolean hasFeature(Object object, String feature) {
                                      ~~~~~~~~~~
    0 errors, 1 warnings
                            """
            )
    }

    @Test
    fun testWarning8() {
        lint()
            .files(
                java(
                        """
                import com.android.example.Utils;

                public class Foo {

                    private void fun() {
                        Utils.bypassTestForFeatures("android.hardware.type.automotive");
                    }
                }
                """
                    )
                    .indented(),
                *stubs
            )
            .issues(FeatureAutomotiveDetector.ISSUE)
            .run()
            .expect(
                """
                    src/com/android/example/Utils.java:19: Warning: $explanation
                public static boolean bypassTestForFeatures(String feature) {
                                      ~~~~~~~~~~~~~~~~~~~~~
    0 errors, 1 warnings
                            """
            )
    }

    @Test
    fun testNoWarning() {
        lint()
            .files(
                java(
                        """
                import android.content.pm.PackageManager;

                public class Foo {
                    private void fun() {
                        String featureName1 = "android.hardware.type.automotive";
                        String featureName2 = PackageManager.FEATURE_AUTOMOTIVE;
                        String notFeatureName = "FEATURE_AUTOMOTIVE";
                        PackageManager.getInstance().hasSystemFeature(notFeatureName);
                        /*
                        PackageManager.getInstance().hasSystemFeature(
                            "android.hardware.type.automotive");
                         */
                    }
                }
                """
                    )
                    .indented(),
                *stubs
            )
            .issues(FeatureAutomotiveDetector.ISSUE)
            .run()
            .expectClean()
    }

    private val pmStub: TestFile =
        java(
            """
        package android.content.pm;

        import java.lang.String;

        public class PackageManager {
            public static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";

            public static PackageManager getInstance() {
                return new PackageManager();
            }

            public boolean hasSystemFeature(String feature) {
                return true;
            }
        }
        """
        )

    private val exampleStub: TestFile =
        java(
            """
        package com.android.example;

        import java.lang.String;

        public class Utils {
            public static boolean hasFeature(String feature) {
                return true;
            }

            public static boolean hasDeviceFeature(String feature) {
                return true;
            }

            public static boolean hasFeature(Object object, String feature) {
                return true;
            }

            public static boolean bypassTestForFeatures(String feature) {
                return true;
            }
        }
        """
        )

    private val stubs = arrayOf(pmStub, exampleStub)
}