aboutsummaryrefslogtreecommitdiffstats
path: root/lint
diff options
context:
space:
mode:
authorTor Norbye <tnorbye@google.com>2012-10-04 18:55:18 -0700
committerTor Norbye <tnorbye@google.com>2012-10-05 08:04:07 -0700
commit6cb5c7f7141a08fd355bb92c6a4153140400890b (patch)
treea2df993be817856f98eefd44c01f5ef56c8aec34 /lint
parentc3e3de0a15a83422d3c43f536a45e53e8156bfe5 (diff)
downloadsdk-6cb5c7f7141a08fd355bb92c6a4153140400890b.zip
sdk-6cb5c7f7141a08fd355bb92c6a4153140400890b.tar.gz
sdk-6cb5c7f7141a08fd355bb92c6a4153140400890b.tar.bz2
Check notification and action bar icons
These icons should not use color (and in the case of notification icons, be white). Change-Id: I32b9422735830a01bb069b90a5ad5a76d7aeb5de
Diffstat (limited to 'lint')
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/Context.java46
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/XmlContext.java35
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java3
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java495
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/StringFormatDetector.java42
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/IconDetectorTest.java126
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/drawable-mdpi/ic_menu_add_clip_normal.pngbin0 -> 687 bytes
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/drawable-mdpi/stat_notify_alarm.pngbin0 -> 835 bytes
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/menu/menu.xml16
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/test/pkg/ActionBarTest.java.txt14
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/test/pkg/NotificationTest.java.txt39
11 files changed, 739 insertions, 77 deletions
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Context.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Context.java
index 9ddd10a..40c9d1f 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Context.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Context.java
@@ -23,12 +23,15 @@ import com.android.tools.lint.client.api.LintClient;
import com.android.tools.lint.client.api.LintDriver;
import com.android.tools.lint.client.api.SdkInfo;
import com.google.common.annotations.Beta;
+import com.google.common.base.Splitter;
import java.io.File;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Context passed to the detectors during an analysis run. It provides
@@ -328,4 +331,47 @@ public class Context {
public void requestRepeat(@NonNull Detector detector, @Nullable EnumSet<Scope> scope) {
mDriver.requestRepeat(detector, scope);
}
+
+ /** Pattern for version qualifiers */
+ private final static Pattern VERSION_PATTERN = Pattern.compile("^v(\\d+)$"); //$NON-NLS-1$
+
+ private static File sCachedFolder = null;
+ private static int sCachedFolderVersion = -1;
+
+ /**
+ * Returns the folder version. For example, for the file values-v14/foo.xml,
+ * it returns 14.
+ *
+ * @return the folder version, or -1 if no specific version was specified
+ */
+ public int getFolderVersion() {
+ return getFolderVersion(file);
+ }
+
+ /**
+ * Returns the folder version of the given file. For example, for the file values-v14/foo.xml,
+ * it returns 14.
+ *
+ * @param file the file to be checked
+ * @return the folder version, or -1 if no specific version was specified
+ */
+ public static int getFolderVersion(File file) {
+ File parent = file.getParentFile();
+ if (parent.equals(sCachedFolder)) {
+ return sCachedFolderVersion;
+ }
+
+ sCachedFolder = parent;
+ sCachedFolderVersion = -1;
+
+ for (String qualifier : Splitter.on('-').split(parent.getName())) {
+ Matcher matcher = VERSION_PATTERN.matcher(qualifier);
+ if (matcher.matches()) {
+ sCachedFolderVersion = Integer.parseInt(matcher.group(1));
+ break;
+ }
+ }
+
+ return sCachedFolderVersion;
+ }
}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/XmlContext.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/XmlContext.java
index 14e0a91..34d6816 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/XmlContext.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/XmlContext.java
@@ -27,8 +27,6 @@ import org.w3c.dom.Document;
import org.w3c.dom.Node;
import java.io.File;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/**
* A {@link Context} used when checking XML files.
@@ -153,37 +151,4 @@ public class XmlContext extends Context {
public ResourceFolderType getResourceFolderType() {
return mFolderType;
}
-
-
- private final static Pattern sVersionPattern = Pattern.compile("^v(\\d+)$");//$NON-NLS-1$
-
- private static File sCachedFolder = null;
- private static int sCachedFolderVersion = -1;
-
- /**
- * Returns the folder version. For example, for the file values-v14/foo.xml,
- * it returns 14.
- *
- * @return the folder version, or -1 if no specific version was specified
- */
- public int getFolderVersion() {
- File parent = file.getParentFile();
- if (parent.equals(sCachedFolder)) {
- return sCachedFolderVersion;
- }
-
- sCachedFolder = parent;
- sCachedFolderVersion = -1;
-
- String[] qualifiers = parent.getName().split("-"); //$NON-NLS-1$
- for (String qualifier : qualifiers) {
- Matcher matcher = sVersionPattern.matcher(qualifier);
- if (matcher.matches()) {
- sCachedFolderVersion = Integer.parseInt(matcher.group(1));
- break;
- }
- }
-
- return sCachedFolderVersion;
- }
}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
index 05765a4..7b18f81 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
@@ -55,7 +55,7 @@ public class BuiltinIssueRegistry extends IssueRegistry {
private static final List<Issue> sIssues;
static {
- final int initialCapacity = 114;
+ final int initialCapacity = 115;
List<Issue> issues = new ArrayList<Issue>(initialCapacity);
issues.add(AccessibilityDetector.ISSUE);
@@ -139,6 +139,7 @@ public class BuiltinIssueRegistry extends IssueRegistry {
issues.add(IconDetector.DUPLICATES_CONFIGURATIONS);
issues.add(IconDetector.ICON_NODPI);
issues.add(IconDetector.ICON_EXTENSION);
+ issues.add(IconDetector.ICON_COLORS);
issues.add(TypographyDetector.DASHES);
issues.add(TypographyDetector.QUOTES);
issues.add(TypographyDetector.FRACTIONS);
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java
index cf87acd..5b2f632 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java
@@ -31,21 +31,36 @@ import static com.android.SdkConstants.DRAWABLE_LDPI;
import static com.android.SdkConstants.DRAWABLE_MDPI;
import static com.android.SdkConstants.DRAWABLE_PREFIX;
import static com.android.SdkConstants.DRAWABLE_XHDPI;
+import static com.android.SdkConstants.MENU_TYPE;
import static com.android.SdkConstants.RES_FOLDER;
+import static com.android.SdkConstants.R_CLASS;
+import static com.android.SdkConstants.R_DRAWABLE_PREFIX;
+import static com.android.SdkConstants.TAG_ACTIVITY;
import static com.android.SdkConstants.TAG_APPLICATION;
+import static com.android.SdkConstants.TAG_ITEM;
+import static com.android.SdkConstants.TAG_PROVIDER;
+import static com.android.SdkConstants.TAG_RECEIVER;
+import static com.android.SdkConstants.TAG_SERVICE;
import static com.android.tools.lint.detector.api.LintUtils.endsWith;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.resources.ResourceFolderType;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
import com.android.tools.lint.detector.api.LintUtils;
import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.Speed;
import com.android.tools.lint.detector.api.XmlContext;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
import org.w3c.dom.Element;
@@ -54,9 +69,11 @@ import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -65,18 +82,30 @@ import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
-import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
+import lombok.ast.AstVisitor;
+import lombok.ast.ConstructorInvocation;
+import lombok.ast.Expression;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+import lombok.ast.Select;
+import lombok.ast.StrictListAccessor;
+import lombok.ast.TypeReference;
+import lombok.ast.TypeReferencePart;
+import lombok.ast.VariableReference;
+
/**
* Checks for common icon problems, such as wrong icon sizes, placing icons in the
* density independent drawable folder, etc.
*/
-public class IconDetector extends Detector implements Detector.XmlScanner {
+public class IconDetector extends ResourceXmlDetector implements Detector.JavaScanner {
private static final boolean INCLUDE_LDPI;
static {
@@ -94,9 +123,6 @@ public class IconDetector extends Detector implements Detector.XmlScanner {
"^drawable-(nodpi|xhdpi|hdpi|mdpi" //$NON-NLS-1$
+ (INCLUDE_LDPI ? "|ldpi" : "") + ")$"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
- /** Pattern for version qualifiers */
- private final static Pattern VERSION_PATTERN = Pattern.compile("^v(\\d+)$");//$NON-NLS-1$
-
private static final String[] REQUIRED_DENSITIES = INCLUDE_LDPI
? new String[] { DRAWABLE_LDPI, DRAWABLE_MDPI, DRAWABLE_HDPI, DRAWABLE_XHDPI }
: new String[] { DRAWABLE_MDPI, DRAWABLE_HDPI, DRAWABLE_XHDPI };
@@ -109,6 +135,12 @@ public class IconDetector extends Detector implements Detector.XmlScanner {
"-xhdpi" //$NON-NLS-1$
};
+ /** Scope needed to detect the types of icons (which involves scanning .java files,
+ * the manifest, menu files etc to see how icons are used
+ */
+ private static final EnumSet<Scope> ICON_TYPE_SCOPE = EnumSet.of(Scope.ALL_RESOURCE_FILES,
+ Scope.JAVA_FILE, Scope.MANIFEST);
+
/** Wrong icon size according to published conventions */
public static final Issue ICON_EXPECTED_SIZE = Issue.create(
"IconExpectedSize", //$NON-NLS-1$
@@ -120,7 +152,7 @@ public class IconDetector extends Detector implements Detector.XmlScanner {
5,
Severity.WARNING,
IconDetector.class,
- Scope.ALL_RESOURCES_SCOPE)
+ ICON_TYPE_SCOPE)
// Still some potential false positives:
.setEnabledByDefault(false)
.setMoreInfo(
@@ -266,8 +298,24 @@ public class IconDetector extends Detector implements Detector.XmlScanner {
IconDetector.class,
Scope.ALL_RESOURCES_SCOPE);
-
- private String mApplicationIcon;
+ /** Wrong filename according to the format */
+ public static final Issue ICON_COLORS = Issue.create(
+ "IconColors", //$NON-NLS-1$
+ "Checks that icons follow the recommended visual style",
+
+ "Notification icons and Action Bar icons should only white and shades of gray. " +
+ "See the Android Design Guide for more details. " +
+ "Note that the way Lint decides whether an icon is an action bar icon or " +
+ "a notification icon is based on the filename prefix: `ic_menu_` for " +
+ "action bar icons, `ic_stat_` for notification icons etc. These correspond " +
+ "to the naming conventions documented in " +
+ "http://developer.android.com/guide/practices/ui_guidelines/icon_design.html",
+ Category.ICONS,
+ 6,
+ Severity.WARNING,
+ IconDetector.class,
+ ICON_TYPE_SCOPE).setMoreInfo(
+ "http://developer.android.com/design/style/iconography.html"); //$NON-NLS-1$
/** Constructs a new {@link IconDetector} check */
public IconDetector() {
@@ -280,7 +328,9 @@ public class IconDetector extends Detector implements Detector.XmlScanner {
@Override
public void beforeCheckProject(@NonNull Context context) {
- mApplicationIcon = null;
+ mLauncherIcons = null;
+ mActionBarIcons = null;
+ mNotificationIcons = null;
}
@Override
@@ -996,6 +1046,27 @@ public class IconDetector extends Detector implements Detector.XmlScanner {
}
}
+ if (context.isEnabled(ICON_COLORS)) {
+ for (File file : files) {
+ String name = file.getName();
+
+ if (isDrawableFile(name)
+ && !endsWith(name, DOT_XML)
+ && !endsWith(name, DOT_9PNG)) {
+ String baseName = getBaseName(name);
+ boolean isActionBarIcon = isActionBarIcon(context, baseName, file);
+ if (isActionBarIcon || isNotificationIcon(baseName)) {
+ Dimension size = checkColor(context, file, isActionBarIcon);
+
+ // Store dimension for size check if we went to the trouble of reading image
+ if (size != null && pixelSizes != null) {
+ pixelSizes.put(file, size);
+ }
+ }
+ }
+ }
+ }
+
// Check icon sizes
if (context.isEnabled(ICON_EXPECTED_SIZE)) {
checkExpectedSizes(context, folder, files);
@@ -1011,7 +1082,8 @@ public class IconDetector extends Detector implements Detector.XmlScanner {
|| endsWith(fileName, DOT_JPEG)) {
// Only scan .png files (except 9-patch png's) and jpg files for
// dip sizes. Duplicate checks can also be performed on ninepatch files.
- if (pixelSizes != null && !endsWith(fileName, DOT_9PNG)) {
+ if (pixelSizes != null && !endsWith(fileName, DOT_9PNG)
+ && !pixelSizes.containsKey(file)) { // already read by checkColor?
Dimension size = getSize(file);
pixelSizes.put(file, size);
}
@@ -1023,6 +1095,122 @@ public class IconDetector extends Detector implements Detector.XmlScanner {
}
}
+ /**
+ * Check whether the icons in the file are okay. Also return the image size
+ * if known (for use by other checks)
+ */
+ private Dimension checkColor(Context context, File file, boolean isActionBarIcon) {
+ int folderVersion = Context.getFolderVersion(file);
+ if (isActionBarIcon) {
+ if (folderVersion != -1 && folderVersion < 11
+ || !isAndroid30(context, folderVersion)) {
+ return null;
+ }
+ } else {
+ if (folderVersion != -1 && folderVersion < 9
+ || !isAndroid23(context, folderVersion)
+ && !isAndroid30(context, folderVersion)) {
+ return null;
+ }
+ }
+
+ // TODO: This only checks icons that are known to be using the Holo style.
+ // However, if the user has minSdk < 11 as well as targetSdk > 11, we should
+ // also check that they actually include a -v11 or -v14 folder with proper
+ // icons, since the below won't flag the older icons.
+ try {
+ BufferedImage image = ImageIO.read(file);
+ if (image != null) {
+ if (isActionBarIcon) {
+ checkPixels:
+ for (int y = 0, height = image.getHeight(); y < height; y++) {
+ for (int x = 0, width = image.getWidth(); x < width; x++) {
+ int rgb = image.getRGB(x, y);
+ if ((rgb & 0xFF000000) != 0) { // else: transparent
+ int r = (rgb & 0xFF0000) >>> 16;
+ int g = (rgb & 0x00FF00) >>> 8;
+ int b = (rgb & 0x0000FF);
+ if (r != g || r != b) {
+ String message = "Action Bar icons should use a single gray "
+ + "color (#333333 for light themes (with 60%/30% "
+ + "opacity for enabled/disabled), and #FFFFFF with "
+ + "opacity 80%/30% for dark themes";
+ context.report(ICON_COLORS, Location.create(file),
+ message, null);
+ break checkPixels;
+ }
+ }
+ }
+ }
+ } else {
+ if (folderVersion >= 11 || isAndroid30(context, folderVersion)) {
+ // Notification icons. Should be white as of API 14
+ checkPixels:
+ for (int y = 0, height = image.getHeight(); y < height; y++) {
+ for (int x = 0, width = image.getWidth(); x < width; x++) {
+ int rgb = image.getRGB(x, y);
+ if ((rgb & 0xFF000000) != 0
+ && rgb != 0xFFFFFFFF) {
+ int r = (rgb & 0xFF0000) >>> 16;
+ int g = (rgb & 0x00FF00) >>> 8;
+ int b = (rgb & 0x0000FF);
+ if (r == g && r == b) {
+ // If the pixel is not white, it might be because of
+ // anti-aliasing. In that case, at least one neighbor
+ // should be of a different color
+ if (x < width - 1 && rgb != image.getRGB(x + 1, y)) {
+ continue;
+ }
+ if (x > 0 && rgb != image.getRGB(x - 1, y)) {
+ continue;
+ }
+ if (y < height - 1 && rgb != image.getRGB(x, y + 1)) {
+ continue;
+ }
+ if (y > 0 && rgb != image.getRGB(x, y - 1)) {
+ continue;
+ }
+ }
+
+ String message = "Notification icons must be entirely white";
+ context.report(ICON_COLORS, Location.create(file),
+ message, null);
+ break checkPixels;
+ }
+ }
+ }
+ } else {
+ // As of API 9, should be gray.
+ checkPixels:
+ for (int y = 0, height = image.getHeight(); y < height; y++) {
+ for (int x = 0, width = image.getWidth(); x < width; x++) {
+ int rgb = image.getRGB(x, y);
+ if ((rgb & 0xFF000000) != 0) { // else: transparent
+ int r = (rgb & 0xFF0000) >>> 16;
+ int g = (rgb & 0x00FF00) >>> 8;
+ int b = (rgb & 0x0000FF);
+ if (r != g || r != b) {
+ String message = "Notification icons should not use "
+ + "colors";
+ context.report(ICON_COLORS, Location.create(file),
+ message, null);
+ break checkPixels;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return new Dimension(image.getWidth(), image.getHeight());
+ }
+ } catch (IOException e) {
+ // Pass: ignore files we can't read
+ }
+
+ return null;
+ }
+
private void checkExtension(Context context, File file) {
try {
ImageInputStream input = ImageIO.createImageInputStream(file);
@@ -1069,20 +1257,24 @@ public class IconDetector extends Detector implements Detector.XmlScanner {
}
}
- private void checkExpectedSizes(Context context, File folder, File[] files) {
- String folderName = folder.getName();
+ private static String getBaseName(String name) {
+ String baseName = name;
+ int index = baseName.indexOf('.');
+ if (index != -1) {
+ baseName = baseName.substring(0, index);
+ }
- int folderVersion = -1;
- String[] qualifiers = folderName.split("-"); //$NON-NLS-1$
- for (String qualifier : qualifiers) {
- if (qualifier.startsWith("v")) {
- Matcher matcher = VERSION_PATTERN.matcher(qualifier);
- if (matcher.matches()) {
- folderVersion = Integer.parseInt(matcher.group(1));
- }
- }
+ return baseName;
+ }
+
+ private void checkExpectedSizes(Context context, File folder, File[] files) {
+ if (files == null || files.length == 0) {
+ return;
}
+ String folderName = folder.getName();
+ int folderVersion = Context.getFolderVersion(files[0]);
+
for (File file : files) {
String name = file.getName();
@@ -1091,17 +1283,12 @@ public class IconDetector extends Detector implements Detector.XmlScanner {
// http://developer.android.com/guide/practices/ui_guidelines/icon_design.html#design-tips
// See if we can figure out other types of icons from usage too.
- String baseName = name;
- int index = baseName.indexOf('.');
- if (index != -1) {
- baseName = baseName.substring(0, index);
- }
+ String baseName = getBaseName(name);
- if (baseName.equals(mApplicationIcon) || name.startsWith("ic_launcher")) { //$NON-NLS-1$
+ if (isLauncherIcon(baseName)) {
// Launcher icons
checkSize(context, folderName, file, 48, 48, true /*exact*/);
- } else if (name.startsWith("ic_action_")) { //$NON-NLS-1$
- // Action Bar
+ } else if (isActionBarIcon(baseName)) {
checkSize(context, folderName, file, 32, 32, true /*exact*/);
} else if (name.startsWith("ic_dialog_")) { //$NON-NLS-1$
// Dialog
@@ -1109,9 +1296,8 @@ public class IconDetector extends Detector implements Detector.XmlScanner {
} else if (name.startsWith("ic_tab_")) { //$NON-NLS-1$
// Tab icons
checkSize(context, folderName, file, 32, 32, true /*exact*/);
- } else if (name.startsWith("ic_stat_")) { //$NON-NLS-1$
+ } else if (isNotificationIcon(baseName)) {
// Notification icons
-
if (isAndroid30(context, folderVersion)) {
checkSize(context, folderName, file, 24, 24, true /*exact*/);
} else if (isAndroid23(context, folderVersion)) {
@@ -1278,7 +1464,62 @@ public class IconDetector extends Detector implements Detector.XmlScanner {
}
}
- // XML detector: Skim manifest
+
+ private Set<String> mActionBarIcons;
+ private Set<String> mNotificationIcons;
+ private Set<String> mLauncherIcons;
+ private Multimap<String, String> mMenuToIcons;
+
+ private boolean isLauncherIcon(String name) {
+ assert name.indexOf('.') == -1; // Should supply base name
+
+ // Naming convention
+ if (name.startsWith("ic_launcher")) { //$NON-NLS-1$
+ return true;
+ }
+ return mLauncherIcons != null && mLauncherIcons.contains(name);
+ }
+
+ private boolean isNotificationIcon(String name) {
+ assert name.indexOf('.') == -1; // Should supply base name
+
+ // Naming convention
+ if (name.startsWith("ic_stat_")) { //$NON-NLS-1$
+ return true;
+ }
+
+ return mNotificationIcons != null && mNotificationIcons.contains(name);
+ }
+
+ private boolean isActionBarIcon(String name) {
+ assert name.indexOf('.') == -1; // Should supply base name
+
+ // Naming convention
+ if (name.startsWith("ic_action_")) { //$NON-NLS-1$
+ return true;
+ }
+
+ // Naming convention
+
+ return mActionBarIcons != null && mActionBarIcons.contains(name);
+ }
+
+ private boolean isActionBarIcon(Context context, String name, File file) {
+ if (isActionBarIcon(name)) {
+ return true;
+ }
+
+ // As of Android 3.0 ic_menu_ are action icons
+ if (file != null && name.startsWith("ic_menu_") //$NON-NLS-1$
+ && isAndroid30(context, Context.getFolderVersion(file))) {
+ // Naming convention
+ return true;
+ }
+
+ return false;
+ }
+
+ // XML detector: Skim manifest and menu files
@Override
public boolean appliesTo(@NonNull Context context, @NonNull File file) {
@@ -1286,16 +1527,196 @@ public class IconDetector extends Detector implements Detector.XmlScanner {
}
@Override
+ public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+ return folderType == ResourceFolderType.MENU;
+ }
+
+ @Override
public Collection<String> getApplicableElements() {
- return Collections.singletonList(TAG_APPLICATION);
+ return Arrays.asList(
+ // Manifest
+ TAG_APPLICATION,
+ TAG_ACTIVITY,
+ TAG_SERVICE,
+ TAG_PROVIDER,
+ TAG_RECEIVER,
+
+ // Menu
+ TAG_ITEM
+ );
}
@Override
public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- assert element.getTagName().equals(TAG_APPLICATION);
- mApplicationIcon = element.getAttributeNS(ANDROID_URI, ATTR_ICON);
- if (mApplicationIcon.startsWith(DRAWABLE_PREFIX)) {
- mApplicationIcon = mApplicationIcon.substring(DRAWABLE_PREFIX.length());
+ String icon = element.getAttributeNS(ANDROID_URI, ATTR_ICON);
+ if (icon != null && icon.startsWith(DRAWABLE_PREFIX)) {
+ icon = icon.substring(DRAWABLE_PREFIX.length());
+
+ String tagName = element.getTagName();
+ if (tagName.equals(TAG_ITEM)) {
+ if (mMenuToIcons == null) {
+ mMenuToIcons = ArrayListMultimap.create();
+ }
+ String menu = getBaseName(context.file.getName());
+ mMenuToIcons.put(menu, icon);
+ } else {
+ // Manifest tags: launcher icons
+ if (mLauncherIcons == null) {
+ mLauncherIcons = Sets.newHashSet();
+ }
+ mLauncherIcons.add(icon);
+ }
+ }
+ }
+
+
+ // ---- Implements JavaScanner ----
+
+ private static final String NOTIFICATION_CLASS = "Notification"; //$NON-NLS-1$
+ private static final String NOTIFICATION_COMPAT_CLASS = "NotificationCompat"; //$NON-NLS-1$
+ private static final String BUILDER_CLASS = "Builder"; //$NON-NLS-1$
+ private static final String SET_SMALL_ICON = "setSmallIcon"; //$NON-NLS-1$
+ private static final String ON_CREATE_OPTIONS_MENU = "onCreateOptionsMenu"; //$NON-NLS-1$
+
+ @Override
+ @Nullable
+ public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
+ return new NotificationFinder();
+ }
+
+ @Override
+ @Nullable
+ public List<Class<? extends Node>> getApplicableNodeTypes() {
+ List<Class<? extends Node>> types = new ArrayList<Class<? extends Node>>(3);
+ types.add(MethodDeclaration.class);
+ types.add(ConstructorInvocation.class);
+ return types;
+ }
+
+ private final class NotificationFinder extends ForwardingAstVisitor {
+ @Override
+ public boolean visitMethodDeclaration(MethodDeclaration node) {
+ if (ON_CREATE_OPTIONS_MENU.equals(node.astMethodName().astValue())) {
+ // Gather any R.menu references found in this method
+ node.accept(new MenuFinder());
+ }
+
+ return super.visitMethodDeclaration(node);
+ }
+
+ @Override
+ public boolean visitConstructorInvocation(ConstructorInvocation node) {
+ TypeReference reference = node.astTypeReference();
+ StrictListAccessor<TypeReferencePart, TypeReference> parts = reference.astParts();
+ String typeName = parts.last().astIdentifier().astValue();
+ if (NOTIFICATION_CLASS.equals(typeName)) {
+ StrictListAccessor<Expression, ConstructorInvocation> args = node.astArguments();
+ if (args.size() == 3) {
+ if (args.first() instanceof Select && handleSelect((Select) args.first())) {
+ return super.visitConstructorInvocation(node);
+ }
+
+ Node method = getParentMethod(node);
+ if (method != null) {
+ // Must track local types
+ String name = StringFormatDetector.getResourceForFirstArg(method, node);
+ if (name != null) {
+ if (mNotificationIcons == null) {
+ mNotificationIcons = Sets.newHashSet();
+ }
+ mNotificationIcons.add(name);
+ }
+ }
+ }
+ } else if (BUILDER_CLASS.equals(typeName)) {
+ boolean isBuilder = false;
+ if (parts.size() == 1) {
+ isBuilder = true;
+ } else if (parts.size() == 2) {
+ String clz = parts.first().astIdentifier().astValue();
+ if (NOTIFICATION_CLASS.equals(clz) || NOTIFICATION_COMPAT_CLASS.equals(clz)) {
+ isBuilder = true;
+ }
+ }
+ if (isBuilder) {
+ Node method = getParentMethod(node);
+ if (method != null) {
+ SetIconFinder finder = new SetIconFinder();
+ method.accept(finder);
+ }
+ }
+ }
+
+ return super.visitConstructorInvocation(node);
+ }
+ }
+
+ @Nullable
+ private Node getParentMethod(@NonNull Node node) {
+ Node method = node;
+ while (method != null && !(method.getParent() instanceof MethodDeclaration)) {
+ method = method.getParent();
+ }
+
+ return method;
+ }
+
+ private boolean handleSelect(Select select) {
+ if (select.toString().startsWith(R_DRAWABLE_PREFIX)) {
+ String name = select.astIdentifier().astValue();
+ if (mNotificationIcons == null) {
+ mNotificationIcons = Sets.newHashSet();
+ }
+ mNotificationIcons.add(name);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private final class SetIconFinder extends ForwardingAstVisitor {
+ @Override
+ public boolean visitMethodInvocation(MethodInvocation node) {
+ if (SET_SMALL_ICON.equals(node.astName().astValue())) {
+ StrictListAccessor<Expression,MethodInvocation> arguments = node.astArguments();
+ if (arguments.size() == 1 && arguments.first() instanceof Select) {
+ handleSelect((Select) arguments.first());
+ }
+ }
+ return super.visitMethodInvocation(node);
+ }
+ }
+
+ private final class MenuFinder extends ForwardingAstVisitor {
+ @Override
+ public boolean visitSelect(Select node) {
+ // R.type.name
+ if (node.astOperand() instanceof Select) {
+ Select select = (Select) node.astOperand();
+ if (select.astOperand() instanceof VariableReference) {
+ VariableReference reference = (VariableReference) select.astOperand();
+ if (reference.astIdentifier().astValue().equals(R_CLASS)) {
+ String type = select.astIdentifier().astValue();
+
+ if (type.equals(MENU_TYPE)) {
+ String name = node.astIdentifier().astValue();
+ // Reclassify icons in the given menu as action bar icons
+ if (mMenuToIcons != null) {
+ Collection<String> icons = mMenuToIcons.get(name);
+ if (icons != null) {
+ if (mActionBarIcons == null) {
+ mActionBarIcons = Sets.newHashSet();
+ }
+ mActionBarIcons.addAll(icons);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return super.visitSelect(node);
}
}
}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/StringFormatDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/StringFormatDetector.java
index f6f0f7e..bf16aeb 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/StringFormatDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/StringFormatDetector.java
@@ -65,6 +65,7 @@ import java.util.regex.Pattern;
import lombok.ast.AstVisitor;
import lombok.ast.CharLiteral;
import lombok.ast.ConstructorDeclaration;
+import lombok.ast.ConstructorInvocation;
import lombok.ast.Expression;
import lombok.ast.FloatingPointLiteral;
import lombok.ast.ForwardingAstVisitor;
@@ -1009,6 +1010,16 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto
}
}
+ /** Returns the resource name corresponding to the first argument in the given call */
+ static String getResourceForFirstArg(lombok.ast.Node method, lombok.ast.Node call) {
+ assert call instanceof MethodInvocation || call instanceof ConstructorInvocation;
+ StringTracker tracker = new StringTracker(method, call);
+ method.accept(tracker);
+ String name = tracker.getFormatStringName();
+
+ return name;
+ }
+
/**
* Given a variable reference, finds the original R.string value corresponding to it.
* For example:
@@ -1038,7 +1049,7 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto
/** Map from variable name to corresponding type */
private final Map<String, Class<?>> mTypes = new HashMap<String, Class<?>>();
/** The AST node for the String.format we're interested in */
- private MethodInvocation mTargetNode;
+ private lombok.ast.Node mTargetNode;
private boolean mDone;
/**
* Result: the name of the string resource being passed to the
@@ -1046,7 +1057,7 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto
*/
private String mName;
- public StringTracker(lombok.ast.Node top, MethodInvocation targetNode) {
+ public StringTracker(lombok.ast.Node top, lombok.ast.Node targetNode) {
mTop = top;
mTargetNode = targetNode;
}
@@ -1076,7 +1087,11 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto
}
public Expression getArgument(int argument) {
- StrictListAccessor<Expression, MethodInvocation> args = mTargetNode.astArguments();
+ if (!(mTargetNode instanceof MethodInvocation)) {
+ return null;
+ }
+ MethodInvocation call = (MethodInvocation) mTargetNode;
+ StrictListAccessor<Expression, MethodInvocation> args = call.astArguments();
if (argument >= args.size()) {
return null;
}
@@ -1152,6 +1167,27 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto
}
@Override
+ public boolean visitConstructorInvocation(ConstructorInvocation node) {
+ if (node == mTargetNode) {
+ StrictListAccessor<Expression, ConstructorInvocation> args = node.astArguments();
+ if (args.size() > 0) {
+ Expression first = args.first();
+ if (first instanceof VariableReference) {
+ VariableReference reference = (VariableReference) first;
+ String variable = reference.astIdentifier().astValue();
+ mName = mMap.get(variable);
+ mDone = true;
+ return true;
+ }
+ }
+ }
+
+ // Is this a getString() call? On a resource object? If so,
+ // promote the resource argument up to the left hand side
+ return super.visitConstructorInvocation(node);
+ }
+
+ @Override
public boolean visitVariableDefinitionEntry(VariableDefinitionEntry node) {
String name = node.astName().astValue();
Expression rhs = node.astInitializer();
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/IconDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/IconDetectorTest.java
index 1a84982..f382085 100644
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/IconDetectorTest.java
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/IconDetectorTest.java
@@ -39,11 +39,11 @@ public class IconDetectorTest extends AbstractCheckTest {
ALL.add(IconDetector.GIF_USAGE);
ALL.add(IconDetector.ICON_DENSITIES);
ALL.add(IconDetector.ICON_DIP_SIZE);
- ALL.add(IconDetector.ICON_EXPECTED_SIZE);
ALL.add(IconDetector.ICON_EXTENSION);
ALL.add(IconDetector.ICON_LOCATION);
ALL.add(IconDetector.ICON_MISSING_FOLDER);
ALL.add(IconDetector.ICON_NODPI);
+ ALL.add(IconDetector.ICON_COLORS);
}
@Override
@@ -205,4 +205,128 @@ public class IconDetectorTest extends AbstractCheckTest {
"res/drawable-mdpi/sample_icon.gif=>res/drawable-mdpi/sample_icon.jpeg",
"res/drawable-mdpi/sample_icon.gif=>res/drawable-mdpi/sample_icon.png"));
}
+
+ public void testColors() throws Exception {
+ mEnabled = Collections.singleton(IconDetector.ICON_COLORS);
+ assertEquals(
+ "res/drawable-mdpi/ic_menu_my_action.png: Warning: Action Bar icons should use a single gray color (#333333 for light themes (with 60%/30% opacity for enabled/disabled), and #FFFFFF with opacity 80%/30% for dark themes [IconColors]\n" +
+ "res/drawable-mdpi-v11/ic_stat_my_notification.png: Warning: Notification icons must be entirely white [IconColors]\n" +
+ "res/drawable-mdpi-v9/ic_stat_my_notification2.png: Warning: Notification icons must be entirely white [IconColors]\n" +
+ "0 errors, 3 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_menu_my_action.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi-v11/ic_stat_my_notification.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi-v9/ic_stat_my_notification2.png",
+ "res/drawable-mdpi/ic_menu_add_clip_normal.png")); // OK
+ }
+
+ public void testNotActionBarIcons() throws Exception {
+ mEnabled = Collections.singleton(IconDetector.ICON_COLORS);
+ assertEquals(
+ "No warnings.",
+
+ // No Java code designates the menu as an action bar menu
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "res/menu/menu.xml",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon1.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon2.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon3.png", // Not action bar
+ "res/drawable-mdpi/ic_menu_add_clip_normal.png")); // OK
+ }
+
+ public void testActionBarIcons() throws Exception {
+ mEnabled = Collections.singleton(IconDetector.ICON_COLORS);
+ assertEquals(
+ "res/drawable-mdpi/icon1.png: Warning: Action Bar icons should use a single gray color (#333333 for light themes (with 60%/30% opacity for enabled/disabled), and #FFFFFF with opacity 80%/30% for dark themes [IconColors]\n" +
+ "res/drawable-mdpi/icon2.png: Warning: Action Bar icons should use a single gray color (#333333 for light themes (with 60%/30% opacity for enabled/disabled), and #FFFFFF with opacity 80%/30% for dark themes [IconColors]\n" +
+ "0 errors, 2 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "res/menu/menu.xml",
+ "src/test/pkg/ActionBarTest.java.txt=>src/test/pkg/ActionBarTest.java",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon1.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon2.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon3.png", // Not action bar
+ "res/drawable-mdpi/ic_menu_add_clip_normal.png")); // OK
+ }
+
+ public void testOkActionBarIcons() throws Exception {
+ mEnabled = Collections.singleton(IconDetector.ICON_COLORS);
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "res/menu/menu.xml",
+ "res/drawable-mdpi/ic_menu_add_clip_normal.png=>res/drawable-mdpi/icon1.png",
+ "res/drawable-mdpi/ic_menu_add_clip_normal.png=>res/drawable-mdpi/icon2.png"));
+ }
+
+ public void testNotificationIcons() throws Exception {
+ mEnabled = Collections.singleton(IconDetector.ICON_COLORS);
+ assertEquals(
+ "res/drawable-mdpi/icon1.png: Warning: Notification icons must be entirely white [IconColors]\n" +
+ "res/drawable-mdpi/icon2.png: Warning: Notification icons must be entirely white [IconColors]\n" +
+ "res/drawable-mdpi/icon3.png: Warning: Notification icons must be entirely white [IconColors]\n" +
+ "res/drawable-mdpi/icon4.png: Warning: Notification icons must be entirely white [IconColors]\n" +
+ "res/drawable-mdpi/icon5.png: Warning: Notification icons must be entirely white [IconColors]\n" +
+ "0 errors, 5 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "src/test/pkg/NotificationTest.java.txt=>src/test/pkg/NotificationTest.java",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon1.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon2.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon3.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon4.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon5.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon6.png", // not a notification
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon7.png", // ditto
+ "res/drawable-mdpi/ic_menu_add_clip_normal.png")); // OK
+ }
+
+ public void testOkNotificationIcons() throws Exception {
+ mEnabled = Collections.singleton(IconDetector.ICON_COLORS);
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "src/test/pkg/NotificationTest.java.txt=>src/test/pkg/NotificationTest.java",
+ "res/drawable-mdpi/ic_menu_add_clip_normal.png=>res/drawable-mdpi/icon1.png",
+ "res/drawable-mdpi/ic_menu_add_clip_normal.png=>res/drawable-mdpi/icon2.png",
+ "res/drawable-mdpi/ic_menu_add_clip_normal.png=>res/drawable-mdpi/icon3.png",
+ "res/drawable-mdpi/ic_menu_add_clip_normal.png=>res/drawable-mdpi/icon4.png",
+ "res/drawable-mdpi/ic_menu_add_clip_normal.png=>res/drawable-mdpi/icon5.png"));
+ }
+
+ public void testExpectedSize() throws Exception {
+ mEnabled = Collections.singleton(IconDetector.ICON_EXPECTED_SIZE);
+ assertEquals(
+ "res/drawable-mdpi/ic_launcher.png: Warning: Incorrect icon size for drawable-mdpi/ic_launcher.png: expected 48x48, but was 24x24 [IconExpectedSize]\n" +
+ "res/drawable-mdpi/icon1.png: Warning: Incorrect icon size for drawable-mdpi/icon1.png: expected 32x32, but was 48x48 [IconExpectedSize]\n" +
+ "res/drawable-mdpi/icon3.png: Warning: Incorrect icon size for drawable-mdpi/icon3.png: expected 24x24, but was 48x48 [IconExpectedSize]\n" +
+ "0 errors, 3 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "src/test/pkg/NotificationTest.java.txt=>src/test/pkg/NotificationTest.java",
+ "res/menu/menu.xml",
+ "src/test/pkg/ActionBarTest.java.txt=>src/test/pkg/ActionBarTest.java",
+
+ // 3 wrong-sized icons:
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon1.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon3.png",
+ "res/drawable-mdpi/stat_notify_alarm.png=>res/drawable-mdpi/ic_launcher.png",
+
+ // OK sizes
+ "res/drawable-mdpi/ic_menu_add_clip_normal.png=>res/drawable-mdpi/icon2.png",
+ "res/drawable-mdpi/stat_notify_alarm.png=>res/drawable-mdpi/icon4.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher2.png"
+ ));
+ }
} \ No newline at end of file
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/drawable-mdpi/ic_menu_add_clip_normal.png b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/drawable-mdpi/ic_menu_add_clip_normal.png
new file mode 100644
index 0000000..26f5afe
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/drawable-mdpi/ic_menu_add_clip_normal.png
Binary files differ
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/drawable-mdpi/stat_notify_alarm.png b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/drawable-mdpi/stat_notify_alarm.png
new file mode 100644
index 0000000..c61626c
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/drawable-mdpi/stat_notify_alarm.png
Binary files differ
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/menu/menu.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/menu/menu.xml
new file mode 100644
index 0000000..4f0839b
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/menu/menu.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <item
+ android:id="@+id/item1"
+ android:icon="@drawable/icon1"
+ android:title="My title 1">
+ </item>
+ <item
+ android:id="@+id/item2"
+ android:icon="@drawable/icon2"
+ android:showAsAction="ifRoom"
+ android:title="My title 2">
+ </item>
+
+</menu> \ No newline at end of file
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/test/pkg/ActionBarTest.java.txt b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/test/pkg/ActionBarTest.java.txt
new file mode 100644
index 0000000..217f3b6
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/test/pkg/ActionBarTest.java.txt
@@ -0,0 +1,14 @@
+package test.pkg;
+
+import android.app.Activity;
+import android.view.Menu;
+import android.view.MenuInflater;
+
+public class ActionBarTest extends Activity {
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.menu, menu);
+ return true;
+ }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/test/pkg/NotificationTest.java.txt b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/test/pkg/NotificationTest.java.txt
new file mode 100644
index 0000000..b03f7a8
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/test/pkg/NotificationTest.java.txt
@@ -0,0 +1,39 @@
+package test.pkg;
+
+import android.app.Notification;
+import android.app.Notification.Builder;
+import android.content.Context;
+import android.graphics.Bitmap;
+
+@SuppressWarnings({ "deprecation", "unused", "javadoc" })
+class NotificationTest {
+ public void test1() {
+ Notification notification = new Notification(R.drawable.icon1, "Test1", 0);
+ }
+
+ public void test2() {
+ int resource = R.drawable.icon2;
+ Notification notification = new Notification(resource, "Test1", 0);
+ }
+
+ public void test3() {
+ int icon = R.drawable.icon3;
+ CharSequence tickerText = "Hello";
+ long when = System.currentTimeMillis();
+ Notification notification = new Notification(icon, tickerText, when);
+ }
+
+ public void test4(Context context, String sender, String subject, Bitmap bitmap) {
+ Notification notification = new Notification.Builder(context)
+ .setContentTitle("New mail from " + sender.toString())
+ .setContentText(subject).setSmallIcon(R.drawable.icon4)
+ .setLargeIcon(bitmap).build();
+ }
+
+ public void test5(Context context, String sender, String subject, Bitmap bitmap) {
+ Notification notification = new Builder(context)
+ .setContentTitle("New mail from " + sender.toString())
+ .setContentText(subject).setSmallIcon(R.drawable.icon5)
+ .setLargeIcon(bitmap).build();
+ }
+}