aboutsummaryrefslogtreecommitdiffstats
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
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
-rw-r--r--common/src/com/android/SdkConstants.java1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java38
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAnnotation.java3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java82
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java14
-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
10 files changed, 216 insertions, 64 deletions
diff --git a/common/src/com/android/SdkConstants.java b/common/src/com/android/SdkConstants.java
index 0708647..48cecc1 100644
--- a/common/src/com/android/SdkConstants.java
+++ b/common/src/com/android/SdkConstants.java
@@ -979,6 +979,7 @@ public final class SdkConstants {
public static final String SUPPRESS_ALL = "all"; //$NON-NLS-1$
public static final String SUPPRESS_LINT = "SuppressLint"; //$NON-NLS-1$
public static final String TARGET_API = "TargetApi"; //$NON-NLS-1$
+ public static final String ATTR_TARGET_API = "targetApi"; //$NON-NLS-1$
public static final String FQCN_SUPPRESS_LINT = "android.annotation." + SUPPRESS_LINT; //$NON-NLS-1$
public static final String FQCN_TARGET_API = "android.annotation." + TARGET_API; //$NON-NLS-1$
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
index 6e051df..c98e94f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
@@ -815,42 +815,6 @@ public class AdtUtils {
}
/**
- * 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 getBuildCodes(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 #getAndroidName and
- // SdkConstants#HIGHEST_KNOWN_API
- }
-
- return null;
- }
-
- /**
* Returns a string label for the given target, of the form
* "API 16: Android 4.1 (Jelly Bean)".
*
@@ -994,7 +958,7 @@ public class AdtUtils {
case 15: return "API 15: Android 4.0.3 (IceCreamSandwich)";
case 16: return "API 16: Android 4.1 (Jelly Bean)";
case 17: return "API 17: Android 4.2 (Jelly Bean)";
- // If you add more versions here, also update #getBuildCodes and
+ // If you add more versions here, also update LintUtils#getBuildCodes and
// SdkConstants#HIGHEST_KNOWN_API
default: {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAnnotation.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAnnotation.java
index 80dac65..59dbe82 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAnnotation.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAnnotation.java
@@ -31,6 +31,7 @@ import com.android.ide.eclipse.adt.internal.editors.IconFactory;
import com.android.tools.lint.checks.AnnotationDetector;
import com.android.tools.lint.checks.ApiDetector;
import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LintUtils;
import com.android.tools.lint.detector.api.Scope;
import org.eclipse.core.resources.IMarker;
@@ -407,7 +408,7 @@ class AddSuppressAnnotation implements IMarkerResolution2 {
// @TargetApi is only valid on methods and classes, not fields etc
&& (body instanceof MethodDeclaration
|| body instanceof TypeDeclaration)) {
- String apiString = AdtUtils.getBuildCodes(api);
+ String apiString = LintUtils.getBuildCode(api);
if (apiString == null) {
apiString = Integer.toString(api);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java
index 23943d5..fdb0383 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java
@@ -17,6 +17,7 @@
package com.android.ide.eclipse.adt.internal.lint;
import static com.android.SdkConstants.ATTR_IGNORE;
+import static com.android.SdkConstants.ATTR_TARGET_API;
import static com.android.SdkConstants.DOT_XML;
import com.android.annotations.NonNull;
@@ -26,6 +27,9 @@ import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
+import com.android.tools.lint.checks.ApiDetector;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.google.common.collect.Lists;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
@@ -38,6 +42,12 @@ import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
/**
* Fix for adding {@code tools:ignore="id"} attributes in XML files.
*/
@@ -47,14 +57,26 @@ class AddSuppressAttribute implements ICompletionProposal {
private final IMarker mMarker;
private final Element mElement;
private final String mDescription;
+ /**
+ * Should it create a {@code tools:targetApi} attribute instead of a
+ * {@code tools:ignore} attribute? If so pass a non null API level
+ */
+ private final String mTargetApi;
+
- private AddSuppressAttribute(AndroidXmlEditor editor, String id, IMarker marker,
- Element element, String description) {
+ private AddSuppressAttribute(
+ @NonNull AndroidXmlEditor editor,
+ @NonNull String id,
+ @NonNull IMarker marker,
+ @NonNull Element element,
+ @NonNull String description,
+ @Nullable String targetApi) {
mEditor = editor;
mId = id;
mMarker = marker;
mElement = element;
mDescription = description;
+ mTargetApi = targetApi;
}
@Override
@@ -84,7 +106,16 @@ class AddSuppressAttribute implements ICompletionProposal {
@Override
public void apply(IDocument document) {
- AdtUtils.setToolsAttribute(mEditor, mElement, "Suppress Lint Warning", ATTR_IGNORE, mId,
+ String attribute;
+ String value;
+ if (mTargetApi != null) {
+ attribute = ATTR_TARGET_API;
+ value = mTargetApi;
+ } else {
+ attribute = ATTR_IGNORE;
+ value = mId;
+ }
+ AdtUtils.setToolsAttribute(mEditor, mElement, mDescription, attribute, value,
true /*reveal*/, true /*append*/);
try {
@@ -92,7 +123,7 @@ class AddSuppressAttribute implements ICompletionProposal {
// (so the user doesn't have to re-run lint just to see it disappear)
mMarker.delete();
} catch (CoreException e) {
- AdtPlugin.log(e, "Could not add suppress annotation");
+ AdtPlugin.log(e, "Could not remove marker");
}
}
@@ -103,17 +134,17 @@ class AddSuppressAttribute implements ICompletionProposal {
* @param editor the associated editor containing the marker
* @param marker the marker to create fixes for
* @param id the issue id
- * @return a fix for this marker, or null if unable
+ * @return a list of fixes for this marker, possibly empty
*/
- @Nullable
- public static AddSuppressAttribute createFix(
+ @NonNull
+ public static List<AddSuppressAttribute> createFixes(
@NonNull AndroidXmlEditor editor,
@NonNull IMarker marker,
@NonNull String id) {
// This only applies to XML files:
String fileName = marker.getResource().getName();
if (!fileName.endsWith(DOT_XML)) {
- return null;
+ return Collections.emptyList();
}
int offset = marker.getAttribute(IMarker.CHAR_START, -1);
@@ -127,7 +158,7 @@ class AddSuppressAttribute implements ICompletionProposal {
node = DomUtilities.getNode(editor.getStructuredDocument(), offset);
}
if (node == null) {
- return null;
+ return Collections.emptyList();
}
Document document = node.getOwnerDocument();
while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
@@ -136,13 +167,40 @@ class AddSuppressAttribute implements ICompletionProposal {
if (node == null) {
node = document.getDocumentElement();
if (node == null) {
- return null;
+ return Collections.emptyList();
}
}
String desc = String.format("Add ignore '%1$s\' to element", id);
Element element = (Element) node;
- return new AddSuppressAttribute(editor, id, marker, element, desc);
+ List<AddSuppressAttribute> fixes = Lists.newArrayListWithExpectedSize(2);
+ fixes.add(new AddSuppressAttribute(editor, id, marker, element, desc, null));
+
+ int api = -1;
+ if (id.equals(ApiDetector.UNSUPPORTED.getId())) {
+ String message = marker.getAttribute(IMarker.MESSAGE, null);
+ if (message != null) {
+ Pattern pattern = Pattern.compile("\\s(\\d+)\\s"); //$NON-NLS-1$
+ Matcher matcher = pattern.matcher(message);
+ if (matcher.find()) {
+ api = Integer.parseInt(matcher.group(1));
+ String targetApi;
+ String buildCode = LintUtils.getBuildCode(api);
+ if (buildCode != null) {
+ targetApi = buildCode.toLowerCase(Locale.US);
+ fixes.add(new AddSuppressAttribute(editor, id, marker, element,
+ String.format("Add targetApi '%1$s\' to element", targetApi),
+ targetApi));
+ }
+ targetApi = Integer.toString(api);
+ fixes.add(new AddSuppressAttribute(editor, id, marker, element,
+ String.format("Add targetApi '%1$s\' to element", targetApi),
+ targetApi));
+ }
+ }
+ }
+
+ return fixes;
}
/**
@@ -170,7 +228,7 @@ class AddSuppressAttribute implements ICompletionProposal {
node = node.getOwnerDocument().getDocumentElement();
String desc = String.format("Add ignore '%1$s\' to element", id);
Element element = (Element) node;
- return new AddSuppressAttribute(editor, id, marker, element, desc);
+ return new AddSuppressAttribute(editor, id, marker, element, desc, null);
}
return null;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java
index 4c356b7..22fe23b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java
@@ -189,11 +189,7 @@ public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssi
String message = marker.getAttribute(IMarker.MESSAGE, null);
proposals.add(new MoreInfoProposal(id, message));
- ICompletionProposal fix = AddSuppressAttribute.createFix(editor, marker, id);
- if (fix != null) {
- proposals.add(fix);
- }
-
+ proposals.addAll(AddSuppressAttribute.createFixes(editor, marker, id));
proposals.add(new SuppressProposal(file, id, false));
proposals.add(new SuppressProposal(file.getProject(), id, true /* all */));
proposals.add(new SuppressProposal(file, id, true /* all */));
@@ -296,11 +292,11 @@ public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssi
assert isXml;
if (part instanceof AndroidXmlEditor) {
AndroidXmlEditor editor = (AndroidXmlEditor) part;
- AddSuppressAttribute fix = AddSuppressAttribute.createFix(editor,
+ List<AddSuppressAttribute> fixes = AddSuppressAttribute.createFixes(editor,
marker, id);
- if (fix != null) {
+ if (fixes.size() > 0) {
IStructuredDocument document = editor.getStructuredDocument();
- fix.apply(document);
+ fixes.get(0).apply(document);
}
}
}
@@ -511,7 +507,7 @@ public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssi
@Override
public String getDisplayString() {
- return "Explain Issue";
+ return String.format("Explain Issue (%1$s)", mId);
}
// ---- Implements MarkerResolution2 ----
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>