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
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 09d378b..9d9cf6d 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 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 4b5ebe2..e3deb3f 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 @@
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 @@
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 d0cf2d4..6ea7899 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 @@
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 96b677e..97cb5f6 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 @@
/** 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 0000000..60ae3a3
--- /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 3fcbcf5..0000000
--- 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/NamespaceDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/NamespaceDetectorTest.java
new file mode 100644
index 0000000..3570f5c
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/NamespaceDetectorTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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 NamespaceDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new NamespaceDetector();
+ }
+
+ 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, " +
+ "expected http://schemas.android.com/apk/res/android",
+
+ lintProject("res/layout/wrong_namespace.xml"));
+ }
+
+ public void testTypo2() throws Exception {
+ assertEquals(
+ "wrong_namespace2.xml:2: Warning: URI is case sensitive: was " +
+ "\"http://schemas.android.com/apk/res/Android\", expected " +
+ "\"http://schemas.android.com/apk/res/android\"",
+
+ lintProject("res/layout/wrong_namespace2.xml"));
+ }
+
+ 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, " +
+ "expected http://schemas.android.com/apk/res/android",
+
+ lintProject("res/layout/wrong_namespace3.xml"));
+ }
+
+ 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/TypoDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/TypoDetectorTest.java
deleted file mode 100644
index b9af7a2..0000000
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/TypoDetectorTest.java
+++ /dev/null
@@ -1,61 +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 com.android.tools.lint.detector.api.Detector;
-
-@SuppressWarnings("javadoc")
-public class TypoDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new TypoDetector();
- }
-
- public void test() throws Exception {
- assertEquals(
- "wrong_namespace.xml:2: Warning: Unexpected namespace URI bound to the " +
- "\"android\" prefix, was http://schemas.android.com/apk/res/andriod, " +
- "expected http://schemas.android.com/apk/res/android",
-
- lintProject("res/layout/wrong_namespace.xml"));
- }
-
- public void test2() throws Exception {
- assertEquals(
- "wrong_namespace2.xml:2: Warning: URI is case sensitive: was " +
- "\"http://schemas.android.com/apk/res/Android\", expected " +
- "\"http://schemas.android.com/apk/res/android\"",
-
- lintProject("res/layout/wrong_namespace2.xml"));
- }
-
- public void test3() throws Exception {
- assertEquals(
- "wrong_namespace3.xml:2: Warning: Unexpected namespace URI bound to the " +
- "\"android\" prefix, was http://schemas.android.com/apk/res/androi, " +
- "expected http://schemas.android.com/apk/res/android",
-
- lintProject("res/layout/wrong_namespace3.xml"));
- }
-
- public void testOk() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject("res/layout/wrong_namespace4.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 0000000..976d636
--- /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 0000000..f633e4b
--- /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>