aboutsummaryrefslogtreecommitdiffstats
path: root/lint
diff options
context:
space:
mode:
authorTor Norbye <tnorbye@google.com>2012-10-15 20:31:30 -0700
committerTor Norbye <tnorbye@google.com>2012-10-16 10:18:18 -0700
commit32b243dd4b800771897ccc2e1d894ece1587bd67 (patch)
treeb67c07de74f1d1c9536c077d0f174eea62915ea5 /lint
parent18f09df5f4251f5c370ba9fb92f414379b1bc530 (diff)
downloadsdk-32b243dd4b800771897ccc2e1d894ece1587bd67.zip
sdk-32b243dd4b800771897ccc2e1d894ece1587bd67.tar.gz
sdk-32b243dd4b800771897ccc2e1d894ece1587bd67.tar.bz2
Check for missing layout_width and layout_height attributes
Also looks at style declarations and attributes on include tags to allow elements to either pick up sizes from styles or from the including context. Change-Id: I91a944805d8a906ff63b5a22f2faa876e7292c19
Diffstat (limited to 'lint')
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java3
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/ControlFlowGraph.java5
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java14
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/MissingIdDetector.java2
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/RequiredAttributeDetector.java519
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/StringFormatDetector.java95
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/XmlReporterTest.java5
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/LabelForDetectorTest.java5
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MissingIdDetectorTest.java5
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/RequiredAttributeDetectorTest.java85
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/size.xml27
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/size2.xml43
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/sizeincluded.xml5
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/sizestyles.xml17
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/test/pkg/InflaterTest.java.txt65
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/client/api/LintClientTest.java5
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/detector/api/IssueTest.java7
17 files changed, 851 insertions, 56 deletions
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
index 97df003..d1c8f67 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
+++ b/lint/libs/lint_checks/src/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 = 118;
+ final int initialCapacity = 119;
List<Issue> issues = new ArrayList<Issue>(initialCapacity);
issues.add(AccessibilityDetector.ISSUE);
@@ -176,6 +176,7 @@ public class BuiltinIssueRegistry extends IssueRegistry {
issues.add(PrivateKeyDetector.ISSUE);
issues.add(AnnotationDetector.ISSUE);
issues.add(SystemPermissionsDetector.ISSUE);
+ issues.add(RequiredAttributeDetector.ISSUE);
assert initialCapacity >= issues.size() : issues.size();
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ControlFlowGraph.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ControlFlowGraph.java
index b7e5787..5f5b2fe 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ControlFlowGraph.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ControlFlowGraph.java
@@ -1,11 +1,11 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.eclipse.org/org/documents/epl-v10.php
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.android.tools.lint.checks;
import com.android.annotations.NonNull;
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java
index 5b2f632..2f416b5 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java
@@ -1616,7 +1616,7 @@ public class IconDetector extends ResourceXmlDetector implements Detector.JavaSc
return super.visitConstructorInvocation(node);
}
- Node method = getParentMethod(node);
+ Node method = StringFormatDetector.getParentMethod(node);
if (method != null) {
// Must track local types
String name = StringFormatDetector.getResourceForFirstArg(method, node);
@@ -1639,7 +1639,7 @@ public class IconDetector extends ResourceXmlDetector implements Detector.JavaSc
}
}
if (isBuilder) {
- Node method = getParentMethod(node);
+ Node method = StringFormatDetector.getParentMethod(node);
if (method != null) {
SetIconFinder finder = new SetIconFinder();
method.accept(finder);
@@ -1651,16 +1651,6 @@ public class IconDetector extends ResourceXmlDetector implements Detector.JavaSc
}
}
- @Nullable
- private Node getParentMethod(@NonNull Node node) {
- Node method = node;
- while (method != null && !(method.getParent() instanceof MethodDeclaration)) {
- method = method.getParent();
- }
-
- return method;
- }
-
private boolean handleSelect(Select select) {
if (select.toString().startsWith(R_DRAWABLE_PREFIX)) {
String name = select.astIdentifier().astValue();
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/MissingIdDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/MissingIdDetector.java
index b02e44a..174261c 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/MissingIdDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/MissingIdDetector.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/RequiredAttributeDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/RequiredAttributeDetector.java
new file mode 100644
index 0000000..f4b3830
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/RequiredAttributeDetector.java
@@ -0,0 +1,519 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX;
+import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_LAYOUT;
+import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
+import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_PARENT;
+import static com.android.SdkConstants.ATTR_STYLE;
+import static com.android.SdkConstants.FD_RES_LAYOUT;
+import static com.android.SdkConstants.FN_RESOURCE_BASE;
+import static com.android.SdkConstants.FQCN_GRID_LAYOUT_V7;
+import static com.android.SdkConstants.GRID_LAYOUT;
+import static com.android.SdkConstants.LAYOUT_RESOURCE_PREFIX;
+import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX;
+import static com.android.SdkConstants.TABLE_LAYOUT;
+import static com.android.SdkConstants.TABLE_ROW;
+import static com.android.SdkConstants.TAG_ITEM;
+import static com.android.SdkConstants.TAG_STYLE;
+import static com.android.SdkConstants.VIEW_INCLUDE;
+import static com.android.SdkConstants.VIEW_MERGE;
+import static com.android.resources.ResourceFolderType.LAYOUT;
+import static com.android.resources.ResourceFolderType.VALUES;
+import static com.android.tools.lint.detector.api.LintUtils.getLayoutName;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.resources.ResourceFolderType;
+import com.android.tools.lint.detector.api.Category;
+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.JavaContext;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.Expression;
+import lombok.ast.MethodInvocation;
+import lombok.ast.NullLiteral;
+import lombok.ast.Select;
+import lombok.ast.StrictListAccessor;
+import lombok.ast.VariableReference;
+
+/**
+ * Ensures that layout width and height attributes are specified
+ */
+public class RequiredAttributeDetector extends LayoutDetector implements Detector.JavaScanner {
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "RequiredSize", //$NON-NLS-1$
+ "Ensures that the layout_width and layout_height are specified for all views",
+
+ "All views must specify an explicit layout_width and layout_height attribute. " +
+ "There is a runtime check for this, so if you fail to specify a size, an exception " +
+ "is thrown at runtime.\n" +
+ "\n" +
+ "It's possible to specify these widths via styles as well. GridLayout, as a special " +
+ "case, does not require you to specify a size.",
+ Category.CORRECTNESS,
+ 4,
+ Severity.ERROR,
+ RequiredAttributeDetector.class,
+ EnumSet.of(Scope.JAVA_FILE, Scope.ALL_RESOURCE_FILES));
+
+ /** Map from each style name to parent style */
+ private @Nullable Map<String, String> mStyleParents;
+
+ /** Set of style names where the style sets the layout width */
+ private @Nullable Set<String> mWidthStyles;
+
+ /** Set of style names where the style sets the layout height */
+ private @Nullable Set<String> mHeightStyles;
+
+ /** Set of layout names for layouts that are included by an {@code <include>} tag
+ * where the width is set on the include */
+ private @Nullable Set<String> mIncludedWidths;
+
+ /** Set of layout names for layouts that are included by an {@code <include>} tag
+ * where the height is set on the include */
+ private @Nullable Set<String> mIncludedHeights;
+
+ /** Set of layout names for layouts that are included by an {@code <include>} tag
+ * where the width is <b>not</b> set on the include */
+ private @Nullable Set<String> mNotIncludedWidths;
+
+ /** Set of layout names for layouts that are included by an {@code <include>} tag
+ * where the height is <b>not</b> set on the include */
+ private @Nullable Set<String> mNotIncludedHeights;
+
+ /** Constructs a new {@link RequiredAttributeDetector} */
+ public RequiredAttributeDetector() {
+ }
+
+ @Override
+ public @NonNull Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+ return folderType == LAYOUT || folderType == VALUES;
+ }
+
+ @Override
+ public void afterCheckProject(@NonNull Context context) {
+ // Process checks in two phases:
+ // Phase 1: Gather styles and includes (styles are encountered after the layouts
+ // so we can't do it in a single phase, and includes can be affected by includes from
+ // layouts we haven't seen yet)
+ // Phase 2: Process layouts, using gathered style and include data, and mark layouts
+ // not known.
+ //
+ if (context.getPhase() == 1) {
+ context.requestRepeat(this, Scope.RESOURCE_FILE_SCOPE);
+ }
+ }
+
+ private boolean isWidthStyle(String style) {
+ return isSizeStyle(style, mWidthStyles);
+ }
+
+ private boolean isHeightStyle(String style) {
+ return isSizeStyle(style, mHeightStyles);
+ }
+
+ private boolean isSizeStyle(String style, Set<String> sizeStyles) {
+ if (sizeStyles == null) {
+ return false;
+ }
+ return isSizeStyle(stripStylePrefix(style), sizeStyles, 0);
+ }
+
+ private boolean isSizeStyle(
+ @NonNull String style,
+ @NonNull Set<String> sizeStyles, int depth) {
+ if (depth == 30) {
+ // Cycle between local and framework attribute style missed
+ // by the fact that we're stripping the distinction between framework
+ // and local styles here
+ return false;
+ }
+
+ assert !style.startsWith(STYLE_RESOURCE_PREFIX)
+ && !style.startsWith(ANDROID_STYLE_RESOURCE_PREFIX);
+
+ if (sizeStyles.contains(style)) {
+ return true;
+ }
+
+ if (mStyleParents != null) {
+ String parentStyle = mStyleParents.get(style);
+ if (parentStyle != null) {
+ parentStyle = stripStylePrefix(parentStyle);
+ if (isSizeStyle(parentStyle, sizeStyles, depth + 1)) {
+ return true;
+ }
+ }
+ }
+
+ int index = style.lastIndexOf('.');
+ if (index > 0) {
+ return isSizeStyle(style.substring(0, index), sizeStyles, depth + 1);
+ }
+
+ return false;
+ }
+
+ private static boolean hasLayoutVariations(File file) {
+ File parent = file.getParentFile();
+ if (parent == null) {
+ return false;
+ }
+ File res = file.getParentFile();
+ if (res == null) {
+ return false;
+ }
+ String name = file.getName();
+ File[] folders = res.listFiles();
+ if (folders == null) {
+ return false;
+ }
+ for (File folder : folders) {
+ if (!folder.getName().startsWith(FD_RES_LAYOUT)) {
+ continue;
+ }
+ if (folder.equals(parent)) {
+ continue;
+ }
+ File other = new File(folder, name);
+ if (other.exists()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static String stripStylePrefix(@NonNull String style) {
+ if (style.startsWith(STYLE_RESOURCE_PREFIX)) {
+ style = style.substring(STYLE_RESOURCE_PREFIX.length());
+ } else if (style.startsWith(ANDROID_STYLE_RESOURCE_PREFIX)) {
+ style = style.substring(ANDROID_STYLE_RESOURCE_PREFIX.length());
+ }
+
+ return style;
+ }
+
+ private static boolean isRootElement(@NonNull Node node) {
+ return node == node.getOwnerDocument().getDocumentElement();
+ }
+
+ // ---- Implements XmlScanner ----
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return ALL;
+ }
+
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ ResourceFolderType folderType = context.getResourceFolderType();
+ int phase = context.getPhase();
+ if (phase == 1 && folderType == VALUES) {
+ String tag = element.getTagName();
+ if (TAG_STYLE.equals(tag)) {
+ String parent = element.getAttribute(ATTR_PARENT);
+ if (parent != null && !parent.isEmpty()) {
+ String name = element.getAttribute(ATTR_NAME);
+ if (name != null && !name.isEmpty()) {
+ if (mStyleParents == null) {
+ mStyleParents = Maps.newHashMap();
+ }
+ mStyleParents.put(name, parent);
+ }
+ }
+ } else if (TAG_ITEM.equals(tag)
+ && TAG_STYLE.equals(element.getParentNode().getNodeName())) {
+ String name = element.getAttribute(ATTR_NAME);
+ if (name.endsWith(ATTR_LAYOUT_WIDTH) &&
+ name.equals(ANDROID_NS_NAME_PREFIX + ATTR_LAYOUT_WIDTH)) {
+ if (mWidthStyles == null) {
+ mWidthStyles = Sets.newHashSet();
+ }
+ String styleName = ((Element) element.getParentNode()).getAttribute(ATTR_NAME);
+ mWidthStyles.add(styleName);
+
+ }
+ if (name.endsWith(ATTR_LAYOUT_HEIGHT) &&
+ name.equals(ANDROID_NS_NAME_PREFIX + ATTR_LAYOUT_HEIGHT)) {
+ if (mHeightStyles == null) {
+ mHeightStyles = Sets.newHashSet();
+ }
+ String styleName = ((Element) element.getParentNode()).getAttribute(ATTR_NAME);
+ mHeightStyles.add(styleName);
+
+ }
+ }
+ } else if (folderType == LAYOUT) {
+ if (phase == 1) {
+ // Gather includes
+ if (element.getTagName().equals(VIEW_INCLUDE)) {
+ String layout = element.getAttribute(ATTR_LAYOUT);
+ if (layout != null && !layout.isEmpty()) {
+ recordIncludeWidth(layout,
+ element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH));
+ recordIncludeHeight(layout,
+ element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_HEIGHT));
+ }
+ }
+ } else {
+ assert phase == 2; // Check everything using style data and include data
+ boolean hasWidth = element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH);
+ boolean hasHeight = element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_HEIGHT);
+ if (hasWidth && hasHeight) {
+ return;
+ }
+
+ if (VIEW_MERGE.equals(element.getNodeName())) {
+ return;
+ }
+
+ boolean certain = true;
+
+ String parentTag = element.getParentNode() != null
+ ? element.getParentNode().getNodeName() : "";
+ if (TABLE_LAYOUT.equals(parentTag) || TABLE_ROW.equals(parentTag)) {
+ return;
+ }
+
+ boolean isRoot = isRootElement(element);
+ if (isRoot || isRootElement(element.getParentNode())
+ && VIEW_MERGE.equals(parentTag)) {
+ String name = LAYOUT_RESOURCE_PREFIX + getLayoutName(context.file);
+ if (!hasWidth && mIncludedWidths != null) {
+ hasWidth = mIncludedWidths.contains(name);
+ // If the layout is *also* included in a context where the width
+ // was not set, we're not certain; it's possible that
+ if (mNotIncludedWidths != null && mNotIncludedWidths.contains(name)) {
+ hasWidth = false;
+ // If we only have a single layout we know that this layout isn't
+ // always included with layout_width or layout_height set, but
+ // if there are multiple layouts, it's possible that at runtime
+ // we only load the size-less layout by the tag which includes
+ // the size
+ certain = !hasLayoutVariations(context.file);
+ }
+ }
+ if (!hasHeight && mIncludedHeights != null) {
+ hasHeight = mIncludedHeights.contains(name);
+ if (mNotIncludedHeights != null && mNotIncludedHeights.contains(name)) {
+ hasHeight = false;
+ certain = !hasLayoutVariations(context.file);
+ }
+ }
+ if (hasWidth && hasHeight) {
+ return;
+ }
+ }
+
+ if (!hasWidth || !hasHeight) {
+ String style = element.getAttribute(ATTR_STYLE);
+ if (style != null && !style.isEmpty()) {
+ if (!hasWidth) {
+ hasWidth = isWidthStyle(style);
+ }
+ if (!hasHeight) {
+ hasHeight = isHeightStyle(style);
+ }
+ }
+ if (hasWidth && hasHeight) {
+ return;
+ }
+ }
+
+ String tag = element.getTagName();
+ if (tag.equals(VIEW_INCLUDE) || tag.equals(GRID_LAYOUT)
+ || tag.equals(FQCN_GRID_LAYOUT_V7)) {
+ return;
+ }
+ String message;
+ if (!(hasWidth || hasHeight)) {
+ if (certain) {
+ message = "The required layout_width and layout_height attributes " +
+ "are missing";
+ } else {
+ message = "The required layout_width and layout_height attributes " +
+ "*may* be missing";
+ }
+ } else {
+ String attribute = hasWidth ? ATTR_LAYOUT_HEIGHT : ATTR_LAYOUT_WIDTH;
+ if (certain) {
+ message = String.format("The required %1$s attribute is missing",
+ attribute);
+ } else {
+ message = String.format("The required %1$s attribute *may* be missing",
+ attribute);
+ }
+ }
+ context.report(ISSUE, element, context.getLocation(element),
+ message, null);
+ }
+ }
+ }
+
+ private void recordIncludeWidth(String layout, boolean providesWidth) {
+ if (providesWidth) {
+ if (mIncludedWidths == null) {
+ mIncludedWidths = Sets.newHashSet();
+ }
+ mIncludedWidths.add(layout);
+ } else {
+ if (mNotIncludedWidths == null) {
+ mNotIncludedWidths = Sets.newHashSet();
+ }
+ mNotIncludedWidths.add(layout);
+ }
+ }
+
+ private void recordIncludeHeight(String layout, boolean providesHeight) {
+ if (providesHeight) {
+ if (mIncludedHeights == null) {
+ mIncludedHeights = Sets.newHashSet();
+ }
+ mIncludedHeights.add(layout);
+ } else {
+ if (mNotIncludedHeights == null) {
+ mNotIncludedHeights = Sets.newHashSet();
+ }
+ mNotIncludedHeights.add(layout);
+ }
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Override
+ @Nullable
+ public List<String> getApplicableMethodNames() {
+ return Collections.singletonList("inflate"); //$NON-NLS-1$
+ }
+
+ @Override
+ public void visitMethod(
+ @NonNull JavaContext context,
+ @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation call) {
+ // Handle
+ // View#inflate(Context context, int resource, ViewGroup root)
+ // LayoutInflater#inflate(int resource, ViewGroup root)
+ // LayoutInflater#inflate(int resource, ViewGroup root, boolean attachToRoot)
+ StrictListAccessor<Expression, MethodInvocation> args = call.astArguments();
+
+ String layout = null;
+ int index = 0;
+ for (Iterator<Expression> iterator = args.iterator(); iterator.hasNext(); index++) {
+ Expression expression = iterator.next();
+ if (expression instanceof Select) {
+ Select outer = (Select) expression;
+ Expression operand = outer.astOperand();
+ if (operand instanceof Select) {
+ Select inner = (Select) operand;
+ if (inner.astOperand() instanceof VariableReference) {
+ VariableReference reference = (VariableReference) inner.astOperand();
+ if (FN_RESOURCE_BASE.equals(reference.astIdentifier().astValue())
+ // TODO: constant
+ && "layout".equals(inner.astIdentifier().astValue())) {
+ layout = LAYOUT_RESOURCE_PREFIX + outer.astIdentifier().astValue();
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (layout == null) {
+ lombok.ast.Node method = StringFormatDetector.getParentMethod(call);
+ if (method != null) {
+ // Must track local types
+ index = 0;
+ String name = StringFormatDetector.getResourceArg(method, call, index);
+ if (name == null) {
+ index = 1;
+ name = StringFormatDetector.getResourceArg(method, call, index);
+ }
+ if (name != null) {
+ layout = LAYOUT_RESOURCE_PREFIX + name;
+ }
+ }
+ if (layout == null) {
+ // Flow analysis didn't succeed
+ return;
+ }
+ }
+
+ // In all the applicable signatures, the view root argument is immediately after
+ // the layout resource id.
+ int viewRootPos = index + 1;
+ if (viewRootPos < args.size()) {
+ int i = 0;
+ Iterator<Expression> iterator = args.iterator();
+ while (iterator.hasNext() && i < viewRootPos) {
+ iterator.next();
+ i++;
+ }
+ if (iterator.hasNext()) {
+ Expression viewRoot = iterator.next();
+ if (viewRoot instanceof NullLiteral) {
+ // Yep, this one inflates the given view with a null parent:
+ // Tag it as such. For now just use the include data structure since
+ // it has the same net effect
+ recordIncludeWidth(layout, true);
+ recordIncludeHeight(layout, true);
+ return;
+ }
+ }
+ }
+ if (args.size() == 2) {
+ } else if (args.size() == 3) {
+ } else {
+// Debugging only
+context.report(ISSUE, context.getLocation(call), "Unexpected inflate signature here", null);
+ }
+ }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/StringFormatDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/StringFormatDetector.java
index bf16aeb..f0c499c 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/StringFormatDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/StringFormatDetector.java
@@ -863,12 +863,7 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto
}
private void checkFormatCall(JavaContext context, MethodInvocation node) {
- lombok.ast.Node current = node.getParent();
- while (current != null
- && !(current instanceof MethodDeclaration)
- && !(current instanceof ConstructorDeclaration)) {
- current = current.getParent();
- }
+ lombok.ast.Node current = getParentMethod(node);
if (current != null) {
checkStringFormatCall(context, current, node);
}
@@ -892,7 +887,7 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto
return;
}
- StringTracker tracker = new StringTracker(method, call);
+ StringTracker tracker = new StringTracker(method, call, 0);
method.accept(tracker);
String name = tracker.getFormatStringName();
if (name == null) {
@@ -1010,10 +1005,33 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto
}
}
+ /** Returns the parent method of the given AST node */
+ @Nullable
+ static lombok.ast.Node getParentMethod(@NonNull lombok.ast.Node node) {
+ lombok.ast.Node current = node.getParent();
+ while (current != null
+ && !(current instanceof MethodDeclaration)
+ && !(current instanceof ConstructorDeclaration)) {
+ current = current.getParent();
+ }
+
+ return current;
+ }
+
/** Returns the resource name corresponding to the first argument in the given call */
static String getResourceForFirstArg(lombok.ast.Node method, lombok.ast.Node call) {
assert call instanceof MethodInvocation || call instanceof ConstructorInvocation;
- StringTracker tracker = new StringTracker(method, call);
+ StringTracker tracker = new StringTracker(method, call, 0);
+ method.accept(tracker);
+ String name = tracker.getFormatStringName();
+
+ return name;
+ }
+
+ /** Returns the resource name corresponding to the given argument in the given call */
+ static String getResourceArg(lombok.ast.Node method, lombok.ast.Node call, int argIndex) {
+ assert call instanceof MethodInvocation || call instanceof ConstructorInvocation;
+ StringTracker tracker = new StringTracker(method, call, argIndex);
method.accept(tracker);
String name = tracker.getFormatStringName();
@@ -1044,6 +1062,8 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto
private static class StringTracker extends ForwardingAstVisitor {
/** Method we're searching within */
private final lombok.ast.Node mTop;
+ /** The argument index in the method we're targeting */
+ private final int mArgIndex;
/** Map from variable name to corresponding string resource name */
private final Map<String, String> mMap = new HashMap<String, String>();
/** Map from variable name to corresponding type */
@@ -1057,8 +1077,9 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto
*/
private String mName;
- public StringTracker(lombok.ast.Node top, lombok.ast.Node targetNode) {
+ public StringTracker(lombok.ast.Node top, lombok.ast.Node targetNode, int argIndex) {
mTop = top;
+ mArgIndex = argIndex;
mTargetNode = targetNode;
}
@@ -1145,19 +1166,38 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto
return false;
}
+ @Nullable
+ private Expression getTargetArgument() {
+ Iterator<Expression> iterator;
+ if (mTargetNode instanceof MethodInvocation) {
+ iterator = ((MethodInvocation) mTargetNode).astArguments().iterator();
+ } else if (mTargetNode instanceof ConstructorInvocation) {
+ iterator = ((ConstructorInvocation) mTargetNode).astArguments().iterator();
+ } else {
+ return null;
+ }
+ int i = 0;
+ while (i < mArgIndex && iterator.hasNext()) {
+ iterator.next();
+ i++;
+ }
+ if (iterator.hasNext()) {
+ return iterator.next();
+ }
+
+ return null;
+ }
+
@Override
public boolean visitMethodInvocation(MethodInvocation node) {
if (node == mTargetNode) {
- StrictListAccessor<Expression, MethodInvocation> args = node.astArguments();
- if (args.size() > 0) {
- Expression first = args.first();
- if (first instanceof VariableReference) {
- VariableReference reference = (VariableReference) first;
- String variable = reference.astIdentifier().astValue();
- mName = mMap.get(variable);
- mDone = true;
- return true;
- }
+ Expression arg = getTargetArgument();
+ if (arg instanceof VariableReference) {
+ VariableReference reference = (VariableReference) arg;
+ String variable = reference.astIdentifier().astValue();
+ mName = mMap.get(variable);
+ mDone = true;
+ return true;
}
}
@@ -1169,16 +1209,13 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto
@Override
public boolean visitConstructorInvocation(ConstructorInvocation node) {
if (node == mTargetNode) {
- StrictListAccessor<Expression, ConstructorInvocation> args = node.astArguments();
- if (args.size() > 0) {
- Expression first = args.first();
- if (first instanceof VariableReference) {
- VariableReference reference = (VariableReference) first;
- String variable = reference.astIdentifier().astValue();
- mName = mMap.get(variable);
- mDone = true;
- return true;
- }
+ Expression arg = getTargetArgument();
+ if (arg instanceof VariableReference) {
+ VariableReference reference = (VariableReference) arg;
+ String variable = reference.astIdentifier().astValue();
+ mName = mMap.get(variable);
+ mDone = true;
+ return true;
}
}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/XmlReporterTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/XmlReporterTest.java
index 5792e1d..95147b4 100644
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/XmlReporterTest.java
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/XmlReporterTest.java
@@ -1,11 +1,11 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.eclipse.org/org/documents/epl-v10.php
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.android.tools.lint;
import com.android.tools.lint.checks.AbstractCheckTest;
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/LabelForDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/LabelForDetectorTest.java
index 7e139af..6d69e00 100644
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/LabelForDetectorTest.java
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/LabelForDetectorTest.java
@@ -1,11 +1,11 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.eclipse.org/org/documents/epl-v10.php
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.android.tools.lint.checks;
import com.android.tools.lint.detector.api.Detector;
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MissingIdDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MissingIdDetectorTest.java
index ac6b6c0..b72d8c8 100644
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MissingIdDetectorTest.java
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MissingIdDetectorTest.java
@@ -1,11 +1,11 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.eclipse.org/org/documents/epl-v10.php
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.android.tools.lint.checks;
import com.android.tools.lint.detector.api.Detector;
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/RequiredAttributeDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/RequiredAttributeDetectorTest.java
new file mode 100644
index 0000000..abf3e04
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/RequiredAttributeDetectorTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+@SuppressWarnings("javadoc")
+public class RequiredAttributeDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new RequiredAttributeDetector();
+ }
+
+ public void test() throws Exception {
+ // Simple: Only consider missing attributes in the layout xml file
+ // (though skip warnings on <merge> tags and under <GridLayout>
+ assertEquals(
+ "res/layout/size.xml:13: Error: The required layout_height attribute is missing [RequiredSize]\n" +
+ " <RadioButton\n" +
+ " ^\n" +
+ "res/layout/size.xml:18: Error: The required layout_width attribute is missing [RequiredSize]\n" +
+ " <EditText\n" +
+ " ^\n" +
+ "res/layout/size.xml:23: Error: The required layout_width and layout_height attributes are missing [RequiredSize]\n" +
+ " <EditText\n" +
+ " ^\n" +
+ "3 errors, 0 warnings\n",
+
+ lintProject("res/layout/size.xml"));
+ }
+
+ public void test2() throws Exception {
+ // Consider styles (specifying sizes) and includes (providing sizes for the root tags)
+ assertEquals(
+ "res/layout/size2.xml:9: Error: The required layout_width and layout_height attributes are missing [RequiredSize]\n" +
+ " <Button\n" +
+ " ^\n" +
+ "res/layout/size2.xml:18: Error: The required layout_height attribute is missing [RequiredSize]\n" +
+ " <Button\n" +
+ " ^\n" +
+ "2 errors, 0 warnings\n",
+
+ lintProject(
+ "res/layout/size2.xml",
+ "res/layout/sizeincluded.xml",
+ "res/values/sizestyles.xml"
+ //"res/layout/sizeincludedmerge"
+ ));
+ }
+
+ public void testInflaters() throws Exception {
+ // Consider java inflation
+ assertEquals(
+ "res/layout/size5.xml:2: Error: The required layout_width and layout_height attributes are missing [RequiredSize]\n" +
+ "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ "^\n" +
+ "1 errors, 0 warnings\n",
+
+ lintProject(
+ "src/test/pkg/InflaterTest.java.txt=>src/test/pkg/InflaterTest.java",
+ "res/layout/sizeincluded.xml=>res/layout/size1.xml",
+ "res/layout/sizeincluded.xml=>res/layout/size2.xml",
+ "res/layout/sizeincluded.xml=>res/layout/size3.xml",
+ "res/layout/sizeincluded.xml=>res/layout/size4.xml",
+ "res/layout/sizeincluded.xml=>res/layout/size5.xml",
+ "res/layout/sizeincluded.xml=>res/layout/size6.xml",
+ "res/layout/sizeincluded.xml=>res/layout/size7.xml"
+ ));
+ }
+
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/size.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/size.xml
new file mode 100644
index 0000000..e4d4a9b
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/size.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <RadioButton
+ android:id="@+id/button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Button" />
+
+ <RadioButton
+ android:id="@+id/button2"
+ android:layout_width="wrap_content"
+ android:text="Button" />
+
+ <EditText
+ android:id="@+id/edittext"
+ android:layout_height="wrap_content"
+ android:text="EditText" />
+
+ <EditText
+ android:id="@+id/edittext2"
+ android:text="EditText" />
+
+</LinearLayout>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/size2.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/size2.xml
new file mode 100644
index 0000000..d06d00f
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/size2.xml
@@ -0,0 +1,43 @@
+<?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="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:ignore="HardcodedText" >
+
+ <Button
+ android:id="@+id/button1"
+ android:text="Button" />
+
+ <Button
+ android:id="@+id/button2"
+ style="@style/WidthAndHeight"
+ android:text="Button" />
+
+ <Button
+ android:id="@+id/button3"
+ style="@style/Width"
+ android:text="Button" />
+
+ <Button
+ android:id="@+id/button4"
+ style="@style/MyStyle"
+ android:text="Button" />
+
+ <Button
+ android:id="@+id/button5"
+ style="@style/MyStyle.Big"
+ android:text="Button" />
+
+ <Button
+ android:id="@+id/button6"
+ style="@style/MyOtherStyle"
+ android:text="Button" />
+
+ <include
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ layout="@layout/sizeincluded" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/sizeincluded.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/sizeincluded.xml
new file mode 100644
index 0000000..5fccff6
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/sizeincluded.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" >
+
+</LinearLayout> \ No newline at end of file
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/sizestyles.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/sizestyles.xml
new file mode 100644
index 0000000..468974e
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/sizestyles.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <style name="WidthAndHeight" parent="@android:attr/textAppearanceMedium">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">match_parent</item>
+ </style>
+
+ <style name="Width" parent="@android:attr/textAppearanceMedium">
+ <item name="android:layout_width">match_parent</item>
+ </style>
+
+ <style name="MyStyle" parent="@style/WidthAndHeight"></style>
+ <style name="MyStyle.Big"></style>
+ <style name="MyOtherStyle" parent="@style/MyStyle.Big"></style>
+
+</resources> \ No newline at end of file
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/test/pkg/InflaterTest.java.txt b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/test/pkg/InflaterTest.java.txt
new file mode 100644
index 0000000..5a4e346
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/test/pkg/InflaterTest.java.txt
@@ -0,0 +1,65 @@
+package test.pkg;
+
+import com.example.includetest.R;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+@SuppressWarnings("unused")
+public class InflaterTest extends Activity {
+ private LayoutInflater mInflater;
+ private View mRootView;
+
+ private LayoutInflater getInflater() {
+ if (mInflater == null) {
+ mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
+ return mInflater;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ public void testInflate1() {
+ View.inflate(this, R.layout.size1, null);
+ }
+
+ public void testInflate2() {
+ mRootView = getInflater().inflate(R.layout.size2, null);
+ }
+
+ public void testInflate4() {
+ getInflater().inflate(R.layout.size3, null, false);
+ }
+
+ public void testInflate5() {
+ int mylayout = R.layout.size4;
+ getInflater().inflate(mylayout, null, false);
+ }
+
+ public void testNotNull(ViewGroup root) {
+ getInflater().inflate(R.layout.size5, root, false); // Should be flagged
+ }
+
+ public void testInflate6() {
+ int mylayout = R.layout.size7;
+ View.inflate(this, mylayout, null);
+ }
+
+ public class MyButton extends Button {
+ public MyButton(Context context) {
+ super(context);
+ }
+
+ public void test() {
+ inflate(getContext(), R.layout.size6, null);
+ }
+ }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/client/api/LintClientTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/client/api/LintClientTest.java
index 2a3a43a..a4376d8 100644
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/client/api/LintClientTest.java
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/client/api/LintClientTest.java
@@ -1,11 +1,11 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.eclipse.org/org/documents/epl-v10.php
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.android.tools.lint.client.api;
import com.android.tools.lint.Main;
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/detector/api/IssueTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/detector/api/IssueTest.java
index eba5fd3..c8aaf12 100644
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/detector/api/IssueTest.java
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/detector/api/IssueTest.java
@@ -1,11 +1,11 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.eclipse.org/org/documents/epl-v10.php
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,10 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.android.tools.lint.detector.api;
-import static com.android.tools.lint.detector.api.Issue.convertMarkup;
import static com.android.SdkConstants.AUTO_URI;
+import static com.android.tools.lint.detector.api.Issue.convertMarkup;
import junit.framework.TestCase;
@SuppressWarnings("javadoc")