Merge "41753: Lint API Check: Consult source files for constants"
diff --git a/lint/cli/src/test/java/com/android/tools/lint/MainTest.java b/lint/cli/src/test/java/com/android/tools/lint/MainTest.java
index ba31ed8..5a48a47 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/MainTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/MainTest.java
@@ -142,6 +142,14 @@
         "Similarly, you can use tools:targetApi=\"11\" in an XML file to indicate that\n" +
         "the element will only be inflated in an adequate context.\n" +
         "\n" +
+        "Lint will also flag certain constants, such as static final integers, which\n" +
+        "were introduced in later versions. These will actually be copied into the\n" +
+        "class files rather than being referenced, which means that the value is\n" +
+        "available even when running on older devices. In some cases that's fine, and\n" +
+        "in other cases it can result in a runtime crash or incorrect behavior. It\n" +
+        "depends on the context, so consider the code carefully and device whether it's\n" +
+        "safe and can be suppressed or whether the code needs to be guarded.\n" +
+        "\n" +
         "\n",
 
         // Expected error
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/ApiDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/ApiDetectorTest.java
index e42280f..1bcbfaa 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/ApiDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/ApiDetectorTest.java
@@ -138,14 +138,14 @@
             "              ~~~~~~~~~~~~~~~~~~~\n" +
             "src/foo/bar/ApiCallTest.java:33: Error: Field requires API level 11 (current min is 1): dalvik.bytecode.OpcodeInfo#MAXIMUM_VALUE [NewApi]\n" +
             "  int field = OpcodeInfo.MAXIMUM_VALUE; // API 11\n" +
-            "                         ~~~~~~~~~~~~~\n" +
+            "              ~~~~~~~~~~~~~~~~~~~~~~~~\n" +
             "src/foo/bar/ApiCallTest.java:38: Error: Field requires API level 14 (current min is 1): android.app.ApplicationErrorReport#batteryInfo [NewApi]\n" +
             "  BatteryInfo batteryInfo = getReport().batteryInfo;\n" +
             "              ~~~~~~~~~~~\n" +
             //             Note: the above error range is wrong; should be pointing to the second
             "src/foo/bar/ApiCallTest.java:41: Error: Field requires API level 11 (current min is 1): android.graphics.PorterDuff.Mode#OVERLAY [NewApi]\n" +
             "  Mode mode = PorterDuff.Mode.OVERLAY; // API 11\n" +
