diff options
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 Binary files differnew file mode 100644 index 0000000..a37d62c --- /dev/null +++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck2.class.data 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); |