aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lint/cli/src/test/java/com/android/tools/lint/MainTest.java8
-rw-r--r--lint/cli/src/test/java/com/android/tools/lint/checks/ApiDetectorTest.java85
-rw-r--r--lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck2.class.databin0 -> 553 bytes
-rw-r--r--lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck2.java.txt18
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiClass.java95
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java136
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/BuiltinIssueRegistry.java3
7 files changed, 273 insertions, 72 deletions
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 8ba40aa..68a1ea3 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
@@ -147,14 +147,6 @@ public class MainTest extends AbstractCheckTest {
"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 a4c7e4a..1d8cdc8 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 @@ public class ApiDetectorTest extends AbstractCheckTest {
" ~~~~~~~~~~~~~~~~~~~\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 @@ public class ApiDetectorTest extends AbstractCheckTest {
" ~~~~~~~~~~~~~~~~~~~\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 @@ public class ApiDetectorTest extends AbstractCheckTest {
" ~~~~~~~~~~~~~~~~~~~\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 @@ public class ApiDetectorTest extends AbstractCheckTest {
" ~~~~~~~~~~~~~~~~~~~\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 @@ public class ApiDetectorTest extends AbstractCheckTest {
" ~~~~~~~~~~~~~~~~~~~\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)
@@ -719,64 +719,64 @@ public class ApiDetectorTest extends AbstractCheckTest {
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"
+ + "src/test/pkg/ApiSourceCheck.java:5: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [InlinedApi]\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"
+ + "src/test/pkg/ApiSourceCheck.java:30: Warning: Field requires API level 11 (current min is 1): android.widget.ZoomControls#MEASURED_STATE_MASK [InlinedApi]\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"
+ + "src/test/pkg/ApiSourceCheck.java:33: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [InlinedApi]\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"
+ + "src/test/pkg/ApiSourceCheck.java:36: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [InlinedApi]\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"
+ + "src/test/pkg/ApiSourceCheck.java:37: Warning: Field requires API level 14 (current min is 1): android.view.View#FIND_VIEWS_WITH_TEXT [InlinedApi]\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"
+ + "src/test/pkg/ApiSourceCheck.java:40: Warning: Field requires API level 12 (current min is 1): android.app.ActivityManager#MOVE_TASK_NO_USER_ACTION [InlinedApi]\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"
+ + "src/test/pkg/ApiSourceCheck.java:41: Warning: Field requires API level 14 (current min is 1): android.widget.ZoomButton#FIND_VIEWS_WITH_CONTENT_DESCRIPTION [InlinedApi]\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"
+ + "src/test/pkg/ApiSourceCheck.java:44: Warning: Field requires API level 9 (current min is 1): android.widget.ZoomControls#OVER_SCROLL_ALWAYS [InlinedApi]\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"
+ + "src/test/pkg/ApiSourceCheck.java:47: Warning: Field requires API level 16 (current min is 1): android.widget.ZoomControls#IMPORTANT_FOR_ACCESSIBILITY_AUTO [InlinedApi]\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"
+ + "src/test/pkg/ApiSourceCheck.java:54: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [InlinedApi]\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"
+ + "src/test/pkg/ApiSourceCheck.java:55: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_HEIGHT_STATE_SHIFT [InlinedApi]\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"
+ + "src/test/pkg/ApiSourceCheck.java:55: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_HEIGHT_STATE_SHIFT [InlinedApi]\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"
+ + "src/test/pkg/ApiSourceCheck.java:55: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [InlinedApi]\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"
+ + "src/test/pkg/ApiSourceCheck.java:90: Warning: Field requires API level 8 (current min is 1): android.R.id#custom [InlinedApi]\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"
+ + "src/test/pkg/ApiSourceCheck.java:94: Warning: Field requires API level 13 (current min is 1): android.Manifest.permission#SET_POINTER_SPEED [InlinedApi]\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"
+ + "src/test/pkg/ApiSourceCheck.java:95: Warning: Field requires API level 13 (current min is 1): android.Manifest.permission#SET_POINTER_SPEED [InlinedApi]\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"
+ + "src/test/pkg/ApiSourceCheck.java:120: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [InlinedApi]\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"
+ + "src/test/pkg/ApiSourceCheck.java:121: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [InlinedApi]\n"
+ " testBenignUsages(View.MEASURED_STATE_MASK); // Not OK\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "19 errors, 0 warnings\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"
+ + "1 errors, 18 warnings\n",
lintProject(
"apicheck/classpath=>.classpath",
@@ -840,4 +840,25 @@ public class ApiDetectorTest extends AbstractCheckTest {
"res/values/styles2.xml=>res/values-v14/styles2.xml"
));
}
+
+ public void testMovedConstants() throws Exception {
+ assertEquals(""
+ // These two constants were introduced in API 11; the other 3 were available
+ // on subclass ListView from API 1
+ + "src/test/pkg/ApiSourceCheck2.java:10: Warning: Field requires API level 11 (current min is 1): android.widget.AbsListView#CHOICE_MODE_MULTIPLE_MODAL [InlinedApi]\n"
+ + " int mode2 = AbsListView.CHOICE_MODE_MULTIPLE_MODAL;\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ApiSourceCheck2.java:14: Warning: Field requires API level 11 (current min is 1): android.widget.ListView#CHOICE_MODE_MULTIPLE_MODAL [InlinedApi]\n"
+ + " int mode6 = ListView.CHOICE_MODE_MULTIPLE_MODAL;\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 2 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "project.properties1=>project.properties",
+ "apicheck/ApiSourceCheck2.java.txt=>src/test/pkg/ApiSourceCheck2.java",
+ "apicheck/ApiSourceCheck2.class.data=>bin/classes/test/pkg/ApiSourceCheck2.class"
+ ));
+ }
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck2.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck2.class.data
new file mode 100644
index 0000000..a37d62c
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck2.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck2.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck2.java.txt
new file mode 100644
index 0000000..adce4f0
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck2.java.txt
@@ -0,0 +1,18 @@
+package test.pkg;
+
+import android.widget.AbsListView;
+import android.widget.ListView;
+
+@SuppressWarnings("unused")
+public class ApiSourceCheck2 {
+ public void test() {
+ int mode1 = AbsListView.CHOICE_MODE_MULTIPLE;
+ int mode2 = AbsListView.CHOICE_MODE_MULTIPLE_MODAL;
+ int mode3 = AbsListView.CHOICE_MODE_NONE;
+ int mode4 = AbsListView.CHOICE_MODE_SINGLE;
+ int mode5 = ListView.CHOICE_MODE_MULTIPLE;
+ int mode6 = ListView.CHOICE_MODE_MULTIPLE_MODAL;
+ int mode7 = ListView.CHOICE_MODE_NONE;
+ int mode8 = ListView.CHOICE_MODE_SINGLE;
+ }
+}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiClass.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiClass.java
index f8564f2..3e0fb9d 100644
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiClass.java
+++ b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiClass.java
@@ -326,4 +326,99 @@ public class ApiClass {
}
}
}
+
+ /* This code can be used to scan through all the fields and look for fields
+ that have moved to a higher class:
+ Field android/view/MotionEvent#CREATOR has api=1 but parent android/view/InputEvent provides it as 9
+ Field android/provider/ContactsContract$CommonDataKinds$Organization#PHONETIC_NAME has api=5 but parent android/provider/ContactsContract$ContactNameColumns provides it as 11
+ Field android/widget/ListView#CHOICE_MODE_MULTIPLE has api=1 but parent android/widget/AbsListView provides it as 11
+ Field android/widget/ListView#CHOICE_MODE_NONE has api=1 but parent android/widget/AbsListView provides it as 11
+ Field android/widget/ListView#CHOICE_MODE_SINGLE has api=1 but parent android/widget/AbsListView provides it as 11
+ Field android/view/KeyEvent#CREATOR has api=1 but parent android/view/InputEvent provides it as 9
+ This is used for example in the ApiDetector to filter out warnings which result
+ when people follow Eclipse's advice to replace
+ ListView.CHOICE_MODE_MULTIPLE
+ references with
+ AbsListView.CHOICE_MODE_MULTIPLE
+ since the latter has API=11 and the former has API=1; since the constant is unchanged
+ between the two, and the literal is copied into the class, using the AbsListView
+ reference works.
+ public void checkFields(Api info) {
+ fieldLoop:
+ for (String field : mFields.keySet()) {
+ Integer since = getField(field, info);
+ if (since == null || since == Integer.MAX_VALUE) {
+ continue;
+ }
+
+ for (Pair<String, Integer> superClass : mSuperClasses) {
+ ApiClass clz = info.getClass(superClass.getFirst());
+ assert clz != null : superClass.getSecond();
+ if (clz != null) {
+ Integer superSince = clz.getField(field, info);
+ if (superSince == Integer.MAX_VALUE) {
+ continue;
+ }
+
+ if (superSince != null && superSince > since) {
+ String declaredIn = clz.findFieldDeclaration(info, field);
+ System.out.println("Field " + getName() + "#" + field + " has api="
+ + since + " but parent " + declaredIn + " provides it as "
+ + superSince);
+ continue fieldLoop;
+ }
+ }
+ }
+
+ // Get methods from implemented interfaces as well;
+ for (Pair<String, Integer> superClass : mInterfaces) {
+ ApiClass clz = info.getClass(superClass.getFirst());
+ assert clz != null : superClass.getSecond();
+ if (clz != null) {
+ Integer superSince = clz.getField(field, info);
+ if (superSince == Integer.MAX_VALUE) {
+ continue;
+ }
+ if (superSince != null && superSince > since) {
+ String declaredIn = clz.findFieldDeclaration(info, field);
+ System.out.println("Field " + getName() + "#" + field + " has api="
+ + since + " but parent " + declaredIn + " provides it as "
+ + superSince);
+ continue fieldLoop;
+ }
+ }
+ }
+ }
+ }
+
+ private String findFieldDeclaration(Api info, String name) {
+ if (mFields.containsKey(name)) {
+ return getName();
+ }
+ for (Pair<String, Integer> superClass : mSuperClasses) {
+ ApiClass clz = info.getClass(superClass.getFirst());
+ assert clz != null : superClass.getSecond();
+ if (clz != null) {
+ String declaredIn = clz.findFieldDeclaration(info, name);
+ if (declaredIn != null) {
+ return declaredIn;
+ }
+ }
+ }
+
+ // Get methods from implemented interfaces as well;
+ for (Pair<String, Integer> superClass : mInterfaces) {
+ ApiClass clz = info.getClass(superClass.getFirst());
+ assert clz != null : superClass.getSecond();
+ if (clz != null) {
+ String declaredIn = clz.findFieldDeclaration(info, name);
+ if (declaredIn != null) {
+ return declaredIn;
+ }
+ }
+ }
+
+ return 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 c5b70e7..2721a30 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
@@ -57,6 +57,7 @@ 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.android.utils.Pair;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
@@ -138,16 +139,20 @@ public class ApiDetector extends ResourceXmlDetector
"This check scans through all the Android API calls in the application and " +
"warns about any calls that are not available on *all* versions targeted " +
- "by this application (according to its minimum SDK attribute in the manifest).\n" +
+ "by this application (according to its minimum SDK attribute in the manifest).\n"
+ +
"\n" +
- "If you really want to use this API and don't need to support older devices just " +
+ "If you really want to use this API and don't need to support older devices just "
+ +
"set the `minSdkVersion` in your `AndroidManifest.xml` file." +
"\n" +
"If your code is *deliberately* accessing newer APIs, and you have ensured " +
- "(e.g. with conditional execution) that this code will only ever be called on a " +
+ "(e.g. with conditional execution) that this code will only ever be called on a "
+ +
"supported platform, then you can annotate your class or method with the " +
"`@TargetApi` annotation specifying the local minimum SDK to apply, such as " +
- "`@TargetApi(11)`, such that this check considers 11 rather than your manifest " +
+ "`@TargetApi(11)`, such that this check considers 11 rather than your manifest "
+ +
"file's minimum SDK as the required API level.\n" +
"\n" +
"If you are deliberately setting `android:` attributes in style definitions, " +
@@ -155,24 +160,48 @@ public class ApiDetector extends ResourceXmlDetector
"into runtime conflicts on certain devices where manufacturers have added " +
"custom attributes whose ids conflict with the new ones on later platforms.\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.\n" +
- "\n" +
- "Lint will also flag certain constants, such as static final integers, " +
+ "Similarly, you can use tools:targetApi=\"11\" in an XML file to indicate that "
+ +
+ "the element will only be inflated in an adequate context.",
+ Category.CORRECTNESS,
+ 6,
+ Severity.ERROR,
+ ApiDetector.class,
+ EnumSet.of(Scope.CLASS_FILE, Scope.RESOURCE_FILE, Scope.MANIFEST))
+ .addAnalysisScope(Scope.RESOURCE_FILE_SCOPE)
+ .addAnalysisScope(Scope.CLASS_FILE_SCOPE);
+
+ /** Accessing an inlined API on older platforms */
+ public static final Issue INLINED = Issue.create("InlinedApi", //$NON-NLS-1$
+ "Finds inlined fields that may or may not work on older platforms",
+
+ "This check scans through all the Android API field references in the application " +
+ "and flags certain constants, such as static final integers and Strings, " +
"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.",
+ "the code carefully and device whether it's safe and can be suppressed " +
+ "or whether the code needs tbe guarded.\n" +
+ "\n" +
+ "If you really want to use this API and don't need to support older devices just "
+ +
+ "set the `minSdkVersion` in your `AndroidManifest.xml` file." +
+ "\n" +
+ "If your code is *deliberately* accessing newer APIs, and you have ensured " +
+ "(e.g. with conditional execution) that this code will only ever be called on a "
+ +
+ "supported platform, then you can annotate your class or method with the " +
+ "`@TargetApi` annotation specifying the local minimum SDK to apply, such as " +
+ "`@TargetApi(11)`, such that this check considers 11 rather than your manifest "
+ +
+ "file's minimum SDK as the required API level.\n",
Category.CORRECTNESS,
6,
- Severity.ERROR,
+ Severity.WARNING,
ApiDetector.class,
- EnumSet.of(Scope.CLASS_FILE, Scope.RESOURCE_FILE, Scope.MANIFEST, Scope.JAVA_FILE))
- .addAnalysisScope(Scope.RESOURCE_FILE_SCOPE)
- .addAnalysisScope(Scope.CLASS_FILE_SCOPE)
+ EnumSet.of(Scope.JAVA_FILE))
.addAnalysisScope(Scope.JAVA_FILE_SCOPE);
/** Accessing an unsupported API */
@@ -207,7 +236,7 @@ public class ApiDetector extends ResourceXmlDetector
private ApiLookup mApiDatabase;
private int mMinApi = -1;
- private Set<String> mWarnedFields;
+ private Map<String, List<Pair<String, Location>>> mPendingFields;
/** Constructs a new API check */
public ApiDetector() {
@@ -623,13 +652,14 @@ public class ApiDetector extends ResourceXmlDetector
continue;
}
String fqcn = ClassContext.getFqcn(owner) + '#' + name;
- 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());
+ if (mPendingFields != null) {
+ mPendingFields.remove(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;
@@ -972,7 +1002,22 @@ public class ApiDetector extends ResourceXmlDetector
context.report(UNSUPPORTED, method, node, location, message, null);
}
- // ---- Implements JavaScanner ----
+ @Override
+ public void afterCheckProject(@NonNull Context context) {
+ if (mPendingFields != null) {
+ for (List<Pair<String, Location>> list : mPendingFields.values()) {
+ for (Pair<String, Location> pair : list) {
+ String message = pair.getFirst();
+ Location location = pair.getSecond();
+ context.report(INLINED, location, message, null);
+ }
+ }
+ }
+
+ super.afterCheckProject(context);
+ }
+
+// ---- Implements JavaScanner ----
@Nullable
@Override
@@ -1282,16 +1327,32 @@ public class ApiDetector extends ResourceXmlDetector
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();
+
+ LintDriver driver = mContext.getDriver();
+ if (driver.isSuppressed(INLINED, node)) {
+ return true;
}
- mWarnedFields.add(fqcn);
+
+ // Also allow to suppress these issues with NewApi, since some
+ // fields used to get identified that way
+ if (driver.isSuppressed(UNSUPPORTED, node)) {
+ return true;
+ }
+
+ // We can't report the issue right away; we don't yet know if
+ // this is an actual inlined (static primitive or String) yet.
+ // So just make a note of it, and report these after the project
+ // checking has finished; any fields that aren't inlined will be
+ // cleared when they're noticed by the class check.
+ if (mPendingFields == null) {
+ mPendingFields = Maps.newHashMapWithExpectedSize(20);
+ }
+ List<Pair<String, Location>> list = mPendingFields.get(fqcn);
+ if (list == null) {
+ list = new ArrayList<Pair<String, Location>>();
+ mPendingFields.put(fqcn, list);
+ }
+ list.add(Pair.of(message, location));
}
return true;
@@ -1305,6 +1366,8 @@ public class ApiDetector extends ResourceXmlDetector
* in a later version of Android than the application's {@code minSdkVersion}.
*
* @param node the instruction to check
+ * @param name the name of the constant
+ * @param owner the field owner
* @return true if the given usage is safe on older versions than the introduction
* level of the constant
*/
@@ -1321,6 +1384,17 @@ public class ApiDetector extends ResourceXmlDetector
&& name.equals("MATCH_PARENT")) { //$NON-NLS-1$
return true;
}
+ if (owner.equals("android/widget/AbsListView") //$NON-NLS-1$
+ && ((name.equals("CHOICE_MODE_NONE") //$NON-NLS-1$
+ || name.equals("CHOICE_MODE_MULTIPLE") //$NON-NLS-1$
+ || name.equals("CHOICE_MODE_SINGLE")))) { //$NON-NLS-1$
+ // android.widget.ListView#CHOICE_MODE_MULTIPLE and friends have API=1,
+ // but in API 11 it was moved up to the parent class AbsListView.
+ // Referencing AbsListView#CHOICE_MODE_MULTIPLE technically requires API 11,
+ // but the constant is the same as the older version, so accept this without
+ // warning.
+ 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
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/BuiltinIssueRegistry.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/BuiltinIssueRegistry.java
index 762446b..070ca3e 100644
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/BuiltinIssueRegistry.java
+++ b/lint/libs/lint_checks/src/main/java/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 = 139;
+ final int initialCapacity = 140;
List<Issue> issues = new ArrayList<Issue>(initialCapacity);
issues.add(AccessibilityDetector.ISSUE);
@@ -64,6 +64,7 @@ public class BuiltinIssueRegistry extends IssueRegistry {
issues.add(FieldGetterDetector.ISSUE);
issues.add(SdCardDetector.ISSUE);
issues.add(ApiDetector.UNSUPPORTED);
+ issues.add(ApiDetector.INLINED);
issues.add(ApiDetector.OVERRIDE);
issues.add(InvalidPackageDetector.ISSUE);
issues.add(DuplicateIdDetector.CROSS_LAYOUT);