-            "                              ~~~~~~~\n" +
+            "              ~~~~~~~~~~~~~~~~~~~~~~~\n" +
             "7 errors, 0 warnings\n",
 
             lintProject(
@@ -172,13 +172,13 @@
             "              ~~~~~~~~~~~~~~~~~~~\n" +
             "src/foo/bar/ApiCallTest.java:33: Error: Field requires API level 11 (current min is 2): dalvik.bytecode.OpcodeInfo#MAXIMUM_VALUE [NewApi]\n" +
             "  int field = OpcodeInfo.MAXIMUM_VALUE; // API 11\n" +
-            "                         ~~~~~~~~~~~~~\n" +
+            "              ~~~~~~~~~~~~~~~~~~~~~~~~\n" +
             "src/foo/bar/ApiCallTest.java:38: Error: Field requires API level 14 (current min is 2): android.app.ApplicationErrorReport#batteryInfo [NewApi]\n" +
             "  BatteryInfo batteryInfo = getReport().batteryInfo;\n" +
             "              ~~~~~~~~~~~\n" +
             "src/foo/bar/ApiCallTest.java:41: Error: Field requires API level 11 (current min is 2): android.graphics.PorterDuff.Mode#OVERLAY [NewApi]\n" +
             "  Mode mode = PorterDuff.Mode.OVERLAY; // API 11\n" +
-            "                              ~~~~~~~\n" +
+            "              ~~~~~~~~~~~~~~~~~~~~~~~\n" +
             "7 errors, 0 warnings\n",
 
             lintProject(
@@ -202,13 +202,13 @@
             "              ~~~~~~~~~~~~~~~~~~~\n" +
             "src/foo/bar/ApiCallTest.java:33: Error: Field requires API level 11 (current min is 4): dalvik.bytecode.OpcodeInfo#MAXIMUM_VALUE [NewApi]\n" +
             "  int field = OpcodeInfo.MAXIMUM_VALUE; // API 11\n" +
-            "                         ~~~~~~~~~~~~~\n" +
+            "              ~~~~~~~~~~~~~~~~~~~~~~~~\n" +
             "src/foo/bar/ApiCallTest.java:38: Error: Field requires API level 14 (current min is 4): android.app.ApplicationErrorReport#batteryInfo [NewApi]\n" +
             "  BatteryInfo batteryInfo = getReport().batteryInfo;\n" +
             "              ~~~~~~~~~~~\n" +
             "src/foo/bar/ApiCallTest.java:41: Error: Field requires API level 11 (current min is 4): android.graphics.PorterDuff.Mode#OVERLAY [NewApi]\n" +
             "  Mode mode = PorterDuff.Mode.OVERLAY; // API 11\n" +
-            "                              ~~~~~~~\n" +
+            "              ~~~~~~~~~~~~~~~~~~~~~~~\n" +
             "6 errors, 0 warnings\n",
 
             lintProject(
@@ -229,13 +229,13 @@
             "              ~~~~~~~~~~~~~~~~~~~\n" +
             "src/foo/bar/ApiCallTest.java:33: Error: Field requires API level 11 (current min is 10): dalvik.bytecode.OpcodeInfo#MAXIMUM_VALUE [NewApi]\n" +
             "  int field = OpcodeInfo.MAXIMUM_VALUE; // API 11\n" +
-            "                         ~~~~~~~~~~~~~\n" +
+            "              ~~~~~~~~~~~~~~~~~~~~~~~~\n" +
             "src/foo/bar/ApiCallTest.java:38: Error: Field requires API level 14 (current min is 10): android.app.ApplicationErrorReport#batteryInfo [NewApi]\n" +
             "  BatteryInfo batteryInfo = getReport().batteryInfo;\n" +
             "              ~~~~~~~~~~~\n" +
             "src/foo/bar/ApiCallTest.java:41: Error: Field requires API level 11 (current min is 10): android.graphics.PorterDuff.Mode#OVERLAY [NewApi]\n" +
             "  Mode mode = PorterDuff.Mode.OVERLAY; // API 11\n" +
-            "                              ~~~~~~~\n" +
+            "              ~~~~~~~~~~~~~~~~~~~~~~~\n" +
             "5 errors, 0 warnings\n",
 
             lintProject(
@@ -366,13 +366,13 @@
             "              ~~~~~~~~~~~~~~~~~~~\n" +
             "src/foo/bar/SuppressTest1.java:89: Error: Field requires API level 11 (current min is 1): dalvik.bytecode.OpcodeInfo#MAXIMUM_VALUE [NewApi]\n" +
             "  int field = OpcodeInfo.MAXIMUM_VALUE; // API 11\n" +
-            "                         ~~~~~~~~~~~~~\n" +
+            "              ~~~~~~~~~~~~~~~~~~~~~~~~\n" +
             "src/foo/bar/SuppressTest1.java:94: Error: Field requires API level 14 (current min is 1): android.app.ApplicationErrorReport#batteryInfo [NewApi]\n" +
             "  BatteryInfo batteryInfo = getReport().batteryInfo;\n" +
             "              ~~~~~~~~~~~\n" +
             "src/foo/bar/SuppressTest1.java:97: Error: Field requires API level 11 (current min is 1): android.graphics.PorterDuff.Mode#OVERLAY [NewApi]\n" +
             "  Mode mode = PorterDuff.Mode.OVERLAY; // API 11\n" +
-            "                              ~~~~~~~\n" +
+            "              ~~~~~~~~~~~~~~~~~~~~~~~\n" +
 
             // Note: These annotations are within the methods, not ON the methods, so they have
             // no effect (because they don't end up in the bytecode)
@@ -716,4 +716,74 @@
                     "apicheck/ApiCallTest12.class.data=>bin/classes/test/pkg/ApiCallTest12.class"
                 ));
     }
+
+    public void testJavaConstants() throws Exception {
+        assertEquals(""
+                + "src/test/pkg/ApiSourceCheck.java:5: Error: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [NewApi]\n"
+                + "import static android.view.View.MEASURED_STATE_MASK;\n"
+                + "              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ApiSourceCheck.java:30: Error: Field requires API level 11 (current min is 1): android.widget.ZoomControls#MEASURED_STATE_MASK [NewApi]\n"
+                + "        int x = MEASURED_STATE_MASK;\n"
+                + "                ~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ApiSourceCheck.java:33: Error: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [NewApi]\n"
+                + "        int y = android.view.View.MEASURED_STATE_MASK;\n"
+                + "                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ApiSourceCheck.java:36: Error: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [NewApi]\n"
+                + "        int z = View.MEASURED_STATE_MASK;\n"
+                + "                ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ApiSourceCheck.java:37: Error: Field requires API level 14 (current min is 1): android.view.View#FIND_VIEWS_WITH_TEXT [NewApi]\n"
+                + "        int find2 = View.FIND_VIEWS_WITH_TEXT; // requires API 14\n"
+                + "                    ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ApiSourceCheck.java:40: Error: Field requires API level 12 (current min is 1): android.app.ActivityManager#MOVE_TASK_NO_USER_ACTION [NewApi]\n"
+                + "        int w = ActivityManager.MOVE_TASK_NO_USER_ACTION;\n"
+                + "                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ApiSourceCheck.java:41: Error: Field requires API level 14 (current min is 1): android.widget.ZoomButton#FIND_VIEWS_WITH_CONTENT_DESCRIPTION [NewApi]\n"
+                + "        int find1 = ZoomButton.FIND_VIEWS_WITH_CONTENT_DESCRIPTION; // requires\n"
+                + "                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ApiSourceCheck.java:44: Error: Field requires API level 9 (current min is 1): android.widget.ZoomControls#OVER_SCROLL_ALWAYS [NewApi]\n"
+                + "        int overScroll = OVER_SCROLL_ALWAYS; // requires API 9\n"
+                + "                         ~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ApiSourceCheck.java:47: Error: Field requires API level 16 (current min is 1): android.widget.ZoomControls#IMPORTANT_FOR_ACCESSIBILITY_AUTO [NewApi]\n"
+                + "        int auto = IMPORTANT_FOR_ACCESSIBILITY_AUTO; // requires API 16\n"
+                + "                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ApiSourceCheck.java:51: Error: Field requires API level 14 (current min is 1): android.widget.ZoomButton#ROTATION_X [NewApi]\n"
+                + "        Object rotationX = ZoomButton.ROTATION_X; // Requires API 14\n"
+                + "                           ~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ApiSourceCheck.java:54: Error: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [NewApi]\n"
+                + "        return (child.getMeasuredWidth() & View.MEASURED_STATE_MASK)\n"
+                + "                                           ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ApiSourceCheck.java:55: Error: Field requires API level 11 (current min is 1): android.view.View#MEASURED_HEIGHT_STATE_SHIFT [NewApi]\n"
+                + "                | ((child.getMeasuredHeight() >> View.MEASURED_HEIGHT_STATE_SHIFT) & (View.MEASURED_STATE_MASK >> View.MEASURED_HEIGHT_STATE_SHIFT));\n"
+                + "                                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ApiSourceCheck.java:55: Error: Field requires API level 11 (current min is 1): android.view.View#MEASURED_HEIGHT_STATE_SHIFT [NewApi]\n"
+                + "                | ((child.getMeasuredHeight() >> View.MEASURED_HEIGHT_STATE_SHIFT) & (View.MEASURED_STATE_MASK >> View.MEASURED_HEIGHT_STATE_SHIFT));\n"
+                + "                                                                                                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ApiSourceCheck.java:55: Error: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [NewApi]\n"
+                + "                | ((child.getMeasuredHeight() >> View.MEASURED_HEIGHT_STATE_SHIFT) & (View.MEASURED_STATE_MASK >> View.MEASURED_HEIGHT_STATE_SHIFT));\n"
+                + "                                                                                      ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ApiSourceCheck.java:90: Error: Field requires API level 8 (current min is 1): android.R.id#custom [NewApi]\n"
+                + "        int custom = android.R.id.custom; // API 8\n"
+                + "                     ~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ApiSourceCheck.java:94: Error: Field requires API level 13 (current min is 1): android.Manifest.permission#SET_POINTER_SPEED [NewApi]\n"
+                + "        String setPointerSpeed = permission.SET_POINTER_SPEED;\n"
+                + "                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ApiSourceCheck.java:95: Error: Field requires API level 13 (current min is 1): android.Manifest.permission#SET_POINTER_SPEED [NewApi]\n"
+                + "        String setPointerSpeed2 = Manifest.permission.SET_POINTER_SPEED;\n"
+                + "                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ApiSourceCheck.java:120: Error: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [NewApi]\n"
+                + "        int y = View.MEASURED_STATE_MASK; // Not OK\n"
+                + "                ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ApiSourceCheck.java:121: Error: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [NewApi]\n"
+                + "        testBenignUsages(View.MEASURED_STATE_MASK); // Not OK\n"
+                + "                         ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "19 errors, 0 warnings\n",
+
+                lintProject(
+                        "apicheck/classpath=>.classpath",
+                        "apicheck/minsdk1.xml=>AndroidManifest.xml",
+                        "project.properties1=>project.properties",
+                        "apicheck/ApiSourceCheck.java.txt=>src/test/pkg/ApiSourceCheck.java",
+                        "apicheck/ApiSourceCheck.class.data=>bin/classes/test/pkg/ApiSourceCheck.class"
+                ));
+    }
 }
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck.class.data
new file mode 100644
index 0000000..726b8a5
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck.java.txt
new file mode 100644
index 0000000..429b388
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck.java.txt
@@ -0,0 +1,123 @@
+package test.pkg;
+
+import android.util.Property;
+import android.view.View;
+import static android.view.View.MEASURED_STATE_MASK;
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+import android.view.*;
+import android.annotation.*;
+import android.app.*;
+import android.widget.*;
+import static android.widget.ZoomControls.*;
+import android.Manifest.permission;
+import android.Manifest;
+
+/** Various tests for source-level checks */
+final class ApiSourceCheck extends LinearLayout {
+    public ApiSourceCheck(android.content.Context context) {
+        super(context);
+    }
+
+    /**
+     * Return only the state bits of {@link #getMeasuredWidthAndState()} and
+     * {@link #getMeasuredHeightAndState()}, combined into one integer. The
+     * width component is in the regular bits {@link #MEASURED_STATE_MASK} and
+     * the height component is at the shifted bits
+     * {@link #MEASURED_HEIGHT_STATE_SHIFT}>>{@link #MEASURED_STATE_MASK}.
+     */
+    public static int m1(View child) {
+        // from static import of field
+        int x = MEASURED_STATE_MASK;
+
+        // fully qualified name field access
+        int y = android.view.View.MEASURED_STATE_MASK;
+
+        // from explicitly imported class
+        int z = View.MEASURED_STATE_MASK;
+        int find2 = View.FIND_VIEWS_WITH_TEXT; // requires API 14
+
+        // from wildcard import of package
+        int w = ActivityManager.MOVE_TASK_NO_USER_ACTION;
+        int find1 = ZoomButton.FIND_VIEWS_WITH_CONTENT_DESCRIPTION; // requires
+                                                                    // API 14
+        // from static wildcard import
+        int overScroll = OVER_SCROLL_ALWAYS; // requires API 9
+
+        // Inherited field from ancestor class (View)
+        int auto = IMPORTANT_FOR_ACCESSIBILITY_AUTO; // requires API 16
+
+        // object field reference: ensure that we don't get two errors
+        // (one from source scan, the other from class scan)
+        Object rotationX = ZoomButton.ROTATION_X; // Requires API 14
+
+        // different type of expression than variable declaration
+        return (child.getMeasuredWidth() & View.MEASURED_STATE_MASK)
+                | ((child.getMeasuredHeight() >> View.MEASURED_HEIGHT_STATE_SHIFT) & (View.MEASURED_STATE_MASK >> View.MEASURED_HEIGHT_STATE_SHIFT));
+    }
+
+    @SuppressLint("NewApi")
+    private void testSuppress1() {
+        // Checks suppress on surrounding method
+        int w = ActivityManager.MOVE_TASK_NO_USER_ACTION;
+    }
+
+    private void testSuppress2() {
+        // Checks suppress on surrounding declaration statement
+        @SuppressLint("NewApi")
+        int w, z = ActivityManager.MOVE_TASK_NO_USER_ACTION;
+    }
+
+    @TargetApi(17)
+    private void testTargetApi1() {
+        // Checks @TargetApi on surrounding method
+        int w, z = ActivityManager.MOVE_TASK_NO_USER_ACTION;
+    }
+
+    @TargetApi(android.os.Build.VERSION_CODES.JELLY_BEAN_MR1)
+    private void testTargetApi2() {
+        // Checks @TargetApi with codename
+        int w, z = ActivityManager.MOVE_TASK_NO_USER_ACTION;
+    }
+
+    @TargetApi(JELLY_BEAN_MR1)
+    private void testTargetApi3() {
+        // Checks @TargetApi with codename
+        int w, z = ActivityManager.MOVE_TASK_NO_USER_ACTION;
+    }
+
+    private void checkOtherFields() {
+        // Look at fields that aren't capitalized
+        int custom = android.R.id.custom; // API 8
+    }
+
+    private void innerclass() {
+        String setPointerSpeed = permission.SET_POINTER_SPEED;
+        String setPointerSpeed2 = Manifest.permission.SET_POINTER_SPEED;
+    }
+
+    private void test() {
+        // Make sure that local variable references which look like fields,
+        // even imported ones, aren't taken as invalid references
+        int OVER_SCROLL_ALWAYS = 1, IMPORTANT_FOR_ACCESSIBILITY_AUTO = 2;
+        int x = OVER_SCROLL_ALWAYS;
+        int y = IMPORTANT_FOR_ACCESSIBILITY_AUTO;
+        findViewById(IMPORTANT_FOR_ACCESSIBILITY_AUTO); // yes, nonsensical
+    }
+
+    private void testBenignUsages(int x) {
+        // Certain types of usages (such as switch/case constants) are okay
+        switch (x) {
+            case View.MEASURED_STATE_MASK: { // OK
+                break;
+            }
+        }
+        if (x == View.MEASURED_STATE_MASK) { // OK
+        }
+        if (false || x == View.MEASURED_STATE_MASK) { // OK
+        }
+        if (x >= View.MEASURED_STATE_MASK) { // OK
+        }
+        int y = View.MEASURED_STATE_MASK; // Not OK
+        testBenignUsages(View.MEASURED_STATE_MASK); // Not OK
+    }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongAnnotation.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongAnnotation.java.txt
index 6fef833..45743ce 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongAnnotation.java.txt
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongAnnotation.java.txt
@@ -28,4 +28,9 @@
         @SuppressLint("NewApi")
         int localvar = 5;
     }
