@Override public boolean isIgnored( @NonNull Context context, @NonNull Issue issue, @Nullable Location location, @NonNull String message, @Nullable Object data) { ensureInitialized(); String id = issue.getId(); List<String> paths = mSuppressed.get(id); if (paths == null) { paths = mSuppressed.get(VALUE_ALL); } if (paths != null && location != null) { File file = location.getFile(); String relativePath = context.getProject().getRelativePath(file); for (String suppressedPath : paths) { if (suppressedPath.equals(relativePath)) { return true; } // Also allow a prefix if (relativePath.startsWith(suppressedPath)) { return true; } } } if (mRegexps != null) { List<Pattern> regexps = mRegexps.get(id); if (regexps == null) { regexps = mRegexps.get(VALUE_ALL); } if (regexps != null && location != null) { File file = location.getFile(); String relativePath = context.getProject().getRelativePath(file); boolean checkUnixPath = false; for (Pattern regexp : regexps) { Matcher matcher = regexp.matcher(relativePath); if (matcher.find()) { return true; } else if (regexp.pattern().indexOf('/') != -1) { checkUnixPath = true; } } if (checkUnixPath && CURRENT_PLATFORM == PLATFORM_WINDOWS) { relativePath = relativePath.replace('\\', '/'); for (Pattern regexp : regexps) { Matcher matcher = regexp.matcher(relativePath); if (matcher.find()) { return true; } } } } } return mParent != null && mParent.isIgnored(context, issue, location, message, data); }
@Override public void afterCheckProject(Context context) { // Make sure no File res = new File(context.getProject().getDir(), RES_FOLDER); if (res.isDirectory()) { File[] folders = res.listFiles(); if (folders != null) { boolean checkFolders = context.isEnabled(ICON_DENSITIES) || context.isEnabled(ICON_MISSING_FOLDER) || context.isEnabled(ICON_NODPI); boolean checkDipSizes = context.isEnabled(ICON_DIP_SIZE); boolean checkDuplicates = context.isEnabled(DUPLICATES_NAMES) || context.isEnabled(DUPLICATES_CONFIGURATIONS); Map<File, Dimension> pixelSizes = null; Map<File, Long> fileSizes = null; if (checkDipSizes || checkDuplicates) { pixelSizes = new HashMap<File, Dimension>(); fileSizes = new HashMap<File, Long>(); } Map<File, Set<String>> folderToNames = new HashMap<File, Set<String>>(); for (File folder : folders) { String folderName = folder.getName(); if (folderName.startsWith(DRAWABLE_FOLDER)) { File[] files = folder.listFiles(); if (files != null) { checkDrawableDir(context, folder, files, pixelSizes, fileSizes); if (checkFolders && DENSITY_PATTERN.matcher(folderName).matches()) { Set<String> names = new HashSet<String>(files.length); for (File f : files) { String name = f.getName(); if (hasBitmapExtension(name)) { names.add(f.getName()); } } folderToNames.put(folder, names); } } } } if (checkDipSizes) { checkDipSizes(context, pixelSizes); } if (checkDuplicates) { checkDuplicates(context, pixelSizes, fileSizes); } if (checkFolders && folderToNames.size() > 0) { checkDensities(context, res, folderToNames); } } } }
/** * Is this drawable folder for an Android 2.3 drawable? This will be the case if it specifies -v9 * or -v10, or if the minimum SDK version declared in the manifest is 9 or 10 (and it does not * specify some higher version like -v11 */ private boolean isAndroid23(Context context, int folderVersion) { if (isAndroid30(context, folderVersion)) { return false; } if (folderVersion == 9 || folderVersion == 10) { return true; } int minSdk = context.getProject().getMinSdk(); return minSdk == 9 || minSdk == 10; }
private void checkDensities(Context context, File res, Map<File, Set<String>> folderToNames) { // TODO: Is there a way to look at the manifest and figure out whether // all densities are expected to be needed? // Note: ldpi is probably not needed; it has very little usage // (about 2%; http://developer.android.com/resources/dashboard/screens.html) // TODO: Use the matrix to check out if we can eliminate densities based // on the target screens? Set<String> definedDensities = new HashSet<String>(); for (File f : folderToNames.keySet()) { definedDensities.add(f.getName()); } // Look for missing folders -- if you define say drawable-mdpi then you // should also define -hdpi and -xhdpi. if (context.isEnabled(ICON_MISSING_FOLDER)) { List<String> missing = new ArrayList<String>(); for (String density : REQUIRED_DENSITIES) { if (!definedDensities.contains(density)) { missing.add(density); } } if (missing.size() > 0) { context.report( ICON_MISSING_FOLDER, null /* location */, String.format( "Missing density variation folders in %1$s: %2$s", context.getProject().getDisplayPath(res), LintUtils.formatList(missing, missing.size())), null); } } if (context.isEnabled(ICON_NODPI)) { Set<String> noDpiNames = new HashSet<String>(); for (Map.Entry<File, Set<String>> entry : folderToNames.entrySet()) { if (isNoDpiFolder(entry.getKey())) { noDpiNames.addAll(entry.getValue()); } } if (noDpiNames.size() > 0) { // Make sure that none of the nodpi names appear in a non-nodpi folder Set<String> inBoth = new HashSet<String>(); List<File> files = new ArrayList<File>(); for (Map.Entry<File, Set<String>> entry : folderToNames.entrySet()) { File folder = entry.getKey(); String folderName = folder.getName(); if (!isNoDpiFolder(folder)) { assert DENSITY_PATTERN.matcher(folderName).matches(); Set<String> overlap = intersection(noDpiNames, entry.getValue()); inBoth.addAll(overlap); for (String name : overlap) { files.add(new File(folder, name)); } } } if (inBoth.size() > 0) { List<String> list = new ArrayList<String>(inBoth); Collections.sort(list); // Chain locations together Collections.sort(files); Location location = null; for (File file : files) { Location linkedLocation = location; location = Location.create(file); location.setSecondary(linkedLocation); } context.report( ICON_DENSITIES, location, String.format( "The following images appear in both -nodpi and in a density folder: %1$s", LintUtils.formatList(list, 10)), null); } } } if (context.isEnabled(ICON_DENSITIES)) { // Look for folders missing some of the specific assets Set<String> allNames = new HashSet<String>(); for (Entry<File, Set<String>> entry : folderToNames.entrySet()) { if (!isNoDpiFolder(entry.getKey())) { Set<String> names = entry.getValue(); allNames.addAll(names); } } for (Map.Entry<File, Set<String>> entry : folderToNames.entrySet()) { File file = entry.getKey(); if (isNoDpiFolder(file)) { continue; } Set<String> names = entry.getValue(); if (names.size() != allNames.size()) { List<String> delta = new ArrayList<String>(difference(allNames, names)); Collections.sort(delta); String foundIn = ""; if (delta.size() == 1) { // Produce list of where the icon is actually defined List<String> defined = new ArrayList<String>(); String name = delta.get(0); for (Map.Entry<File, Set<String>> e : folderToNames.entrySet()) { if (e.getValue().contains(name)) { defined.add(e.getKey().getName()); } } if (defined.size() > 0) { foundIn = String.format(" (found in %1$s)", LintUtils.formatList(defined, 5)); } } context.report( ICON_DENSITIES, Location.create(file), String.format( "Missing the following drawables in %1$s: %2$s%3$s", file.getName(), LintUtils.formatList(delta, 5), foundIn), null); } } } }
/** * Is this drawable folder for an Android 3.0 drawable? This will be the case if it specifies * -v11+, or if the minimum SDK version declared in the manifest is at least 11. */ private boolean isAndroid30(Context context, int folderVersion) { return folderVersion >= 11 || context.getProject().getMinSdk() >= 11; }