aboutsummaryrefslogtreecommitdiffstats
path: root/lint
diff options
context:
space:
mode:
authorTor Norbye <tnorbye@google.com>2012-11-30 13:48:47 -0800
committerTor Norbye <tnorbye@google.com>2012-11-30 17:47:09 -0800
commitdefb28521b99e2b42d6e60e2eda1259803514196 (patch)
tree1dea7b7c6f8f7539c60c99a556c124435a3f36dd /lint
parent6c7924f6fea737bf5c87c1a6b845d690b8c46099 (diff)
downloadsdk-defb28521b99e2b42d6e60e2eda1259803514196.zip
sdk-defb28521b99e2b42d6e60e2eda1259803514196.tar.gz
sdk-defb28521b99e2b42d6e60e2eda1259803514196.tar.bz2
Add tools:targetApi to control local minSdk (like @TargetApi)
Also add XML editor quickfix to set the attribute on an API violation. Change-Id: Ife95d73659656e98a6fb1a322354f5fcfcef1888
Diffstat (limited to 'lint')
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java36
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java58
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/MainTest.java3
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ApiDetectorTest.java10
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/layout_targetapi.xml35
5 files changed, 137 insertions, 5 deletions
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java
index c0d5a85..44c0dc2 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java
@@ -755,4 +755,40 @@ public class LintUtils {
return imported;
}
+
+ /**
+ * Returns the applicable build code (for
+ * {@code android.os.Build.VERSION_CODES}) for the corresponding API level,
+ * or null if it's unknown.
+ *
+ * @param api the API level to look up a version code for
+ * @return the corresponding build code field name, or null
+ */
+ @Nullable
+ public static String getBuildCode(int api) {
+ // See http://developer.android.com/reference/android/os/Build.VERSION_CODES.html
+ switch (api) {
+ case 1: return "BASE"; //$NON-NLS-1$
+ case 2: return "BASE_1_1"; //$NON-NLS-1$
+ case 3: return "CUPCAKE"; //$NON-NLS-1$
+ case 4: return "DONUT"; //$NON-NLS-1$
+ case 5: return "ECLAIR"; //$NON-NLS-1$
+ case 6: return "ECLAIR_0_1"; //$NON-NLS-1$
+ case 7: return "ECLAIR_MR1"; //$NON-NLS-1$
+ case 8: return "FROYO"; //$NON-NLS-1$
+ case 9: return "GINGERBREAD"; //$NON-NLS-1$
+ case 10: return "GINGERBREAD_MR1"; //$NON-NLS-1$
+ case 11: return "HONEYCOMB"; //$NON-NLS-1$
+ case 12: return "HONEYCOMB_MR1"; //$NON-NLS-1$
+ case 13: return "HONEYCOMB_MR2"; //$NON-NLS-1$
+ case 14: return "ICE_CREAM_SANDWICH"; //$NON-NLS-1$
+ case 15: return "ICE_CREAM_SANDWICH_MR1"; //$NON-NLS-1$
+ case 16: return "JELLY_BEAN"; //$NON-NLS-1$
+ case 17: return "JELLY_BEAN_MR1"; //$NON-NLS-1$
+ // If you add more versions here, also update AdtUtils#getAndroidName and
+ // SdkConstants#HIGHEST_KNOWN_API
+ }
+
+ return null;
+ }
}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java
index fd7bba9..7129b9a 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java
@@ -19,14 +19,17 @@ package com.android.tools.lint.checks;
import static com.android.SdkConstants.ANDROID_PREFIX;
import static com.android.SdkConstants.ANDROID_THEME_PREFIX;
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.TARGET_API;
+import static com.android.SdkConstants.TOOLS_URI;
import static com.android.SdkConstants.VIEW_TAG;
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;
import static com.android.tools.lint.detector.api.Location.SearchDirection.NEAREST;
+import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.resources.ResourceFolderType;
import com.android.tools.lint.client.api.LintDriver;
@@ -35,6 +38,7 @@ import com.android.tools.lint.detector.api.ClassContext;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.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.ResourceXmlDetector;
@@ -88,7 +92,10 @@ public class ApiDetector extends ResourceXmlDetector implements Detector.ClassSc
"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.",
+ "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.",
Category.CORRECTNESS,
6,
Severity.ERROR,
@@ -168,7 +175,8 @@ public class ApiDetector extends ResourceXmlDetector implements Detector.ClassSc
}
int api = mApiDatabase.getFieldVersion(owner, name);
int minSdk = getMinSdk(context);
- if (api > minSdk && api > context.getFolderVersion()) {
+ if (api > minSdk && api > context.getFolderVersion()
+ && api > getLocalMinSdk(attribute.getOwnerElement())) {
Location location = context.getLocation(attribute);
String message = String.format(
"%1$s requires API level %2$d (current min is %3$d)",
@@ -210,7 +218,8 @@ public class ApiDetector extends ResourceXmlDetector implements Detector.ClassSc
}
int api = mApiDatabase.getFieldVersion(owner, name);
int minSdk = getMinSdk(context);
- if (api > minSdk && api > context.getFolderVersion()) {
+ if (api > minSdk && api > context.getFolderVersion()
+ && api > getLocalMinSdk(element)) {
Location location = context.getLocation(textNode);
String message = String.format(
"%1$s requires API level %2$d (current min is %3$d)",
@@ -243,7 +252,8 @@ public class ApiDetector extends ResourceXmlDetector implements Detector.ClassSc
// "(Landroid/content/Context;Landroid/util/AttributeSet;I)V"); //$NON-NLS-1$
"(Landroid/content/Context;)"); //$NON-NLS-1$
int minSdk = getMinSdk(context);
- if (api > minSdk && api > context.getFolderVersion()) {
+ if (api > minSdk && api > context.getFolderVersion()
+ && api > getLocalMinSdk(element)) {
Location location = context.getLocation(element);
String message = String.format(
"View requires API level %1$d (current min is %2$d): <%3$s>",
@@ -650,7 +660,7 @@ public class ApiDetector extends ResourceXmlDetector implements Detector.ClassSc
* @return the API level to use for this node, or -1
*/
@SuppressWarnings({"unchecked", "rawtypes"})
- private int getLocalMinSdk(List annotations) {
+ private static int getLocalMinSdk(List annotations) {
if (annotations != null) {
for (AnnotationNode annotation : (List<AnnotationNode>)annotations) {
String desc = annotation.desc;
@@ -680,6 +690,44 @@ public class ApiDetector extends ResourceXmlDetector implements Detector.ClassSc
return -1;
}
+ /**
+ * Returns the minimum SDK to use in the given element context, or -1 if no
+ * {@code tools:targetApi} attribute was found.
+ *
+ * @param element the element to look at, including parents
+ * @return the API level to use for this element, or -1
+ */
+ private int getLocalMinSdk(@NonNull Element element) {
+ while (element != null) {
+ String targetApi = element.getAttributeNS(TOOLS_URI, ATTR_TARGET_API);
+ if (targetApi != null && !targetApi.isEmpty()) {
+ if (Character.isDigit(targetApi.charAt(0))) {
+ try {
+ return Integer.parseInt(targetApi);
+ } catch (NumberFormatException nufe) {
+ break;
+ }
+ }
+
+ for (int api = 1; api < SdkConstants.HIGHEST_KNOWN_API; api++) {
+ String code = LintUtils.getBuildCode(api);
+ if (code != null && code.equalsIgnoreCase(targetApi)) {
+ return api;
+ }
+ }
+ }
+
+ Node parent = element.getParentNode();
+ if (parent != null && parent.getNodeType() == Node.ELEMENT_NODE) {
+ element = (Element) parent;
+ } else {
+ break;
+ }
+ }
+
+ return -1;
+ }
+
private void report(final ClassContext context, String message, AbstractInsnNode node,
MethodNode method, String patternStart, String patternEnd, SearchHints hints) {
int lineNumber = node != null ? ClassContext.findLineNumber(node) : -1;
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/MainTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/MainTest.java
index d015a3d..ba31ed8 100644
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/MainTest.java
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/MainTest.java
@@ -139,6 +139,9 @@ public class MainTest extends AbstractCheckTest {
"@TargetApi(11), such that this check considers 11 rather than your manifest\n" +
"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\n" +
+ "the element will only be inflated in an adequate context.\n" +
+ "\n" +
"\n",
// Expected error
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ApiDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ApiDetectorTest.java
index 3ef09aa..9823f23 100644
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ApiDetectorTest.java
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ApiDetectorTest.java
@@ -72,6 +72,16 @@ public class ApiDetectorTest extends AbstractCheckTest {
));
}
+ public void testXmlApi1TargetApi() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "apicheck/layout_targetapi.xml=>res/layout/layout.xml"
+ ));
+ }
+
public void testXmlApiFolderVersion11() throws Exception {
assertEquals(
"res/color-v11/colors.xml:9: Error: @android:color/holo_red_light requires API level 14 (current min is 1) [NewApi]\n" +
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/layout_targetapi.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/layout_targetapi.xml
new file mode 100644
index 0000000..51ce945
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/layout_targetapi.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="fill_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:targetApi="11" >
+
+ <!-- Requires API 5 -->
+
+ <QuickContactBadge
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <!-- Requires API 11 -->
+
+ <CalendarView
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent" />
+
+ <!-- Requires API 14 -->
+
+ <GridLayout
+ foo="@android:attr/actionBarSplitStyle"
+ bar="@android:color/holo_red_light"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ tools:targetApi="ICE_CREAM_SANDWICH" >
+
+ <Button
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent" />
+ </GridLayout>
+
+</LinearLayout>