+
+    private static void test() {
+        @SuppressLint("NewApi") // Invalid
+        int a = View.MEASURED_STATE_MASK;
+    }
 }
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/JavaVisitor.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/JavaVisitor.java
index b74693a..81a0339 100644
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/JavaVisitor.java
+++ b/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/JavaVisitor.java
@@ -284,6 +284,9 @@
     private class DispatchVisitor extends AstVisitor {
         @Override
         public void endVisit(Node node) {
+            for (VisitingDetector v : mAllDetectors) {
+                v.getVisitor().endVisit(node);
+            }
         }
 
         @Override
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/ClassContext.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/ClassContext.java
index 800e969..161f088 100644
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/ClassContext.java
+++ b/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/ClassContext.java
@@ -29,6 +29,7 @@
 import com.android.tools.lint.detector.api.Location.SearchDirection;
 import com.android.tools.lint.detector.api.Location.SearchHints;
 import com.google.common.annotations.Beta;
+import com.google.common.base.Splitter;
 
 import org.objectweb.asm.Type;
 import org.objectweb.asm.tree.AbstractInsnNode;
@@ -658,10 +659,13 @@
      */
     @NonNull
     public static String getInternalName(@NonNull String fqcn) {
-        String[] parts = fqcn.split("\\."); //$NON-NLS-1$
+        if (fqcn.indexOf('.') == -1) {
+            return fqcn;
+        }
+
         StringBuilder sb = new StringBuilder(fqcn.length());
         String prev = null;
-        for (String part : parts) {
+        for (String part : Splitter.on('.').split(fqcn)) {
             if (prev != null) {
                 if (Character.isUpperCase(prev.charAt(0))) {
                     sb.append('$');
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/AnnotationDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/AnnotationDetector.java
index 2d2a2ef..f091269 100644
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/AnnotationDetector.java
+++ b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/AnnotationDetector.java
@@ -47,10 +47,12 @@
 import lombok.ast.MethodDeclaration;
 import lombok.ast.Modifiers;
 import lombok.ast.Node;
+import lombok.ast.Select;
 import lombok.ast.StrictListAccessor;
 import lombok.ast.StringLiteral;
 import lombok.ast.TypeBody;
 import lombok.ast.VariableDefinition;
+import lombok.ast.VariableDefinitionEntry;
 
 /**
  * Checks annotations to make sure they are valid
@@ -157,7 +159,11 @@
         private boolean checkId(Annotation node, String id) {
             IssueRegistry registry = mContext.getDriver().getRegistry();
             Issue issue = registry.getIssue(id);
-            if (issue != null && !issue.getScope().contains(Scope.JAVA_FILE)) {
+            // Special-case the ApiDetector issue, since it does both source file analysis
+            // only on field references, and class file analysis on the rest, so we allow
+            // annotations outside of methods only on fields
+            if (issue != null && !issue.getScope().contains(Scope.JAVA_FILE)
+                    || issue == ApiDetector.UNSUPPORTED) {
                 // Ensure that this isn't a field
                 Node parent = node.getParent();
                 while (parent != null) {
@@ -167,6 +173,15 @@
                         break;
                     } else if (parent instanceof TypeBody) { // It's a field
                         return true;
+                    } else if (issue == ApiDetector.UNSUPPORTED
+                            && parent instanceof VariableDefinition) {
+                        VariableDefinition definition = (VariableDefinition) parent;
+                        for (VariableDefinitionEntry entry : definition.astVariables()) {
+                            Expression initializer = entry.astInitializer();
+                            if (initializer instanceof Select) {
+                                return true;
+                            }
+                        }
                     }
                     parent = parent.getParent();
                     if (parent == null) {
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java
index d2a7844..2c0d773 100644
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java
+++ b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java
@@ -21,9 +21,12 @@
 import static com.android.SdkConstants.ATTR_CLASS;
 import static com.android.SdkConstants.ATTR_TARGET_API;
 import static com.android.SdkConstants.CONSTRUCTOR_NAME;
+import static com.android.SdkConstants.R_CLASS;
 import static com.android.SdkConstants.TARGET_API;
 import static com.android.SdkConstants.TOOLS_URI;
 import static com.android.SdkConstants.VIEW_TAG;
+import static com.android.tools.lint.detector.api.ClassContext.getFqcn;
+import static com.android.tools.lint.detector.api.ClassContext.getInternalName;
 import static com.android.tools.lint.detector.api.LintUtils.getNextInstruction;
 import static com.android.tools.lint.detector.api.Location.SearchDirection.BACKWARD;
 import static com.android.tools.lint.detector.api.Location.SearchDirection.FORWARD;
@@ -31,21 +34,28 @@
 
 import com.android.SdkConstants;
 import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
 import com.android.resources.ResourceFolderType;
 import com.android.tools.lint.client.api.LintDriver;
 import com.android.tools.lint.detector.api.Category;
 import com.android.tools.lint.detector.api.ClassContext;
 import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.DefaultPosition;
 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.Location.SearchHints;
+import com.android.tools.lint.detector.api.Position;
 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.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
@@ -65,15 +75,47 @@
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.EnumSet;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import lombok.ast.Annotation;
+import lombok.ast.AnnotationElement;
+import lombok.ast.AnnotationValue;
+import lombok.ast.AstVisitor;
+import lombok.ast.BinaryExpression;
+import lombok.ast.Case;
+import lombok.ast.ClassDeclaration;
+import lombok.ast.ConstructorDeclaration;
+import lombok.ast.ConstructorInvocation;
+import lombok.ast.Expression;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.If;
+import lombok.ast.ImportDeclaration;
+import lombok.ast.IntegralLiteral;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Modifiers;
+import lombok.ast.Select;
+import lombok.ast.StrictListAccessor;
+import lombok.ast.StringLiteral;
+import lombok.ast.SuperConstructorInvocation;
+import lombok.ast.Switch;
+import lombok.ast.TypeReference;
+import lombok.ast.VariableDefinition;
+import lombok.ast.VariableDefinitionEntry;
+import lombok.ast.VariableReference;
 
 /**
  * Looks for usages of APIs that are not supported in all the versions targeted
  * by this application (according to its minimum API requirement in the manifest).
  */
-public class ApiDetector extends ResourceXmlDetector implements Detector.ClassScanner {
+public class ApiDetector extends ResourceXmlDetector
+        implements Detector.ClassScanner, Detector.JavaScanner {
     /**
      * Whether we flag variable, field, parameter and return type declarations of a type
      * not yet available. It appears Dalvik is very forgiving and doesn't try to preload
@@ -105,14 +147,24 @@
             "file's minimum SDK as the required API level.\n" +
             "\n" +
             "Similarly, you can use tools:targetApi=\"11\" in an XML file to indicate that " +
-            "the element will only be inflated in an adequate context.",
+            "the element will only be inflated in an adequate context.\n" +
+            "\n" +
+            "Lint will also flag certain constants, such as static final integers, " +
+            "which were introduced in later versions. These will actually be copied " +
+            "into the class files rather than being referenced, which means that " +
+            "the value is available even when running on older devices. In some " +
+            "cases that's fine, and in other cases it can result in a runtime " +
+            "crash or incorrect behavior. It depends on the context, so consider " +
+            "the code carefully and device whether it's safe and can be suppressed "  +
+            "or whether the code needs to be guarded.",
             Category.CORRECTNESS,
             6,
             Severity.ERROR,
             ApiDetector.class,
-            EnumSet.of(Scope.CLASS_FILE, Scope.RESOURCE_FILE, Scope.MANIFEST))
+            EnumSet.of(Scope.CLASS_FILE, Scope.RESOURCE_FILE, Scope.MANIFEST, Scope.JAVA_FILE))
             .addAnalysisScope(Scope.RESOURCE_FILE_SCOPE)
-            .addAnalysisScope(Scope.CLASS_FILE_SCOPE);
+            .addAnalysisScope(Scope.CLASS_FILE_SCOPE)
+            .addAnalysisScope(Scope.JAVA_FILE_SCOPE);
 
     /** Accessing an unsupported API */
     public static final Issue OVERRIDE = Issue.create("Override", //$NON-NLS-1$
@@ -146,6 +198,7 @@
 
     private ApiLookup mApiDatabase;
     private int mMinApi = -1;
+    private Set<String> mWarnedFields;
 
     /** Constructs a new API check */
     public ApiDetector() {
@@ -547,11 +600,13 @@
                             continue;
                         }
                         String fqcn = ClassContext.getFqcn(owner) + '#' + name;
-                        String message = String.format(
-                                "Field requires API level %1$d (current min is %2$d): %3$s",
-                                api, minSdk, fqcn);
-                        report(context, message, node, method, name, null,
-                                SearchHints.create(FORWARD).matchJavaSymbol());
+                        if (mWarnedFields == null || !mWarnedFields.contains(fqcn)) {
+                            String message = String.format(
+                                    "Field requires API level %1$d (current min is %2$d): %3$s",
+                                    api, minSdk, fqcn);
+                            report(context, message, node, method, name, null,
+                                    SearchHints.create(FORWARD).matchJavaSymbol());
+                        }
                     }
                 } else if (type == AbstractInsnNode.LDC_INSN) {
                     LdcInsnNode node = (LdcInsnNode) instruction;
@@ -845,7 +900,7 @@
                     }
                 }
 
-                for (int api = 1; api < SdkConstants.HIGHEST_KNOWN_API; api++) {
+                for (int api = 1; api <= SdkConstants.HIGHEST_KNOWN_API; api++) {
                     String code = LintUtils.getBuildCode(api);
                     if (code != null && code.equalsIgnoreCase(targetApi)) {
                         return api;
@@ -893,4 +948,490 @@
                 hints);
         context.report(UNSUPPORTED, method, node, location, message, null);
     }
+
+    // ---- Implements JavaScanner ----
+
+    @Nullable
+    @Override
+    public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
+        return new ApiVisitor(context);
+    }
+
+    @Nullable
+    @Override
+    public List<Class<? extends lombok.ast.Node>> getApplicableNodeTypes() {
+        List<Class<? extends lombok.ast.Node>> types =
+                new ArrayList<Class<? extends lombok.ast.Node>>(2);
+        types.add(ImportDeclaration.class);
+        types.add(Select.class);
+        types.add(MethodDeclaration.class);
+        types.add(ConstructorDeclaration.class);
+        types.add(VariableDefinitionEntry.class);
+        types.add(VariableReference.class);
+        return types;
+    }
+
+    private final class ApiVisitor extends ForwardingAstVisitor {
+        private JavaContext mContext;
+        private Map<String, String> mClassToImport = Maps.newHashMap();
+        private List<String> mStarImports;
+        private Set<String> mLocalVars;
+        private lombok.ast.Node mCurrentMethod;
+        private Set<String> mFields;
+        private List<String> mStaticStarImports;
+
+        private ApiVisitor(JavaContext context) {
+            mContext = context;
+        }
+
+        @Override
+        public boolean visitImportDeclaration(ImportDeclaration node) {
+            if (node.astStarImport()) {
+                // Similarly, if you're inheriting from a constants class, figure out
+                // how that works... :=(
+                String fqcn = node.asFullyQualifiedName();
+                int strip = fqcn.lastIndexOf('*');
+                if (strip != -1) {
+                    strip = fqcn.lastIndexOf('.', strip);
+                    if (strip != -1) {
+                        String pkgName = getInternalName(fqcn.substring(0, strip));
+                        if (ApiLookup.isRelevantOwner(pkgName)) {
+                            if (node.astStaticImport()) {
+                                if (mStaticStarImports == null) {
+                                    mStaticStarImports = Lists.newArrayList();
+                                }
+                                mStaticStarImports.add(pkgName);
+                            } else {
+                                if (mStarImports == null) {
+                                    mStarImports = Lists.newArrayList();
+                                }
+                                mStarImports.add(pkgName);
+                            }
+                        }
+                    }
+                }
+            } else if (node.astStaticImport()) {
+                String fqcn = node.asFullyQualifiedName();
+                String fieldName = getInternalName(fqcn);
+                int index = fieldName.lastIndexOf('$');
+                if (index != -1) {
+                    String owner = fieldName.substring(0, index);
+                    String name = fieldName.substring(index + 1);
+                    checkField(node, name, owner);
+                }
+            } else {
+                // Store in map -- if it's "one of ours"
+                // Use override detector's map for that purpose
+                String fqcn = node.asFullyQualifiedName();
+
+                int last = fqcn.lastIndexOf('.');
+                if (last != -1) {
+                    String className = fqcn.substring(last + 1);
+                    mClassToImport.put(className, fqcn);
+                }
+            }
+
+            return super.visitImportDeclaration(node);
+        }
+
+        @Override
+        public boolean visitSelect(Select node) {
+            boolean result = super.visitSelect(node);
+
+            if (node.getParent() instanceof Select) {
+                // We only want to look at the leaf expressions; e.g. if you have
+                // "foo.bar.baz" we only care about the select foo.bar.baz, not foo.bar
+                return result;
+            }
+
+            // See if this corresponds to a field reference. We assume it's a field if
+            // it's a select (x.y) and either the identifier y is capitalized (e.g.
+            // foo.VIEW_MASK) or if it's a member of an R class (R.id.foo).
+            String name = node.astIdentifier().astValue();
+            boolean isField = Character.isUpperCase(name.charAt(0));
+            if (!isField) {
+                // See if there's an R class
+                Select current = node;
+                while (current != null) {
+                    Expression operand = current.astOperand();
+                    if (operand instanceof Select) {
+                        current = (Select) operand;
+                        if (R_CLASS.equals(current.astIdentifier().astValue())) {
+                            isField = true;
+                            break;
+                        }
+                    } else if (operand instanceof VariableReference) {
+                        VariableReference reference = (VariableReference) operand;
+                        if (R_CLASS.equals(reference.astIdentifier().astValue())) {
+                            isField = true;
+                        }
+                        break;
+                    } else {
+                        break;
+                    }
+                }
+            }
+
+            if (isField) {
+                Expression operand = node.astOperand();
+                if (operand.getClass() == Select.class) {
+                    // Possibly a fully qualified name in place
+                    String cls = operand.toString();
+
+                    // See if it's an imported class with an inner class
+                    // (e.g. Manifest.permission.FIELD)
+                    if (Character.isUpperCase(cls.charAt(0))) {
+                        int firstDot = cls.indexOf('.');
+                        if (firstDot != -1) {
+                            String base = cls.substring(0, firstDot);
+                            String fqcn = mClassToImport.get(base);
+                            if (fqcn != null) {
+                                // Yes imported
+                                String owner = getInternalName(fqcn + cls.substring(firstDot));
+                                checkField(node, name, owner);
+                                return result;
+                            }
+
+                            // Might be a star import: have to iterate and check here
+                            if (mStarImports != null) {
+                                for (String packagePrefix : mStarImports) {
+                                    String owner = getInternalName(packagePrefix + '/' + cls);
+                                    if (checkField(node, name, owner)) {
+                                        mClassToImport.put(name, owner);
+                                        return result;
+                                    }
+                                }
+                            }
+                        }
+                    }
+
+                    // See if it's a fully qualified reference in place
+                    String owner = getInternalName(cls);
+                    checkField(node, name, owner);
+                    return result;
+                } else if (operand.getClass() == VariableReference.class) {
+                    String className = ((VariableReference) operand).astIdentifier().astValue();
+                    // Not a FQCN that we care about: look in imports
+                    String fqcn = mClassToImport.get(className);
+                    if (fqcn != null) {
+                        // Yes imported
+                        String owner = getInternalName(fqcn);
+                        checkField(node, name, owner);
+                        return result;
+                    }
+
+                    if (Character.isUpperCase(className.charAt(0))) {
+                        // Might be a star import: have to iterate and check here
+                        if (mStarImports != null) {
+                            for (String packagePrefix : mStarImports) {
+                                String owner = getInternalName(packagePrefix) + '/' + className;
+                                if (checkField(node, name, owner)) {
+                                    mClassToImport.put(name, owner);
+                                    return result;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            return result;
+        }
+
+        @Override
+        public boolean visitVariableReference(VariableReference node) {
+            boolean result = super.visitVariableReference(node);
+
+            if (node.getParent() != null) {
+                lombok.ast.Node parent = node.getParent();
+                Class<? extends lombok.ast.Node> parentClass = parent.getClass();
+                if (parentClass == Select.class
+                        || parentClass == Switch.class // look up on the switch expression type
+                        || parentClass == Case.class
+                        || parentClass == ConstructorInvocation.class
+                        || parentClass == SuperConstructorInvocation.class
+                        || parentClass == AnnotationElement.class) {
+                    return result;
+                }
+
+                if (parent instanceof MethodInvocation &&
+                        ((MethodInvocation) parent).astOperand() == node) {
+                    return result;
+                } else if (parent instanceof BinaryExpression) {
+                    BinaryExpression expression = (BinaryExpression) parent;
+                    if (expression.astLeft() == node) {
+                        return result;
+                    }
+                }
+            }
+
+            String name = node.astIdentifier().astValue();
+            if (Character.isUpperCase(name.charAt(0))
+                    && (mLocalVars == null || !mLocalVars.contains(name))
+                    && (mFields == null || !mFields.contains(name))) {
+                // Potential field reference: check it
+                if (mStaticStarImports != null) {
+                    for (String owner : mStaticStarImports) {
+                        if (checkField(node, name, owner)) {
+                            break;
+                        }
+                    }
+                }
+            }
+
+            return result;
+        }
+
+        @Override
+        public boolean visitVariableDefinitionEntry(VariableDefinitionEntry node) {
+            if (mCurrentMethod != null) {
+                if (mLocalVars == null) {
+                    mLocalVars = Sets.newHashSet();
+                }
+                mLocalVars.add(node.astName().astValue());
+            } else {
+                if (mFields == null) {
+                    mFields = Sets.newHashSet();
+                }
+                mFields.add(node.astName().astValue());
+            }
+            return super.visitVariableDefinitionEntry(node);
+        }
+
+        @Override
+        public boolean visitMethodDeclaration(MethodDeclaration node) {
+            mLocalVars = null;
+            mCurrentMethod = node;
+            return super.visitMethodDeclaration(node);
+        }
+
+        @Override
+        public boolean visitConstructorDeclaration(ConstructorDeclaration node) {
+            mLocalVars = null;
+            mCurrentMethod = node;
+            return super.visitConstructorDeclaration(node);
+        }
+
+        @Override
+        public void endVisit(lombok.ast.Node node) {
+            if (node == mCurrentMethod) {
+                mCurrentMethod = null;
+            }
+            super.endVisit(node);
+        }
+
+        /**
+         * Checks a Java source field reference. Returns true if the field is known
+         * regardless of whether it's an invalid field or not
+         */
+        private boolean checkField(
+                @NonNull lombok.ast.Node node,
+                @NonNull String name,
+                @NonNull String owner) {
+            int api = mApiDatabase.getFieldVersion(owner, name);
+            if (api != -1) {
+                int minSdk = getMinSdk(mContext);
+                if (api > minSdk
+                        && api > getLocalMinSdk(node)) {
+                    if (isBenignConstantUsage(node, name, owner)) {
+                        return true;
+                    }
+
+                    Location location = mContext.getLocation(node);
+                    String fqcn = getFqcn(owner) + '#' + name;
+
+                    if (node instanceof ImportDeclaration) {
+                        // Replace import statement location range with just
+                        // the identifier part
+                        ImportDeclaration d = (ImportDeclaration) node;
+                        int startOffset = d.astParts().first().getPosition().getStart();
+                        Position start = location.getStart();
+                        int startColumn = start.getColumn();
+                        int startLine = start.getLine();
+                        start = new DefaultPosition(startLine,
+                                startColumn + startOffset - start.getOffset(), startOffset);
+                        int fqcnLength = fqcn.length();
+                        Position end = new DefaultPosition(startLine,
+                                start.getColumn() + fqcnLength,
+                                start.getOffset() + fqcnLength);
+                        location = Location.create(location.getFile(), start, end);
+                    }
+
+                    String message = String.format(
+                            "Field requires API level %1$d (current min is %2$d): %3$s",
+                            api, minSdk, fqcn);
+                    mContext.report(UNSUPPORTED, node, location, message, null);
+
+                    // Record this field as already reported such that when we scan the
+                    // class files later, we don't report the same error again.
+                    // (This happens when a field isn't a final primitive value which
+                    // gets copied into the .class file)
+                    if (mWarnedFields == null) {
+                        mWarnedFields = Sets.newHashSet();
+                    }
+                    mWarnedFields.add(fqcn);
+                }
+
+                return true;
+            }
+
+            return false;
+        }
+
+        /**
+         * Checks whether the given instruction is a benign usage of a constant defined
+         * in a later version of Android than the application's {@code minSdkVersion}.
+         *
+         * @param node the instruction to check
+         * @return true if the given usage is safe on older versions than the introduction
+         * level of the constant
+         */
+        public boolean isBenignConstantUsage(
+                @NonNull lombok.ast.Node node,
+                @NonNull String name,
+                @NonNull String owner) {
+            if (owner.equals("android/os/Build$VERSION_CODES")) {     //$NON-NLS-1$
+                // These constants are required for compilation, not execution
+                // and valid code checks it even on older platforms
+                return true;
+            }
+            if (owner.equals("android/view/ViewGroup$LayoutParams")   //$NON-NLS-1$
+                    && name.equals("MATCH_PARENT")) {                 //$NON-NLS-1$
+                return true;
+            }
+
+            // It's okay to reference the constant as a case constant (since that
+            // code path won't be taken) or in a condition of an if statement
+            lombok.ast.Node curr = node.getParent();
+            boolean usedInExpression = false;
+            while (curr != null) {
+                Class<? extends lombok.ast.Node> nodeType = curr.getClass();
+                if (nodeType == Case.class) {
+                    return true;
+                } else if (nodeType == BinaryExpression.class) {
+                    usedInExpression = true;
+                } else if (nodeType == If.class) {
+                    return usedInExpression;
+                }
+                curr = curr.getParent();
+            }
+
+            return false;
+        }
+
+        /**
+         * Returns the minimum SDK to use according to the given AST node, or null
+         * if no {@code TargetApi} annotations were found
+         *
+         * @return the API level to use for this node, or -1
+         */
+        public int getLocalMinSdk(@Nullable lombok.ast.Node scope) {
+            while (scope != null) {
+                Class<? extends lombok.ast.Node> type = scope.getClass();
+                // The Lombok AST uses a flat hierarchy of node type implementation classes
+                // so no need to do instanceof stuff here.
+                if (type == VariableDefinition.class) {
+                    // Variable
+                    VariableDefinition declaration = (VariableDefinition) scope;
+                    int targetApi = getLocalMinSdk(declaration.astModifiers());
+                    if (targetApi != -1) {
+                        return targetApi;
+                    }
+                } else if (type == MethodDeclaration.class) {
+                    // Method
+                    // Look for annotations on the method
+                    MethodDeclaration declaration = (MethodDeclaration) scope;
+                    int targetApi = getLocalMinSdk(declaration.astModifiers());
+                    if (targetApi != -1) {
+                        return targetApi;
+                    }
+                } else if (type == ConstructorDeclaration.class) {
+                    // Constructor
+                    // Look for annotations on the method
+                    ConstructorDeclaration declaration = (ConstructorDeclaration) scope;
+                    int targetApi = getLocalMinSdk(declaration.astModifiers());
+                    if (targetApi != -1) {
+                        return targetApi;
+                    }
+                } else if (type == ClassDeclaration.class) {
+                    // Class
+                    ClassDeclaration declaration = (ClassDeclaration) scope;
+                    int targetApi = getLocalMinSdk(declaration.astModifiers());
+                    if (targetApi != -1) {
+                        return targetApi;
+                    }
+                }
+
+                scope = scope.getParent();
+            }
+
+            return -1;
+        }
+
+        /**
+         * Returns true if the given AST modifier has a suppress annotation for the
+         * given issue (which can be null to check for the "all" annotation)
+         *
+         * @param modifiers the modifier to check
+         * @return true if the issue or all issues should be suppressed for this
+         *         modifier
+         */
+        private int getLocalMinSdk(@Nullable Modifiers modifiers) {
+            if (modifiers == null) {
+                return -1;
+            }
+            StrictListAccessor<Annotation, Modifiers> annotations = modifiers.astAnnotations();
+            if (annotations == null) {
+                return -1;
+            }
+
+            Iterator<Annotation> iterator = annotations.iterator();
+            while (iterator.hasNext()) {
+                Annotation annotation = iterator.next();
+                TypeReference t = annotation.astAnnotationTypeReference();
+                String typeName = t.getTypeName();
+                if (typeName.endsWith(TARGET_API)) {
+                    StrictListAccessor<AnnotationElement, Annotation> values =
+                            annotation.astElements();
+                    if (values != null) {
+                        Iterator<AnnotationElement> valueIterator = values.iterator();
+                        while (valueIterator.hasNext()) {
+                            AnnotationElement element = valueIterator.next();
+                            AnnotationValue valueNode = element.astValue();
+                            if (valueNode == null) {
+                                continue;
+                            }
+                            if (valueNode instanceof IntegralLiteral) {
+                                IntegralLiteral literal = (IntegralLiteral) valueNode;
+                                return literal.astIntValue();
+                            } else if (valueNode instanceof StringLiteral) {
+                                String value = ((StringLiteral) valueNode).astValue();
+                                return codeNameToApi(value);
+                            } else if (valueNode instanceof Select) {
+                                Select select = (Select) valueNode;
+                                String codename = select.astIdentifier().astValue();
+                                return codeNameToApi(codename);
+                            } else if (valueNode instanceof VariableReference) {
+                                VariableReference reference = (VariableReference) valueNode;
+                                String codename = reference.astIdentifier().astValue();
+                                return codeNameToApi(codename);
+                            }
+                        }
+                    }
+                }
+            }
+
+            return -1;
+        }
+    }
+
+    private static int codeNameToApi(String codename) {
+        for (int api = 1; api <= SdkConstants.HIGHEST_KNOWN_API; api++) {
+            String code = LintUtils.getBuildCode(api);
+            if (code != null && code.equalsIgnoreCase(codename)) {
+                return api;
+            }
+        }
+
+        return -1;
+    }
 }
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiLookup.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiLookup.java
index a056822..6a4de6e 100644
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiLookup.java
+++ b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiLookup.java
@@ -16,6 +16,7 @@
 
 package com.android.tools.lint.checks;
 
+import static com.android.SdkConstants.ANDROID_PKG;
 import static com.android.SdkConstants.DOT_XML;
 
 import com.android.annotations.NonNull;
@@ -341,7 +342,7 @@
             System.out.println("\nRead API database in " + (end - start)
                     + " milliseconds.");
             System.out.println("Size of data table: " + mData.length + " bytes ("
-                    + Integer.toString(mData.length/1024) + "k)\n");
+                    + Integer.toString(mData.length / 1024) + "k)\n");
         }
     }
 
@@ -372,6 +373,11 @@
                 javaPackageSet.add(pkg);
             }
 
+            if (!isRelevantOwner(className)) {
+                System.out.println("Warning: The isRelevantOwner method does not pass "
+                        + className);
+            }
+
             Set<String> allMethods = apiClass.getAllMethods(info);
             Set<String> allFields = apiClass.getAllFields(info);
 
@@ -755,6 +761,44 @@
     }
 
     /**
+     * Returns true if the given owner (in VM format) is relevant to the database.
+     * This allows quick filtering out of owners that won't return any data
+     * for the various {@code #getFieldVersion} etc methods.
+     *
+     * @param owner the owner to look up
+     * @return true if the owner might be relevant to the API database
+     */
+    public static boolean isRelevantOwner(@NonNull String owner) {
+        if (owner.startsWith("java")) {                   //$NON-NLS-1$ // includes javax/
+            return true;
+        }
+        if (owner.startsWith(ANDROID_PKG)) {
+            if (owner.startsWith("/support/", 7)) {       //$NON-NLS-1$
+                return false;
+            }
+            return true;
+        } else if (owner.startsWith("org/")) {            //$NON-NLS-1$
+            if (owner.startsWith("xml", 4)                //$NON-NLS-1$
+                    || owner.startsWith("w3c/", 4)        //$NON-NLS-1$
+                    || owner.startsWith("json/", 4)       //$NON-NLS-1$
+                    || owner.startsWith("apache/", 4)) {  //$NON-NLS-1$
+                return true;
+            }
+        } else if (owner.startsWith("com/")) {            //$NON-NLS-1$
+            if (owner.startsWith("google/", 4)            //$NON-NLS-1$
+                    || owner.startsWith("android/", 4)) { //$NON-NLS-1$
+                return true;
+            }
+        } else if (owner.startsWith("junit")              //$NON-NLS-1$
+                    || owner.startsWith("dalvik")) {      //$NON-NLS-1$
+            return true;
+        }
+
+        return false;
+    }
+
+
+    /**
      * Returns true if the given owner (in VM format) is a valid Java package supported
      * in any version of Android.
      *