aboutsummaryrefslogtreecommitdiffstats
path: root/lint
diff options
context:
space:
mode:
authorTor Norbye <tnorbye@google.com>2012-02-14 13:06:43 -0800
committerTor Norbye <tnorbye@google.com>2012-02-14 15:30:59 -0800
commit8f0ee1ea762ccb743f6ee33550d67edd3c8bb200 (patch)
treeeeb7d922ddda3f28a7b1d07f72ee5545ab935cf1 /lint
parentef6d36c7bece54393e9f1390c8162c6c3059d77e (diff)
downloadsdk-8f0ee1ea762ccb743f6ee33550d67edd3c8bb200.tar.gz
sdk-8f0ee1ea762ccb743f6ee33550d67edd3c8bb200.tar.bz2
sdk-8f0ee1ea762ccb743f6ee33550d67edd3c8bb200.zip
Add lint check for custom attributes in library projects
Also adds a lint check for unused namespace declarations, and migrates the TypoDetector code into this new namespace detector. Change-Id: I5ec2214ea4c59e14194f8eaecef422ea19baa35e
Diffstat (limited to 'lint')
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintConstants.java1
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java9
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/DetectMissingPrefix.java3
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java2
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/NamespaceDetector.java217
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/TypoDetector.java95
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/NamespaceDetectorTest.java (renamed from lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/TypoDetectorTest.java)53
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/customview.xml30
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/unused_namespace.xml14
9 files changed, 319 insertions, 105 deletions
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintConstants.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintConstants.java
index 09d378bc6..9d9cf6d90 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintConstants.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintConstants.java
@@ -250,6 +250,7 @@ public class LintConstants {
public static final String ANDROID_STRING_RESOURCE_PREFIX = "@android:string/"; //$NON-NLS-1$
// Packages
+ public static final String ANDROID_PKG_PREFIX = "android."; //$NON-NLS-1$
public static final String WIDGET_PKG_PREFIX = "android.widget."; //$NON-NLS-1$
public static final String VIEW_PKG_PREFIX = "android.view."; //$NON-NLS-1$
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 4b5ebe295..e3deb3fc8 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
@@ -53,7 +53,8 @@ public class BuiltinIssueRegistry extends IssueRegistry {
private static final List<Issue> sIssues;
static {
- List<Issue> issues = new ArrayList<Issue>(75);
+ final int initialCapacity = 77;
+ List<Issue> issues = new ArrayList<Issue>(initialCapacity);
issues.add(AccessibilityDetector.ISSUE);
issues.add(MathDetector.ISSUE);
@@ -126,11 +127,15 @@ public class BuiltinIssueRegistry extends IssueRegistry {
issues.add(ViewTypeDetector.ISSUE);
issues.add(WrongImportDetector.ISSUE);
issues.add(ViewConstructorDetector.ISSUE);
- issues.add(TypoDetector.ISSUE);
+ issues.add(NamespaceDetector.CUSTOMVIEW);
+ issues.add(NamespaceDetector.UNUSED);
+ issues.add(NamespaceDetector.TYPO);
issues.add(AlwaysShowActionDetector.ISSUE);
issues.add(JavaPerformanceDetector.PAINT_ALLOC);
issues.add(JavaPerformanceDetector.USE_SPARSEARRAY);
+ assert initialCapacity >= issues.size() : issues.size();
+
addCustomIssues(issues);
sIssues = Collections.unmodifiableList(issues);
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/DetectMissingPrefix.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/DetectMissingPrefix.java
index d0cf2d4c7..6ea789933 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/DetectMissingPrefix.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/DetectMissingPrefix.java
@@ -16,6 +16,7 @@
package com.android.tools.lint.checks;
+import static com.android.tools.lint.detector.api.LintConstants.ANDROID_PKG_PREFIX;
import static com.android.tools.lint.detector.api.LintConstants.ATTR_CLASS;
import static com.android.tools.lint.detector.api.LintConstants.ATTR_LAYOUT;
import static com.android.tools.lint.detector.api.LintConstants.ATTR_STYLE;
@@ -114,6 +115,6 @@ public class DetectMissingPrefix extends LayoutDetector {
return true;
}
- return tag.indexOf('.') != -1 && !tag.startsWith("android."); //$NON-NLS-1$
+ return tag.indexOf('.') != -1 && !tag.startsWith(ANDROID_PKG_PREFIX);
}
}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java
index 96b677eca..97cb5f67d 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java
@@ -66,7 +66,7 @@ public class ManifestOrderDetector extends Detector implements Detector.XmlScann
/** Missing a {@code <uses-sdk>} element */
public static final Issue USES_SDK = Issue.create(
- "UsesSdkMinTarget", //$NON-NLS-1$
+ "UsesMinSdkAttributes", //$NON-NLS-1$
"Checks that the minimum SDK and target SDK attributes are defined",
"The manifest should contain a <uses-sdk> element which defines the " +
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/NamespaceDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/NamespaceDetector.java
new file mode 100644
index 000000000..60ae3a394
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/NamespaceDetector.java
@@ -0,0 +1,217 @@
+/*
+ * 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.tools.lint.detector.api.LintConstants.ANDROID_PKG_PREFIX;
+import static com.android.tools.lint.detector.api.LintConstants.ANDROID_URI;
+import static com.android.tools.lint.detector.api.LintConstants.XMLNS_PREFIX;
+
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.LintUtils;
+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 org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Checks for various issues related to XML namespaces
+ */
+public class NamespaceDetector extends LayoutDetector {
+ /** Typos in the namespace */
+ public static final Issue TYPO = Issue.create(
+ "NamespaceTypo", //$NON-NLS-1$
+ "Looks for misspellings in namespace declarations",
+
+ "Accidental misspellings in namespace declarations can lead to some very " +
+ "obscure error messages. This check looks for potential misspellings to " +
+ "help track these down.",
+ Category.CORRECTNESS,
+ 8,
+ Severity.WARNING,
+ NamespaceDetector.class,
+ Scope.RESOURCE_FILE_SCOPE);
+
+ /** Unused namespace declarations */
+ public static final Issue UNUSED = Issue.create(
+ "UnusedNamespace", //$NON-NLS-1$
+ "Finds unused namespaces in XML documents",
+
+ "Unused namespace declarations take up space and require processing that is not " +
+ "necessary",
+
+ Category.CORRECTNESS,
+ 1,
+ Severity.WARNING,
+ NamespaceDetector.class,
+ Scope.RESOURCE_FILE_SCOPE);
+
+ /** Using custom namespace attributes in a library project */
+ public static final Issue CUSTOMVIEW = Issue.create(
+ "LibraryCustomView", //$NON-NLS-1$
+ "Flags custom views in libraries, which currently do not work",
+
+ "Using a custom view in a library project (where the custom view " +
+ "requires XML attributes from a custom namespace) does not yet " +
+ "work.",
+ Category.CORRECTNESS,
+ 6,
+ Severity.ERROR,
+ NamespaceDetector.class,
+ Scope.RESOURCE_FILE_SCOPE);
+
+ /** Prefix relevant for custom namespaces */
+ private static final String XMLNS_ANDROID = "xmlns:android"; //$NON-NLS-1$
+ private static final String XMLNS_A = "xmlns:a"; //$NON-NLS-1$
+ private static final String URI_PREFIX = "http://schemas.android.com/apk/res/"; //$NON-NLS-1$
+
+ private Map<String, Attr> mUnusedNamespaces;
+ private boolean mCheckUnused;
+ private boolean mCheckCustomAttrs;
+
+ /** Constructs a new {@link NamespaceDetector} */
+ public NamespaceDetector() {
+ }
+
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public void visitDocument(XmlContext context, Document document) {
+ boolean haveCustomNamespace = false;
+ Element root = document.getDocumentElement();
+ NamedNodeMap attributes = root.getAttributes();
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Node item = attributes.item(i);
+ if (item.getNodeName().startsWith(XMLNS_PREFIX)) {
+ String value = item.getNodeValue();
+
+ if (!value.equals(ANDROID_URI)) {
+ Attr attribute = (Attr) item;
+
+ if (value.startsWith(URI_PREFIX)) {
+ haveCustomNamespace = true;
+ if (mUnusedNamespaces == null) {
+ mUnusedNamespaces = new HashMap<String, Attr>();
+ }
+ mUnusedNamespaces.put(item.getNodeName().substring(XMLNS_PREFIX.length()),
+ attribute);
+ }
+
+ String name = attribute.getName();
+ if (!name.equals(XMLNS_ANDROID) && !name.equals(XMLNS_A)) {
+ continue;
+ }
+
+ if (!context.isEnabled(TYPO)) {
+ continue;
+ }
+
+ if (name.equals(XMLNS_A)) {
+ // For the "android" prefix we always assume that the namespace prefix
+ // should be our expected prefix, but for the "a" prefix we make sure
+ // that it's at least "close"; if you're bound it to something completely
+ // different, don't complain.
+ if (LintUtils.editDistance(ANDROID_URI, value) > 4) {
+ continue;
+ }
+ }
+
+ if (value.equalsIgnoreCase(ANDROID_URI)) {
+ context.report(TYPO, attribute, context.getLocation(attribute),
+ String.format(
+ "URI is case sensitive: was \"%1$s\", expected \"%2$s\"",
+ value, ANDROID_URI), null);
+ } else {
+ context.report(TYPO, attribute, context.getLocation(attribute),
+ String.format(
+ "Unexpected namespace URI bound to the \"android\" " +
+ "prefix, was %1$s, expected %2$s", value, ANDROID_URI),
+ null);
+ }
+ }
+ }
+ }
+
+ if (haveCustomNamespace) {
+ mCheckCustomAttrs = context.isEnabled(CUSTOMVIEW) && context.getProject().isLibrary();
+ mCheckUnused = context.isEnabled(UNUSED);
+ checkElement(context, document.getDocumentElement());
+
+ if (mCheckUnused && mUnusedNamespaces.size() > 0) {
+ for (Map.Entry<String, Attr> entry : mUnusedNamespaces.entrySet()) {
+ String prefix = entry.getKey();
+ Attr attribute = entry.getValue();
+ context.report(UNUSED, attribute, context.getLocation(attribute),
+ String.format("Unused namespace %1$s", prefix), null);
+ }
+ }
+ }
+ }
+
+ private void checkElement(XmlContext context, Node node) {
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ if (mCheckCustomAttrs) {
+ String tag = node.getNodeName();
+ if (tag.indexOf('.') != -1
+ // Don't consider android.support.* and android.app.FragmentBreadCrumbs etc
+ && !tag.startsWith(ANDROID_PKG_PREFIX)) {
+ NamedNodeMap attributes = ((Element) node).getAttributes();
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Attr attribute = (Attr) attributes.item(i);
+ String uri = attribute.getNamespaceURI();
+ if (uri != null && uri.length() > 0 && uri.startsWith(URI_PREFIX)
+ && !uri.equals(ANDROID_URI)) {
+ context.report(CUSTOMVIEW, attribute, context.getLocation(attribute),
+ "Using a custom namespace attributes in a library project does " +
+ "not yet work", null);
+ }
+ }
+ }
+ }
+
+ if (mCheckUnused) {
+ NamedNodeMap attributes = ((Element) node).getAttributes();
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Attr attribute = (Attr) attributes.item(i);
+ String prefix = attribute.getPrefix();
+ if (prefix != null) {
+ mUnusedNamespaces.remove(prefix);
+ }
+ }
+ }
+
+ NodeList childNodes = node.getChildNodes();
+ for (int i = 0, n = childNodes.getLength(); i < n; i++) {
+ checkElement(context, childNodes.item(i));
+ }
+ }
+ }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TypoDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TypoDetector.java
deleted file mode 100644
index 3fcbcf503..000000000
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TypoDetector.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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.tools.lint.detector.api.LintConstants.ANDROID_URI;
-
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-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 org.w3c.dom.Attr;
-
-import java.util.Arrays;
-import java.util.Collection;
-
-/**
- * Check which looks for likely typos in various places.
- */
-public class TypoDetector extends ResourceXmlDetector {
- private static final String XMLNS_ANDROID = "xmlns:android"; //$NON-NLS-1$
- private static final String XMLNS_A = "xmlns:a"; //$NON-NLS-1$
-
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "NamespaceTypo", //$NON-NLS-1$
- "Looks for misspellings in namespace declarations",
-
- "Accidental misspellings in namespace declarations can lead to some very " +
- "obscure error messages. This check looks for potential misspellings to " +
- "help track these down.",
- Category.CORRECTNESS,
- 8,
- Severity.WARNING,
- TypoDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Constructs a new {@link TypoDetector} */
- public TypoDetector() {
- }
-
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableAttributes() {
- return Arrays.asList(XMLNS_ANDROID, XMLNS_A);
- }
-
- @Override
- public void visitAttribute(XmlContext context, Attr attribute) {
- String value = attribute.getValue();
- if (!value.equals(ANDROID_URI)) {
- if (attribute.getName().equals(XMLNS_A)) {
- // For the "android" prefix we always assume that the namespace prefix
- // should be our expected prefix, but for the "a" prefix we make sure
- // that it's at least "close"; if you're bound it to something completely
- // different, don't complain.
- if (LintUtils.editDistance(ANDROID_URI, value) > 4) {
- return;
- }
- }
-
- if (value.equalsIgnoreCase(ANDROID_URI)) {
- context.report(ISSUE, attribute, context.getLocation(attribute),
- String.format("URI is case sensitive: was \"%1$s\", expected \"%2$s\"",
- value, ANDROID_URI), null);
- } else {
- context.report(ISSUE, attribute, context.getLocation(attribute),
- String.format("Unexpected namespace URI bound to the \"android\" " +
- "prefix, was %1$s, expected %2$s", value, ANDROID_URI), null);
- }
- }
- }
-}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/TypoDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/NamespaceDetectorTest.java
index b9af7a2e3..3570f5cdb 100644
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/TypoDetectorTest.java
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/NamespaceDetectorTest.java
@@ -19,13 +19,39 @@ package com.android.tools.lint.checks;
import com.android.tools.lint.detector.api.Detector;
@SuppressWarnings("javadoc")
-public class TypoDetectorTest extends AbstractCheckTest {
+public class NamespaceDetectorTest extends AbstractCheckTest {
@Override
protected Detector getDetector() {
- return new TypoDetector();
+ return new NamespaceDetector();
}
- public void test() throws Exception {
+ public void testCustom() throws Exception {
+ assertEquals(
+ "customview.xml:16: Error: Using a custom namespace attributes in a library project does not yet work",
+
+ lintProject(
+ "multiproject/library-manifest.xml=>AndroidManifest.xml",
+ "multiproject/library.properties=>project.properties",
+ "res/layout/customview.xml"
+ ));
+ }
+
+ public void testCustomOk() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "multiproject/library-manifest.xml=>AndroidManifest.xml",
+
+ // Use a standard project properties instead: no warning since it's
+ // not a library project:
+ //"multiproject/library.properties=>project.properties",
+
+ "res/layout/customview.xml"
+ ));
+ }
+
+ public void testTypo() throws Exception {
assertEquals(
"wrong_namespace.xml:2: Warning: Unexpected namespace URI bound to the " +
"\"android\" prefix, was http://schemas.android.com/apk/res/andriod, " +
@@ -34,7 +60,7 @@ public class TypoDetectorTest extends AbstractCheckTest {
lintProject("res/layout/wrong_namespace.xml"));
}
- public void test2() throws Exception {
+ public void testTypo2() throws Exception {
assertEquals(
"wrong_namespace2.xml:2: Warning: URI is case sensitive: was " +
"\"http://schemas.android.com/apk/res/Android\", expected " +
@@ -43,7 +69,7 @@ public class TypoDetectorTest extends AbstractCheckTest {
lintProject("res/layout/wrong_namespace2.xml"));
}
- public void test3() throws Exception {
+ public void testTypo3() throws Exception {
assertEquals(
"wrong_namespace3.xml:2: Warning: Unexpected namespace URI bound to the " +
"\"android\" prefix, was http://schemas.android.com/apk/res/androi, " +
@@ -52,10 +78,25 @@ public class TypoDetectorTest extends AbstractCheckTest {
lintProject("res/layout/wrong_namespace3.xml"));
}
- public void testOk() throws Exception {
+ public void testTypoOk() throws Exception {
assertEquals(
"No warnings.",
lintProject("res/layout/wrong_namespace4.xml"));
}
+
+ public void testUnused() throws Exception {
+ assertEquals(
+ "unused_namespace.xml:3: Warning: Unused namespace unused1\n" +
+ "unused_namespace.xml:4: Warning: Unused namespace unused2",
+
+ lintProject("res/layout/unused_namespace.xml"));
+ }
+
+ public void testUnusedOk() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject("res/layout/layout1.xml"));
+ }
}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/customview.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/customview.xml
new file mode 100644
index 000000000..976d636cd
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/customview.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:other="http://schemas.foo.bar.com/other"
+ xmlns:foo="http://schemas.android.com/apk/res/foo"
+ android:id="@+id/newlinear"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <foo.bar.Baz
+ android:id="@+id/button1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Button1"
+ foo:misc="Custom attribute"
+ tools:ignore="HardcodedText" >
+ </foo.bar.Baz>
+
+ <!-- Wrong namespace uri prefix: Don't warn -->
+ <foo.bar.Baz
+ android:id="@+id/button1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Button1"
+ other:misc="Custom attribute"
+ tools:ignore="HardcodedText" >
+ </foo.bar.Baz>
+
+</LinearLayout>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/unused_namespace.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/unused_namespace.xml
new file mode 100644
index 000000000..f633e4b8b
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/unused_namespace.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<foo.bar.LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:unused1="http://schemas.android.com/apk/res/unused1"
+ xmlns:unused2="http://schemas.android.com/apk/res/unused1"
+ xmlns:unused3="http://foo.bar.com/foo"
+ xmlns:notunused="http://schemas.android.com/apk/res/notunused"
+ xmlns:tools="http://schemas.android.com/tools" >
+
+ <foo.bar.Button
+ notunused:foo="Foo"
+ tools:ignore="HardcodedText" >
+ </foo.bar.Button>
+
+</foo.bar.LinearLayout>