1 /* 2 * Copyright (C) 2011 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.tools.lint.checks; 18 19 import static com.android.tools.lint.detector.api.LintUtils.assertionsEnabled; 20 import static com.android.tools.lint.detector.api.LintUtils.endsWith; 21 22 import com.android.prefs.AndroidLocation; 23 import com.android.prefs.AndroidLocation.AndroidLocationException; 24 import com.android.tools.lint.client.api.IssueRegistry; 25 import com.android.tools.lint.detector.api.Issue; 26 import com.google.common.annotations.Beta; 27 28 import java.io.File; 29 import java.io.IOException; 30 import java.net.MalformedURLException; 31 import java.net.URL; 32 import java.net.URLClassLoader; 33 import java.util.ArrayList; 34 import java.util.Collections; 35 import java.util.HashSet; 36 import java.util.List; 37 import java.util.Set; 38 import java.util.jar.Attributes; 39 import java.util.jar.JarFile; 40 import java.util.jar.Manifest; 41 42 /** Registry which provides a list of checks to be performed on an Android project */ 43 public class BuiltinIssueRegistry extends IssueRegistry { 44 /** Folder name in the .android dir where additional detector jars are found */ 45 private static final String LINT_FOLDER = "lint"; //$NON-NLS-1$ 46 47 /** 48 * Manifest constant for declaring an issue provider. Example: 49 * Lint-Issues: foo.bar.CustomIssueRegistry 50 */ 51 private static final String MF_LINT_REGISTRY = "Lint-Registry"; //$NON-NLS-1$ 52 53 private static final List<Issue> sIssues; 54 55 static { 56 final int initialCapacity = 88; 57 List<Issue> issues = new ArrayList<Issue>(initialCapacity); 58 59 issues.add(AccessibilityDetector.ISSUE); 60 issues.add(MathDetector.ISSUE); 61 issues.add(FieldGetterDetector.ISSUE); 62 issues.add(SdCardDetector.ISSUE); 63 issues.add(ApiDetector.UNSUPPORTED); 64 issues.add(DuplicateIdDetector.CROSS_LAYOUT); 65 issues.add(DuplicateIdDetector.WITHIN_LAYOUT); 66 issues.add(WrongIdDetector.UNKNOWN_ID); 67 issues.add(WrongIdDetector.UNKNOWN_ID_LAYOUT); 68 issues.add(StateListDetector.ISSUE); 69 issues.add(StyleCycleDetector.ISSUE); 70 issues.add(InefficientWeightDetector.INEFFICIENT_WEIGHT); 71 issues.add(InefficientWeightDetector.NESTED_WEIGHTS); 72 issues.add(InefficientWeightDetector.BASELINE_WEIGHTS); 73 issues.add(ScrollViewChildDetector.ISSUE); 74 issues.add(DeprecationDetector.ISSUE); 75 issues.add(ObsoleteLayoutParamsDetector.ISSUE); 76 issues.add(MergeRootFrameLayoutDetector.ISSUE); 77 issues.add(NestedScrollingWidgetDetector.ISSUE); 78 issues.add(ChildCountDetector.SCROLLVIEW_ISSUE); 79 issues.add(ChildCountDetector.ADAPTERVIEW_ISSUE); 80 issues.add(UseCompoundDrawableDetector.ISSUE); 81 issues.add(UselessViewDetector.USELESS_PARENT); 82 issues.add(UselessViewDetector.USELESS_LEAF); 83 issues.add(TooManyViewsDetector.TOO_MANY); 84 issues.add(TooManyViewsDetector.TOO_DEEP); 85 issues.add(GridLayoutDetector.ISSUE); 86 issues.add(OnClickDetector.ISSUE); 87 issues.add(RegistrationDetector.ISSUE); 88 issues.add(TranslationDetector.EXTRA); 89 issues.add(TranslationDetector.MISSING); 90 issues.add(HardcodedValuesDetector.ISSUE); 91 issues.add(Utf8Detector.ISSUE); 92 issues.add(ProguardDetector.WRONGKEEP); 93 issues.add(ProguardDetector.SPLITCONFIG); 94 issues.add(PxUsageDetector.ISSUE); 95 issues.add(TextFieldDetector.ISSUE); 96 issues.add(TextViewDetector.ISSUE); 97 issues.add(UnusedResourceDetector.ISSUE); 98 issues.add(UnusedResourceDetector.ISSUE_IDS); 99 issues.add(ExtraTextDetector.ISSUE); 100 issues.add(PrivateResourceDetector.ISSUE); 101 issues.add(ArraySizeDetector.INCONSISTENT); 102 issues.add(HardcodedDebugModeDetector.ISSUE); 103 issues.add(ManifestOrderDetector.ORDER); 104 issues.add(ManifestOrderDetector.USES_SDK); 105 issues.add(ManifestOrderDetector.MULTIPLE_USES_SDK); 106 issues.add(ManifestOrderDetector.WRONG_PARENT); 107 issues.add(SecurityDetector.EXPORTED_PROVIDER); 108 issues.add(SecurityDetector.EXPORTED_SERVICE); 109 issues.add(SecurityDetector.OPEN_PROVIDER); 110 issues.add(SecurityDetector.WORLD_READABLE); 111 issues.add(SecurityDetector.WORLD_WRITEABLE); 112 issues.add(IconDetector.GIF_USAGE); 113 issues.add(IconDetector.ICON_DENSITIES); 114 issues.add(IconDetector.ICON_MISSING_FOLDER); 115 issues.add(IconDetector.ICON_DIP_SIZE); 116 issues.add(IconDetector.ICON_EXPECTED_SIZE); 117 issues.add(IconDetector.ICON_LOCATION); 118 issues.add(IconDetector.DUPLICATES_NAMES); 119 issues.add(IconDetector.DUPLICATES_CONFIGURATIONS); 120 issues.add(IconDetector.ICON_NODPI); 121 issues.add(TypographyDetector.DASHES); 122 issues.add(TypographyDetector.QUOTES); 123 issues.add(TypographyDetector.FRACTIONS); 124 issues.add(TypographyDetector.ELLIPSIS); 125 issues.add(TypographyDetector.OTHER); 126 issues.add(ButtonDetector.ORDER); 127 issues.add(ButtonDetector.CASE); 128 issues.add(ButtonDetector.BACKBUTTON); 129 issues.add(DetectMissingPrefix.MISSING_NAMESPACE); 130 issues.add(OverdrawDetector.ISSUE); 131 issues.add(StringFormatDetector.INVALID); 132 issues.add(StringFormatDetector.ARG_COUNT); 133 issues.add(StringFormatDetector.ARG_TYPES); 134 issues.add(ViewTypeDetector.ISSUE); 135 issues.add(WrongImportDetector.ISSUE); 136 issues.add(ViewConstructorDetector.ISSUE); 137 issues.add(NamespaceDetector.CUSTOMVIEW); 138 issues.add(NamespaceDetector.UNUSED); 139 issues.add(NamespaceDetector.TYPO); 140 issues.add(AlwaysShowActionDetector.ISSUE); 141 issues.add(ColorUsageDetector.ISSUE); 142 issues.add(JavaPerformanceDetector.PAINT_ALLOC); 143 issues.add(JavaPerformanceDetector.USE_VALUEOF); 144 issues.add(JavaPerformanceDetector.USE_SPARSEARRAY); 145 issues.add(SetJavaScriptEnabledDetector.ISSUE); 146 issues.add(ToastDetector.ISSUE); 147 148 assert initialCapacity >= issues.size() : issues.size(); 149 150 addCustomIssues(issues); 151 152 sIssues = Collections.unmodifiableList(issues); 153 154 // Check that ids are unique 155 if (assertionsEnabled()) { 156 Set<String> ids = new HashSet<String>(); 157 for (Issue issue : sIssues) { 158 String id = issue.getId(); 159 assert !ids.contains(id) : "Duplicate id " + id; //$NON-NLS-1$ 160 ids.add(id); 161 } 162 } 163 } 164 165 /** 166 * Constructs a new {@link BuiltinIssueRegistry} 167 */ 168 public BuiltinIssueRegistry() { 169 } 170 171 @Override 172 public List<Issue> getIssues() { 173 return sIssues; 174 } 175 176 /** 177 * Add in custom issues registered by the user - via an environment variable 178 * or in the .android/lint directory. 179 */ 180 private static void addCustomIssues(List<Issue> issues) { 181 // Look for additional detectors registered by the user, via 182 // (1) an environment variable (useful for build servers etc), and 183 // (2) via jar files in the .android/lint directory 184 Set<File> files = null; 185 try { 186 File lint = new File(AndroidLocation.getFolder() + File.separator + LINT_FOLDER); 187 if (lint.exists()) { 188 File[] list = lint.listFiles(); 189 if (list != null) { 190 for (File jarFile : list) { 191 if (endsWith(jarFile.getName(), ".jar")) { //$NON-NLS-1$ 192 if (files == null) { 193 files = new HashSet<File>(); 194 } 195 files.add(jarFile); 196 addIssuesFromJar(jarFile, issues); 197 } 198 } 199 } 200 } 201 } catch (AndroidLocationException e) { 202 // Ignore -- no android dir, so no rules to load. 203 } 204 205 String lintClassPath = System.getenv("ANDROID_LINT_JARS"); //$NON-NLS-1$ 206 if (lintClassPath != null && lintClassPath.length() > 0) { 207 String[] paths = lintClassPath.split(File.pathSeparator); 208 for (String path : paths) { 209 File jarFile = new File(path); 210 if (jarFile.exists() && (files == null || !files.contains(jarFile))) { 211 addIssuesFromJar(jarFile, issues); 212 } 213 } 214 } 215 216 } 217 218 /** Add the issues found in the given jar file into the given list of issues */ 219 private static void addIssuesFromJar(File jarFile, List<Issue> issues) { 220 try { 221 JarFile jarfile = new JarFile(jarFile); 222 Manifest manifest = jarfile.getManifest(); 223 Attributes attrs = manifest.getMainAttributes(); 224 Object object = attrs.get(new Attributes.Name(MF_LINT_REGISTRY)); 225 if (object instanceof String) { 226 String className = (String) object; 227 228 // Make a class loader for this jar 229 try { 230 URL url = jarFile.toURI().toURL(); 231 URLClassLoader loader = new URLClassLoader(new URL[] { url }, 232 BuiltinIssueRegistry.class.getClassLoader()); 233 try { 234 Class<?> registryClass = Class.forName(className, true, loader); 235 IssueRegistry registry = (IssueRegistry) registryClass.newInstance(); 236 for (Issue issue : registry.getIssues()) { 237 issues.add(issue); 238 } 239 } catch (Throwable e) { 240 log(e); 241 } 242 } catch (MalformedURLException e) { 243 log(e); 244 } 245 } 246 } catch (IOException e) { 247 log(e); 248 } 249 } 250 251 private static void log(Throwable e) { 252 // TODO: Where do we log this? There's no embedding tool context here. For now, 253 // just dump to the console so detector developers get some feedback on what went 254 // wrong. 255 e.printStackTrace(); 256 } 257 258 private static Set<Issue> sAdtFixes; 259 260 /** 261 * Returns true if the given issue has an automatic IDE fix. 262 * 263 * @param tool the name of the tool to be checked 264 * @param issue the issue to be checked 265 * @return true if the given tool is known to have an automatic fix for the 266 * given issue 267 */ 268 @Beta 269 public boolean hasAutoFix(String tool, Issue issue) { 270 assert tool.equals("adt"); // This is not yet a generic facility; 271 // the primary purpose right now is to allow for example the HTML report 272 // to give a hint to the user that some fixes don't require manual work 273 274 if (sAdtFixes == null) { 275 sAdtFixes = new HashSet<Issue>(20); 276 sAdtFixes.add(InefficientWeightDetector.INEFFICIENT_WEIGHT); 277 sAdtFixes.add(AccessibilityDetector.ISSUE); 278 sAdtFixes.add(InefficientWeightDetector.BASELINE_WEIGHTS); 279 sAdtFixes.add(HardcodedValuesDetector.ISSUE); 280 sAdtFixes.add(UselessViewDetector.USELESS_LEAF); 281 sAdtFixes.add(UselessViewDetector.USELESS_PARENT); 282 sAdtFixes.add(PxUsageDetector.ISSUE); 283 sAdtFixes.add(TextFieldDetector.ISSUE); 284 sAdtFixes.add(SecurityDetector.EXPORTED_SERVICE); 285 sAdtFixes.add(DetectMissingPrefix.MISSING_NAMESPACE); 286 sAdtFixes.add(ScrollViewChildDetector.ISSUE); 287 sAdtFixes.add(ObsoleteLayoutParamsDetector.ISSUE); 288 sAdtFixes.add(TypographyDetector.DASHES); 289 sAdtFixes.add(TypographyDetector.ELLIPSIS); 290 sAdtFixes.add(TypographyDetector.FRACTIONS); 291 sAdtFixes.add(TypographyDetector.OTHER); 292 sAdtFixes.add(TypographyDetector.QUOTES); 293 sAdtFixes.add(UseCompoundDrawableDetector.ISSUE); 294 sAdtFixes.add(ApiDetector.UNSUPPORTED); 295 } 296 297 return sAdtFixes.contains(issue); 298 } 299 } 300