1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemfeatures.errorprone; 18 19 import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; 20 21 import com.android.systemfeatures.RoSystemFeaturesMetadata; 22 23 import com.google.auto.service.AutoService; 24 import com.google.errorprone.BugPattern; 25 import com.google.errorprone.VisitorState; 26 import com.google.errorprone.bugpatterns.BugChecker; 27 import com.google.errorprone.fixes.SuggestedFix; 28 import com.google.errorprone.matchers.Description; 29 import com.google.errorprone.matchers.Matcher; 30 import com.google.errorprone.matchers.Matchers; 31 import com.google.errorprone.util.ASTHelpers; 32 import com.sun.source.tree.ExpressionTree; 33 import com.sun.source.tree.MethodInvocationTree; 34 import com.sun.tools.javac.code.Symbol; 35 36 @AutoService(BugChecker.class) 37 @BugPattern( 38 name = "RoSystemFeaturesChecker", 39 summary = "Use RoSystemFeature instead of PackageManager.hasSystemFeature", 40 explanation = 41 "Directly invoking `PackageManager.hasSystemFeature` is less efficient than using" 42 + " the `RoSystemFeatures` helper class. This check flags invocations like" 43 + " `context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FOO)`" 44 + " and suggests replacing them with" 45 + " `com.android.internal.pm.RoSystemFeatures.hasFeatureFoo(context)`.", 46 severity = WARNING) 47 public class RoSystemFeaturesChecker extends BugChecker 48 implements BugChecker.MethodInvocationTreeMatcher { 49 50 private static final String PACKAGE_MANAGER_CLASS = "android.content.pm.PackageManager"; 51 private static final String CONTEXT_CLASS = "android.content.Context"; 52 private static final String RO_SYSTEM_FEATURE_SIMPLE_CLASS = "RoSystemFeatures"; 53 private static final String RO_SYSTEM_FEATURE_CLASS = 54 "com.android.internal.pm." + RO_SYSTEM_FEATURE_SIMPLE_CLASS; 55 private static final String GET_PACKAGE_MANAGER_METHOD = "getPackageManager"; 56 private static final String HAS_SYSTEM_FEATURE_METHOD = "hasSystemFeature"; 57 private static final String FEATURE_PREFIX = "FEATURE_"; 58 59 private static final Matcher<ExpressionTree> HAS_SYSTEM_FEATURE_MATCHER = 60 Matchers.instanceMethod() 61 .onDescendantOf(PACKAGE_MANAGER_CLASS) 62 .named(HAS_SYSTEM_FEATURE_METHOD) 63 .withParameters(String.class.getName()); 64 65 private static final Matcher<ExpressionTree> GET_PACKAGE_MANAGER_MATCHER = 66 Matchers.instanceMethod() 67 .onDescendantOf(CONTEXT_CLASS) 68 .named(GET_PACKAGE_MANAGER_METHOD); 69 70 @Override matchMethodInvocation(MethodInvocationTree tree, VisitorState state)71 public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { 72 if (!HAS_SYSTEM_FEATURE_MATCHER.matches(tree, state)) { 73 return Description.NO_MATCH; 74 } 75 76 // Check if the PackageManager was obtained from a Context instance. 77 ExpressionTree packageManager = ASTHelpers.getReceiver(tree); 78 if (!GET_PACKAGE_MANAGER_MATCHER.matches(packageManager, state)) { 79 return Description.NO_MATCH; 80 } 81 82 // Get the feature argument and check if it's a PackageManager.FEATURE_X constant. 83 ExpressionTree feature = tree.getArguments().isEmpty() ? null : tree.getArguments().get(0); 84 Symbol featureSymbol = ASTHelpers.getSymbol(feature); 85 if (featureSymbol == null 86 || !featureSymbol.isStatic() 87 || !featureSymbol.getSimpleName().toString().startsWith(FEATURE_PREFIX) 88 || ASTHelpers.enclosingClass(featureSymbol) == null 89 || !ASTHelpers.enclosingClass(featureSymbol) 90 .getQualifiedName() 91 .contentEquals(PACKAGE_MANAGER_CLASS)) { 92 return Description.NO_MATCH; 93 } 94 95 // Check if the feature argument is part of the RoSystemFeatures API surface. 96 String featureName = featureSymbol.getSimpleName().toString(); 97 String methodName = RoSystemFeaturesMetadata.getMethodNameForFeatureName(featureName); 98 if (methodName == null) { 99 return Description.NO_MATCH; 100 } 101 102 // Generate the appropriate fix. 103 String replacement = 104 String.format( 105 "%s.%s(%s)", 106 RO_SYSTEM_FEATURE_SIMPLE_CLASS, 107 methodName, 108 state.getSourceForNode(ASTHelpers.getReceiver(packageManager))); 109 // Note that ErrorProne doesn't offer a seamless way of removing the `PackageManager` import 110 // if unused after fix application, so for now we only offer best effort import suggestions. 111 SuggestedFix fix = 112 SuggestedFix.builder() 113 .replace(tree, replacement) 114 .addImport(RO_SYSTEM_FEATURE_CLASS) 115 .removeStaticImport(PACKAGE_MANAGER_CLASS + "." + featureName) 116 .build(); 117 return describeMatch(tree, fix); 118 } 119 } 120