Static analyzer

This changeset adds a static analyzer, "lint", which looks for various
potential bugs in Android projects.  It has 3 parts:

(1) A library which performs the actual static checks.
    This library is standalone: it does not depend on Eclipse.
    (Technically the library has two halves: an API half, for use
    by third party developers to write additional detectors, and
    an actual implementation of a bunch of built-in checks.)

(2) A command line driver, "lint", which runs the static checks and
    emits any warnings to standard out. This can be thought of as
    a replacement for the layoutopt tool.

(3) Eclipse integration. Lint errors are added to the Problems view as
    well as shown as editor annotations. There's an options panel for
    controlling which detectors are enabled. There's also a quickfix
    for disabling errors directly within the editor and a marker
    resolution for disabling them via the Problems view.

    The static checks are run on an XML file right after it has been
    saved. (This is optional via a toggle on the same preference page
    as the detector list.)

    The static checks are also run when you export an APK, and if any
    fatal errors are found the export is abandoned. (This is also
    optional via an option).

    Finally you can run a full lint through the Android Tools menu,
    and there's also an action to clear all the lint markers there.

    There's also a new indicator on the layout editor which shows
    whether there are lint errors on the associated file, and when
    clicked brings up a dialog listing the specific errors.

This changeset also includes a number of checks:

* An accessibility detector which warns about images missing
  contentDescriptions
* A drawable selector detector which warns about state lists where not
  all states are reachable (e.g. it is not the case that only the last
  item in the list omits a state qualifier)
* A detector finding duplicate ids, not just in the current layout but
  across included layouts (transitively) as well
* All the layoutopt ones ported to Java + DOM
* Unit tests for the above.

The focus here is on getting the infrastructure in place, and it
currently focuses on XML resource files and analyzing them
efficiently. See the comment in XmlVisitor for details on that.

Change-Id: Ic5f5f37d92bfb96ff901b959aaac24db33552ff7
diff --git a/build/tools.atree b/build/tools.atree
index 7b8bb76..eb273eb 100644
--- a/build/tools.atree
+++ b/build/tools.atree
@@ -61,6 +61,7 @@
 bin/traceview       tools/traceview
 bin/android         tools/android
 bin/monkeyrunner    tools/monkeyrunner
+bin/lint            tools/lint
 
 
 # sdk.git Ant templates for project build files
@@ -105,6 +106,9 @@
 framework/jsilver.jar            tools/lib/jsilver.jar
 framework/jython.jar             tools/lib/jython.jar
 framework/mkidentity-prebuilt.jar tools/lib/mkidentity.jar
+framework/lint.jar               tools/lib/lint.jar
+framework/lint_api.jar           tools/lib/lint_api.jar
+framework/lint_checks.jar        tools/lib/lint_checks.jar
 
 # 3rd Party java libraries
 framework/groovy-all-1.7.0.jar                                tools/lib/groovy-all-1.7.0.jar
diff --git a/build/tools.windows.atree b/build/tools.windows.atree
index 066050e..1340498 100755
--- a/build/tools.windows.atree
+++ b/build/tools.windows.atree
@@ -33,6 +33,9 @@
 rm tools/draw9patch
 sdk/draw9patch/etc/draw9patch.bat                 tools/draw9patch.bat
 
+rm tools/lint
+sdk/lint/cli/etc/lint.bat                         tools/lint.bat
+
 rm tools/emulator
 rm tools/emulator-arm
 rm tools/emulator-x86
diff --git a/eclipse/dictionary.txt b/eclipse/dictionary.txt
index 4eb4b94..66911d5 100644
--- a/eclipse/dictionary.txt
+++ b/eclipse/dictionary.txt
@@ -123,6 +123,7 @@
 instanceof
 instantiatable
 int
+inter
 interpolator
 interpolators
 iterable
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/.classpath b/eclipse/plugins/com.android.ide.eclipse.adt/.classpath
index 82159df..d7bf98e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/.classpath
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/.classpath
@@ -19,6 +19,8 @@
 	<classpathentry kind="lib" path="libs/sdkuilib.jar" sourcepath="/SdkUiLib"/>
 	<classpathentry kind="lib" path="libs/common.jar" sourcepath="/common"/>
 	<classpathentry kind="lib" path="libs/rule_api.jar" sourcepath="/rule_api"/>
+	<classpathentry kind="lib" path="libs/lint_api.jar" sourcepath="/lint-api"/>
+	<classpathentry kind="lib" path="libs/lint_checks.jar" sourcepath="/lint-checks"/>
 	<classpathentry kind="lib" path="libs/assetstudio.jar" sourcepath="/assetstudio"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/ddmuilib"/>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
index 3855d87..8de0617 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
@@ -16,6 +16,8 @@
  libs/common.jar,
  libs/rule_api.jar,
  libs/assetstudio.jar,
+ libs/lint_api.jar,
+ libs/lint_checks.jar,
  libs/httpclient-4.1.1.jar,
  libs/httpcore-4.1.jar,
  libs/httpmime-4.1.1.jar,
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
index 73e3d6b..fb0357e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
@@ -49,8 +49,19 @@
         <super type="org.eclipse.core.resources.textmarker" />
         <persistent value="true" />
     </extension>
+    <extension
+        id="com.android.ide.eclipse.adt.lintProblem"
+        name="Android Lint Problem"
+        point="org.eclipse.core.resources.markers">
+        <super type="org.eclipse.core.resources.problemmarker" />
+        <super type="org.eclipse.core.resources.textmarker" />
+        <persistent value="true" />
+    </extension>
     <extension point="org.eclipse.ui.ide.markerResolution">
         <markerResolutionGenerator
+            markerType="com.android.ide.eclipse.adt.lintProblem"
+            class="com.android.ide.eclipse.adt.internal.lint.LintFixGenerator" />
+        <markerResolutionGenerator
             markerType="com.android.ide.eclipse.common.aaptProblem"
             class="com.android.ide.eclipse.adt.internal.build.AaptQuickFix" />
     </extension>
@@ -258,6 +269,8 @@
                 path="additions">
                 <separator name="group1" />
                 <separator name="group2" />
+                <separator name="group3" />
+                <separator name="group4" />
             </menu>
             <filter
                 name="projectNature"
@@ -320,6 +333,18 @@
                 id="com.android.ide.eclipse.adt.DexDumpAction"
                 label="Display dex bytecode"
                 menubarPath="com.android.ide.eclipse.adt.AndroidTools/group3" />
+            <action
+                class="com.android.ide.eclipse.adt.internal.lint.ClearLintMarkersAction"
+                enablesFor="1"
+                id="com.android.ide.eclipse.adt.internal.lint.ClearLintMarkersAction"
+                label="Clear Lint Markers"
+                menubarPath="com.android.ide.eclipse.adt.AndroidTools/group4" />
+            <action
+                class="com.android.ide.eclipse.adt.internal.lint.RunLintAction"
+                enablesFor="1"
+                id="com.android.ide.eclipse.adt.internal.lint.RunLintAction"
+                label="Run Lint: Check for Common Errors"
+                menubarPath="com.android.ide.eclipse.adt.AndroidTools/group4" />
         </objectContribution>
     </extension>
     <extension point="org.eclipse.ui.preferencePages">
@@ -349,6 +374,12 @@
             id="com.android.ide.eclipse.adt.preferences.EditorsPage"
             name="Editors">
         </page>
+        <page
+            category="com.android.ide.eclipse.preferences.main"
+            class="com.android.ide.eclipse.adt.internal.preferences.LintPreferencePage"
+            id="com.android.ide.eclipse.common.preferences.LintPreferencePage"
+            name="Lint Error Checking">
+        </page>
     </extension>
     <extension point="org.eclipse.core.runtime.preferences">
         <initializer class="com.android.ide.eclipse.adt.internal.preferences.AdtPrefs" />
@@ -587,11 +618,7 @@
         </sourceViewerConfiguration>
         <provisionalConfiguration
             type="org.eclipse.jface.text.quickassist.IQuickAssistProcessor"
-            class="com.android.ide.eclipse.adt.internal.build.AaptQuickFix"
-            target="org.eclipse.wst.xml.XML_DEFAULT" />
-        <provisionalConfiguration
-            type="org.eclipse.jface.text.quickassist.IQuickAssistProcessor"
-            class="com.android.ide.eclipse.adt.internal.editors.layout.refactoring.RefactoringAssistant"
+            class="com.android.ide.eclipse.adt.internal.editors.formatting.XmlQuickAssistManager"
             target="org.eclipse.wst.xml.XML_DEFAULT" />
         <provisionalConfiguration
             type="characterpairmatcher"
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java
index 63746ed..0a1f2bb 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java
@@ -243,6 +243,8 @@
     /** final packaging error marker, only to be used in {@link PostCompilerBuilder} */
     public final static String MARKER_PACKAGING = AdtPlugin.PLUGIN_ID + ".packagingProblem"; //$NON-NLS-1$
 
+    /** Marker for lint errors */
+    public final static String MARKER_LINT = AdtPlugin.PLUGIN_ID + ".lintProblem"; //$NON-NLS-1$
 
     /** Name for the "type" marker attribute */
     public final static String MARKER_ATTR_TYPE = "android.type"; //$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 17090fd..8cd03d4 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
@@ -17,10 +17,16 @@
 package com.android.ide.eclipse.adt;
 
 import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.IWorkspaceRoot;
 import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
 import org.eclipse.ui.IEditorInput;
 import org.eclipse.ui.IEditorPart;
 import org.eclipse.ui.IFileEditorInput;
@@ -29,6 +35,10 @@
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.texteditor.ITextEditor;
 
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
 
 /** Utility methods for ADT */
 public class AdtUtils {
@@ -291,4 +301,81 @@
         IPath workspacePath = workspace.getLocation();
         return workspacePath.append(resource.getFullPath());
     }
+
+    /**
+     * Converts a {@link File} to an {@link IFile}, if possible.
+     *
+     * @param file a file to be converted
+     * @return the corresponding {@link IFile}, or null
+     */
+    public static IFile fileToIFile(File file) {
+        IPath filePath = new Path(file.getPath());
+        return pathToIFile(filePath);
+    }
+
+    /**
+     * Converts a {@link IPath} to an {@link IFile}, if possible.
+     *
+     * @param path a path to be converted
+     * @return the corresponding {@link IFile}, or null
+     */
+    public static IFile pathToIFile(IPath path) {
+        IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot();
+        IPath workspacePath = workspace.getLocation();
+        if (workspacePath.isPrefixOf(path)) {
+            IPath relativePath = path.makeRelativeTo(workspacePath);
+            IResource member = workspace.findMember(relativePath);
+            if (member instanceof IFile) {
+                return (IFile) member;
+            }
+        } else if (path.isAbsolute()) {
+            return workspace.getFileForLocation(path);
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns all markers in a file/document that fit on the same line as the given offset
+     *
+     * @param markerType the marker type
+     * @param file the file containing the markers
+     * @param document the document showing the markers
+     * @param offset the offset to be checked
+     * @return a list (possibly empty but never null) of matching markers
+     */
+    public static List<IMarker> findMarkersOnLine(String markerType,
+            IFile file, IDocument document, int offset) {
+        List<IMarker> matchingMarkers = new ArrayList<IMarker>(2);
+        try {
+            IMarker[] markers = file.findMarkers(markerType, true, IResource.DEPTH_ZERO);
+
+            // Look for a match on the same line as the caret.
+            IRegion lineInfo = document.getLineInformationOfOffset(offset);
+            int lineStart = lineInfo.getOffset();
+            int lineEnd = lineStart + lineInfo.getLength();
+            int offsetLine = document.getLineOfOffset(offset);
+
+
+            for (IMarker marker : markers) {
+                int start = marker.getAttribute(IMarker.CHAR_START, -1);
+                int end = marker.getAttribute(IMarker.CHAR_END, -1);
+                if (start >= lineStart && start <= lineEnd && end > start) {
+                    matchingMarkers.add(marker);
+                } else if (start == -1 && end == -1) {
+                    // Some markers don't set character range, they only set the line
+                    int line = marker.getAttribute(IMarker.LINE_NUMBER, -1);
+                    if (line == offsetLine + 1) {
+                        matchingMarkers.add(marker);
+                    }
+                }
+            }
+        } catch (CoreException e) {
+            AdtPlugin.log(e, null);
+        } catch (BadLocationException e) {
+            AdtPlugin.log(e, null);
+        }
+
+        return matchingMarkers;
+    }
 }
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFix.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFix.java
index 7d79086..c6928a9 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFix.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFix.java
@@ -20,6 +20,7 @@
 
 import com.android.ide.eclipse.adt.AdtConstants;
 import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
 import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor;
 import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
@@ -58,6 +59,8 @@
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
+import java.util.List;
+
 /**
  * Shared handler for both quick assist processors (Control key handler) and quick fix
  * marker resolution (Problem view handling), since there is a lot of overlap between
@@ -159,34 +162,24 @@
         AndroidXmlEditor editor = AndroidXmlEditor.getAndroidXmlEditor(sourceViewer);
         if (editor != null) {
             IFile file = editor.getInputFile();
-
+            IDocument document = sourceViewer.getDocument();
+            List<IMarker> markers = AdtUtils.findMarkersOnLine(AdtConstants.MARKER_AAPT_COMPILE,
+                    file, document, invocationContext.getOffset());
             try {
-                IMarker[] markers = file.findMarkers(AdtConstants.MARKER_AAPT_COMPILE, true,
-                        IResource.DEPTH_ZERO);
-
-                // Look for a match on the same line as the caret.
-                int offset = invocationContext.getOffset();
-                IDocument document = sourceViewer.getDocument();
-                IRegion lineInfo = document.getLineInformationOfOffset(offset);
-                int lineStart = lineInfo.getOffset();
-                int lineEnd = lineStart + lineInfo.getLength();
-
                 for (IMarker marker : markers) {
                     String message = marker.getAttribute(IMarker.MESSAGE, ""); //$NON-NLS-1$
                     if (message.contains(getTargetMarkerErrorMessage())) {
                         int start = marker.getAttribute(IMarker.CHAR_START, 0);
                         int end = marker.getAttribute(IMarker.CHAR_END, 0);
-                        if (start >= lineStart && start <= lineEnd && end > start) {
-                            int length = end - start;
-                            String resource = document.get(start, length);
-                            // Can only offer create value for non-framework value
-                            // resources
-                            if (ResourceHelper.canCreateResource(resource)) {
-                                IProject project = editor.getProject();
-                                return new ICompletionProposal[] {
-                                    new CreateResourceProposal(project, resource)
-                                };
-                            }
+                        int length = end - start;
+                        String resource = document.get(start, length);
+                        // Can only offer create value for non-framework value
+                        // resources
+                        if (ResourceHelper.canCreateResource(resource)) {
+                            IProject project = editor.getProject();
+                            return new ICompletionProposal[] {
+                                new CreateResourceProposal(project, resource)
+                            };
                         }
                     } else if (message.contains(getUnboundErrorMessage())) {
                         return new ICompletionProposal[] {
@@ -194,8 +187,6 @@
                         };
                     }
                 }
-            } catch (CoreException e) {
-                AdtPlugin.log(e, null);
             } catch (BadLocationException e) {
                 AdtPlugin.log(e, null);
             }
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java
index c854b48..4b0673f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java
@@ -18,8 +18,11 @@
 
 import static org.eclipse.wst.sse.ui.internal.actions.StructuredTextEditorActionConstants.ACTION_NAME_FORMAT_DOCUMENT;
 
+import com.android.ide.eclipse.adt.AdtConstants;
 import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.ide.eclipse.adt.internal.lint.LintRunner;
 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
@@ -28,6 +31,7 @@
 import com.android.sdklib.IAndroidTarget;
 
 import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.IResourceChangeEvent;
@@ -38,6 +42,7 @@
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.QualifiedName;
 import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
 import org.eclipse.jface.action.IAction;
 import org.eclipse.jface.dialogs.ErrorDialog;
 import org.eclipse.jface.text.BadLocationException;
@@ -65,6 +70,7 @@
 import org.eclipse.ui.forms.events.HyperlinkEvent;
 import org.eclipse.ui.forms.events.IHyperlinkListener;
 import org.eclipse.ui.forms.widgets.FormText;
+import org.eclipse.ui.ide.IGotoMarker;
 import org.eclipse.ui.internal.browser.WorkbenchBrowserSupport;
 import org.eclipse.ui.part.MultiPageEditorPart;
 import org.eclipse.ui.part.WorkbenchPart;
@@ -243,6 +249,34 @@
 
     // ---- Base Class Overrides, Interfaces Implemented ----
 
+    @Override
+    public Object getAdapter(Class adapter) {
+        Object result = super.getAdapter(adapter);
+
+        if (result != null && adapter.equals(IGotoMarker.class) ) {
+            final IGotoMarker gotoMarker = (IGotoMarker) result;
+            return new IGotoMarker() {
+                public void gotoMarker(IMarker marker) {
+                    gotoMarker.gotoMarker(marker);
+                    try {
+                        // Lint markers should always jump to XML text
+                        if (marker.getType().equals(AdtConstants.MARKER_LINT)) {
+                            IEditorPart editor = AdtUtils.getActiveEditor();
+                            if (editor instanceof AndroidXmlEditor) {
+                                AndroidXmlEditor xmlEditor = (AndroidXmlEditor) editor;
+                                xmlEditor.setActivePage(AndroidXmlEditor.TEXT_EDITOR_ID);
+                            }
+                        }
+                    } catch (CoreException e) {
+                        AdtPlugin.log(e, null);
+                    }
+                }
+            };
+        }
+
+        return result;
+    }
+
     /**
      * Creates the pages of the multi-page editor.
      */
@@ -522,6 +556,17 @@
 
         // The actual "save" operation is done by the Structured XML Editor
         getEditor(mTextPageIndex).doSave(monitor);
+
+        runLintOnSave();
+    }
+
+    protected Job runLintOnSave() {
+        // Check for errors, if enabled
+        if (AdtPrefs.getPrefs().isLintOnSave()) {
+            return LintRunner.startLint(getInputFile(), getStructuredDocument());
+        }
+
+        return null;
     }
 
     /* (non-Javadoc)
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlQuickAssistManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlQuickAssistManager.java
new file mode 100644
index 0000000..659bfe1
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlQuickAssistManager.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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
+ *
+ * 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.ide.eclipse.adt.internal.editors.formatting;
+
+import com.android.ide.eclipse.adt.internal.build.AaptQuickFix;
+import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.RefactoringAssistant;
+import com.android.ide.eclipse.adt.internal.lint.LintFixGenerator;
+
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext;
+import org.eclipse.jface.text.quickassist.IQuickAssistProcessor;
+import org.eclipse.jface.text.source.Annotation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class implements Quick Assists for XML files. It does not perform any
+ * quick assistance on its own, but it coordinates the various separate quick
+ * assists available for XML such that the order is logical. This is necessary
+ * because without it, the order of suggestions (when more than one assistant
+ * provides suggestions) is not always optimal. There doesn't seem to be a way
+ * from non-Java languages to set the sorting order (see
+ * https://bugs.eclipse.org/bugs/show_bug.cgi?id=229983 ). So instead of
+ * registering our multiple XML quick assistants via the plugin.xml file, we
+ * register <b>just</b> this manager, which delegates to the various XML quick
+ * assistants as appropriate.
+ */
+public class XmlQuickAssistManager implements IQuickAssistProcessor {
+    private final IQuickAssistProcessor[] mProcessors;
+
+    /** Constructs a new {@link XmlQuickAssistManager} which orders the quick fixes */
+    public XmlQuickAssistManager() {
+        mProcessors = new IQuickAssistProcessor[] {
+                new AaptQuickFix(),
+                new LintFixGenerator(),
+                new RefactoringAssistant()
+        };
+    }
+
+    public String getErrorMessage() {
+        return null;
+    }
+
+    public boolean canFix(Annotation annotation) {
+        for (IQuickAssistProcessor processor : mProcessors) {
+            if (processor.canFix(annotation)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public boolean canAssist(IQuickAssistInvocationContext invocationContext) {
+        for (IQuickAssistProcessor processor : mProcessors) {
+            if (processor.canAssist(invocationContext)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public ICompletionProposal[] computeQuickAssistProposals(
+            IQuickAssistInvocationContext invocationContext) {
+        List<ICompletionProposal> allProposals = null;
+        for (IQuickAssistProcessor processor : mProcessors) {
+            if (processor.canAssist(invocationContext)) {
+                ICompletionProposal[] proposals =
+                        processor.computeQuickAssistProposals(invocationContext);
+                if (proposals != null && proposals.length > 0) {
+                    if (allProposals == null) {
+                        allProposals = new ArrayList<ICompletionProposal>();
+                    }
+                    for (ICompletionProposal proposal : proposals) {
+                        allProposals.add(proposal);
+                    }
+                }
+            }
+        }
+
+        if (allProposals != null) {
+            return allProposals.toArray(new ICompletionProposal[allProposals.size()]);
+        }
+
+        return null;
+    }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java
index 109201f..de1e3a3 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java
@@ -27,6 +27,7 @@
 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutActionBar;
 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.OutlinePage;
 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.PropertySheetPage;
 import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
@@ -40,6 +41,9 @@
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.jobs.IJobChangeEvent;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.core.runtime.jobs.JobChangeAdapter;
 import org.eclipse.jface.text.source.ISourceViewer;
 import org.eclipse.ui.IEditorInput;
 import org.eclipse.ui.IEditorPart;
@@ -164,6 +168,21 @@
         return true;
     }
 
+    @Override
+    protected Job runLintOnSave() {
+        Job job = super.runLintOnSave();
+        if (job != null) {
+            job.addJobChangeListener(new JobChangeAdapter() {
+                @Override
+                public void done(IJobChangeEvent event) {
+                    LayoutActionBar bar = getGraphicalEditor().getLayoutActionBar();
+                    bar.updateErrorIndicator();
+                }
+            });
+        }
+        return job;
+    }
+
     /**
      * Create the various form pages.
      */
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
index f3e625e..13c7944 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
@@ -58,6 +58,7 @@
 import com.android.ide.eclipse.adt.internal.editors.ui.DecorComposite;
 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.ide.eclipse.adt.internal.lint.LintEclipseContext;
 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
@@ -340,6 +341,9 @@
         mActionBar = new LayoutActionBar(layoutBarAndCanvas, SWT.NONE, this);
         GridData detailsData = new GridData(SWT.FILL, SWT.FILL, false, false, 1, 1);
         mActionBar.setLayoutData(detailsData);
+        if (file != null) {
+            mActionBar.updateErrorIndicator(LintEclipseContext.hasMarkers(file));
+        }
 
         mSashError = new SashForm(layoutBarAndCanvas, SWT.VERTICAL | SWT.BORDER);
         mSashError.setLayoutData(new GridData(GridData.FILL_BOTH));
@@ -887,6 +891,8 @@
         if (!mActive) {
             mActive = true;
 
+            mActionBar.updateErrorIndicator();
+
             boolean changed = mConfigComposite.syncRenderState();
             if (changed) {
                 // Will also force recomputeLayout()
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
index 844c8d3..5469d07 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
@@ -28,9 +28,11 @@
 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
 import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
 import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
+import com.android.ide.eclipse.adt.internal.lint.LintEclipseContext;
 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
 import com.android.sdkuilib.internal.widgets.ResolutionChooserDialog;
 
+import org.eclipse.core.resources.IFile;
 import org.eclipse.jface.window.Window;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.SelectionAdapter;
@@ -40,12 +42,15 @@
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.Event;
 import org.eclipse.swt.widgets.Listener;
 import org.eclipse.swt.widgets.Menu;
 import org.eclipse.swt.widgets.MenuItem;
 import org.eclipse.swt.widgets.ToolBar;
 import org.eclipse.swt.widgets.ToolItem;
+import org.eclipse.ui.ISharedImages;
+import org.eclipse.ui.PlatformUI;
 
 import java.net.URL;
 import java.util.ArrayList;
@@ -59,12 +64,14 @@
 public class LayoutActionBar extends Composite {
     private GraphicalEditorPart mEditor;
     private ToolBar mLayoutToolBar;
+    private ToolBar mLintToolBar;
     private ToolBar mZoomToolBar;
     private ToolItem mZoomRealSizeButton;
     private ToolItem mZoomOutButton;
     private ToolItem mZoomResetButton;
     private ToolItem mZoomInButton;
     private ToolItem mZoomFitButton;
+    private ToolItem mLintButton;
 
     /**
      * Creates a new {@link LayoutActionBar} and adds it to the given parent.
@@ -77,13 +84,18 @@
         super(parent, style | SWT.NO_FOCUS);
         mEditor = editor;
 
-        GridLayout layout = new GridLayout(2, false);
+        GridLayout layout = new GridLayout(3, false);
         setLayout(layout);
 
         mLayoutToolBar = new ToolBar(this, SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL);
         mLayoutToolBar.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, true, false));
         mZoomToolBar = createZoomControls();
         mZoomToolBar.setLayoutData(new GridData(SWT.END, SWT.BEGINNING, true, false));
+        mLintToolBar = createLintControls();
+
+        GridData lintData = new GridData(SWT.END, SWT.BEGINNING, false, false);
+        lintData.exclude = true;
+        mLintToolBar.setLayoutData(lintData);
     }
 
     /** Updates the layout contents based on the current selection */
@@ -364,6 +376,7 @@
 
     // ---- Zoom Controls ----
 
+    @SuppressWarnings("unused") // SWT constructors have side effects, they are not unused
     private ToolBar createZoomControls() {
         ToolBar toolBar = new ToolBar(this, SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL);
 
@@ -432,6 +445,61 @@
         return toolBar;
     }
 
+    @SuppressWarnings("unused") // SWT constructors have side effects, they are not unused
+    private ToolBar createLintControls() {
+        ToolBar toolBar = new ToolBar(this, SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL);
+
+        // Separate from adjacent toolbar
+        new ToolItem(toolBar, SWT.SEPARATOR);
+
+        ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
+        mLintButton = new ToolItem(toolBar, SWT.PUSH);
+        mLintButton.setToolTipText("Show Lint Warnings for this Layout");
+        mLintButton.setImage(sharedImages.getImage(ISharedImages.IMG_OBJS_WARN_TSK));
+        mLintButton.addSelectionListener(new SelectionAdapter() {
+            @Override
+            public void widgetSelected(SelectionEvent e) {
+                IFile file = mEditor.getLayoutEditor().getInputFile();
+                LintEclipseContext.showErrors(getShell(), file);
+            }
+        });
+
+        return toolBar;
+    }
+
+    /**
+     * Updates the lint indicator state in the given layout editor
+     */
+    public void updateErrorIndicator() {
+        updateErrorIndicator(LintEclipseContext.hasMarkers(mEditor.getEditedFile()));
+    }
+
+    /**
+     * Sets whether the action bar should show the "lint warnings" button
+     *
+     * @param hasLintWarnings whether there are lint errors to be shown
+     */
+    void updateErrorIndicator(final boolean hasLintWarnings) {
+        Display display = getDisplay();
+        if (display.getThread() != Thread.currentThread()) {
+            display.asyncExec(new Runnable() {
+                public void run() {
+                    if (!isDisposed()) {
+                        updateErrorIndicator(hasLintWarnings);
+                    }
+                }
+            });
+            return;
+        }
+
+        GridData layoutData = (GridData) mLintToolBar.getLayoutData();
+        if (layoutData.exclude == hasLintWarnings) {
+            layoutData.exclude = !hasLintWarnings;
+            mLintToolBar.setVisible(hasLintWarnings);
+            layout();
+        }
+    }
+
     /**
      * Returns true if zooming in/out/to-fit/etc is allowed (which is not the case while
      * emulating real size)
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java
index 8d0ea33..c2425fc 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java
@@ -249,7 +249,7 @@
         }
 
         public String getAdditionalProposalInfo() {
-            return "Initiates the given refactoring operation";
+            return String.format("Initiates the \"%1$s\" refactoring", mRefactoring.getName());
         }
 
         public IContextInformation getContextInformation() {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java
index ec68323..c434712 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java
@@ -81,8 +81,6 @@
 import org.eclipse.core.resources.IFolder;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
-import org.eclipse.core.resources.IWorkspaceRoot;
-import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.NullProgressMonitor;
@@ -536,19 +534,9 @@
     private static void openPath(IPath filePath, IRegion region, int offset) {
         IEditorPart sourceEditor = getEditor();
         IWorkbenchPage page = sourceEditor.getEditorSite().getPage();
-        IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot();
-        IPath workspacePath = workspace.getLocation();
-        IFile file = null;
-        if (workspacePath.isPrefixOf(filePath)) {
-            IPath relativePath = filePath.makeRelativeTo(workspacePath);
-            IResource member = workspace.findMember(relativePath);
-            if (member instanceof IFile) {
-                file = (IFile) member;
-            }
-        } else if (filePath.isAbsolute()) {
-            file = workspace.getFileForLocation(filePath);
-        }
-        if (file != null) {
+
+        IFile file = AdtUtils.pathToIFile(filePath);
+        if (file != null && file.exists()) {
             try {
                 AdtPlugin.openFile(file, region);
                 return;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ClearLintMarkersAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ClearLintMarkersAction.java
new file mode 100644
index 0000000..21d90e6
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ClearLintMarkersAction.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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
+ *
+ * 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.ide.eclipse.adt.internal.lint;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.ui.IActionDelegate;
+
+/** Action which clear lint markers from the current project */
+public class ClearLintMarkersAction implements IActionDelegate {
+
+    private ISelection mSelection;
+
+    public void selectionChanged(IAction action, ISelection selection) {
+        mSelection = selection;
+    }
+
+    public void run(IAction action) {
+        IProject project = RunLintAction.getSelectedProject(mSelection);
+        if (project != null) {
+            LintRunner.cancelCurrentJobs();
+            LintEclipseContext.clearMarkers(project);
+        }
+    }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintEclipseContext.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintEclipseContext.java
new file mode 100644
index 0000000..1ae061b
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintEclipseContext.java
@@ -0,0 +1,506 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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
+ *
+ * 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.ide.eclipse.adt.internal.lint;
+
+import static com.android.ide.eclipse.adt.AdtConstants.MARKER_LINT;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
+import com.android.tools.lint.api.DetectorRegistry;
+import com.android.tools.lint.api.IDomParser;
+import com.android.tools.lint.api.ToolContext;
+import com.android.tools.lint.checks.BuiltinDetectorRegistry;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Position;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.util.Pair;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.editors.text.TextFileDocumentProvider;
+import org.eclipse.ui.texteditor.IDocumentProvider;
+import org.eclipse.wst.sse.core.StructuredModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Eclipse implementation for running lint on workspace files and projects.
+ */
+@SuppressWarnings("restriction") // DOM model
+public class LintEclipseContext implements ToolContext, IDomParser {
+    static final String MARKER_CHECKID_PROPERTY = "checkid";    //$NON-NLS-1$
+    private static final String DOCUMENT_PROPERTY = "document"; //$NON-NLS-1$
+    private final DetectorRegistry mRegistry;
+    private final IResource mResource;
+    private final IDocument mDocument;
+    private boolean mFatal;
+    private Map<Issue, Severity> mSeverities;
+    private String mDisabledIdsList; // String version of map: used to detect when to replace map
+    private Set<String> mDisabledIds;
+
+
+    /**
+     * Creates a new {@link LintEclipseContext}.
+     *
+     * @param registry the associated detector registry
+     * @param resource the associated resource (project, file or null)
+     * @param document the associated document, or null if the {@code resource}
+     *            param is not a file
+     */
+    public LintEclipseContext(DetectorRegistry registry, IResource resource, IDocument document) {
+        mRegistry = registry;
+        mResource = resource;
+        mDocument = document;
+    }
+
+    // ----- Implements ToolContext -----
+
+    public void log(Throwable exception, String format, Object... args) {
+        if (exception == null) {
+            AdtPlugin.log(IStatus.WARNING, format, args);
+        } else {
+            AdtPlugin.log(exception, format, args);
+        }
+    }
+
+    public IDomParser getParser() {
+        return this;
+    }
+
+    public boolean isEnabled(Issue issue) {
+        IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
+        String idList = store.getString(AdtPrefs.PREFS_DISABLED_ISSUES);
+        if (idList == null) {
+            idList = ""; //$NON-NLS-1$
+        }
+        if (mDisabledIds == null || !mDisabledIdsList.equals(idList)) {
+            mDisabledIdsList = idList;
+            if (idList.length() > 0) {
+                String[] ids = idList.split(","); //$NON-NLS-1$
+                mDisabledIds = new HashSet<String>(ids.length);
+                for (String s : ids) {
+                    mDisabledIds.add(s);
+                }
+            } else {
+                mDisabledIds = Collections.emptySet();
+            }
+        }
+
+        return !mDisabledIds.contains(issue.getId());
+    }
+
+    // ----- Implements IDomParser -----
+    public Document parse(Context context) {
+        // Map File to IFile
+        IFile file = AdtUtils.fileToIFile(context.file);
+        if (file == null || !file.exists()) {
+            String path = context.file.getPath();
+            AdtPlugin.log(IStatus.ERROR, "Can't find file %1$s in workspace", path);
+            return null;
+        }
+
+        IStructuredModel model = null;
+        try {
+            IModelManager modelManager = StructuredModelManager.getModelManager();
+            model = modelManager.getModelForRead(file);
+            if (model instanceof IDOMModel) {
+                context.setProperty(DOCUMENT_PROPERTY, model.getStructuredDocument());
+                IDOMModel domModel = (IDOMModel) model;
+                return domModel.getDocument();
+            }
+        } catch (IOException e) {
+            AdtPlugin.log(e, "Cannot read XML file");
+        } catch (CoreException e) {
+            AdtPlugin.log(e, null);
+        } finally {
+            if (model != null) {
+                // TODO: This may be too early...
+                model.releaseFromRead();
+            }
+        }
+
+        return null;
+    }
+
+    private static Position getPosition(Context context, int offset) {
+        IStructuredDocument doc = (IStructuredDocument) context.getProperty(DOCUMENT_PROPERTY);
+        if (doc != null && offset < doc.getLength()) {
+            int line = doc.getLineOfOffset(offset);
+            int column = -1;
+            try {
+                int lineOffset = doc.getLineOffset(line);
+                column = offset - lineOffset;
+            } catch (BadLocationException e) {
+                AdtPlugin.log(e, null);
+            }
+            return new OffsetPosition(line, column, offset);
+        }
+
+        return null;
+    }
+
+    public Position getStartPosition(Context context, Node node) {
+        if (node instanceof IndexedRegion) {
+            IndexedRegion region = (IndexedRegion) node;
+            return getPosition(context, region.getStartOffset());
+        }
+
+        return null;
+    }
+
+    public Position getEndPosition(Context context, Node node) {
+        if (node instanceof IndexedRegion) {
+            IndexedRegion region = (IndexedRegion) node;
+            return getPosition(context, region.getEndOffset());
+        }
+
+        return null;
+    }
+
+    public Severity getSeverity(Issue issue) {
+        if (mSeverities == null) {
+            mSeverities = new HashMap<Issue, Severity>();
+            IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
+            String assignments = store.getString(AdtPrefs.PREFS_LINT_SEVERITIES);
+            if (assignments != null && assignments.length() > 0) {
+                for (String assignment : assignments.split(",")) { //$NON-NLS-1$
+                    String[] s = assignment.split("="); //$NON-NLS-1$
+                    if (s.length == 2) {
+                        Issue d = mRegistry.getIssue(s[0]);
+                        if (d != null) {
+                            Severity severity = Severity.valueOf(s[1]);
+                            if (severity != null) {
+                                mSeverities.put(d, severity);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        Severity severity = mSeverities.get(issue);
+        if (severity != null) {
+            return severity;
+        }
+
+        return issue.getDefaultSeverity();
+    }
+
+    /**
+     * Sets the custom severity for the given detector to the given new severity
+     *
+     * @param severities a map from detector to severity to use from now on
+     */
+    public void setSeverities(Map<Issue, Severity> severities) {
+        mSeverities = null;
+
+        IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
+        if (severities.size() == 0) {
+            store.setToDefault(AdtPrefs.PREFS_LINT_SEVERITIES);
+            return;
+        }
+        List<Issue> sortedKeys = new ArrayList<Issue>(severities.keySet());
+        Collections.sort(sortedKeys);
+
+        StringBuilder sb = new StringBuilder(severities.size() * 20);
+        for (Issue issue : sortedKeys) {
+            Severity severity = severities.get(issue);
+            if (severity != issue.getDefaultSeverity()) {
+                if (sb.length() > 0) {
+                    sb.append(',');
+                }
+                sb.append(issue.getId());
+                sb.append('=');
+                sb.append(severity.name());
+            }
+        }
+
+        if (sb.length() > 0) {
+            store.setValue(AdtPrefs.PREFS_LINT_SEVERITIES, sb.toString());
+        } else {
+            store.setToDefault(AdtPrefs.PREFS_LINT_SEVERITIES);
+        }
+    }
+
+    public void report(Issue issue, Location location, String message) {
+        if (!isEnabled(issue)) {
+            return;
+        }
+
+        Severity s = getSeverity(issue);
+        if (s == Severity.IGNORE) {
+            return;
+        }
+
+        int severity = getMarkerSeverity(s);
+        IMarker marker = null;
+        if (location == null) {
+            marker = BaseProjectHelper.markResource(mResource, MARKER_LINT,
+                    message, 0, severity);
+        } else {
+            Position startPosition = location.getStart();
+            if (startPosition == null) {
+                marker = BaseProjectHelper.markResource(mResource, MARKER_LINT,
+                        message, 0, severity);
+            } else {
+                Position endPosition = location.getEnd();
+                int line = startPosition.getLine() + 1; // Marker API is 1-based
+                IFile file = AdtUtils.fileToIFile(location.getFile());
+                if (file != null) {
+                    Pair<Integer, Integer> r = getRange(file, mDocument,
+                            startPosition, endPosition);
+                    int startOffset = r.getFirst();
+                    int endOffset = r.getSecond();
+
+                    marker = BaseProjectHelper.markResource(file, MARKER_LINT,
+                            message, line, startOffset, endOffset, severity);
+                }
+            }
+        }
+
+        if (marker != null) {
+            // Store marker id such that we can recognize it from the suppress quickfix
+            try {
+                marker.setAttribute(MARKER_CHECKID_PROPERTY, issue.getId());
+            } catch (CoreException e) {
+                AdtPlugin.log(e, null);
+            }
+        }
+
+        if (s == Severity.ERROR) {
+            mFatal = true;
+        }
+    }
+
+    public boolean isSuppressed(Issue issue, Location range, String message, Severity severity) {
+        // Not yet supported
+        return false;
+    }
+
+    /** Clears any lint markers from the given resource (project, folder or file) */
+    static void clearMarkers(IResource resource) {
+        try {
+            resource.deleteMarkers(MARKER_LINT, false, IResource.DEPTH_INFINITE);
+        } catch (CoreException e) {
+            AdtPlugin.log(e, null);
+        }
+
+        IEditorPart active = AdtUtils.getActiveEditor();
+        if (active instanceof LayoutEditor) {
+            LayoutEditor editor = (LayoutEditor) active;
+            editor.getGraphicalEditor().getLayoutActionBar().updateErrorIndicator();
+        }
+    }
+
+    /**
+     * Returns whether the given resource has one or more lint markers
+     *
+     * @param resource the resource to be checked, typically a source file
+     * @return true if the given resource has one or more lint markers
+     */
+    public static boolean hasMarkers(IResource resource) {
+        return getMarkers(resource).length > 0;
+    }
+
+    /**
+     * Returns the lint marker for the given resource (which may be a project, folder or file)
+     *
+     * @param resource the resource to be checked, typically a source file
+     * @return an array of markers, possibly empty but never null
+     */
+    public static IMarker[] getMarkers(IResource resource) {
+        try {
+            return resource.findMarkers(MARKER_LINT, false, IResource.DEPTH_INFINITE);
+        } catch (CoreException e) {
+            AdtPlugin.log(e, null);
+        }
+
+        return new IMarker[0];
+    }
+
+    private static int getMarkerSeverity(Severity severity) {
+        switch (severity) {
+            case INFORMATIONAL:
+                return IMarker.SEVERITY_INFO;
+            case WARNING:
+                return IMarker.SEVERITY_WARNING;
+            case ERROR:
+            default:
+                return IMarker.SEVERITY_ERROR;
+        }
+    }
+
+    private static Pair<Integer, Integer> getRange(IFile file, IDocument doc,
+            Position startPosition, Position endPosition) {
+        int startOffset = startPosition.getOffset();
+        int endOffset = endPosition != null ? endPosition.getOffset() : -1;
+        if (endOffset != -1) {
+            // Attribute ranges often include trailing whitespace; trim this up
+            if (doc == null) {
+                IDocumentProvider provider = new TextFileDocumentProvider();
+                try {
+                    provider.connect(file);
+                    doc = provider.getDocument(file);
+                    if (doc != null) {
+                        return trimTrailingSpace(doc, startOffset, endOffset);
+                    }
+                } catch (Exception e) {
+                    AdtPlugin.log(e, "Can't find range information for %1$s", file.getName());
+                } finally {
+                    provider.disconnect(file);
+                }
+            } else {
+                return trimTrailingSpace(doc, startOffset, endOffset);
+            }
+        }
+
+        return Pair.of(startOffset, startOffset);
+    }
+
+    /** Trim off any trailing space on the given offset range in the given document */
+    private static Pair<Integer, Integer> trimTrailingSpace(IDocument doc, int startOffset,
+            int endOffset) {
+        if (doc != null) {
+            while (endOffset > startOffset && endOffset < doc.getLength()) {
+                try {
+                    if (!Character.isWhitespace(doc.getChar(endOffset - 1))) {
+                        break;
+                    } else {
+                        endOffset--;
+                    }
+                } catch (BadLocationException e) {
+                    // Pass - we've already validated offset range above
+                    break;
+                }
+            }
+        }
+
+        return Pair.of(startOffset, endOffset);
+    }
+
+    /**
+     * Returns true if a fatal error was encountered
+     *
+     * @return true if a fatal error was encountered
+     */
+    public boolean isFatal() {
+        return mFatal;
+    }
+
+    /**
+     * Show a dialog with errors for the given file
+     *
+     * @param shell the parent shell to attach the dialog to
+     * @param file the file to show the errors for
+     */
+    public static void showErrors(Shell shell, final IFile file) {
+        LintListDialog dialog = new LintListDialog(shell, file);
+        dialog.open();
+    }
+
+    /**
+     * Returns the registry of detectors to use from within Eclipse. This method
+     * should be used rather than calling
+     * {@link BuiltinDetectorRegistry#BuiltinDetectorRegistry} directly since it can replace
+     * some detectors with Eclipse-optimized replacements.
+     *
+     * @return the detector registry to use to access detectors and issues
+     */
+    public static DetectorRegistry getRegistry() {
+        return new BuiltinDetectorRegistry();
+    }
+
+    private static class OffsetPosition extends Position {
+        /** The line number (0-based where the first line is line 0) */
+        private final int mLine;
+
+        /**
+         * The column number (where the first character on the line is 0), or -1 if
+         * unknown
+         */
+        private final int mColumn;
+
+        /** The character offset */
+        private final int mOffset;
+
+        /**
+         * Creates a new {@link Position}
+         *
+         * @param line the 0-based line number, or -1 if unknown
+         * @param column the 0-based column number, or -1 if unknown
+         * @param offset the offset, or -1 if unknown
+         */
+        public OffsetPosition(int line, int column, int offset) {
+            super();
+            this.mLine = line;
+            this.mColumn = column;
+            this.mOffset = offset;
+        }
+
+        /**
+         * Returns the line number (0-based where the first line is line 0)
+         *
+         * @return the 0-based line number
+         */
+        @Override
+        public int getLine() {
+            return mLine;
+        }
+
+        @Override
+        public int getOffset() {
+            return mOffset;
+        }
+
+        /**
+         * Returns the column number (where the first character on the line is 0),
+         * or -1 if unknown
+         *
+         * @return the 0-based column number
+         */
+        @Override
+        public int getColumn() {
+            return mColumn;
+        }
+    }
+}
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
new file mode 100644
index 0000000..174b1c9
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java
@@ -0,0 +1,396 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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
+ *
+ * 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.ide.eclipse.adt.internal.lint;
+
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+import com.android.tools.lint.detector.api.Issue;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.eclipse.jface.text.contentassist.IContextInformation;
+import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext;
+import org.eclipse.jface.text.quickassist.IQuickAssistProcessor;
+import org.eclipse.jface.text.source.Annotation;
+import org.eclipse.jface.text.source.ISourceViewer;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IMarkerResolution;
+import org.eclipse.ui.IMarkerResolution2;
+import org.eclipse.ui.IMarkerResolutionGenerator2;
+import org.eclipse.ui.ISharedImages;
+import org.eclipse.ui.PlatformUI;
+
+import java.util.List;
+
+/**
+ * A quickfix and marker resolution for disabling lint checks, and any
+ * IDE specific implementations for fixing the warnings.
+ * <p>
+ * I would really like for this quickfix to show up as a light bulb on top of the error
+ * icon in the editor, and I've spent a whole day trying to make it work. I did not
+ * succeed, but here are the steps I tried in case I want to pick up the work again
+ * later:
+ * <ul>
+ * <li>
+ *     The WST has some support for quick fixes, and I came across some forum posts
+ *     referencing the ability to show light bulbs. However, it turns out that the
+ *     quickfix support for annotations in WST is hardcoded to source validation
+ *     errors *only*.
+ * <li>
+ *     I tried defining my own editor annotations, and customizing the icon directly
+ *     by either setting an icon or using the image provider. This works fine
+ *     if I make my marker be a new independent marker type. However, whenever I
+ *     switch the marker type back to extend the "Problem" type, then the icon reverts
+ *     back to the standard error icon and it ignores my custom settings.
+ *     And if I switch away from the Problems marker type, then the errors no longer
+ *     show up in the Problems view. (I also tried extending the JDT marker but that
+ *     still didn't work.)
+ * <li>
+ *     It looks like only JDT handles quickfix icons. It has a bunch of custom code
+ *     to handle this, along with its own Annotation subclass used by the editor.
+ *     I tried duplicating some of this by subclassing StructuredTextEditor, but
+ *     it was evident that I'd have to pull in a *huge* amount of duplicated code to
+ *     make this work, which seems risky given that all this is internal code that
+ *     can change from one Eclipse version to the next.
+ * </ul>
+ * It looks like our best bet would be to reconsider whether these should show up
+ * in the Problems view; perhaps we should use a custom view for these. That would also
+ * make marker management more obvious.
+ */
+public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssistProcessor {
+    /** Constructs a new {@link LintFixGenerator} */
+    public LintFixGenerator() {
+    }
+
+    // ---- Implements IMarkerResolutionGenerator2 ----
+
+    public boolean hasResolutions(IMarker marker) {
+        try {
+            assert marker.getType().equals(AdtConstants.MARKER_LINT);
+        } catch (CoreException e) {
+        }
+
+        return true;
+    }
+
+    public IMarkerResolution[] getResolutions(IMarker marker) {
+        String id = marker.getAttribute(LintRunner.MARKER_CHECKID_PROPERTY,
+                ""); //$NON-NLS-1$
+        IResource resource = marker.getResource();
+        return new IMarkerResolution[] {
+                new MoreInfoProposal(id, marker.getAttribute(IMarker.MESSAGE, null)),
+                new SuppressProposal(id, true /* all */),
+                // Not yet implemented
+                //new SuppressProposal(id, false),
+                new ClearMarkersProposal(resource, false /* all */),
+                new ClearMarkersProposal(resource, true /* all */),
+        };
+    }
+
+    // ---- Implements IQuickAssistProcessor ----
+
+    public String getErrorMessage() {
+        return "Disable Lint Error";
+    }
+
+    public boolean canFix(Annotation annotation) {
+        return true;
+    }
+
+    public boolean canAssist(IQuickAssistInvocationContext invocationContext) {
+        return true;
+    }
+
+    public ICompletionProposal[] computeQuickAssistProposals(
+            IQuickAssistInvocationContext invocationContext) {
+        ISourceViewer sourceViewer = invocationContext.getSourceViewer();
+        AndroidXmlEditor editor = AndroidXmlEditor.getAndroidXmlEditor(sourceViewer);
+        if (editor != null) {
+            IFile file = editor.getInputFile();
+            IDocument document = sourceViewer.getDocument();
+            List<IMarker> markers = AdtUtils.findMarkersOnLine(AdtConstants.MARKER_LINT,
+                    file, document, invocationContext.getOffset());
+            if (markers.size() > 0) {
+                for (IMarker marker : markers) {
+                    String id = marker.getAttribute(LintRunner.MARKER_CHECKID_PROPERTY,
+                            ""); //$NON-NLS-1$
+                    return new ICompletionProposal[] {
+                            new MoreInfoProposal(id, marker.getAttribute(IMarker.MESSAGE, null)),
+                            new SuppressProposal(id, true /* all */),
+                            // Not yet implemented
+                            //new SuppressProposal(id, false),
+                            new ClearMarkersProposal(file, false /* all */),
+                            new ClearMarkersProposal(file, true /* all */),
+                    };
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Suppress the given detector, and rerun the checks on the file
+     *
+     * @param id the id of the detector to be suppressed
+     */
+    public static void suppressDetector(String id) {
+        // Excluded checks
+        IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
+        String value = store.getString(AdtPrefs.PREFS_DISABLED_ISSUES);
+        assert value == null || !value.contains(id);
+        if (value == null || value.length() == 0) {
+            value = id;
+        } else {
+            value = value + ',' + id;
+        }
+        store.setValue(AdtPrefs.PREFS_DISABLED_ISSUES, value);
+
+        // Rerun analysis on the current file to remove this and related markers.
+        // TODO: if mGlobal, rerun on whole project?
+        IEditorPart activeEditor = AdtUtils.getActiveEditor();
+        if (activeEditor instanceof AndroidXmlEditor) {
+            AndroidXmlEditor editor = (AndroidXmlEditor) activeEditor;
+            LintRunner.startLint(editor.getInputFile(), editor.getStructuredDocument());
+        }
+    }
+
+    private static class SuppressProposal implements ICompletionProposal, IMarkerResolution2 {
+        private final String mId;
+        private final boolean mGlobal;
+
+        public SuppressProposal(String check, boolean global) {
+            super();
+            mId = check;
+            mGlobal = global;
+        }
+
+        private void perform() {
+            suppressDetector(mId);
+        }
+
+        public String getDisplayString() {
+            return mGlobal ? "Disable Check" : "Disable Check in this file only";
+        }
+
+        // ---- Implements MarkerResolution2 ----
+
+        public String getLabel() {
+            return getDisplayString();
+        }
+
+        public void run(IMarker marker) {
+            perform();
+        }
+
+        public String getDescription() {
+            return getAdditionalProposalInfo();
+        }
+
+        // ---- Implements ICompletionProposal ----
+
+        public void apply(IDocument document) {
+            perform();
+        }
+
+        public Point getSelection(IDocument document) {
+            return null;
+        }
+
+        public String getAdditionalProposalInfo() {
+            StringBuilder sb = new StringBuilder(200);
+            if (mGlobal) {
+                sb.append("Suppresses this type of lint warning in all files.");
+            } else {
+                sb.append("Suppresses this type of lint warning in the current file only.");
+            }
+            sb.append("<br><br>"); //$NON-NLS-1$
+            sb.append("You can re-enable checks from the \"Android > Lint Error Checking\" preference page.");
+
+            return sb.toString();
+        }
+
+        public Image getImage() {
+            ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
+            return sharedImages.getImage(ISharedImages.IMG_OBJS_WARN_TSK);
+        }
+
+        public IContextInformation getContextInformation() {
+            return null;
+        }
+    }
+
+    private static class ClearMarkersProposal implements ICompletionProposal, IMarkerResolution2 {
+        private final boolean mGlobal;
+        private final IResource mResource;
+
+        public ClearMarkersProposal(IResource resource, boolean global) {
+            mResource = resource;
+            mGlobal = global;
+        }
+
+        private void perform() {
+            IResource resource = mGlobal ? mResource.getProject() : mResource;
+            LintEclipseContext.clearMarkers(resource);
+        }
+
+        public String getDisplayString() {
+            return mGlobal ? "Clear All Lint Markers" : "Clear Markers in This File Only";
+        }
+
+        // ---- Implements MarkerResolution2 ----
+
+        public String getLabel() {
+            return getDisplayString();
+        }
+
+        public void run(IMarker marker) {
+            perform();
+        }
+
+        public String getDescription() {
+            return getAdditionalProposalInfo();
+        }
+
+        // ---- Implements ICompletionProposal ----
+
+        public void apply(IDocument document) {
+            perform();
+        }
+
+        public Point getSelection(IDocument document) {
+            return null;
+        }
+
+        public String getAdditionalProposalInfo() {
+            StringBuilder sb = new StringBuilder(200);
+            if (mGlobal) {
+                sb.append("Clears all lint warning markers from the project.");
+            } else {
+                sb.append("Clears all lint warnings from this file.");
+            }
+            sb.append("<br><br>"); //$NON-NLS-1$
+            sb.append("This temporarily hides the problem, but does not suppress it. " +
+                    "Running Lint again can bring the error back.");
+            if (AdtPrefs.getPrefs().isLintOnSave()) {
+                sb.append(' ');
+                sb.append("This will happen the next time the file is saved since lint-on-save " +
+                        "is enabled. You can turn this off in the \"Lint Error Checking\" " +
+                        "preference page.");
+            }
+
+            return sb.toString();
+        }
+
+        public Image getImage() {
+            ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
+            return sharedImages.getImage(ISharedImages.IMG_OBJS_WARN_TSK);
+        }
+
+        public IContextInformation getContextInformation() {
+            return null;
+        }
+    }
+
+    private static class MoreInfoProposal implements ICompletionProposal, IMarkerResolution2 {
+        private final String mId;
+        private final String mMessage;
+
+        public MoreInfoProposal(String id, String message) {
+            mId = id;
+            mMessage = message;
+        }
+
+        private void perform() {
+            Issue issue = LintEclipseContext.getRegistry().getIssue(mId);
+            assert issue != null : mId;
+
+            StringBuilder sb = new StringBuilder(300);
+            sb.append(mMessage);
+            sb.append('\n').append('\n');
+            sb.append("Issue Explanation:");
+            sb.append('\n');
+            if (issue.getExplanation() != null) {
+                sb.append('\n');
+                sb.append(issue.getExplanation());
+            } else {
+                sb.append(issue.getDescription());
+            }
+
+            if (issue.getMoreInfo() != null) {
+                sb.append('\n').append('\n');
+                sb.append("More Information: ");
+                sb.append(issue.getMoreInfo());
+            }
+
+            MessageDialog.openInformation(AdtPlugin.getDisplay().getActiveShell(), "More Info",
+                    sb.toString());
+        }
+
+        public String getDisplayString() {
+            return "Explain Issue";
+        }
+
+        // ---- Implements MarkerResolution2 ----
+
+        public String getLabel() {
+            return getDisplayString();
+        }
+
+        public void run(IMarker marker) {
+            perform();
+        }
+
+        public String getDescription() {
+            return getAdditionalProposalInfo();
+        }
+
+        // ---- Implements ICompletionProposal ----
+
+        public void apply(IDocument document) {
+            perform();
+        }
+
+        public Point getSelection(IDocument document) {
+            return null;
+        }
+
+        public String getAdditionalProposalInfo() {
+            return "Provides more information about this issue";
+        }
+
+        public Image getImage() {
+            ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
+            return sharedImages.getImage(ISharedImages.IMG_OBJS_INFO_TSK);
+        }
+
+        public IContextInformation getContextInformation() {
+            return null;
+        }
+    }
+
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintListDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintListDialog.java
new file mode 100644
index 0000000..29d7275
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintListDialog.java
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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
+ *
+ * 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.ide.eclipse.adt.internal.lint;
+
+import static com.android.ide.eclipse.adt.internal.lint.LintEclipseContext.MARKER_CHECKID_PROPERTY;
+
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutActionBar;
+import com.android.tools.lint.api.DetectorRegistry;
+import com.android.tools.lint.detector.api.Issue;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IMarkerDelta;
+import org.eclipse.core.resources.IResourceChangeEvent;
+import org.eclipse.core.resources.IResourceChangeListener;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.TitleAreaDialog;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.viewers.ILabelProviderListener;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.ITableLabelProvider;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.TableItem;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.ISharedImages;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+
+class LintListDialog extends TitleAreaDialog implements SelectionListener,
+        IResourceChangeListener {
+    private static final String PROJECT_LOGO_LARGE = "icons/android-64.png"; //$NON-NLS-1$
+    private IFile mFile;
+    private Table mTable;
+    private Button mFixButton;
+    private Button mIgnoreButton;
+    private Button mShowButton;
+    private Text mDetailsText;
+    private Button mIgnoreTypeButton;
+    private TableViewer mTableViewer;
+
+    LintListDialog(Shell parentShell, IFile file) {
+        super(parentShell);
+        this.mFile = file;
+    }
+
+    @Override
+    protected Control createContents(Composite parent) {
+      Control contents = super.createContents(parent);
+      setTitle("Lint Warnings in Layout");
+      setMessage("Lint Errors found for the current layout:");
+      setTitleImage(AdtPlugin.getImageDescriptor(PROJECT_LOGO_LARGE).createImage());
+      return contents;
+    }
+
+    @SuppressWarnings("unused") // SWT constructors have side effects, they are not unused
+    @Override
+    protected Control createDialogArea(Composite parent) {
+        Composite area = (Composite) super.createDialogArea(parent);
+        Composite container = new Composite(area, SWT.NONE);
+        container.setLayoutData(new GridData(GridData.FILL_BOTH));
+
+        container.setLayout(new GridLayout(2, false));
+        mTableViewer = new TableViewer(container, SWT.BORDER | SWT.FULL_SELECTION);
+        mTable = mTableViewer.getTable();
+        mTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 5));
+
+        TableViewerColumn messageViewerColumn = new TableViewerColumn(mTableViewer, SWT.FILL);
+        final TableColumn messageColumn = messageViewerColumn.getColumn();
+        messageColumn.setWidth(100);
+        messageColumn.setText("Message");
+
+        TableViewerColumn lineViewerColumn = new TableViewerColumn(mTableViewer, SWT.NONE);
+        final TableColumn lineColumn = lineViewerColumn.getColumn();
+        lineColumn.setWidth(100);
+        lineColumn.setText("Line");
+        lineColumn.setAlignment(SWT.RIGHT);
+
+        mTableViewer.setContentProvider(new ContentProvider());
+        mTableViewer.setLabelProvider(new LabelProvider());
+
+        mShowButton = new Button(container, SWT.NONE);
+        mShowButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
+        mShowButton.setText("Show");
+        mShowButton.addSelectionListener(this);
+
+        mIgnoreButton = new Button(container, SWT.NONE);
+        mIgnoreButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
+        mIgnoreButton.setText("Ignore");
+        mIgnoreButton.setEnabled(false);
+        mIgnoreButton.addSelectionListener(this);
+
+        mIgnoreTypeButton = new Button(container, SWT.NONE);
+        mIgnoreTypeButton.setText("Ignore Type");
+        mIgnoreTypeButton.addSelectionListener(this);
+
+        mFixButton = new Button(container, SWT.NONE);
+        mFixButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
+        mFixButton.setText("Fix");
+        mFixButton.setEnabled(false);
+        mFixButton.addSelectionListener(this);
+
+        new Label(container, SWT.NONE);
+
+        mDetailsText = new Text(container, SWT.BORDER | SWT.READ_ONLY | SWT.WRAP
+                | SWT.V_SCROLL | SWT.MULTI);
+        GridData gdText = new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1);
+        gdText.heightHint = 80;
+        mDetailsText.setLayoutData(gdText);
+
+        IMarker[] markers = LintEclipseContext.getMarkers(mFile);
+        mTableViewer.setInput(markers);
+
+        mTable.setLinesVisible(true);
+        mTable.setHeaderVisible(true);
+        new Label(container, SWT.NONE);
+
+        mTable.addSelectionListener(this);
+
+        // Add a listener to resize the column to the full width of the
+        // table
+        mTable.addControlListener(new ControlAdapter() {
+            @Override
+            public void controlResized(ControlEvent e) {
+                Rectangle r = mTable.getClientArea();
+                int availableWidth = r.width;
+
+                int categoryFixedSize = 50;
+                lineColumn.setWidth(categoryFixedSize);
+                availableWidth -= categoryFixedSize;
+
+                // Name absorbs everything else
+                messageColumn.setWidth(availableWidth);
+            }
+        });
+
+        if (mTable.getItemCount() > 0) {
+            mTable.select(0);
+            IMarker marker = (IMarker) mTable.getItem(0).getData();
+            selectMarker(marker);
+        }
+
+        ResourcesPlugin.getWorkspace().addResourceChangeListener(
+                this,
+                IResourceChangeEvent.POST_CHANGE
+                        | IResourceChangeEvent.PRE_BUILD
+                        | IResourceChangeEvent.POST_BUILD);
+
+
+        return area;
+    }
+
+    /**
+     * Create contents of the button bar.
+     */
+    @Override
+    protected void createButtonsForButtonBar(Composite parent) {
+        createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
+    }
+
+    /**
+     * Return the initial size of the dialog.
+     */
+    @Override
+    protected Point getInitialSize() {
+        return new Point(600, 400);
+    }
+
+    private void selectMarker(IMarker marker) {
+        String id = getId(marker);
+        DetectorRegistry registry = LintEclipseContext.getRegistry();
+        Issue issue = registry.getIssue(id);
+        String summary = issue.getDescription();
+        String explanation = issue.getExplanation();
+
+        StringBuilder sb = new StringBuilder(summary.length() + explanation.length() + 20);
+        sb.append(summary);
+        sb.append('\n').append('\n');
+        sb.append(explanation);
+        mDetailsText.setText(sb.toString());
+    }
+
+    private String getId(IMarker marker) {
+        try {
+            return (String) marker.getAttribute(MARKER_CHECKID_PROPERTY);
+        } catch (CoreException e) {
+            AdtPlugin.log(e, null);
+        }
+
+        return null;
+    }
+
+    private void showMarker(IMarker marker) {
+        IRegion region = null;
+        try {
+            int start = marker.getAttribute(IMarker.CHAR_START, -1);
+            int end = marker.getAttribute(IMarker.CHAR_END, -1);
+            if (start >= 0 && end >= 0) {
+                region = new org.eclipse.jface.text.Region(start, end - start);
+            }
+
+            AdtPlugin.openFile(mFile, region, true /* showEditorTab */);
+        } catch (PartInitException ex) {
+            AdtPlugin.log(ex, null);
+        }
+    }
+
+    @Override
+    public boolean close() {
+        ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
+
+        return super.close();
+    }
+
+    // ---- Implements SelectionListener ----
+
+    public void widgetSelected(SelectionEvent e) {
+        Object source = e.getSource();
+        if (source == mTable) {
+            TableItem item = (TableItem) e.item;
+            IMarker marker = (IMarker) item.getData();
+            selectMarker(marker);
+        } else if (source == mShowButton) {
+            int index = mTable.getSelectionIndex();
+            if (index != -1) {
+                IMarker marker = (IMarker) mTable.getItem(index).getData();
+                showMarker(marker);
+            }
+        } else if (source == mIgnoreTypeButton) {
+            int index = mTable.getSelectionIndex();
+            if (index != -1) {
+                IMarker marker = (IMarker) mTable.getItem(index).getData();
+                String id = getId(marker);
+                if (id != null) {
+                    mTableViewer.setInput(null);
+                    mTableViewer.refresh();
+                    LintFixGenerator.suppressDetector(id);
+                }
+            }
+        }
+    }
+
+    public void widgetDefaultSelected(SelectionEvent e) {
+        Object source = e.getSource();
+        if (source == mTable) {
+            // Jump to editor
+            TableItem item = (TableItem) e.item;
+            IMarker marker = (IMarker) item.getData();
+            showMarker(marker);
+            close();
+        }
+    }
+
+    // ---- Implements IResourceChangeListener ----
+
+    public void resourceChanged(IResourceChangeEvent event) {
+        IMarkerDelta[] deltas = event.findMarkerDeltas(AdtConstants.MARKER_LINT, true);
+        if (deltas.length > 0) {
+            for (IMarkerDelta delta : deltas) {
+                if (delta.getResource().equals(mFile)) {
+                    Shell shell = LintListDialog.this.getShell();
+                    if (shell == null) {
+                        return;
+                    }
+                    Display display = shell.getDisplay();
+                    if (display == null) {
+                        return;
+                    }
+                    display.asyncExec(new Runnable() {
+                        public void run() {
+                            mTableViewer.setInput(null);
+                            IMarker[] markers = LintEclipseContext.getMarkers(mFile);
+                            if (markers.length == 0) {
+                                IEditorPart active = AdtUtils.getActiveEditor();
+                                if (active instanceof LayoutEditor) {
+                                    LayoutEditor editor = (LayoutEditor) active;
+                                    if (mFile.equals(editor.getInputFile())) {
+                                        GraphicalEditorPart g = editor.getGraphicalEditor();
+                                        LayoutActionBar bar = g.getLayoutActionBar();
+                                        bar.updateErrorIndicator();
+                                    }
+                                }
+
+                                close();
+                                return;
+                            }
+                            mTableViewer.setInput(markers);
+                            mTableViewer.refresh();
+                        }
+                    });
+                    return;
+                }
+            }
+        }
+    }
+
+
+    private class ContentProvider implements IStructuredContentProvider {
+        public Object[] getElements(Object inputElement) {
+            if (inputElement == null) {
+                return new IMarker[0];
+            }
+
+            return (IMarker[]) inputElement;
+        }
+
+        public void dispose() {
+        }
+
+        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+        }
+    }
+
+    private class LabelProvider implements ITableLabelProvider {
+        public Image getColumnImage(Object element, int columnIndex) {
+            if (columnIndex != 0) {
+                return null;
+            }
+
+            IMarker marker = (IMarker) element;
+            int severity = marker.getAttribute(IMarker.SEVERITY, 0);
+            ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
+            switch (severity) {
+                case IMarker.SEVERITY_ERROR:
+                    return sharedImages.getImage(ISharedImages.IMG_OBJS_ERROR_TSK);
+                case IMarker.SEVERITY_WARNING:
+                    return sharedImages.getImage(ISharedImages.IMG_OBJS_WARN_TSK);
+                case IMarker.SEVERITY_INFO:
+                    return sharedImages.getImage(ISharedImages.IMG_OBJS_INFO_TSK);
+                default:
+                    return null;
+            }
+        }
+
+        public String getColumnText(Object element, int columnIndex) {
+            IMarker marker = (IMarker) element;
+            try {
+                switch (columnIndex) {
+                    case 0:
+                        return (String) marker.getAttribute(IMarker.MESSAGE);
+                    case 1: {
+                        int line = marker.getAttribute(IMarker.LINE_NUMBER, 0);
+                        return Integer.toString(line);
+                    }
+                }
+            } catch (CoreException e) {
+                AdtPlugin.log(e, null);
+            }
+
+            return ""; //$NON-NLS-1$
+        }
+
+        public void addListener(ILabelProviderListener listener) {
+        }
+
+        public void removeListener(ILabelProviderListener listener) {
+        }
+
+        public boolean isLabelProperty(Object element, String property) {
+            return false;
+        }
+
+        public void dispose() {
+        }
+    }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintRunner.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintRunner.java
new file mode 100644
index 0000000..9bfbe29
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintRunner.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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
+ *
+ * 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.ide.eclipse.adt.internal.lint;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+import com.android.tools.lint.api.DetectorRegistry;
+import com.android.tools.lint.api.Lint;
+import com.android.tools.lint.detector.api.Scope;
+
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.IJobManager;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.swt.widgets.Shell;
+
+import java.io.File;
+import java.util.Collections;
+
+/**
+ * Eclipse implementation for running lint on workspace files and projects.
+ */
+public class LintRunner {
+    static final String MARKER_CHECKID_PROPERTY = "checkid";    //$NON-NLS-1$
+
+    /**
+     * Runs lint and updates the markers, and waits for the result. Returns
+     * true if fatal errors were found.
+     *
+     * @param resource the resource (project, folder or file) to be analyzed
+     * @param doc the associated document, if known, or null
+     * @return true if any fatal errors were encountered.
+     */
+    public static boolean runLint(IResource resource, IDocument doc) {
+        CheckFileJob job = (CheckFileJob) startLint(resource, doc);
+        try {
+            job.join();
+            return job.isFatal();
+        } catch (InterruptedException e) {
+            AdtPlugin.log(e, null);
+        }
+
+        return false;
+    }
+
+    /**
+     * Runs lint and updates the markers. Does not wait for the job to
+     * finish - just returns immediately.
+     *
+     * @param resource the resource (project, folder or file) to be analyzed
+     * @param doc the associated document, if known, or null
+     * @return the job running lint in the background.
+     */
+    public static Job startLint(IResource resource, IDocument doc) {
+        if (resource != null) {
+            cancelCurrentJobs();
+
+            CheckFileJob job = new CheckFileJob(resource, doc);
+            job.schedule();
+            return job;
+        }
+
+        return null;
+    }
+
+    /**
+     * Run Lint for an Export APK action. If it succeeds (no fatal errors)
+     * returns true, and if it fails it will display an error message and return
+     * false.
+     *
+     * @param shell the parent shell to show error messages in
+     * @param project the project to run lint on
+     * @return true if the lint run succeeded with no fatal errors
+     */
+    public static boolean runLintOnExport(Shell shell, IProject project) {
+        if (AdtPrefs.getPrefs().isLintOnExport()) {
+            boolean fatal = LintRunner.runLint(project, null);
+            if (fatal) {
+                MessageDialog.openWarning(shell,
+                        "Export Aborted",
+                        "Export aborted because fatal lint errors were found. These " +
+                        "are listed in the Problems view. Either fix these before " +
+                        "running Export again, or turn off \"Run full error check " +
+                        "when exporting app\" in the Android > Lint Error Checking " +
+                        "preference page.");
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /** Cancels the current lint jobs, if any */
+    static void cancelCurrentJobs() {
+        // Cancel any current running jobs first
+        IJobManager jobManager = Job.getJobManager();
+        Job[] jobs = jobManager.find(CheckFileJob.FAMILY_RUN_LINT);
+        for (Job job : jobs) {
+            job.cancel();
+        }
+    }
+
+    private static final class CheckFileJob extends Job {
+        /** Job family */
+        private static final Object FAMILY_RUN_LINT = new Object();
+        private final IResource mResource;
+        private final IDocument mDocument;
+        private Lint mLint;
+        private boolean mFatal;
+
+        private CheckFileJob(IResource resource, IDocument doc) {
+            super("Running Android Lint");
+            this.mResource = resource;
+            this.mDocument = doc;
+        }
+
+        @Override
+        public boolean belongsTo(Object family) {
+            return family == FAMILY_RUN_LINT;
+        }
+
+        @Override
+        protected void canceling() {
+            super.canceling();
+            if (mLint != null) {
+                mLint.cancel();
+            }
+        }
+
+        @Override
+        protected IStatus run(IProgressMonitor monitor) {
+            try {
+                monitor.beginTask("Looking for errors", IProgressMonitor.UNKNOWN);
+                LintEclipseContext.clearMarkers(mResource);
+
+                DetectorRegistry registry = LintEclipseContext.getRegistry();
+                LintEclipseContext toolContext = new LintEclipseContext(registry, mResource,
+                        mDocument);
+                File file = AdtUtils.getAbsolutePath(mResource).toFile();
+                Scope scope = (mResource instanceof IProject) ? Scope.PROJECT :
+                        (mResource instanceof IFolder) ? Scope.RESOURCES : Scope.SINGLE_FILE;
+                mLint = new Lint(registry, toolContext, scope);
+                mLint.analyze(Collections.singletonList(file));
+                mFatal = toolContext.isFatal();
+                return Status.OK_STATUS;
+            } catch (Exception e) {
+                return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, Status.ERROR,
+                                  "Failed", e); //$NON-NLS-1$
+            } finally {
+                if (monitor != null) {
+                    monitor.done();
+                }
+            }
+        }
+
+        /**
+         * Returns true if a fatal error was encountered
+         */
+        boolean isFatal() {
+            return mFatal;
+        }
+    }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/RunLintAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/RunLintAction.java
new file mode 100644
index 0000000..e6a09fd
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/RunLintAction.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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
+ *
+ * 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.ide.eclipse.adt.internal.lint;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.jobs.IJobChangeEvent;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.core.runtime.jobs.JobChangeAdapter;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.IActionDelegate;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+
+/** Action which runs Lint on the current project */
+public class RunLintAction implements IActionDelegate {
+
+     private ISelection mSelection;
+
+    public void selectionChanged(IAction action, ISelection selection) {
+        mSelection = selection;
+    }
+
+    public void run(IAction action) {
+        final IProject project = RunLintAction.getSelectedProject(mSelection);
+        if (project != null) {
+            Job job = LintRunner.startLint(project, null);
+            if (job != null) {
+                job.addJobChangeListener(new FinishListener(project));
+            }
+
+            // Show problems view since that's where the results are listed
+            IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
+            if (window != null) {
+                IWorkbenchPage page = window.getActivePage();
+                if (page != null) {
+                    try {
+                        String id = "org.eclipse.ui.views.ProblemView"; //$NON-NLS-1$
+                        page.showView(id);
+                    } catch (PartInitException e) {
+                        AdtPlugin.log(e, "Cannot open Problems View");
+                    }
+                }
+            }
+        }
+    }
+
+    /** Returns the selected project, if there is exactly one selected project */
+    static IProject getSelectedProject(ISelection selection) {
+        if (selection instanceof IStructuredSelection) {
+            IStructuredSelection structuredSelection = (IStructuredSelection) selection;
+            // get the unique selected item.
+            if (structuredSelection.size() == 1) {
+                Object element = structuredSelection.getFirstElement();
+
+                // get the project object from it.
+                IProject project = null;
+                if (element instanceof IProject) {
+                    project = (IProject) element;
+                } else if (element instanceof IAdaptable) {
+                    project = (IProject) ((IAdaptable) element).getAdapter(IProject.class);
+                }
+
+                return project;
+            }
+        }
+
+        return null;
+    }
+
+    private final class FinishListener extends JobChangeAdapter implements Runnable {
+        private final IProject mProject;
+
+        private FinishListener(IProject project) {
+            this.mProject = project;
+        }
+
+        @Override
+        public void done(IJobChangeEvent event) {
+            Display display = AdtPlugin.getDisplay();
+            if (display.getThread() != Thread.currentThread()) {
+                display.asyncExec(this);
+            } else {
+                run();
+            }
+        }
+
+        // ---- Implements Runnable ----
+
+        public void run() {
+            IMarker[] markers = LintEclipseContext.getMarkers(mProject);
+            int warningCount = 0;
+            int errorCount = 0;
+            for (IMarker marker : markers) {
+                int severity = marker.getAttribute(IMarker.SEVERITY, -1);
+                if (severity == IMarker.SEVERITY_ERROR) {
+                    errorCount++;
+                } else if (severity == IMarker.SEVERITY_WARNING) {
+                    warningCount++;
+                }
+            }
+            String message = null;
+            if (errorCount == 0 && warningCount == 0) {
+                message = "Finished running lint, no problems found.";
+            } else {
+                message = String.format(
+                "Finished running lint, found %1$d errors and %2$d warnings.\n" +
+                "Results are shown in the Problems View.\n" +
+                "Warnings can be configured in the \"Lint Error Checking\" preferences page.",
+                errorCount, warningCount);
+            }
+            MessageDialog.openInformation(AdtPlugin.getDisplay().getActiveShell(),
+                    "Finished Checking", message);
+        }
+    }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AdtPrefs.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AdtPrefs.java
index 577c431..b35af5c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AdtPrefs.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AdtPrefs.java
@@ -61,7 +61,11 @@
     public final static String PREFS_ONE_ATTR_PER_LINE = AdtPlugin.PLUGIN_ID + ".oneAttrPerLine"; //$NON-NLS-1$
     public final static String PREFS_SPACE_BEFORE_CLOSE = AdtPlugin.PLUGIN_ID + ".spaceBeforeClose"; //$NON-NLS-1$
     public final static String PREFS_FORMAT_ON_SAVE = AdtPlugin.PLUGIN_ID + ".formatOnSave"; //$NON-NLS-1$
+    public final static String PREFS_LINT_ON_SAVE = AdtPlugin.PLUGIN_ID + ".lintOnSave"; //$NON-NLS-1$
+    public final static String PREFS_LINT_ON_EXPORT = AdtPlugin.PLUGIN_ID + ".lintOnExport"; //$NON-NLS-1$
     public final static String PREFS_ATTRIBUTE_SORT = AdtPlugin.PLUGIN_ID + ".attrSort"; //$NON-NLS-1$
+    public final static String PREFS_DISABLED_ISSUES = AdtPlugin.PLUGIN_ID + ".disabedIssues"; //$NON-NLS-1$
+    public final static String PREFS_LINT_SEVERITIES = AdtPlugin.PLUGIN_ID + ".lintSeverities"; //$NON-NLS-1$
 
     /** singleton instance */
     private final static AdtPrefs sThis = new AdtPrefs();
@@ -88,6 +92,8 @@
     private boolean mOneAttributeOnFirstLine;
     private boolean mSpaceBeforeClose;
     private boolean mFormatOnSave;
+    private boolean mLintOnSave;
+    private boolean mLintOnExport;
     private AttributeSortOrder mAttributeSort;
 
     public static enum BuildVerbosity {
@@ -225,6 +231,14 @@
         if (property == null || PREFS_FORMAT_ON_SAVE.equals(property)) {
             mFormatOnSave = mStore.getBoolean(PREFS_FORMAT_ON_SAVE);
         }
+
+        if (property == null || PREFS_LINT_ON_SAVE.equals(property)) {
+            mLintOnSave = mStore.getBoolean(PREFS_LINT_ON_SAVE);
+        }
+
+        if (property == null || PREFS_LINT_ON_EXPORT.equals(property)) {
+            mLintOnExport = mStore.getBoolean(PREFS_LINT_ON_EXPORT);
+        }
     }
 
     /**
@@ -334,6 +348,26 @@
         return mFormatOnSave;
     }
 
+    public boolean isLintOnSave() {
+        return mLintOnSave;
+    }
+
+    public void setLintOnSave(boolean on) {
+        mLintOnSave = on;
+        IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
+        store.setValue(PREFS_LINT_ON_SAVE, on);
+    }
+
+    public boolean isLintOnExport() {
+        return mLintOnExport;
+    }
+
+    public void setLintOnExport(boolean on) {
+        mLintOnExport = on;
+        IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
+        store.setValue(PREFS_LINT_ON_EXPORT, on);
+    }
+
     public boolean getBuildForceErrorOnNativeLibInJar() {
         return mBuildForceErrorOnNativeLibInJar;
     }
@@ -407,6 +441,8 @@
         store.setDefault(PREFS_USE_CUSTOM_XML_FORMATTER, true);
         store.setDefault(PREFS_ONE_ATTR_PER_LINE, true);
         store.setDefault(PREFS_SPACE_BEFORE_CLOSE, true);
+        store.setDefault(PREFS_LINT_ON_SAVE, true);
+        store.setDefault(PREFS_LINT_ON_EXPORT, true);
 
         // Defaults already handled; no need to write into map:
         //store.setDefault(PREFS_ATTRIBUTE_SORT, AttributeSortOrder.LOGICAL.key);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java
new file mode 100644
index 0000000..6c55fc0
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java
@@ -0,0 +1,493 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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
+ *
+ * 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.ide.eclipse.adt.internal.preferences;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.lint.LintEclipseContext;
+import com.android.ide.eclipse.adt.internal.lint.LintRunner;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
+import com.android.sdklib.SdkConstants;
+import com.android.tools.lint.api.DetectorRegistry;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Severity;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.preference.PreferencePage;
+import org.eclipse.jface.viewers.CellEditor;
+import org.eclipse.jface.viewers.CheckboxTableViewer;
+import org.eclipse.jface.viewers.ColumnViewer;
+import org.eclipse.jface.viewers.ComboBoxViewerCellEditor;
+import org.eclipse.jface.viewers.EditingSupport;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.ILabelProviderListener;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.ITableLabelProvider;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.TableItem;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.ISharedImages;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+import org.eclipse.ui.PlatformUI;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** Preference page for configuring Lint preferences */
+public class LintPreferencePage extends PreferencePage implements IWorkbenchPreferencePage {
+    private static final int CATEGORY_COLUMN_WIDTH = 60;
+    private static final int SEVERITY_COLUMN_WIDTH = 80;
+    private static final int ID_COLUMN_WIDTH = 80;
+
+    private Map<Issue, Severity> mSeverities = new HashMap<Issue, Severity>();
+    private LintEclipseContext mContext;
+    private DetectorRegistry mRegistry;
+
+    private Table mTable;
+    private Text mDetailsText;
+    private Text mSuppressedText;
+    private Button mCheckFileCheckbox;
+    private Button mCheckExportCheckbox;
+
+    /**
+     * Create the preference page.
+     */
+    public LintPreferencePage() {
+        setPreferenceStore(AdtPlugin.getDefault().getPreferenceStore());
+    }
+
+    @Override
+    public Control createContents(Composite parent) {
+        Composite container = new Composite(parent, SWT.NULL);
+        container.setLayout(new GridLayout(2, false));
+
+        mCheckFileCheckbox = new Button(container, SWT.CHECK);
+        mCheckFileCheckbox.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1));
+        mCheckFileCheckbox.setSelection(true);
+        mCheckFileCheckbox.setText("When saving files, check for errors");
+
+        mCheckExportCheckbox = new Button(container, SWT.CHECK);
+        mCheckExportCheckbox.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1));
+        mCheckExportCheckbox.setSelection(true);
+        mCheckExportCheckbox.setText("Run full error check when exporting app");
+
+        Label label = new Label(container, SWT.SEPARATOR | SWT.HORIZONTAL);
+        label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 2, 1));
+
+        Label checkListLabel = new Label(container, SWT.NONE);
+        checkListLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1));
+        checkListLabel.setText("Enabled checks:");
+
+        CheckboxTableViewer checkboxTableViewer = CheckboxTableViewer.newCheckList(
+                container, SWT.BORDER | SWT.FULL_SELECTION);
+        mTable = checkboxTableViewer.getTable();
+        mTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
+
+        TableViewerColumn column1 = new TableViewerColumn(checkboxTableViewer, SWT.NONE);
+        final TableColumn idColumn = column1.getColumn();
+        idColumn.setWidth(100);
+        idColumn.setText("Id");
+
+        TableViewerColumn column2 = new TableViewerColumn(checkboxTableViewer, SWT.FILL);
+        final TableColumn nameColumn = column2.getColumn();
+        nameColumn.setWidth(100);
+        nameColumn.setText("Name");
+
+        TableViewerColumn column3 = new TableViewerColumn(checkboxTableViewer, SWT.NONE);
+        final TableColumn categoryColumn = column3.getColumn();
+        categoryColumn.setWidth(100);
+        categoryColumn.setText("Category");
+
+        TableViewerColumn column4 = new TableViewerColumn(checkboxTableViewer, SWT.NONE);
+        final TableColumn severityColumn = column4.getColumn();
+        severityColumn.setWidth(100);
+        severityColumn.setText("Severity");
+        column4.setEditingSupport(new SeverityEditingSupport(column4.getViewer()));
+
+        checkboxTableViewer.setContentProvider(new ContentProvider());
+        checkboxTableViewer.setLabelProvider(new LabelProvider());
+
+        mDetailsText = new Text(container, SWT.BORDER | SWT.READ_ONLY | SWT.WRAP |
+                SWT.V_SCROLL | SWT.MULTI);
+        GridData gdText = new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1);
+        gdText.heightHint = 80;
+        mDetailsText.setLayoutData(gdText);
+
+        mRegistry = LintEclipseContext.getRegistry();
+        mContext = new LintEclipseContext(mRegistry, null, null);
+
+        List<Issue> issues = mRegistry.getIssues();
+        for (Issue issue : issues) {
+            mSeverities.put(issue, mContext.getSeverity(issue));
+        }
+        checkboxTableViewer.setInput(issues);
+
+        mTable.setLinesVisible(true);
+        mTable.setHeaderVisible(true);
+
+        Label suppressLabel = new Label(container, SWT.NONE);
+        suppressLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
+        suppressLabel.setText("Suppressed Warnings:");
+
+        mSuppressedText = new Text(container, SWT.BORDER);
+        // Default path relative to the project
+        mSuppressedText.setText("${project}/lint-suppressed.xml"); //$NON-NLS-1$
+        mSuppressedText.setEnabled(false);
+        mSuppressedText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+
+        mTable.addSelectionListener(new SelectionListener() {
+            public void widgetDefaultSelected(SelectionEvent e) {
+                widgetSelected(e);
+            }
+
+            public void widgetSelected(SelectionEvent e) {
+                TableItem item = (TableItem) e.item;
+                Issue issue = (Issue) item.getData();
+                String summary = issue.getDescription();
+                String explanation = issue.getExplanation();
+
+                StringBuilder sb = new StringBuilder(summary.length() + explanation.length() + 20);
+                sb.append(summary);
+                sb.append('\n').append('\n');
+                sb.append(explanation);
+                mDetailsText.setText(sb.toString());
+            }
+        });
+
+        // Add a listener to resize the column to the full width of the table
+        mTable.addControlListener(new ControlAdapter() {
+            @Override
+            public void controlResized(ControlEvent e) {
+                Rectangle r = mTable.getClientArea();
+                int availableWidth = r.width;
+
+                // On the Mac, the width of the checkbox column is not included (and checkboxes
+                // are shown if mAllowSelection=true). Subtract this size from the available
+                // width to be distributed among the columns.
+                if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) {
+                    availableWidth -= getCheckboxWidth(e.display.getActiveShell());
+                }
+
+                idColumn.setWidth(ID_COLUMN_WIDTH);
+                availableWidth -= ID_COLUMN_WIDTH;
+
+                severityColumn.setWidth(SEVERITY_COLUMN_WIDTH);
+                availableWidth -= SEVERITY_COLUMN_WIDTH;
+
+                categoryColumn.setWidth(CATEGORY_COLUMN_WIDTH);
+                availableWidth -= CATEGORY_COLUMN_WIDTH;
+
+                // Name absorbs everything else
+                nameColumn.setWidth(availableWidth);
+            }
+        });
+
+        loadSettings();
+
+        return container;
+    }
+
+    /**
+     * Initialize the preference page.
+     */
+    public void init(IWorkbench workbench) {
+        // Initialize the preference page
+    }
+
+    @Override
+    public void dispose() {
+        super.dispose();
+    }
+
+    /** Cache for {@link #getCheckboxWidth()} */
+    private static int sCheckboxWidth = -1;
+
+    /** Computes the width of a checkbox */
+    private int getCheckboxWidth(Shell shell) {
+        if (sCheckboxWidth == -1) {
+            Shell tempShell = new Shell(shell, SWT.NO_TRIM);
+            Button checkBox = new Button(tempShell, SWT.CHECK);
+            sCheckboxWidth = checkBox.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
+            tempShell.dispose();
+        }
+
+        return sCheckboxWidth;
+    }
+
+    @Override
+    public boolean performOk() {
+        storeSettings();
+        return true;
+    }
+
+    private void loadSettings() {
+        AdtPrefs prefs = AdtPrefs.getPrefs();
+        mCheckFileCheckbox.setSelection(prefs.isLintOnSave());
+        mCheckExportCheckbox.setSelection(prefs.isLintOnExport());
+
+        IPreferenceStore store = getPreferenceStore();
+        Set<String> excluded = new HashSet<String>();
+        String ids = store.getString(AdtPrefs.PREFS_DISABLED_ISSUES);
+        if (ids != null && ids.length() > 0) {
+            for (String s : ids.split(",")) { //$NON-NLS-1$
+                excluded.add(s);
+            }
+        }
+        TableItem[] itemList = mTable.getItems();
+        for (int i = 0; i < itemList.length; i++) {
+            Issue issue = (Issue) itemList[i].getData();
+            itemList[i].setChecked(!excluded.contains(issue.getId()));
+        }
+    }
+
+    private void storeSettings() {
+        // Lint on Save, Lint on Export
+        AdtPrefs prefs = AdtPrefs.getPrefs();
+        prefs.setLintOnExport(mCheckExportCheckbox.getSelection());
+        prefs.setLintOnSave(mCheckFileCheckbox.getSelection());
+
+        // Severities
+        mContext.setSeverities(mSeverities);
+
+        // Excluded checks
+        StringBuilder sb = new StringBuilder();
+        TableItem[] itemList = mTable.getItems();
+        for (int i = 0; i < itemList.length; i++) {
+            if (!itemList[i].getChecked()) {
+                Issue check = (Issue) itemList[i].getData();
+                if (sb.length() > 0) {
+                    sb.append(',');
+                }
+                sb.append(check.getId());
+            }
+        }
+        String value = sb.toString();
+        if (value.length() == 0) {
+            value = null;
+        }
+
+        IPreferenceStore store = getPreferenceStore();
+        String previous = store.getString(AdtPrefs.PREFS_DISABLED_ISSUES);
+        boolean unchanged = (previous != null && previous.equals(value)) || (previous == value);
+        if (!unchanged) {
+            if (value == null) {
+                store.setToDefault(AdtPrefs.PREFS_DISABLED_ISSUES);
+            } else {
+                store.setValue(AdtPrefs.PREFS_DISABLED_ISSUES, value);
+            }
+
+            // Ask user whether we should re-run the rules.
+            MessageDialog dialog = new MessageDialog(
+                    null, "Lint Settings Have Changed", null,
+                    "The list of enabled checks has changed. Would you like to run lint now " +
+                            "to update the results?",
+                    MessageDialog.QUESTION,
+                    new String[] {
+                            "Yes", "No"
+                    },
+                    0); // yes is the default
+            int result = dialog.open();
+            if (result == 0) {
+                // Run lint on all the open Android projects
+                IWorkspace workspace = ResourcesPlugin.getWorkspace();
+                IProject[] projects = workspace.getRoot().getProjects();
+                for (IProject project : projects) {
+                    if (project.isOpen() && BaseProjectHelper.isAndroidProject(project)) {
+                        LintRunner.startLint(project, null);
+                    }
+                }
+            }
+        }
+    }
+
+    private static class ContentProvider implements IStructuredContentProvider {
+        public Object[] getElements(Object inputElement) {
+            @SuppressWarnings("unchecked")
+            List<Issue> issues = (List<Issue>) inputElement;
+            return issues.toArray();
+        }
+        public void dispose() {
+        }
+        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+        }
+    }
+
+    private class LabelProvider implements ITableLabelProvider {
+        // TODO: add IColorProvider ?
+
+        public void addListener(ILabelProviderListener listener) {
+        }
+
+        public void dispose() {
+        }
+
+        public boolean isLabelProperty(Object element, String property) {
+            return true;
+        }
+
+        public void removeListener(ILabelProviderListener listener) {
+        }
+
+        public Image getColumnImage(Object element, int columnIndex) {
+            if (columnIndex == 3) {
+                Issue issue = (Issue) element;
+                Severity severity = mSeverities.get(issue);
+                if (severity == null) {
+                    return null;
+                }
+
+                ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
+                switch (severity) {
+                    case ERROR:
+                        return sharedImages.getImage(ISharedImages.IMG_OBJS_ERROR_TSK);
+                    case WARNING:
+                        return sharedImages.getImage(ISharedImages.IMG_OBJS_WARN_TSK);
+                    case INFORMATIONAL:
+                        return sharedImages.getImage(ISharedImages.IMG_OBJS_INFO_TSK);
+                    case IGNORE:
+                        // TBD: Is this icon okay?
+                        return sharedImages.getImage(ISharedImages.IMG_ELCL_REMOVE_DISABLED);
+                }
+            }
+            return null;
+        }
+
+        public String getColumnText(Object element, int columnIndex) {
+            Issue issue = (Issue) element;
+            switch (columnIndex) {
+                case 0:
+                    return issue.getId();
+                case 1:
+                    return issue.getDescription();
+                case 2:
+                    return issue.getCategory();
+                case 3: {
+                    Severity severity = mSeverities.get(issue);
+                    if (severity == null) {
+                        return null;
+                    }
+                    return severity.getDescription();
+                }
+            }
+
+            return null;
+        }
+    }
+
+    /** Editing support for the severity column */
+    private class SeverityEditingSupport extends EditingSupport
+            implements ILabelProvider, IStructuredContentProvider {
+        private final ComboBoxViewerCellEditor mCellEditor;
+
+        @SuppressWarnings("deprecation") // Can't use the new form of setContentProvider until 3.7
+        private SeverityEditingSupport(ColumnViewer viewer) {
+            super(viewer);
+            Composite control = (Composite) getViewer().getControl();
+            mCellEditor = new ComboBoxViewerCellEditor(control, SWT.READ_ONLY);
+            mCellEditor.setLabelProvider(this);
+            mCellEditor.setContenProvider(this);
+            mCellEditor.setInput(Severity.values());
+        }
+
+        @Override
+        protected boolean canEdit(Object element) {
+            return true;
+        }
+
+        @Override
+        protected Object getValue(Object element) {
+            if (element instanceof Issue) {
+                Issue issue = (Issue) element;
+                Severity severity = mSeverities.get(issue);
+                return severity;
+            }
+
+            return null;
+        }
+
+        @Override
+        protected void setValue(Object element, Object value) {
+            if (element instanceof Issue && value instanceof Severity) {
+                Issue issue = (Issue) element;
+                Severity newValue = (Severity) value;
+                mSeverities.put(issue, newValue);
+                getViewer().update(element, null);
+            }
+        }
+
+        @Override
+        protected CellEditor getCellEditor(Object element) {
+            return mCellEditor;
+        }
+
+        // ---- Implements ILabelProvider ----
+
+        public String getText(Object element) {
+            return ((Severity) element).getDescription();
+        }
+
+        public void addListener(ILabelProviderListener listener) {
+        }
+
+        public void dispose() {
+        }
+
+        public boolean isLabelProperty(Object element, String property) {
+            return false;
+        }
+
+        public void removeListener(ILabelProviderListener listener) {
+        }
+
+        public Image getImage(Object element) {
+            return null;
+        }
+
+        // ---- Implements IStructuredContentProvider ----
+
+        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+        }
+
+        public Object[] getElements(Object inputElement) {
+            return Severity.values();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportAction.java
index f9ef11b..41cf397 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportAction.java
@@ -16,6 +16,7 @@
 
 package com.android.ide.eclipse.adt.internal.wizards.actions;
 
+import com.android.ide.eclipse.adt.internal.lint.LintRunner;
 import com.android.ide.eclipse.adt.internal.project.ExportHelper;
 import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
@@ -59,6 +60,10 @@
 
                 // and finally do the action
                 if (project != null) {
+                    if (!LintRunner.runLintOnExport(mShell, project)) {
+                        return;
+                    }
+
                     ProjectState state = Sdk.getProjectState(project);
                     if (state.isLibrary()) {
                         MessageDialog.openError(mShell, "Android Export",
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportWizardAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportWizardAction.java
index dc5dbeb..dfd05c0 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportWizardAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportWizardAction.java
@@ -16,6 +16,7 @@
 
 package com.android.ide.eclipse.adt.internal.wizards.actions;
 
+import com.android.ide.eclipse.adt.internal.lint.LintRunner;
 import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
 import com.android.ide.eclipse.adt.internal.wizards.export.ExportWizard;
@@ -61,6 +62,11 @@
 
                 // and finally do the action
                 if (project != null) {
+                    if (!LintRunner.runLintOnExport(
+                            mWorkbench.getActiveWorkbenchWindow().getShell(), project)) {
+                        return;
+                    }
+
                     ProjectState state = Sdk.getProjectState(project);
                     if (state.isLibrary()) {
                         MessageDialog.openError(mWorkbench.getDisplay().getActiveShell(),
diff --git a/eclipse/scripts/create_adt_symlinks.sh b/eclipse/scripts/create_adt_symlinks.sh
index 9298ac0..97ed877 100755
--- a/eclipse/scripts/create_adt_symlinks.sh
+++ b/eclipse/scripts/create_adt_symlinks.sh
@@ -21,6 +21,10 @@
 echo "make java libs ..."
 make -j3 showcommands $LIBS || die "ADT: Fail to build one of $LIBS."
 
+# Add in lint_api and lint_checks: Not build targets but are copy targets
+LIBS="$LIBS lint_api lint_checks"
+make -j3 showcommands lint || die "ADT: Fail to build one of $LIBS."
+
 echo "Copying java libs to $DEST"
 
 # Prebuilts required by sdklib & co, to be linked/copied in the ADT libs folder
diff --git a/layoutopt/app/src/com/android/layoutopt/cli/Main.java b/layoutopt/app/src/com/android/layoutopt/cli/Main.java
index 0275aa0..ed937d5 100644
--- a/layoutopt/app/src/com/android/layoutopt/cli/Main.java
+++ b/layoutopt/app/src/com/android/layoutopt/cli/Main.java
@@ -40,10 +40,13 @@
         Parameters p = checkParameters(args);
         if (!p.valid) {
             displayHelpMessage();
+            displayObsoleteMessage();
             exit();
         }
 
         analyzeFiles(p.files);
+
+        displayObsoleteMessage();
     }
 
     private static void analyzeFiles(File[] files) {
@@ -80,6 +83,11 @@
         System.out.println("usage: layoutopt <directories/files to analyze>");
     }
 
+    /** Layoutopt is obsolete; inform user */
+    private static void displayObsoleteMessage() {
+        System.err.println("\"layoutopt\" is obsolete; use \"lint\" instead which includes layout analysis.");
+    }
+
     /**
      * Builds a valid Parameters object. Parses the paramters if necessary
      * and checks for errors.
diff --git a/lint/.gitignore b/lint/.gitignore
new file mode 100644
index 0000000..99d9ac8
--- /dev/null
+++ b/lint/.gitignore
@@ -0,0 +1,4 @@
+cli/bin
+libs/lint_api/bin
+libs/lint_checks/bin
+libs/lint_checks/tests/bin
diff --git a/lint/Android.mk b/lint/Android.mk
new file mode 100644
index 0000000..8a99bd7
--- /dev/null
+++ b/lint/Android.mk
@@ -0,0 +1,5 @@
+# Copyright 2011 The Android Open Source Project
+#
+LINT_LOCAL_DIR := $(call my-dir)
+include $(LINT_LOCAL_DIR)/libs/Android.mk
+include $(LINT_LOCAL_DIR)/cli/Android.mk
diff --git a/lint/MODULE_LICENSE_APACHE2 b/lint/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lint/MODULE_LICENSE_APACHE2
diff --git a/lint/cli/.classpath b/lint/cli/.classpath
new file mode 100644
index 0000000..f6efb87
--- /dev/null
+++ b/lint/cli/.classpath
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/lint-api"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/lint-checks"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/lint/cli/.project b/lint/cli/.project
new file mode 100644
index 0000000..2809612
--- /dev/null
+++ b/lint/cli/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>lint-cli</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/lint/cli/.settings/org.eclipse.jdt.core.prefs b/lint/cli/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..e755df2
--- /dev/null
+++ b/lint/cli/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,71 @@
+#Thu Jun 09 12:26:44 PDT 2011
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nullReference=error
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
diff --git a/lint/cli/Android.mk b/lint/cli/Android.mk
new file mode 100644
index 0000000..5b99dc4
--- /dev/null
+++ b/lint/cli/Android.mk
@@ -0,0 +1,22 @@
+# Copyright 2011 The Android Open Source Project
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_JAVA_RESOURCE_DIRS := src
+
+LOCAL_JAR_MANIFEST := etc/manifest.txt
+
+# If the dependency list is changed, etc/manifest.txt
+LOCAL_JAVA_LIBRARIES := \
+	lint_api \
+	lint_checks
+LOCAL_MODULE := lint
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+
+# Build all sub-directories
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/lint/cli/NOTICE b/lint/cli/NOTICE
new file mode 100644
index 0000000..becc120
--- /dev/null
+++ b/lint/cli/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2011, 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.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/lint/cli/etc/Android.mk b/lint/cli/etc/Android.mk
new file mode 100644
index 0000000..987b452
--- /dev/null
+++ b/lint/cli/etc/Android.mk
@@ -0,0 +1,10 @@
+# Copyright 2011 The Android Open Source Project
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_PREBUILT_EXECUTABLES := lint
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_HOST_PREBUILT)
+
diff --git a/lint/cli/etc/lint b/lint/cli/etc/lint
new file mode 100755
index 0000000..2b53df6
--- /dev/null
+++ b/lint/cli/etc/lint
@@ -0,0 +1,72 @@
+#!/bin/bash
+# Copyright 2011, 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.
+
+# Set up prog to be the path of this script, including following symlinks,
+# and set up progdir to be the fully-qualified pathname of its directory.
+prog="$0"
+while [ -h "${prog}" ]; do
+    newProg=`/bin/ls -ld "${prog}"`
+    newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
+    if expr "x${newProg}" : 'x/' >/dev/null; then
+        prog="${newProg}"
+    else
+        progdir=`dirname "${prog}"`
+        prog="${progdir}/${newProg}"
+    fi
+done
+oldwd=`pwd`
+progdir=`dirname "${prog}"`
+cd "${progdir}"
+progdir=`pwd`
+prog="${progdir}"/`basename "${prog}"`
+cd "${oldwd}"
+
+jarfile=lint.jar
+frameworkdir="$progdir"
+libdir="$progdir"
+if [ ! -r "$frameworkdir/$jarfile" ]
+then
+    frameworkdir=`dirname "$progdir"`/tools/lib
+    libdir=`dirname "$progdir"`/tools/lib
+fi
+if [ ! -r "$frameworkdir/$jarfile" ]
+then
+    frameworkdir=`dirname "$progdir"`/framework
+    libdir=`dirname "$progdir"`/lib
+fi
+if [ ! -r "$frameworkdir/$jarfile" ]
+then
+    echo `basename "$prog"`": can't find $jarfile"
+    exit 1
+fi
+
+# Check args.
+if [ debug = "$1" ]; then
+    # add this in for debugging
+    java_debug=-agentlib:jdwp=transport=dt_socket,server=y,address=8050,suspend=y
+    shift 1
+else
+    java_debug=
+fi
+
+javaCmd="java"
+
+jarpath="$frameworkdir/$jarfile"
+
+exec "$javaCmd" \
+    -Xmx256M $os_opts $java_debug \
+    -Dcom.android.tools.lint.bindir="$progdir" \
+    -classpath "$jarpath" \
+    com.android.tools.lint.Main "$@"
diff --git a/lint/cli/etc/lint.bat b/lint/cli/etc/lint.bat
new file mode 100755
index 0000000..41e52d8
--- /dev/null
+++ b/lint/cli/etc/lint.bat
@@ -0,0 +1,55 @@
+@echo off
+rem Copyright (C) 2011 The Android Open Source Project
+rem
+rem Licensed under the Apache License, Version 2.0 (the "License");
+rem you may not use this file except in compliance with the License.
+rem You may obtain a copy of the License at
+rem
+rem      http://www.apache.org/licenses/LICENSE-2.0
+rem
+rem Unless required by applicable law or agreed to in writing, software
+rem distributed under the License is distributed on an "AS IS" BASIS,
+rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+rem See the License for the specific language governing permissions and
+rem limitations under the License.
+
+rem don't modify the caller's environment
+setlocal
+
+rem Set up prog to be the path of this script, including following symlinks,
+rem and set up progdir to be the fully-qualified pathname of its directory.
+set prog=%~f0
+
+rem Change current directory and drive to where the script is, to avoid
+rem issues with directories containing whitespaces.
+cd /d %~dp0
+
+rem Get the CWD as a full path with short names only (without spaces)
+for %%i in ("%cd%") do set prog_dir=%%~fsi
+
+rem Check we have a valid Java.exe in the path.
+set java_exe=
+call lib\find_java.bat
+if not defined java_exe goto :EOF
+
+set jarfile=lint.jar
+set frameworkdir=
+
+if exist %frameworkdir%%jarfile% goto JarFileOk
+    set frameworkdir=lib\
+
+if exist %frameworkdir%%jarfile% goto JarFileOk
+    set frameworkdir=..\framework\
+
+:JarFileOk
+
+if debug NEQ "%1" goto NoDebug
+    set java_debug=-agentlib:jdwp=transport=dt_socket,server=y,address=8050,suspend=y
+    shift 1
+:NoDebug
+
+set jarpath=%frameworkdir%%jarfile%
+set javaextdirs=%frameworkdir%
+
+call %java_exe% %java_debug% -Dcom.android.tools.lint.bindir=%prog_dir% -classpath "%jarpath%" com.android.tools.lint.Main %*
+
diff --git a/lint/cli/etc/manifest.txt b/lint/cli/etc/manifest.txt
new file mode 100644
index 0000000..e75087d
--- /dev/null
+++ b/lint/cli/etc/manifest.txt
@@ -0,0 +1,2 @@
+Main-Class: com.android.tools.lint.Main
+Class-Path: lint_api.jar lint_checks.jar
diff --git a/lint/cli/src/com/android/tools/lint/Main.java b/lint/cli/src/com/android/tools/lint/Main.java
new file mode 100644
index 0000000..9e8dc28
--- /dev/null
+++ b/lint/cli/src/com/android/tools/lint/Main.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2011 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;
+
+import com.android.tools.lint.api.DetectorRegistry;
+import com.android.tools.lint.api.IDomParser;
+import com.android.tools.lint.api.Lint;
+import com.android.tools.lint.api.ToolContext;
+import com.android.tools.lint.checks.BuiltinDetectorRegistry;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Position;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Command line driver for the rules framework
+ * <p>
+ * TODO:
+ * <ul>
+ * <li>Offer priority or category sorting
+ * <li>Offer suppressing violations
+ * </ul>
+ */
+public class Main implements ToolContext {
+    private static final int ERRNO_ERRORS = -1;
+    private static final int ERRNO_USAGE = -2;
+    private static final int ERRNO_EXISTS = -3;
+    private static final int ERRNO_HELP = -4;
+    private static final int ERRNO_INVALIDARGS = -5;
+
+    private Set<String> mSuppress = new HashSet<String>();
+    private Set<String> mEnabled = null;
+    private StringBuilder mOutput = new StringBuilder(2000);
+    private boolean mFatal;
+    private String mCommonPrefix;
+
+    /** Creates a CLI driver */
+    public Main() {
+    }
+
+    /**
+     * Runs the static analysis command line driver
+     *
+     * @param args program arguments
+     */
+    public static void main(String[] args) {
+        new Main().run(args);
+    }
+
+    /**
+     * Runs the static analysis command line driver
+     *
+     * @param args program arguments
+     */
+    private void run(String[] args) {
+        if (args.length < 1) {
+            printUsage();
+            System.exit(ERRNO_USAGE);
+        }
+
+        DetectorRegistry registry = new BuiltinDetectorRegistry();
+
+        List<File> files = new ArrayList<File>();
+        for (int index = 0; index < args.length; index++) {
+            String arg = args[index];
+            if (arg.equals("--help") || arg.equals("-h")) { //$NON-NLS-1$ //$NON-NLS-2$
+                printUsage();
+                System.err.println("\n" +
+                        "Run with --suppress <category1[,category2,...]> to suppress categories.");
+                System.exit(ERRNO_HELP);
+            } else if (arg.equals("--suppress")) {
+                if (index == args.length - 1) {
+                    System.err.println("Missing categories to suppress");
+                    System.exit(ERRNO_INVALIDARGS);
+                }
+                String[] ids = args[++index].split(",");
+                for (String id : ids) {
+                    if (!registry.isIssueId(id)) {
+                        System.err.println("Invalid id \"" + id + "\".");
+                        displayValidIds(registry);
+                        System.exit(ERRNO_INVALIDARGS);
+                    }
+                    mSuppress.add(id);
+                }
+            } else if (arg.equals("--enable")) {
+                if (index == args.length - 1) {
+                    System.err.println("Missing categories to enable");
+                    System.exit(ERRNO_INVALIDARGS);
+                }
+                String[] ids = args[++index].split(",");
+                mEnabled = new HashSet<String>();
+                for (String id : ids) {
+                    if (!registry.isIssueId(id)) {
+                        System.err.println("Invalid id \"" + id + "\".");
+                        displayValidIds(registry);
+                        System.exit(ERRNO_INVALIDARGS);
+                    }
+                    mEnabled.add(id);
+                }
+            } else {
+                String filename = arg;
+                File file = new File(filename);
+                if (!file.exists()) {
+                    System.err.println(String.format("%1$s does not exist.", filename));
+                    System.exit(ERRNO_EXISTS);
+                }
+                files.add(file);
+            }
+            // TODO: Add flag to point to a file of specific errors to suppress
+        }
+
+        if (files.size() == 0) {
+            System.err.println("No files to analyze.");
+            System.exit(ERRNO_INVALIDARGS);
+        }
+
+        mCommonPrefix = files.get(0).getPath();
+        for (int i = 1; i < files.size(); i++) {
+            File file = files.get(i);
+            String path = file.getPath();
+            mCommonPrefix = getCommonPrefix(mCommonPrefix, path);
+        }
+
+        Lint analyzer = new Lint(new BuiltinDetectorRegistry(), this, Scope.PROJECT);
+        analyzer.analyze(files);
+        if (mOutput.length() == 0) {
+            System.out.println("No warnings.");
+            System.exit(0); // Success error code
+        } else {
+            System.err.println(mOutput.toString());
+            System.err.println("Run with --suppress to turn off specific types of errors, or this message");
+
+            System.exit(mFatal ? ERRNO_ERRORS : 0);
+        }
+    }
+
+    private void displayValidIds(DetectorRegistry registry) {
+        List<Issue> issues = registry.getIssues();
+        System.err.println("Valid issue ids:");
+        for (Issue issue : issues) {
+            System.err.println("\"" + issue.getId() + "\": " + issue.getDescription());
+        }
+    }
+
+    private static void printUsage() {
+        // TODO: Look up launcher script name!
+        System.err.println("Usage: lint [--suppress ids] [--enable ids] <project | file> ...");
+    }
+
+    private static String getCommonPrefix(String a, String b) {
+        int aLength = a.length();
+        int bLength = b.length();
+        int aIndex = 0, bIndex = 0;
+        for (; aIndex < aLength && bIndex < bLength; aIndex++, bIndex++) {
+            if (a.charAt(aIndex) != b.charAt(bIndex)) {
+                break;
+            }
+        }
+
+        return a.substring(0, aIndex);
+    }
+
+    public void log(Throwable exception, String format, Object... args) {
+        System.err.println(String.format(format, args));
+        if (exception != null) {
+            exception.printStackTrace();
+        }
+    }
+
+    public IDomParser getParser() {
+        return new PositionXmlParser();
+    }
+
+    public boolean isEnabled(Issue issue) {
+        if (mEnabled != null) {
+            return mEnabled.contains(issue.getId());
+        }
+        return !mSuppress.contains(issue.getId());
+    }
+
+    public void report(Issue issue, Location location, String message) {
+        if (!isEnabled(issue)) {
+            return;
+        }
+
+        Severity severity = getSeverity(issue);
+        if (severity == Severity.IGNORE) {
+            return;
+        }
+        if (severity == Severity.ERROR){
+            mFatal = true;
+        }
+
+        int startLength = mOutput.length();
+
+        File file = location.getFile();
+        if (file != null) {
+            String path = file.getPath();
+            if (path.startsWith(mCommonPrefix)) {
+                int chop = mCommonPrefix.length();
+                if (path.length() > chop && path.charAt(chop) == File.separatorChar) {
+                    chop++;
+                }
+                path = path.substring(chop);
+            }
+            mOutput.append(path);
+            mOutput.append(':');
+        }
+
+        Position startPosition = location.getStart();
+        if (startPosition != null) {
+            int line = startPosition.getLine();
+            if (line >= 0) {
+                // line is 0-based, should display 1-based
+                mOutput.append(Integer.toString(line + 1));
+                mOutput.append(':');
+            }
+        }
+
+        // Column is not particularly useful here
+        //int column = location.getColumn();
+        //if (column > 0) {
+        //    mOutput.append(Integer.toString(column));
+        //    mOutput.append(':');
+        //}
+
+        if (startLength < mOutput.length()) {
+            mOutput.append(' ');
+        }
+        mOutput.append(severity.getDescription());
+        mOutput.append(':');
+        mOutput.append(' ');
+
+        mOutput.append(message);
+        if (issue != null) {
+            mOutput.append(' ').append('[');
+            mOutput.append(issue.getId());
+            mOutput.append(']');
+        }
+
+        mOutput.append('\n');
+    }
+
+    public boolean isSuppressed(Issue issue, Location range, String message,
+            Severity severity) {
+        // Not yet supported
+        return false;
+    }
+
+    public Severity getSeverity(Issue issue) {
+        return issue.getDefaultSeverity();
+    }
+}
diff --git a/lint/cli/src/com/android/tools/lint/PositionXmlParser.java b/lint/cli/src/com/android/tools/lint/PositionXmlParser.java
new file mode 100644
index 0000000..0857425
--- /dev/null
+++ b/lint/cli/src/com/android/tools/lint/PositionXmlParser.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2011 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;
+
+import com.android.tools.lint.api.IDomParser;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Position;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.XMLFilterImpl;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+import java.io.StringReader;
+
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.sax.SAXSource;
+
+/** A simple XML parser which can store and retrieve line:column information for the nodes */
+public class PositionXmlParser implements IDomParser {
+    private static final String ATTR_LOCATION = "location";                     //$NON-NLS-1$
+    private static final String PRIVATE_NAMESPACE = "http://tools.android.com"; //$NON-NLS-1$
+    private static final String PRIVATE_PREFIX = "temp";                        //$NON-NLS-1$
+
+    public Document parse(Context context) {
+        InputSource input = new InputSource(new StringReader(context.getContents()));
+        try {
+            Filter filter = new Filter(XMLReaderFactory.createXMLReader());
+            Transformer transformer = TransformerFactory.newInstance().newTransformer();
+            DOMResult result = new DOMResult();
+            transformer.transform(new SAXSource(filter, input), result);
+            return (Document) result.getNode();
+        } catch (SAXException e) {
+            // The file doesn't parse: not an exception. Infrastructure will log a warning
+            // that this file was not analyzed.
+            return null;
+        } catch (TransformerConfigurationException e) {
+            context.toolContext.log(e, null);
+        } catch (TransformerException e) {
+            context.toolContext.log(e, null);
+        }
+
+        return null;
+    }
+
+    private static class Filter extends XMLFilterImpl {
+        private Locator mLocator;
+
+        Filter(XMLReader reader) {
+            super(reader);
+        }
+
+        @Override
+        public void setDocumentLocator(Locator locator) {
+            super.setDocumentLocator(locator);
+            this.mLocator = locator;
+        }
+
+        @Override
+        public void startElement(String uri, String localName, String qualifiedName,
+                Attributes attributes) throws SAXException {
+            int lineno = mLocator.getLineNumber();
+            int column = mLocator.getColumnNumber();
+            String location = Integer.toString(lineno) + ':' + Integer.toString(column);
+
+            // Modify attributes parameter to a copy that includes our private attribute
+            AttributesImpl wrapper = new AttributesImpl(attributes);
+            wrapper.addAttribute(PRIVATE_NAMESPACE, ATTR_LOCATION,
+                    PRIVATE_PREFIX + ':' + ATTR_LOCATION, "CDATA", location); //$NON-NLS-1$
+
+            super.startElement(uri, localName, qualifiedName, wrapper);
+        }
+    }
+
+    public Position getStartPosition(Context context, Node node) {
+        if (node instanceof Attr) {
+            Attr attr = (Attr) node;
+            node = attr.getOwnerElement();
+        }
+        if (node instanceof Element) {
+            Attr attribute = ((Element) node).getAttributeNodeNS(PRIVATE_NAMESPACE, ATTR_LOCATION);
+            if (attribute != null) {
+                String position = attribute.getValue();
+                int separator = position.indexOf(':');
+                int line = Integer.parseInt(position.substring(0, separator));
+                int column = Integer.parseInt(position.substring(separator + 1));
+                return new OffsetPosition(line, column, -1);
+            }
+        }
+
+        return null;
+    }
+
+    public Position getEndPosition(Context context, Node node) {
+        // TODO: Currently unused
+        return null;
+    }
+
+    private static class OffsetPosition extends Position {
+        /** The line number (0-based where the first line is line 0) */
+        private final int mLine;
+
+        /**
+         * The column number (where the first character on the line is 0), or -1 if
+         * unknown
+         */
+        private final int mColumn;
+
+        /** The character offset */
+        private final int mOffset;
+
+        /**
+         * Creates a new {@link Position}
+         *
+         * @param line the 0-based line number, or -1 if unknown
+         * @param column the 0-based column number, or -1 if unknown
+         * @param offset the offset, or -1 if unknown
+         */
+        public OffsetPosition(int line, int column, int offset) {
+            this.mLine = line;
+            this.mColumn = column;
+            this.mOffset = offset;
+        }
+
+        @Override
+        public int getLine() {
+            return mLine;
+        }
+
+        @Override
+        public int getOffset() {
+            return mOffset;
+        }
+
+        @Override
+        public int getColumn() {
+            return mColumn;
+        }
+    }
+}
diff --git a/lint/libs/Android.mk b/lint/libs/Android.mk
new file mode 100644
index 0000000..67aeea2
--- /dev/null
+++ b/lint/libs/Android.mk
@@ -0,0 +1,5 @@
+# Copyright 2011 The Android Open Source Project
+#
+CHECKERLIBS_LOCAL_DIR := $(call my-dir)
+include $(CHECKERLIBS_LOCAL_DIR)/lint_api/Android.mk
+include $(CHECKERLIBS_LOCAL_DIR)/lint_checks/Android.mk
diff --git a/lint/libs/lint_api/.classpath b/lint/libs/lint_api/.classpath
new file mode 100644
index 0000000..012d361
--- /dev/null
+++ b/lint/libs/lint_api/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry excluding="Android.mk" kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/common"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/lint/libs/lint_api/.project b/lint/libs/lint_api/.project
new file mode 100644
index 0000000..7c50676
--- /dev/null
+++ b/lint/libs/lint_api/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>lint-api</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/lint/libs/lint_api/.settings/org.eclipse.jdt.core.prefs b/lint/libs/lint_api/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..e755df2
--- /dev/null
+++ b/lint/libs/lint_api/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,71 @@
+#Thu Jun 09 12:26:44 PDT 2011
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nullReference=error
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
diff --git a/lint/libs/lint_api/.settings/org.moreunit.prefs b/lint/libs/lint_api/.settings/org.moreunit.prefs
new file mode 100644
index 0000000..73d4d8e
--- /dev/null
+++ b/lint/libs/lint_api/.settings/org.moreunit.prefs
@@ -0,0 +1,6 @@
+#Tue Oct 18 10:20:08 PDT 2011
+eclipse.preferences.version=1
+org.moreunit.extendedTestMethodSearch=true
+org.moreunit.prefixes=
+org.moreunit.unitsourcefolder=lint-api\:src\:lint_check-tests\:src
+org.moreunit.useprojectsettings=true
diff --git a/lint/libs/lint_api/Android.mk b/lint/libs/lint_api/Android.mk
new file mode 100644
index 0000000..847f52e
--- /dev/null
+++ b/lint/libs/lint_api/Android.mk
@@ -0,0 +1,31 @@
+# Copyright (C) 2011 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Only compile source java files in this lib.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_JAVA_RESOURCE_DIRS := src
+LOCAL_JAVA_LIBRARIES := \
+        common
+
+LOCAL_MODULE := lint_api
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+# Build all sub-directories
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/lint/libs/lint_api/NOTICE b/lint/libs/lint_api/NOTICE
new file mode 100644
index 0000000..becc120
--- /dev/null
+++ b/lint/libs/lint_api/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2011, 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.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/api/DetectorRegistry.java b/lint/libs/lint_api/src/com/android/tools/lint/api/DetectorRegistry.java
new file mode 100644
index 0000000..591114a
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/api/DetectorRegistry.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2011 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.api;
+
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/** Registry which provides a list of checks to be performed on an Android project */
+public abstract class DetectorRegistry {
+    private static List<Issue> sIssues;
+
+    /**
+     * Returns the list of detectors to be run.
+     *
+     * @return the list of checks to be performed (including those that may be
+     *         disabled!)
+     */
+    public abstract List<? extends Detector> getDetectors();
+
+    /**
+     * Returns true if the given id represents a valid issue id
+     *
+     * @param id the id to be checked
+     * @return true if the given id is valid
+     */
+    public boolean isIssueId(String id) {
+        return getIssue(id) != null;
+    }
+
+
+    /**
+     * Returns the issue for the given id, or null if it's not a valid id
+     *
+     * @param id the id to be checked
+     * @return the corresponding issue, or null
+     */
+    public Issue getIssue(String id) {
+        for (Issue issue : getIssues()) {
+            if (issue.getId().equals(id)) {
+                return issue;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns the list of issues that can be found by all known detectors.
+     *
+     * @return the list of issues to be checked (including those that may be
+     *         disabled!)
+     */
+    @SuppressWarnings("all") // Turn off warnings for the intentional assertion side effect below
+    public List<Issue> getIssues() {
+        if (sIssues == null) {
+            List<Issue> issues = new ArrayList<Issue>();
+            for (Detector detector : getDetectors()) {
+                for (Issue issue : detector.getIssues()) {
+                    issues.add(issue);
+                }
+            }
+
+            sIssues = Collections.unmodifiableList(issues);
+
+            // Check that ids are unique
+            boolean assertionsEnabled = false;
+            assert assertionsEnabled = true; // Intentional side-effect
+            if (assertionsEnabled) {
+                Set<String> ids = new HashSet<String>();
+                for (Issue issue : sIssues) {
+                    String id = issue.getId();
+                    assert !ids.contains(id) : "Duplicate id " + id; //$NON-NLS-1$
+                    ids.add(id);
+                }
+            }
+        }
+
+        return sIssues;
+    }
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/api/IDomParser.java b/lint/libs/lint_api/src/com/android/tools/lint/api/IDomParser.java
new file mode 100644
index 0000000..12861c5
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/api/IDomParser.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2011 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.api;
+
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Position;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+/**
+ * A wrapper for XML parser. This allows tools integrating lint to map directly
+ * to builtin services, such as already-parsed data structures in XML editors.
+ * <p/>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+public interface IDomParser {
+    /**
+     * Parse the file pointed to by the given context and return as a Document
+     *
+     * @param context the context pointing to the file to be parsed, typically
+     *            via {@link Context#getContents()} but the file handle (
+     *            {@link Context#file} can also be used to map to an existing
+     *            editor buffer in the surrounding tool, etc)
+     * @return the parsed DOM document, or null if parsing fails
+     */
+    public Document parse(Context context);
+
+    /**
+     * Returns the starting position of the given DOM node (which may not be
+     * just an element but can for example also be an {@link Attr} node). The
+     * node will *always* be from the same DOM document that was returned by
+     * this parser.
+     *
+     * @param context information about the file being parsed
+     * @param node the node to look up a starting position for
+     * @return the position of the beginning of the node
+     */
+    public Position getStartPosition(Context context, Node node);
+
+    /**
+     * Returns the ending position of the given DOM node.
+     *
+     * @param context information about the file being parsed
+     * @param node the node to look up a ending position for
+     * @return the position of the end of the node
+     */
+    public Position getEndPosition(Context context, Node node);
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/api/Lint.java b/lint/libs/lint_api/src/com/android/tools/lint/api/Lint.java
new file mode 100644
index 0000000..7cbbb79
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/api/Lint.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2011 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.api;
+
+import com.android.resources.ResourceFolderType;
+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.Location;
+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 java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/** Analyzes Android projects and files */
+public class Lint {
+    private static final String RES_FOLDER_NAME = "res"; //$NON-NLS-1$
+    private final ToolContext mToolContext;
+    private volatile boolean mCanceled;
+    private DetectorRegistry mRegistry;
+    private Scope mScope;
+
+    /**
+     * Creates a new {@link Lint}
+     *
+     * @param registry The registry containing rules to be run
+     * @param toolContext a context for the tool wrapping the analyzer, such as
+     *            an IDE or a CLI
+     * @param scope the scope of the analysis; detectors with a wider scope will
+     *            not be run
+     */
+    public Lint(DetectorRegistry registry, ToolContext toolContext, Scope scope) {
+        assert toolContext != null;
+        mRegistry = registry;
+        mToolContext = toolContext;
+        mScope = scope;
+    }
+
+    /** Cancels the current lint run as soon as possible */
+    public void cancel() {
+        mCanceled = true;
+    }
+
+    /**
+     * Analyze the given file (which can point to an Android project). Issues found
+     * are reported to the associated {@link ToolContext}.
+     *
+     * @param files the files and directories to be analyzed
+     */
+    public void analyze(List<File> files) {
+        List<? extends Detector> availableChecks = mRegistry.getDetectors();
+
+        // Filter out disabled checks
+        List<Detector> checks = new ArrayList<Detector>(availableChecks.size());
+        for (Detector detector : availableChecks) {
+            if (!detector.getScope().within(mScope)) {
+                continue;
+            }
+            // A detector is enabled if at least one of its issues is enabled
+            for (Issue issue : detector.getIssues()) {
+                if (mToolContext.isEnabled(issue)) {
+                    checks.add(detector);
+                    break;
+                }
+            }
+        }
+
+        // Process XML files in a single pass
+        List<ResourceXmlDetector> xmlChecks = new ArrayList<ResourceXmlDetector>(checks.size());
+        List<Detector> other = new ArrayList<Detector>(checks.size());
+        for (Detector check : checks) {
+            if (check instanceof ResourceXmlDetector) {
+                xmlChecks.add((ResourceXmlDetector) check);
+            } else {
+                // TODO:
+                other.add(check);
+            }
+        }
+
+        Context projectContext = new Context(mToolContext, null);
+        for (Detector check : checks) {
+            check.beforeCheckProject(projectContext);
+            if (mCanceled) {
+                return;
+            }
+        }
+
+        for (File file : files) {
+            if (file.isDirectory()) {
+                // Is it a resource folder?
+                ResourceFolderType type = ResourceFolderType.getFolderType(file.getName());
+                if (type != null && new File(file.getParentFile(), RES_FOLDER_NAME).exists()) {
+                    // Yes.
+                    checkResourceFolder(file, type, xmlChecks);
+                } else if (file.getName().equals(RES_FOLDER_NAME)) { // Is it the "res" folder?
+                    // Yes
+                    checkResFolder(file, xmlChecks);
+                } else {
+                    // It must be a project
+                    File res = new File(file, RES_FOLDER_NAME);
+                    if (res.exists()) {
+                        checkResFolder(res, xmlChecks);
+                    } else {
+                        mToolContext.log(null, "Unexpected folder %1$s; should be project, " +
+                                "\"res\" folder or resource folder", file.getPath());
+                    }
+                }
+            } else if (file.isFile()) {
+                // Did we point to an XML resource?
+                if (ResourceXmlDetector.isXmlFile(file)) {
+                    // Yes, find out its resource type
+                    String folderName = file.getParentFile().getName();
+                    ResourceFolderType type = ResourceFolderType.getFolderType(folderName);
+                    if (type != null) {
+                        XmlVisitor visitor = getVisitor(xmlChecks, type);
+                        if (visitor != null) {
+                            Context context = new Context(mToolContext, file);
+                            visitor.visitFile(context, file);
+                        }
+                    }
+                } else {
+                    if (other.size() > 0) {
+                        Context context = new Context(mToolContext, file);
+                        context.location = new Location(file, null, null);
+                        for (Detector detector : other) {
+                            if (detector.appliesTo(context, file)) {
+                                detector.beforeCheckFile(context);
+                                detector.run(context);
+                                detector.afterCheckFile(context);
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (mCanceled) {
+                return;
+            }
+        }
+
+        for (Detector check : checks) {
+            check.afterCheckProject(projectContext);
+            if (mCanceled) {
+                return;
+            }
+        }
+
+        if (mCanceled) {
+            mToolContext.report(
+                    // Must provide an issue since API guarantees that the issue parameter
+                    // is valid
+                    Issue.create("dummy", "", "", "", 0, Severity.INFORMATIONAL, null), //$NON-NLS-1$
+                    null /*range*/,
+                    "Lint canceled by user");
+        }
+    }
+
+    private ResourceFolderType mCurrentFolderType;
+    private List<ResourceXmlDetector> mCurrentXmlDetectors;
+    private XmlVisitor mCurrentVisitor;
+
+    private XmlVisitor getVisitor(List<ResourceXmlDetector> checks, ResourceFolderType type) {
+        if (type != mCurrentFolderType) {
+            mCurrentFolderType = type;
+
+            // Determine which XML resource detectors apply to the given folder type
+            List<ResourceXmlDetector> applicableChecks =
+                    new ArrayList<ResourceXmlDetector>(checks.size());
+            for (ResourceXmlDetector check : checks) {
+                if (check.appliesTo(type)) {
+                    applicableChecks.add(check);
+                }
+            }
+
+            // If the list of detectors hasn't changed, then just use the current visitor!
+            if (mCurrentXmlDetectors != null && mCurrentXmlDetectors.equals(applicableChecks)) {
+                return mCurrentVisitor;
+            }
+
+            if (applicableChecks.size() == 0) {
+                mCurrentVisitor = null;
+                return null;
+            }
+
+            mCurrentVisitor = new XmlVisitor(mToolContext.getParser(), applicableChecks);
+        }
+
+        return mCurrentVisitor;
+    }
+
+    private void checkResFolder(File res, List<ResourceXmlDetector> xmlChecks) {
+        assert res.isDirectory();
+        File[] resourceDirs = res.listFiles();
+        if (resourceDirs == null) {
+            return;
+        }
+
+        // Sort alphabetically such that we can process related folder types at the
+        // same time
+
+        Arrays.sort(resourceDirs);
+        ResourceFolderType type = null;
+        for (File dir : resourceDirs) {
+            if (!dir.isDirectory()) {
+                continue;
+            }
+
+            type = ResourceFolderType.getFolderType(dir.getName());
+            if (type != null) {
+                checkResourceFolder(dir, type, xmlChecks);
+            }
+
+            if (mCanceled) {
+                return;
+            }
+        }
+    }
+
+    private void checkResourceFolder(File dir, ResourceFolderType type,
+            List<ResourceXmlDetector> xmlChecks) {
+        // Process the resource folder
+        File[] xmlFiles = dir.listFiles();
+        if (xmlFiles != null && xmlFiles.length > 0) {
+            XmlVisitor visitor = getVisitor(xmlChecks, type);
+            if (visitor != null) { // if not, there are no applicable rules in this folder
+                for (File xmlFile : xmlFiles) {
+                    if (ResourceXmlDetector.isXmlFile(xmlFile)) {
+                        Context context = new Context(mToolContext, xmlFile);
+                        visitor.visitFile(context, xmlFile);
+                        if (mCanceled) {
+                            return;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the associated tool context for the surrounding tool that is
+     * embedding lint analysis
+     *
+     * @return the surrounding tool context
+     */
+    public ToolContext getToolContext() {
+        return mToolContext;
+    }
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/api/ToolContext.java b/lint/libs/lint_api/src/com/android/tools/lint/api/ToolContext.java
new file mode 100644
index 0000000..cc3592f
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/api/ToolContext.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2011 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.api;
+
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Severity;
+
+/**
+ * Information about the tool embedding the lint analyzer
+ * <p/>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+public interface ToolContext {
+    /**
+     * Report the given issue.
+     *
+     * @param issue the issue that was found
+     * @param location the location of the issue
+     * @param message the associated user message
+     */
+    public void report(Issue issue, Location location, String message);
+
+    /**
+     * Checks whether this issue should be ignored because the user has already
+     * suppressed the error? Note that this refers to individual issues being
+     * suppressed/ignored, not a whole detector being disabled via something
+     * like {@link #isEnabled(Issue)}.
+     *
+     * @param issue the issue that was found
+     * @param location the location of the issue
+     * @param message the associated user message
+     * @param severity the severity of the issue
+     * @return true if this issue should be suppressed
+     */
+    public boolean isSuppressed(Issue issue, Location location, String message, Severity severity);
+
+
+    /**
+     * Send an exception to the log
+     *
+     * @param exception the exception, possibly null
+     * @param format the error message using {@link String#format} syntax
+     * @param args any arguments for the format string
+     */
+    public void log(Throwable exception, String format, Object... args);
+
+    /**
+     * Returns a {@link IDomParser} to use to parse XML
+     *
+     * @return a new {@link IDomParser}
+     */
+    public IDomParser getParser();
+
+    /**
+     * Returns false if the given issue has been disabled
+     *
+     * @param issue the issue to check
+     * @return false if the issue has been disabled
+     */
+    public boolean isEnabled(Issue issue);
+
+    /**
+     * Returns the severity for a given issue. This is the same as the
+     * {@link Issue#getDefaultSeverity()} unless the user has selected a custom
+     * severity (which is tool context dependent).
+     *
+     * @param issue the issue to look up the severity from
+     * @return the severity use for issues for the given detector
+     */
+    public Severity getSeverity(Issue issue);
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/api/XmlVisitor.java b/lint/libs/lint_api/src/com/android/tools/lint/api/XmlVisitor.java
new file mode 100644
index 0000000..1908682
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/api/XmlVisitor.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2011 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.api;
+
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+import com.android.tools.lint.detector.api.Severity;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.RandomAccess;
+
+/**
+ * Specialized visitor for running detectors on an XML document.
+ * It operates in two phases:
+ * <ol>
+ *   <li> First, it computes a set of maps where it generates a map from each
+ *        significant element name, and each significant attribute name, to a list
+ *        of detectors to consult for that element or attribute name.
+ *        The set of element names or attribute names (or both) that a detector
+ *        is interested in is provided by the detectors themselves.
+ *   <li> Second, it iterates over the document a single time. For each element and
+ *        attribute it looks up the list of interested detectors, and runs them.
+ * </ol>
+ * It also notifies all the detectors before and after the document is processed
+ * such that they can do pre- and post-processing.
+ */
+class XmlVisitor {
+    private final Map<String, List<ResourceXmlDetector>> mElementToCheck =
+            new HashMap<String, List<ResourceXmlDetector>>();
+    private final Map<String, List<ResourceXmlDetector>> mAttributeToCheck =
+            new HashMap<String, List<ResourceXmlDetector>>();
+    private final List<ResourceXmlDetector> mDocumentDetectors =
+            new ArrayList<ResourceXmlDetector>();
+    private final List<ResourceXmlDetector> mAllElementDetectors =
+            new ArrayList<ResourceXmlDetector>();
+    private final List<ResourceXmlDetector> mAllAttributeDetectors =
+            new ArrayList<ResourceXmlDetector>();
+    private final List<ResourceXmlDetector> mAllDetectors;
+    private final IDomParser mParser;
+
+    XmlVisitor(IDomParser parser, List<ResourceXmlDetector> detectors) {
+        mParser = parser;
+        mAllDetectors = detectors;
+
+        // TODO: Check appliesTo() for files, and find a quick way to enable/disable
+        // rules when running through a full project!
+        for (ResourceXmlDetector detector : detectors) {
+            Collection<String> attributes = detector.getApplicableAttributes();
+            if (attributes == ResourceXmlDetector.ALL) {
+                mAllAttributeDetectors.add(detector);
+            }  else if (attributes != null) {
+                for (String attribute : attributes) {
+                    List<ResourceXmlDetector> list = mAttributeToCheck.get(attribute);
+                    if (list == null) {
+                        list = new ArrayList<ResourceXmlDetector>();
+                        mAttributeToCheck.put(attribute, list);
+                    }
+                    list.add(detector);
+                }
+            }
+            Collection<String> elements = detector.getApplicableElements();
+            if (elements == ResourceXmlDetector.ALL) {
+                mAllElementDetectors.add(detector);
+            } else if (elements != null) {
+                for (String element : elements) {
+                    List<ResourceXmlDetector> list = mElementToCheck.get(element);
+                    if (list == null) {
+                        list = new ArrayList<ResourceXmlDetector>();
+                        mElementToCheck.put(element, list);
+                    }
+                    list.add(detector);
+                }
+            }
+
+            if ((attributes == null || (attributes.size() == 0
+                    && attributes != ResourceXmlDetector.ALL))
+                  && (elements == null || (elements.size() == 0
+                  && elements != ResourceXmlDetector.ALL))) {
+                mDocumentDetectors.add(detector);
+            }
+        }
+    }
+
+    void visitFile(Context context, File file) {
+        assert ResourceXmlDetector.isXmlFile(file);
+
+        context.location = null;
+        context.parser = mParser;
+
+        if (context.document == null) {
+            context.document = mParser.parse(context);
+            if (context.document == null) {
+                context.toolContext.report(
+                        // Must provide an issue since API guarantees that the issue parameter
+                        // is valid
+                        Issue.create("dummy", "", "", "", 0, Severity.ERROR, null), //$NON-NLS-1$
+                        new Location(file, null, null),
+                        "Skipped file because it contains parsing errors");
+                return;
+            }
+            if (context.document.getDocumentElement() == null) {
+                // Ignore empty documents
+                return;
+            }
+        }
+
+        for (ResourceXmlDetector check : mAllDetectors) {
+            check.beforeCheckFile(context);
+        }
+
+        for (ResourceXmlDetector check : mDocumentDetectors) {
+            check.visitDocument(context, context.document);
+        }
+
+        if (mElementToCheck.size() > 0 || mAttributeToCheck.size() > 0
+                || mAllAttributeDetectors.size() > 0 || mAllElementDetectors.size() > 0) {
+            visitElement(context, context.document.getDocumentElement());
+        }
+
+        for (ResourceXmlDetector check : mAllDetectors) {
+            check.afterCheckFile(context);
+        }
+    }
+
+    private void visitElement(Context context, Element element) {
+        context.element = element;
+
+        List<ResourceXmlDetector> elementChecks = mElementToCheck.get(element.getTagName());
+        if (elementChecks != null) {
+            assert elementChecks instanceof RandomAccess;
+            for (int i = 0, n = elementChecks.size(); i < n; i++) {
+                ResourceXmlDetector check = elementChecks.get(i);
+                check.visitElement(context, element);
+            }
+        }
+        if (mAllElementDetectors.size() > 0) {
+            for (int i = 0, n = mAllElementDetectors.size(); i < n; i++) {
+                ResourceXmlDetector check = mAllElementDetectors.get(i);
+                check.visitElement(context, element);
+            }
+        }
+
+        if (mAttributeToCheck.size() > 0 || mAllAttributeDetectors.size() > 0) {
+            NamedNodeMap attributes = element.getAttributes();
+            for (int i = 0, n = attributes.getLength(); i < n; i++) {
+                Attr attribute = (Attr) attributes.item(i);
+                List<ResourceXmlDetector> list = mAttributeToCheck.get(attribute.getLocalName());
+                if (list != null) {
+                    for (int j = 0, max = list.size(); j < max; j++) {
+                        ResourceXmlDetector check = list.get(j);
+                        check.visitAttribute(context, attribute);
+                    }
+                }
+                if (mAllAttributeDetectors.size() > 0) {
+                    for (int j = 0, max = mAllAttributeDetectors.size(); j < max; j++) {
+                        ResourceXmlDetector check = mAllAttributeDetectors.get(j);
+                        check.visitAttribute(context, attribute);
+                    }
+                }
+            }
+        }
+
+        // Visit children
+        NodeList childNodes = element.getChildNodes();
+        for (int i = 0, n = childNodes.getLength(); i < n; i++) {
+            Node child = childNodes.item(i);
+            if (child.getNodeType() == Node.ELEMENT_NODE) {
+                visitElement(context, (Element) child);
+            }
+        }
+
+        // Post hooks
+        if (elementChecks != null) {
+            for (int i = 0, n = elementChecks.size(); i < n; i++) {
+                ResourceXmlDetector check = elementChecks.get(i);
+                check.visitElementAfter(context, element);
+            }
+        }
+        if (mAllElementDetectors.size() > 0) {
+            for (int i = 0, n = mAllElementDetectors.size(); i < n; i++) {
+                ResourceXmlDetector check = mAllElementDetectors.get(i);
+                check.visitElementAfter(context, element);
+            }
+        }
+    }
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Context.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Context.java
new file mode 100644
index 0000000..8f4ff4e
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Context.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2011 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.detector.api;
+
+import com.android.tools.lint.api.IDomParser;
+import com.android.tools.lint.api.ToolContext;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Context passed to the detectors during an analysis run. It provides
+ * information about the file being analyzed, it allows shared properties (so
+ * the detectors can share results), it contains the current location in the
+ * document, etc.
+ * <p>
+ * TODO: This needs some cleanup. Perhaps we should split up into a FileContext
+ * and a ProjectContext.
+ * <p/>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+public class Context {
+    public final File file;
+    public final ToolContext toolContext;
+    public Document document;
+    public Location location;
+    public Element element;
+    public IDomParser parser;
+    private String contents;
+    private Map<String, Object> properties;
+
+    public Context(ToolContext toolContext, File file) {
+        super();
+        this.toolContext = toolContext;
+        this.file = file;
+    }
+
+    public Location getLocation(Node node) {
+        if (parser != null) {
+            return new Location(file,
+                    parser.getStartPosition(this, node),
+                    parser.getEndPosition(this, node));
+        }
+
+        return location;
+    }
+
+    public Location getLocation(Context context) {
+        if (location == null && element != null && parser != null) {
+            return getLocation(element);
+        }
+        return location;
+    }
+
+    public String getContents() {
+        if (contents == null) {
+            contents = ""; //$NON-NLS-1$
+
+            BufferedReader reader = null;
+            try {
+                reader = new BufferedReader(new FileReader(file));
+                StringBuilder sb = new StringBuilder((int) file.length());
+                while (true) {
+                    int c = reader.read();
+                    if (c == -1) {
+                        contents = sb.toString();
+                        break;
+                    } else {
+                        sb.append((char)c);
+                    }
+                }
+            } catch (IOException e) {
+                // pass -- ignore files we can't read
+            } finally {
+                try {
+                    if (reader != null) {
+                        reader.close();
+                    }
+                } catch (IOException e) {
+                    toolContext.log(e, null);
+                }
+            }
+        }
+
+        return contents;
+    }
+
+    public Object getProperty(String name) {
+        if (properties == null) {
+            return null;
+        }
+
+        return properties.get(name);
+    }
+
+    public void setProperty(String name, Object value) {
+        if (properties == null) {
+            properties = new HashMap<String, Object>();
+        }
+
+        properties.put(name, value);
+    }
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java
new file mode 100644
index 0000000..12696a4
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2011 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.detector.api;
+
+import java.io.File;
+
+/**
+ * A detector is able to find a particular problem. It might also be thought of as enforcing
+ * a rule, but "rule" is a bit overloaded in ADT terminology since ViewRules are used in
+ * the Rules API to allow views to specify designtime behavior in the graphical layout editor.
+ * <p>
+ * Each detector provides information about the issues it can find, such as an explanation
+ * of how to fix the issue, the priority, the category, etc. It also has an id which is
+ * used to persistently identify a particular type of error.
+ * <p/>
+ * NOTE: Detectors might be constructed just once and shared between lint runs, so
+ * any per-detector state should be initialized and reset via the before/after
+ * methods.
+ * <p/>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+public abstract class Detector /*implements Comparable<Detector>*/ {
+    /**
+     * Returns a list of issues detected by this detector.
+     *
+     * @return a list of issues detected by this detector, never null.
+     */
+    public abstract Issue[] getIssues();
+
+//    /**
+//     * Returns the id of this detector. These should not change over time since
+//     * they are used to persist the names of disabled detectors etc. It is
+//     * typically a single camel-cased word.
+//     *
+//     * @return the associated fixed id
+//     */
+//    public abstract String getId();
+
+    /**
+     * Runs the detector
+     * @param context the context describing the work to be done
+     */
+    public abstract void run(Context context);
+
+
+    public abstract boolean appliesTo(Context context, File file);
+
+    /** Analysis is about to begin, perform any setup steps. */
+    public void beforeCheckProject(Context context) {
+    }
+
+    /**
+     * Analysis has just been finished for the whole project, perform any
+     * cleanup or report issues found
+     */
+    public void afterCheckProject(Context context) {
+    }
+
+    /** Analysis is about to be performed on a specific file, perform any setup steps. */
+    public void beforeCheckFile(Context context) {
+    }
+
+    /**
+     * Analysis has just been finished for a specific file, perform any cleanup
+     * or report issues found
+     */
+    public void afterCheckFile(Context context) {
+    }
+
+    /**
+     * Returns the expected speed of this detector
+     *
+     * @return the expected speed of this detector
+     */
+    public abstract Speed getSpeed();
+
+    /**
+     * Returns the scope of this detector
+     *
+     * @return the scope of this detector
+     */
+    public abstract Scope getScope();
+
+    protected static final String CATEGORY_CORRECTNESS = "Correctness";
+    protected static final String CATEGORY_PERFORMANCE = "Performance";
+    protected static final String CATEGORY_USABILITY = "Usability";
+    protected static final String CATEGORY_I18N = "Internationalization";
+    protected static final String CATEGORY_A11Y = "Accessibility";
+    protected static final String CATEGORY_LAYOUT = "Layout";
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Issue.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Issue.java
new file mode 100644
index 0000000..5ce9ecc
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Issue.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2011 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.detector.api;
+
+
+
+/**
+ * An issue is a potential bug in an Android application. An issue is discovered
+ * by a {@link Detector}, and has an associated {@link Severity}.
+ * <p>
+ * Issues and detectors are separate classes because a detector can discover
+ * multiple different issues as it's analyzing code, and we want to be able to
+ * different severities for different issues, the ability to suppress one but
+ * not other issues from the same detector, and so on.
+ * <p/>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+public final class Issue implements Comparable<Issue> {
+    private final String mId;
+    private final String mDescription;
+    private final String mExplanation;
+    private final String mCategory;
+    private final int mPriority;
+    private final Severity mSeverity;
+    private final String mMoreInfoUrl;
+
+    // Use factory methods
+    private Issue(String id, String description, String explanation, String category, int priority,
+            Severity severity, String moreInfoUrl) {
+        super();
+        mId = id;
+        mDescription = description;
+        mExplanation = explanation;
+        mCategory = category;
+        mPriority = priority;
+        mSeverity = severity;
+        mMoreInfoUrl = moreInfoUrl;
+    }
+
+    /**
+     * Creates a new issue
+     *
+     * @param id the fixed id of the issue
+     * @param description the quick summary of the issue (one line)
+     * @param explanation a full explanation of the issue, with suggestions for
+     *            how to fix it
+     * @param category the associated category, if any
+     * @param priority the priority, a number from 1 to 10 with 10 being most
+     *            important/severe
+     * @param severity the default severity of the issue
+     * @param moreInfo an (optional) URL string to a resource which provides more
+     *            information
+     * @return a new {@link Issue}
+     */
+    public static Issue create(String id, String description, String explanation, String category,
+            int priority, Severity severity, String moreInfo) {
+        return new Issue(id, description, explanation, category, priority, severity, moreInfo);
+    }
+
+    /**
+     * Returns the unique id of this issue. These should not change over time
+     * since they are used to persist the names of issues suppressed by the user
+     * etc. It is typically a single camel-cased word.
+     *
+     * @return the associated fixed id, never null and always unique
+     */
+    public String getId() {
+        return mId;
+    }
+
+    /**
+     * Briefly (one line) describes the kinds of checks performed by this rule
+     *
+     * @return a quick summary of the issue, never null
+     */
+    public String getDescription() {
+        return mDescription;
+    }
+
+    /**
+     * Describes the error found by this rule, e.g.
+     * "Buttons must define contentDescriptions". Preferably the explanation
+     * should also contain a description of how the problem should be solved.
+     * Additional info can be provided via {@link #getMoreInfo()}.
+     *
+     * @return an explanation of the issue, never null.
+     */
+    public String getExplanation() {
+        return mExplanation;
+    }
+
+    /**
+     * The category, or null if no category has been assigned
+     *
+     * @return the category, or null if no category has been assigned
+     */
+    public String getCategory() {
+        return mCategory;
+    }
+
+    /**
+     * Returns a priority, in the range 1-10, with 10 being the most severe and
+     * 1 the least
+     *
+     * @return a priority from 1 to 10
+     */
+    public int getPriority() {
+        return mPriority;
+    }
+
+    /**
+     * Returns the default severity of the issues found by this detector (some
+     * tools may allow the user to specify custom severities for detectors).
+     *
+     * @return the severity of the issues found by this detector
+     */
+    public Severity getDefaultSeverity() {
+        return mSeverity;
+    }
+
+    /**
+     * Returns a link (a URL string) to more information, or null
+     *
+     * @return a link to more information, or null
+     */
+    public String getMoreInfo() {
+        return mMoreInfoUrl;
+    }
+
+    /**
+     * Sorts the detectors alphabetically by id. This is intended to make it
+     * convenient to store settings for detectors in a fixed order. It is not
+     * intended as the order to be shown to the user; for that, a tool embedding
+     * lint might consider the priorities, categories, severities etc of the
+     * various detectors.
+     *
+     * @param other the {@link Issue} to compare this issue to
+     */
+    public int compareTo(Issue other) {
+        return getId().compareTo(other.getId());
+    }
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LayoutDetector.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LayoutDetector.java
new file mode 100644
index 0000000..967d3a1
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LayoutDetector.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2011 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.detector.api;
+
+import com.android.resources.ResourceFolderType;
+
+import org.w3c.dom.Element;
+
+/**
+ * Abstract class specifically intended for layout detectors which provides some
+ * common utility methods shared by layout detectors.
+ * <p/>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+public abstract class LayoutDetector extends ResourceXmlDetector {
+    // Layouts
+    protected static final String FRAME_LAYOUT = "FrameLayout";           //$NON-NLS-1$
+    protected static final String LINEAR_LAYOUT = "LinearLayout";         //$NON-NLS-1$
+    protected static final String SCROLL_VIEW = "ScrollView";             //$NON-NLS-1$
+    protected static final String GALLERY = "Gallery";                    //$NON-NLS-1$
+    protected static final String GRID_VIEW = "GridView";                 //$NON-NLS-1$
+    protected static final String LIST_VIEW = "ListView";                 //$NON-NLS-1$
+    protected static final String TEXT_VIEW = "TextView";                 //$NON-NLS-1$
+    protected static final String IMAGE_VIEW = "ImageView";               //$NON-NLS-1$
+    protected static final String INCLUDE = "include";                    //$NON-NLS-1$
+    protected static final String MERGE = "merge";                        //$NON-NLS-1$
+    protected static final String HORIZONTAL_SCROLL_VIEW = "HorizontalScrollView"; //$NON-NLS-1$
+
+    // Attributes
+    protected static final String ATTR_ID = "id";                         //$NON-NLS-1$
+    protected static final String ATTR_LAYOUT_GRAVITY = "layout_gravity"; //$NON-NLS-1$
+    protected static final String ATTR_LAYOUT_WIDTH = "layout_width";     //$NON-NLS-1$
+    protected static final String ATTR_LAYOUT_HEIGHT = "layout_height";   //$NON-NLS-1$
+    protected static final String ATTR_LAYOUT_WEIGHT = "layout_weight";   //$NON-NLS-1$
+    protected static final String ATTR_PADDING = "padding";               //$NON-NLS-1$
+    protected static final String ATTR_PADDING_BOTTOM = "paddingBottom";  //$NON-NLS-1$
+    protected static final String ATTR_PADDING_TOP = "paddingTop";        //$NON-NLS-1$
+    protected static final String ATTR_PADDING_RIGHT = "paddingRight";    //$NON-NLS-1$
+    protected static final String ATTR_PADDING_LEFT = "paddingLeft";      //$NON-NLS-1$
+    protected static final String ATTR_FOREGROUND = "foreground";         //$NON-NLS-1$
+    protected static final String ATTR_BACKGROUND = "background";         //$NON-NLS-1$
+    protected static final String ATTR_ORIENTATION = "orientation";       //$NON-NLS-1$
+    protected static final String ATTR_LAYOUT = "layout";                 //$NON-NLS-1$
+
+    // Attribute values
+    protected static final String VALUE_FILL_PARENT = "fill_parent";       //$NON-NLS-1$
+    protected static final String VALUE_MATCH_PARENT = "match_parent";     //$NON-NLS-1$
+    protected static final String VALUE_VERTICAL = "vertical";             //$NON-NLS-1$
+    protected static final String VALUE_LAYOUT_PREFIX = "@layout/";        //$NON-NLS-1$
+
+    @Override
+    public boolean appliesTo(ResourceFolderType folderType) {
+        return folderType == ResourceFolderType.LAYOUT;
+    }
+
+    private static boolean isFillParent(Element element, String dimension) {
+        String width = element.getAttributeNS(ANDROID_URI, dimension);
+        return width.equals(VALUE_MATCH_PARENT) || width.equals(VALUE_FILL_PARENT);
+    }
+
+    protected static boolean isWidthFillParent(Element element) {
+        return isFillParent(element, ATTR_LAYOUT_WIDTH);
+    }
+
+    protected static boolean isHeightFillParent(Element element) {
+        return isFillParent(element, ATTR_LAYOUT_HEIGHT);
+    }
+
+    protected boolean hasPadding(Element root) {
+        return root.hasAttributeNS(ANDROID_URI, ATTR_PADDING)
+                || root.hasAttributeNS(ANDROID_URI, ATTR_PADDING_LEFT)
+                || root.hasAttributeNS(ANDROID_URI, ATTR_PADDING_RIGHT)
+                || root.hasAttributeNS(ANDROID_URI, ATTR_PADDING_TOP)
+                || root.hasAttributeNS(ANDROID_URI, ATTR_PADDING_BOTTOM);
+    }
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Location.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Location.java
new file mode 100644
index 0000000..feac79e
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Location.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2011 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.detector.api;
+
+import java.io.File;
+
+/**
+ * Location information for a warning
+ * <p/>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+public class Location {
+    private final File mFile;
+    private final Position mStart;
+    private final Position mEnd;
+    private Location mSecondary;
+
+    /**
+     * Constructs a new location range for the given file, from start to end. If
+     * the length of the range is not known, end may be null.
+     *
+     * @param file the associated file (but see the documentation for
+     *            {@link #getFile()} for more information on what the file
+     *            represents)
+     * @param start the starting position, never null
+     * @param end the ending position, or null
+     */
+    public Location(File file, Position start, Position end) {
+        super();
+        this.mFile = file;
+        this.mStart = start;
+        this.mEnd = end;
+    }
+
+    /**
+     * Returns the file containing the warning. Note that the file *itself* may
+     * not yet contain the error. When editing a file in the IDE for example,
+     * the tool could generate warnings in the background even before the
+     * document is saved. However, the file is used as a identifying token for
+     * the document being edited, and the IDE integration can map this back to
+     * error locations in the editor source code.
+     *
+     * @return the file handle for the location
+     */
+    public File getFile() {
+        return mFile;
+    }
+
+    /**
+     * The start position of the range
+     *
+     * @return the start position of the range, never null
+     */
+    public Position getStart() {
+        return mStart;
+    }
+
+    /**
+     * The end position of the range
+     *
+     * @return the start position of the range, may be null for an empty range
+     */
+    public Position getEnd() {
+        return mEnd;
+    }
+
+    /**
+     * Returns a secondary location associated with this location (if
+     * applicable), or null.
+     *
+     * @return a secondary location or null
+     */
+    public Location getSecondary() {
+        return mSecondary;
+    }
+
+    /**
+     * Sets a secondary location for this location.
+     *
+     * @param secondary a secondary location associated with this location
+     */
+    public void setSecondary(Location secondary) {
+        this.mSecondary = secondary;
+    }
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Position.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Position.java
new file mode 100644
index 0000000..e5c1a9a
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Position.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2011 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.detector.api;
+
+/**
+ * Information about a position in a file/document.
+ * <p/>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+public abstract class Position {
+    /**
+     * Returns the line number (0-based where the first line is line 0)
+     *
+     * @return the 0-based line number
+     */
+    public abstract int getLine();
+
+    /**
+     * The character offset
+     *
+     * @return the 0-based character offset
+     */
+    public abstract int getOffset();
+
+    /**
+     * Returns the column number (where the first character on the line is 0),
+     * or -1 if unknown
+     *
+     * @return the 0-based column number
+     */
+    public abstract int getColumn();
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ResourceXmlDetector.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ResourceXmlDetector.java
new file mode 100644
index 0000000..818d53f
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ResourceXmlDetector.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2011 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.detector.api;
+
+import com.android.resources.ResourceFolderType;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Specialized detector intended for XML resources. Detectors that apply to XML
+ * resources should extend this detector instead since it provides special
+ * iteration hooks that are more efficient.
+ * <p/>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+public abstract class ResourceXmlDetector extends Detector {
+    private static final String XML_SUFFIX = ".xml"; //$NON-NLS-1$
+
+    /**
+     * Special marker collection returned by {@link #getApplicableElements()} or
+     * {@link #getApplicableAttributes()} to indicate that the check should be
+     * invoked on all elements or all attributes
+     */
+    public static final List<String> ALL = new ArrayList<String>(0);
+
+    protected final static String ANDROID_URI =
+            "http://schemas.android.com/apk/res/android";                   //$NON-NLS-1$
+
+    @Override
+    public boolean appliesTo(Context context, File file) {
+        return isXmlFile(file);
+    }
+
+    /**
+     * Returns whether this detector applies to the given folder type. This
+     * allows the detectors to be pruned from iteration, so for example when we
+     * are analyzing a string value file we don't need to look up detectors
+     * related to layout.
+     *
+     * @param folderType the folder type to be visited
+     * @return true if this detector can apply to resources in folders of the
+     *         given type
+     */
+    public boolean appliesTo(ResourceFolderType folderType) {
+        return true;
+    }
+
+    @Override
+    public void run(Context context) {
+        // The infrastructure should never call this method on an xml detector since
+        // it will run the various visitors instead
+        assert false;
+    }
+
+    /**
+     * Visit the given document. The detector is responsible for its own iteration
+     * through the document.
+     * @param context information about the document being analyzed
+     * @param document the document to examine
+     */
+    public void visitDocument(Context context, Document document) {
+        // Only called if getApplicableElements() *and* getApplicableAttributes() returned null
+        throw new IllegalArgumentException(this.getClass() + " must override visitDocument");
+    }
+
+    /**
+     * Visit the given element.
+     * @param context information about the document being analyzed
+     * @param element the element to examine
+     */
+    public void visitElement(Context context, Element element) {
+        // Only called if getApplicableElements() returned non-null
+        throw new IllegalArgumentException(this.getClass() + " must override visitElement");
+    }
+
+    /**
+     * Visit the given element after its children have been analyzed.
+     * @param context information about the document being analyzed
+     * @param element the element to examine
+     */
+    public void visitElementAfter(Context context, Element element) {
+        // Optional
+    }
+
+    /**
+     * Visit the given attribute.
+     * @param context information about the document being analyzed
+     * @param attribute the attribute node to examine
+     */
+    public void visitAttribute(Context context, Attr attribute) {
+        // Only called if getApplicableAttributes() returned non-null
+        throw new IllegalArgumentException(this.getClass() + " must override visitAttribute");
+    }
+
+    /**
+     * Returns the list of elements that this detector wants to analyze. If non
+     * null, this detector will be called (specifically, the
+     * {@link #visitElement} method) for each matching element in the document.
+     * <p>
+     * If this method returns null, and {@link #getApplicableAttributes()} also returns
+     * null, then the {@link #visitDocument} method will be called instead.
+     *
+     * @return a collection of elements, or null, or the special
+     *         {@link ResourceXmlDetector#ALL} marker to indicate that every single
+     *         element should be analyzed.
+     */
+    public Collection<String> getApplicableElements() {
+        return null;
+    }
+
+    /**
+     * Returns the list of attributes that this detector wants to analyze. If non
+     * null, this detector will be called (specifically, the
+     * {@link #visitAttribute} method) for each matching attribute in the document.
+     * <p>
+     * If this method returns null, and {@link #getApplicableElements()} also returns
+     * null, then the {@link #visitDocument} method will be called instead.
+     *
+     * @return a collection of attributes, or null, or the special
+     *         {@link ResourceXmlDetector#ALL} marker to indicate that every single
+     *         attribute should be analyzed.
+     */
+    public Collection<String> getApplicableAttributes() {
+        return null;
+    }
+
+    /**
+     * Returns true if the given file represents an XML file
+     *
+     * @param file the file to be checked
+     * @return true if the given file is an xml file
+     */
+    public static boolean isXmlFile(File file) {
+        String string = file.getName();
+        return string.regionMatches(true, string.length() - XML_SUFFIX.length(),
+                XML_SUFFIX, 0, XML_SUFFIX.length());
+    }
+
+    /**
+     * Returns the children elements of the given node
+     *
+     * @param node the parent node
+     * @return a list of element children, never null
+     */
+    public static List<Element> getChildren(Node node) {
+        NodeList childNodes = node.getChildNodes();
+        List<Element> children = new ArrayList<Element>(childNodes.getLength());
+        for (int i = 0, n = childNodes.getLength(); i < n; i++) {
+            Node child = childNodes.item(i);
+            if (child.getNodeType() == Node.ELEMENT_NODE) {
+                children.add((Element) child);
+            }
+        }
+
+        return children;
+    }
+
+    /**
+     * Returns the <b>number</b> of children of the given node
+     *
+     * @param node the parent node
+     * @return the count of element children
+     */
+    public static int getChildCount(Node node) {
+        NodeList childNodes = node.getChildNodes();
+        int childCount = 0;
+        for (int i = 0, n = childNodes.getLength(); i < n; i++) {
+            Node child = childNodes.item(i);
+            if (child.getNodeType() == Node.ELEMENT_NODE) {
+                childCount++;
+            }
+        }
+
+        return childCount;
+    }
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java
new file mode 100644
index 0000000..e9382fb
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2011 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.detector.api;
+
+/**
+ * The scope of a detector is the set of files a detector must consider when
+ * performing its analysis. This can be used to determine when issues are
+ * potentially obsolete, whether a detector should re-run on a file save, etc.
+ */
+public enum Scope {
+    /** The analysis only considers a single file at a time */
+    SINGLE_FILE,
+
+    /** The analysis considers more than one file but only resource files */
+    RESOURCES,
+
+    /** The analysis considers more than one file but only the Java code */
+    JAVA_CODE,
+
+    /**
+     * The analysis considers both the Java code in the project and any
+     * libraries
+     */
+    JAVA,
+
+    /** The analysis considers the full project */
+    PROJECT;
+
+    /**
+     * Returns true if this scope is within the given scope. For example a file
+     * scope is within a project scope, but a project scope is not within a file
+     * scope.
+     *
+     * @param scope the scope to compare with
+     * @return true if this scope is within the other scope
+     */
+    public boolean within(Scope scope) {
+        if (this == scope) {
+            return true;
+        }
+        if (scope == PROJECT) {
+            // Everything is within a project
+            return true;
+        }
+
+        if (this == SINGLE_FILE) {
+            // A single file is within everything else
+            return true;
+        }
+
+        if (this == JAVA_CODE) {
+            return scope == JAVA; // or scope == PROJECT but that's handled above
+        }
+
+        return false;
+    }
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Severity.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Severity.java
new file mode 100644
index 0000000..3161cd2
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Severity.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2011 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.detector.api;
+
+/**
+ * Severity of an issue found by lint
+ * <p/>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+public enum Severity {
+    /**
+     * Errors: Use sparingly because a warning marked as an error will be
+     * considered fatal and will abort Export APK etc in ADT
+     */
+    ERROR("Error"),
+
+    /**
+     * Warning: Probably a problem.
+     */
+    WARNING("Warning"),
+
+    /**
+     * Information only: Might not be a problem, but the check has found
+     * something interesting to say about the code.
+     */
+    INFORMATIONAL("Information"),
+
+    /**
+     * Ignore: The user doesn't want to see this issue
+     */
+    IGNORE("Ignore");
+
+    private String mDisplay;
+
+    private Severity(String display) {
+        mDisplay = display;
+    }
+
+    /**
+     * Returns a description of this severity suitable for display to the user
+     *
+     * @return a description of the severity
+     */
+    public String getDescription() {
+        return mDisplay;
+    }
+}
\ No newline at end of file
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Speed.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Speed.java
new file mode 100644
index 0000000..2f7f17a
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Speed.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2011 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.detector.api;
+
+/** Enum which describes the different computation speeds of various detectors */
+public enum Speed {
+    /** The detector can run very quickly */
+    FAST("Fast"),
+
+    /** The detector runs reasonably fast */
+    NORMAL("Normal"),
+
+    /** The detector might take a long time to run */
+    SLOW("Slow");
+
+    private String mDisplayName;
+
+    Speed(String displayName) {
+        mDisplayName = displayName;
+    }
+
+    /**
+     * Returns the user-visible description of the speed of the given
+     * detector
+     *
+     * @return the description of the speed to display to the user
+     */
+    public String getDisplayName() {
+        return mDisplayName;
+    }
+}
\ No newline at end of file
diff --git a/lint/libs/lint_checks/.classpath b/lint/libs/lint_checks/.classpath
new file mode 100644
index 0000000..1172683
--- /dev/null
+++ b/lint/libs/lint_checks/.classpath
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/lint-api"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/common"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/lint/libs/lint_checks/.project b/lint/libs/lint_checks/.project
new file mode 100644
index 0000000..0dc9856
--- /dev/null
+++ b/lint/libs/lint_checks/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>lint-checks</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/lint/libs/lint_checks/.settings/org.eclipse.jdt.core.prefs b/lint/libs/lint_checks/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..e755df2
--- /dev/null
+++ b/lint/libs/lint_checks/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,71 @@
+#Thu Jun 09 12:26:44 PDT 2011
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nullReference=error
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
diff --git a/lint/libs/lint_checks/.settings/org.moreunit.prefs b/lint/libs/lint_checks/.settings/org.moreunit.prefs
new file mode 100644
index 0000000..1777680
--- /dev/null
+++ b/lint/libs/lint_checks/.settings/org.moreunit.prefs
@@ -0,0 +1,5 @@
+#Wed Oct 12 13:47:50 PDT 2011
+eclipse.preferences.version=1
+org.moreunit.prefixes=
+org.moreunit.unitsourcefolder=lint-checks\:src\:lint_check-tests\:src
+org.moreunit.useprojectsettings=true
diff --git a/lint/libs/lint_checks/Android.mk b/lint/libs/lint_checks/Android.mk
new file mode 100644
index 0000000..7970c1c
--- /dev/null
+++ b/lint/libs/lint_checks/Android.mk
@@ -0,0 +1,23 @@
+# Copyright 2011 The Android Open Source Project
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+# Only compile source java files in this lib.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_JAVA_RESOURCE_DIRS := src
+
+LOCAL_JAR_MANIFEST := etc/manifest.txt
+
+# If the dependency list is changed, etc/manifest.txt
+LOCAL_JAVA_LIBRARIES := \
+        common \
+	lint_api
+
+LOCAL_MODULE := lint_checks
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+# Build all sub-directories
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/lint/libs/lint_checks/NOTICE b/lint/libs/lint_checks/NOTICE
new file mode 100644
index 0000000..becc120
--- /dev/null
+++ b/lint/libs/lint_checks/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2011, 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.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/lint/libs/lint_checks/etc/manifest.txt b/lint/libs/lint_checks/etc/manifest.txt
new file mode 100644
index 0000000..2fd2a3a
--- /dev/null
+++ b/lint/libs/lint_checks/etc/manifest.txt
@@ -0,0 +1 @@
+Class-Path: lint_api.jar common.jar
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/AccessibilityDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/AccessibilityDetector.java
new file mode 100644
index 0000000..0b0a527
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/AccessibilityDetector.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2011 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.Context;
+import com.android.tools.lint.detector.api.Issue;
+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 org.w3c.dom.Element;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * Check which looks for accessibility problems like missing content descriptions
+ * <p>
+ * TODO: Resolve styles and don't warn where styles are defining the content description
+ * (though this seems unusual; content descriptions are not typically generic enough to
+ * put in styles)
+ */
+public class AccessibilityDetector extends LayoutDetector {
+    /** The attribute for describing visual content */
+    public static final String ATTR_CONTENT_DESCRIPTION = "contentDescription"; //$NON-NLS-1$
+
+    /** The main issue discovered by this detector */
+    public static final Issue ISSUE = Issue.create(
+            "ContentDescription", //$NON-NLS-1$
+            "Ensures that image widgets provide a contentDescription",
+            "Non-textual widgets like ImageViews and ImageButtons should use the " +
+            "contentDescription attribute to specify a textual description of " +
+            "the widget such that screen readers and other accessibility tools " +
+            "can adequately describe the user interface.",
+            CATEGORY_A11Y, 5, Severity.WARNING, null);
+
+    /** Constructs a new accessibility check */
+    public AccessibilityDetector() {
+    }
+
+    @Override
+    public Issue[] getIssues() {
+        return new Issue[] { ISSUE };
+    }
+
+    @Override
+    public Speed getSpeed() {
+        return Speed.FAST;
+    }
+
+    @Override
+    public Scope getScope() {
+        return Scope.SINGLE_FILE;
+    }
+
+    @Override
+    public Collection<String> getApplicableElements() {
+        return Arrays.asList(new String[] {
+                "ImageButton", //$NON-NLS-1$
+                "ImageView"    //$NON-NLS-1$
+        });
+    }
+
+    @Override
+    public void visitElement(Context context, Element element) {
+        if (!element.hasAttributeNS(ANDROID_URI, ATTR_CONTENT_DESCRIPTION)) {
+            context.toolContext.report(ISSUE, context.getLocation(context),
+                    "[Accessibility] Missing contentDescription attribute on image");
+        } else {
+            String attribute = element.getAttributeNS(ANDROID_URI, ATTR_CONTENT_DESCRIPTION);
+            if (attribute.length() == 0 || attribute.equals("TODO")) { //$NON-NLS-1$
+                context.toolContext.report(ISSUE, context.getLocation(context),
+                        "[Accessibility] Empty contentDescription attribute on image");
+            }
+        }
+    }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinDetectorRegistry.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinDetectorRegistry.java
new file mode 100644
index 0000000..704d9f8
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinDetectorRegistry.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2011 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;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** Registry which provides a list of checks to be performed on an Android project */
+public class BuiltinDetectorRegistry extends com.android.tools.lint.api.DetectorRegistry {
+    private static final List<Detector> sDetectors;
+    static {
+        // TODO: Maybe just store class names here instead such that
+        // each invocation can have its own context with fields without
+        // worrying about having to clear state in beforeCheckProject or beforeCheckFile?
+        List<Detector> detectors = new ArrayList<Detector>();
+        detectors.add(new AccessibilityDetector());
+        detectors.add(new DuplicateIdDetector());
+        detectors.add(new StateListDetector());
+        detectors.add(new InefficientWeightDetector());
+        detectors.add(new ScrollViewChildDetector());
+        detectors.add(new MergeRootFrameLayoutDetector());
+        detectors.add(new NestedScrollingWidgetDetector());
+        detectors.add(new ChildCountDetector());
+        detectors.add(new UseCompoundDrawableDetector());
+        detectors.add(new UselessViewDetector());
+        detectors.add(new TooManyViewsDetector());
+
+        // TODO: Populate dynamically somehow?
+
+        sDetectors = Collections.unmodifiableList(detectors);
+    }
+
+    /**
+     * Constructs a new {@link BuiltinDetectorRegistry}
+     */
+    public BuiltinDetectorRegistry() {
+    }
+
+    @Override
+    public List<? extends Detector> getDetectors() {
+        return sDetectors;
+    }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ChildCountDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ChildCountDetector.java
new file mode 100644
index 0000000..34aed38
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ChildCountDetector.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2011 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.Context;
+import com.android.tools.lint.detector.api.Issue;
+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 org.w3c.dom.Element;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * Check which makes sure that views have the expected number of declared
+ * children (e.g. at most one in ScrollViews and none in AdapterViews)
+ */
+public class ChildCountDetector extends LayoutDetector {
+
+    /** The main issue discovered by this detector */
+    public static final Issue SCROLLVIEW_ISSUE = Issue.create(
+            "ScrollViewCount", //$NON-NLS-1$
+            "Checks that ScrollViews have exactly one child widget",
+            "ScrollViews can only have one child widget. If you want more children, wrap them " +
+            "in a container layout.",
+            CATEGORY_LAYOUT, 8, Severity.WARNING, null);
+
+    /** The main issue discovered by this detector */
+    public static final Issue ADAPTERVIEW_ISSUE = Issue.create(
+            "AdapterViewChildren", //$NON-NLS-1$
+            "Checks that AdapterViews do not define their children in XML",
+            "AdapterViews such as ListViews must be configured with data from Java code, " +
+            "such as a ListAdapter.",
+            CATEGORY_LAYOUT, 8, Severity.WARNING,
+            "http://developer.android.com/reference/android/widget/AdapterView.html"); //$NON-NLS-1$
+
+    /** Constructs a new {@link ChildCountDetector} */
+    public ChildCountDetector() {
+    }
+
+    @Override
+    public Issue[] getIssues() {
+        return new Issue[] { SCROLLVIEW_ISSUE, ADAPTERVIEW_ISSUE };
+    }
+
+    @Override
+    public Speed getSpeed() {
+        return Speed.FAST;
+    }
+
+    @Override
+    public Scope getScope() {
+        return Scope.SINGLE_FILE;
+    }
+
+    @Override
+    public Collection<String> getApplicableElements() {
+        return Arrays.asList(new String[] {
+                SCROLL_VIEW,
+                HORIZONTAL_SCROLL_VIEW,
+                LIST_VIEW,
+                GRID_VIEW
+                // TODO: Shouldn't Spinner be in this list too? (Was not there in layoutopt)
+        });
+    }
+
+    @Override
+    public void visitElement(Context context, Element element) {
+        int childCount = getChildCount(element);
+        String tagName = element.getTagName();
+        if (tagName.equals(SCROLL_VIEW) || tagName.equals(HORIZONTAL_SCROLL_VIEW)) {
+            if (childCount > 1) {
+                context.toolContext.report(SCROLLVIEW_ISSUE, context.getLocation(element),
+                        "A scroll view can have only one child");
+            }
+        } else {
+            // Adapter view
+            if (childCount > 0) {
+                context.toolContext.report(ADAPTERVIEW_ISSUE, context.getLocation(element),
+                        "A list/grid should have no children declared in XML");
+            }
+        }
+    }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/DuplicateIdDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/DuplicateIdDetector.java
new file mode 100644
index 0000000..4c6e177
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/DuplicateIdDetector.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2011 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.resources.ResourceFolderType;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.Location;
+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 org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * Checks for duplicate ids within a layout and within an included layout
+ */
+public class DuplicateIdDetector extends LayoutDetector {
+    private Set<String> mIds;
+    private Map<File, Set<String>> mFileToIds;
+    private Map<File, List<String>> mIncludes;
+
+    /** The main issue discovered by this detector */
+    public static final Issue ISSUE = Issue.create(
+            "DuplicateIds", //$NON-NLS-1$
+            "Checks for duplicate ids within a single layout or within an include hierarchy",
+            "It's okay for two independent layouts to use the same ids. However, within " +
+            "a single layout (including the case where two separate layouts are fused " +
+            "together with an include tag) the ids should be unique such that the" +
+            "Activity#findViewById() method can work predictably.",
+            CATEGORY_LAYOUT, 7, Severity.WARNING, null);
+
+    /** Constructs a duplicate id check */
+    public DuplicateIdDetector() {
+    };
+
+
+    @Override
+    public Issue[] getIssues() {
+        return new Issue[] { ISSUE };
+    }
+
+    @Override
+    public boolean appliesTo(ResourceFolderType folderType) {
+        return folderType == ResourceFolderType.LAYOUT || folderType == ResourceFolderType.MENU;
+    }
+
+    @Override
+    public Speed getSpeed() {
+        return Speed.FAST;
+    }
+
+    @Override
+    public Scope getScope() {
+        // TODO: Split this detector in half, since single-layout duplicates can be checked
+        // quickly.
+        return Scope.RESOURCES;
+    }
+
+    @Override
+    public Collection<String> getApplicableAttributes() {
+        return Collections.singletonList(ATTR_ID);
+    }
+
+    @Override
+    public Collection<String> getApplicableElements() {
+        return Collections.singletonList(INCLUDE);
+    }
+
+    @Override
+    public void beforeCheckFile(Context context) {
+        mIds = new HashSet<String>();
+    }
+
+    @Override
+    public void afterCheckFile(Context context) {
+        // Store this layout's set of ids for full project analysis in afterCheckProject
+        mFileToIds.put(context.file, mIds);
+
+        mIds = null;
+    }
+
+    @Override
+    public void beforeCheckProject(Context context) {
+        mFileToIds = new HashMap<File, Set<String>>();
+        mIncludes = new HashMap<File, List<String>>();
+    }
+
+    @Override
+    public void afterCheckProject(Context context) {
+        // Look for duplicates
+        if (mIncludes.size() > 0) {
+            // Traverse all the include chains and ensure that there are no duplicates
+            // across.
+            // First perform a topological sort such such
+            checkForIncludeDuplicates(context);
+        }
+
+        mFileToIds = null;
+        mIncludes = null;
+    }
+
+    @Override
+    public void visitElement(Context context, Element element) {
+        // Record include graph such that we can look for inter-layout duplicates after the
+        // project has been fully checked
+
+        String layout = element.getAttribute(ATTR_LAYOUT); // NOTE: Not in android: namespace
+        if (layout.startsWith(VALUE_LAYOUT_PREFIX)) { // Ignore @android:layout/ layouts
+            layout = layout.substring(VALUE_LAYOUT_PREFIX.length());
+
+            List<String> to = mIncludes.get(context.file);
+            if (to == null) {
+                to = new ArrayList<String>();
+                mIncludes.put(context.file, to);
+            }
+            to.add(layout);
+        }
+    }
+
+    private void checkForIncludeDuplicates(Context context) {
+        // Consider this scenario:
+        //     first/foo.xml: include @layout/second
+        //     first-land/foo.xml: define @+id/foo
+        //     second-land/bar.xml define @+id/bar
+        //     second-port/bar.xml define @+id/foo
+        // Here there's no problem, because even though @layout/first includes @layout/second,
+        // the only duplicate is "foo" which appears only in the combination first-land and
+        // second-port which won't be matched up together.
+        // In this analysis we won't go that far; we'll just look at the OVERALL set of
+        // includes. In other words, we'll consider the set of ids defined by "first" to
+        // be {"foo"}, and the set of ids defined by "second" to be {"foo","bar"}, and
+        // so there is a potential conflict.
+
+        // Map from layout resource name (instead of file) to referenced layouts.
+        // Note: Unlike mIncludes, this merges all the configurations for a single layout
+        Map<String, Set<String>> resourceToLayouts =
+                new HashMap<String, Set<String>>(mIncludes.size());
+        Map<String, Set<String>> resourceToIds =
+                new HashMap<String, Set<String>>(mIncludes.size());
+
+        for (Entry<File, List<String>> entry : mIncludes.entrySet()) {
+            File file = entry.getKey();
+            String from = getLayoutName(file);
+
+            // Merge include lists
+            List<String> layouts = entry.getValue();
+            Set<String> set = resourceToLayouts.get(from);
+            if (set == null) {
+                resourceToLayouts.put(from, new HashSet<String>(layouts));
+            } else {
+                set.addAll(layouts);
+            }
+        }
+
+        // Merge id maps
+        for (Entry<File, Set<String>> entry : mFileToIds.entrySet()) {
+            File file = entry.getKey();
+            String from = getLayoutName(file);
+            Set<String> ids = entry.getValue();
+            if (ids != null) {
+                Set<String> set = resourceToIds.get(from);
+                if (set == null) {
+                    // I might be able to just reuse the set instance here instead of duplicating
+                    resourceToIds.put(from, new HashSet<String>(ids));
+                } else {
+                    set.addAll(ids);
+                }
+            }
+        }
+
+        // Set of layouts that are included from somewhere else. We will use
+        Set<String> included = new HashSet<String>();
+        for (Set<String> s : resourceToLayouts.values()) {
+            included.addAll(s);
+        }
+
+        // Compute the set of layouts which include some other layouts, but which are not
+        // included themselves, meaning they are the "roots" to start searching through
+        // include chains from
+        Set<String> entryPoints = new HashSet<String>(resourceToLayouts.keySet());
+        entryPoints.removeAll(included);
+
+        // Perform DFS on the include graph and look for a cycle; if we find one, produce
+        // a chain of includes on the way back to show to the user
+        HashMap<String, Set<String>> mergedIds = new HashMap<String, Set<String>>();
+        Set<String> visiting = new HashSet<String>();
+        for (String from : entryPoints) {
+            visiting.clear();
+            getMergedIds(context, from, visiting, resourceToLayouts, resourceToIds, mergedIds);
+        }
+    }
+
+    private String getLayoutName(File file) {
+        String name = file.getName();
+        int dotIndex = name.indexOf('.');
+        if (dotIndex != -1) {
+            name = name.substring(0, dotIndex);
+        }
+        return name;
+    }
+
+    /**
+     * Computes the complete set of ids in a layout (including included layouts,
+     * transitively) and emits warnings when it detects that there is a
+     * duplication
+     */
+    private Set<String> getMergedIds(
+            Context context,
+            String from,
+            Set<String> visiting,
+            Map<String, Set<String>> resourceToLayouts,
+            Map<String, Set<String>> resourceToIds,
+            Map<String, Set<String>> mergedIds) {
+
+        Set<String> merged = mergedIds.get(from);
+        if (merged == null) {
+            visiting.add(from);
+
+            Set<String> currentIds = resourceToIds.get(from);
+            if (currentIds != null && currentIds.size() > 0) {
+                merged = new HashSet<String>(currentIds);
+            } else {
+                merged = new HashSet<String>();
+            }
+            Set<String> includes = resourceToLayouts.get(from);
+            if (includes != null && includes.size() > 0) {
+                for (String include : includes) {
+                    if (!visiting.contains(include)) {
+                        Set<String> otherIds = getMergedIds(context, include, visiting,
+                                resourceToLayouts, resourceToIds, mergedIds);
+                        // Look for overlap
+                        for (String id : otherIds) {
+                            if (merged.contains(id)) {
+                                // Find the (first) file among the various configuration variations
+                                // which defines the id
+                                File first = null;
+                                File second = null;
+                                for (Map.Entry<File, Set<String>> entry : mFileToIds.entrySet()) {
+                                    File file = entry.getKey();
+                                    String name = getLayoutName(file);
+                                    if (name.equals(from)) {
+                                        Set<String> fileIds = entry.getValue();
+                                        if (fileIds.contains(id)) {
+                                            first = file;
+                                        }
+                                    }
+                                    if (name.equals(include)) {
+                                        Set<String> fileIds = entry.getValue();
+                                        if (fileIds.contains(id)) {
+                                            second = file;
+                                        }
+                                    }
+                                }
+                                if (first == null) {
+                                    for (Map.Entry<File, List<String>> entry
+                                            : mIncludes.entrySet()) {
+                                        File file = entry.getKey();
+                                        String name = getLayoutName(file);
+                                        if (name.equals(from)) {
+                                            first = file;
+                                        }
+                                    }
+                                }
+
+                                String includer = second != null ? second.getName() : include;
+                                List<String> chain = new ArrayList<String>();
+                                chain.add(from);
+                                findOrigin(chain, from, id, new HashSet<String>(),
+                                        resourceToLayouts, resourceToIds);
+                                String msg = null;
+                                if (chain.size() > 2) { // < 2: it's a directly include & obvious
+                                    StringBuilder sb = new StringBuilder();
+                                    for (String layout : chain) {
+                                        if (sb.length() > 0) {
+                                            sb.append(" => ");
+                                        }
+                                        sb.append(layout);
+                                    }
+                                    msg = String.format(
+                                            "Duplicate id %1$s, already defined in layout %2$s which is included in this layout (%3$s)",
+                                            id, includer, sb.toString());
+                                } else {
+                                    msg = String.format(
+                                            "Duplicate id %1$s, already defined in layout %2$s which is included in this layout",
+                                            id, includer);
+                                }
+
+                                Location location = new Location(first, null, null);
+                                if (second != null) {
+                                    // Also record the secondary location
+                                    location.setSecondary(new Location(second, null, null));
+                                }
+                                context.toolContext.report(ISSUE, location, msg);
+                            } else {
+                                merged.add(id);
+                            }
+                        }
+                    }
+                }
+            }
+            mergedIds.put(from, merged);
+            visiting.remove(from);
+        }
+
+        return merged;
+    }
+
+    /**
+     * Compute the include chain which provided id into this layout. We could
+     * have tracked this while we were already performing a depth first search,
+     * but we're choosing to be faster before we know there's an error and take
+     * ore time to produce diagnostics if an actual error is found.
+     */
+    private boolean findOrigin(
+            List<String> chain,
+            String from,
+            String id,
+            Set<String> visiting,
+            Map<String, Set<String>> resourceToLayouts,
+            Map<String, Set<String>> resourceToIds) {
+        visiting.add(from);
+
+        Set<String> includes = resourceToLayouts.get(from);
+        if (includes != null && includes.size() > 0) {
+            for (String include : includes) {
+                if (visiting.contains(include)) {
+                    return false;
+                }
+
+                Set<String> ids = resourceToIds.get(include);
+                if (ids != null && ids.contains(id)) {
+                    chain.add(include);
+                    return true;
+                }
+
+                if (findOrigin(chain, include, id, visiting, resourceToLayouts, resourceToIds)) {
+                    chain.add(include);
+                    return true;
+                }
+            }
+        }
+
+        visiting.remove(from);
+
+        return false;
+    }
+
+    @Override
+    public void visitAttribute(Context context, Attr attribute) {
+        assert attribute.getLocalName().equals(ATTR_ID);
+        String id = attribute.getValue();
+        if (mIds.contains(id)) {
+            context.toolContext.report(ISSUE, context.getLocation(attribute),
+                    String.format("Duplicate id %1$s, already defined earlier in this layout",
+                            id));
+        } else if (id.startsWith("@+id/")) { //$NON-NLS-1$
+            mIds.add(id);
+        }
+    }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/InefficientWeightDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/InefficientWeightDetector.java
new file mode 100644
index 0000000..e29eaca
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/InefficientWeightDetector.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2011 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.Context;
+import com.android.tools.lint.detector.api.Issue;
+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 org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Checks whether a layout_weight is declared inefficiently.
+ */
+public class InefficientWeightDetector extends LayoutDetector {
+
+    /** The main issue discovered by this detector */
+    public static final Issue ISSUE = Issue.create(
+            "InefficientWeight", //$NON-NLS-1$
+            "Looks for inefficient weight declarations in LinearLayouts",
+            "When only a single widget in a LinearLayout defines a weight, it is more " +
+            "efficient to assign a width/height of 0dp to it since it will absorb all " +
+            "the remaining space anyway. With a declared width/height of 0dp it " +
+            "does not have to measure its own size first.",
+            CATEGORY_LAYOUT, 5, Severity.WARNING, null);
+
+    /** Constructs a new {@link InefficientWeightDetector} */
+    public InefficientWeightDetector() {
+    }
+
+    @Override
+    public Issue[] getIssues() {
+        return new Issue[] { ISSUE };
+    }
+
+    @Override
+    public Speed getSpeed() {
+        return Speed.FAST;
+    }
+
+    @Override
+    public Scope getScope() {
+        return Scope.SINGLE_FILE;
+    }
+
+    @Override
+    public Collection<String> getApplicableElements() {
+        return Collections.singletonList(LINEAR_LAYOUT);
+    }
+
+    @Override
+    public void visitElement(Context context, Element element) {
+        List<Element> children = getChildren(element);
+        // See if there is exactly one child with a weight
+        Element weightChild = null;
+        for (Element child : children) {
+            if (child.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WEIGHT)) {
+                if (weightChild != null) {
+                    // More than one child defining a weight!
+                    return;
+                } else {
+                    weightChild = child;
+                }
+            }
+        }
+
+        if (weightChild != null) {
+            String dimension;
+            if (VALUE_VERTICAL.equals(element.getAttributeNS(ANDROID_URI, ATTR_ORIENTATION))) {
+                dimension = ATTR_LAYOUT_HEIGHT;
+            } else {
+                dimension = ATTR_LAYOUT_WIDTH;
+            }
+            Attr sizeNode = weightChild.getAttributeNodeNS(ANDROID_URI, dimension);
+            String size = sizeNode != null ? sizeNode.getValue() : "(undefined)";
+            if (!size.startsWith("0")) { //$NON-NLS-1$
+                String msg = String.format(
+                        "Use a %1$s of 0dip instead of %2$s for better performance",
+                        dimension, size);
+                context.toolContext.report(ISSUE,
+                        context.getLocation(sizeNode != null ? sizeNode : weightChild),
+                        msg);
+
+            }
+        }
+    }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/MergeRootFrameLayoutDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/MergeRootFrameLayoutDetector.java
new file mode 100644
index 0000000..54d795e
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/MergeRootFrameLayoutDetector.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2011 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.Context;
+import com.android.tools.lint.detector.api.Issue;
+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 org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * Checks whether a root FrameLayout can be replaced with a {@code <merge>} tag.
+ */
+public class MergeRootFrameLayoutDetector extends LayoutDetector {
+
+    /** The main issue discovered by this detector */
+    public static final Issue ISSUE = Issue.create(
+            "MergeRootFrame", //$NON-NLS-1$
+            "Checks whether a root <FrameLayout> can be replaced with a <merge> tag",
+            "If a <FrameLayout> is the root of a layout and does not provide background " +
+            "or padding etc, it can be replaced with a <merge> tag which is slightly " +
+            "more efficient.",
+            CATEGORY_LAYOUT, 4, Severity.WARNING, null);
+
+    /** Constructs a new {@link MergeRootFrameLayoutDetector} */
+    public MergeRootFrameLayoutDetector() {
+    }
+
+    @Override
+    public Issue[] getIssues() {
+        return new Issue[] { ISSUE };
+    }
+
+    @Override
+    public Speed getSpeed() {
+        return Speed.FAST;
+    }
+
+    @Override
+    public Scope getScope() {
+        return Scope.SINGLE_FILE;
+    }
+
+    @Override
+    public void visitDocument(Context context, Document document) {
+        Element root = document.getDocumentElement();
+        if (root.getTagName().equals(FRAME_LAYOUT) &&
+            ((isWidthFillParent(root) && isHeightFillParent(root)) ||
+                    !root.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_GRAVITY))
+                && !root.hasAttributeNS(ANDROID_URI, ATTR_BACKGROUND)
+                && !root.hasAttributeNS(ANDROID_URI, ATTR_FOREGROUND)
+                && !hasPadding(root)) {
+            context.toolContext.report(ISSUE,
+                    context.getLocation(root),
+                    "This <FrameLayout> can be replaced with a <merge> tag");
+        }
+    }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/NestedScrollingWidgetDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/NestedScrollingWidgetDetector.java
new file mode 100644
index 0000000..d719fcc
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/NestedScrollingWidgetDetector.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2011 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.Context;
+import com.android.tools.lint.detector.api.Issue;
+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 org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * Checks whether a root FrameLayout can be replaced with a {@code <merge>} tag.
+ */
+public class NestedScrollingWidgetDetector extends LayoutDetector {
+    private int mVisitingHorizontalScroll;
+    private int mVisitingVerticalScroll;
+
+    /** The main issue discovered by this detector */
+    public static final Issue ISSUE = Issue.create(
+            "NestedScrolling", //$NON-NLS-1$
+            "Checks whether a scrolling widget has any nested scrolling widgets within",
+            // TODO: Better description!
+            "A scrolling widget such as a ScrollView should not contain any nested " +
+            "scrolling widgets since this has various usability issues",
+            CATEGORY_LAYOUT, 7, Severity.WARNING, null);
+
+    /** Constructs a new {@link NestedScrollingWidgetDetector} */
+    public NestedScrollingWidgetDetector() {
+    }
+
+    @Override
+    public Issue[] getIssues() {
+        return new Issue[] { ISSUE };
+    }
+
+    @Override
+    public void beforeCheckFile(Context context) {
+        mVisitingHorizontalScroll = 0;
+        mVisitingVerticalScroll = 0;
+    }
+
+    @Override
+    public Speed getSpeed() {
+        return Speed.FAST;
+    }
+
+    @Override
+    public Scope getScope() {
+        return Scope.SINGLE_FILE;
+    }
+
+    @Override
+    public Collection<String> getApplicableElements() {
+        return Arrays.asList(new String[] {
+                SCROLL_VIEW,
+                LIST_VIEW,
+                GRID_VIEW,
+                // Horizontal
+                GALLERY,
+                HORIZONTAL_SCROLL_VIEW
+        });
+    }
+
+    private Element findOuterScrollingWidget(Node node, boolean vertical) {
+        Collection<String> applicableElements = getApplicableElements();
+        while (node != null) {
+            if (node instanceof Element) {
+                Element element = (Element) node;
+                String tagName = element.getTagName();
+                if (applicableElements.contains(tagName)
+                        && vertical == isVerticalScroll(element)) {
+                    return element;
+                }
+            }
+
+            node = node.getParentNode();
+        }
+
+        return null;
+    }
+
+    @Override
+    public void visitElement(Context context, Element element) {
+        boolean vertical = isVerticalScroll(element);
+        if (vertical) {
+            mVisitingVerticalScroll++;
+        } else {
+            mVisitingHorizontalScroll++;
+        }
+
+        if (mVisitingHorizontalScroll > 1 || mVisitingVerticalScroll > 1) {
+            Element parent = findOuterScrollingWidget(element.getParentNode(), vertical);
+            if (parent != null) {
+                String format;
+                if (mVisitingVerticalScroll > 1) {
+                    format = "The vertically scrolling %1$s should not contain another " +
+                            "vertically scrolling widget (%2$s)";
+                } else {
+                    format = "The horizontally scrolling %1$s should not contain another " +
+                            "horizontally scrolling widget (%2$s)";
+                }
+                String msg = String.format(format, parent.getTagName(), element.getTagName());
+                context.toolContext.report(ISSUE, context.getLocation(element),
+                        msg);
+            }
+        }
+    }
+
+    @Override
+    public void visitElementAfter(Context context, Element element) {
+        if (isVerticalScroll(element)) {
+            mVisitingVerticalScroll--;
+            assert mVisitingVerticalScroll >= 0;
+        } else {
+            mVisitingHorizontalScroll--;
+            assert mVisitingHorizontalScroll >= 0;
+        }
+    }
+
+    private boolean isVerticalScroll(Element element) {
+        String view = element.getTagName();
+        if (view.equals(GALLERY) || view.equals(HORIZONTAL_SCROLL_VIEW)) {
+            return false;
+        } else {
+            // This method should only be called with one of the 5 widget types
+            // listed in getApplicableElements
+            assert view.equals(SCROLL_VIEW) || view.equals(LIST_VIEW) || view.equals(GRID_VIEW);
+            return true;
+        }
+    }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ScrollViewChildDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ScrollViewChildDetector.java
new file mode 100644
index 0000000..d2ec187
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ScrollViewChildDetector.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2011 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.Context;
+import com.android.tools.lint.detector.api.Issue;
+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 org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Check which looks at the children of ScrollViews and ensures that they fill/match
+ * the parent width instead of setting wrap_content.
+ */
+public class ScrollViewChildDetector extends LayoutDetector {
+    /** The main issue discovered by this detector */
+    public static final Issue ISSUE = Issue.create(
+            "ScrollViewSize", //$NON-NLS-1$
+            "Checks that ScrollViews use wrap_content in scrolling dimension",
+            // TODO add a better explanation here!
+            "ScrollView children must set their layout_width or layout_height attributes " +
+            "to wrap_content rather than fill_parent or match_parent in the scrolling " +
+            "dimension",
+            CATEGORY_LAYOUT, 7, Severity.WARNING, null);
+
+    /** Constructs a new {@link ScrollViewChildDetector} */
+    public ScrollViewChildDetector() {
+    }
+
+    @Override
+    public Issue[] getIssues() {
+        return new Issue[] { ISSUE };
+    }
+
+    @Override
+    public Speed getSpeed() {
+        return Speed.FAST;
+    }
+
+    @Override
+    public Scope getScope() {
+        return Scope.SINGLE_FILE;
+    }
+
+    @Override
+    public Collection<String> getApplicableElements() {
+        return Arrays.asList(new String[] {
+                SCROLL_VIEW,
+                HORIZONTAL_SCROLL_VIEW
+        });
+    }
+
+    @Override
+    public void visitElement(Context context, Element element) {
+        List<Element> children = getChildren(element);
+        boolean isHorizontal = HORIZONTAL_SCROLL_VIEW.equals(element.getTagName());
+        String attributeName = isHorizontal ? ATTR_LAYOUT_WIDTH : ATTR_LAYOUT_HEIGHT;
+        for (Element child : children) {
+            Attr sizeNode = child.getAttributeNodeNS(ANDROID_URI, attributeName);
+            String value = sizeNode != null ? sizeNode.getValue() : null;
+            if (VALUE_FILL_PARENT.equals(value) || VALUE_MATCH_PARENT.equals(value)) {
+                String msg = String.format("This %1$s should use android:%2$s=\"wrap_content\"",
+                        child.getTagName(), attributeName);
+                context.toolContext.report(ISSUE, context.getLocation(sizeNode),
+                        msg);
+            }
+        }
+    }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/StateListDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/StateListDetector.java
new file mode 100644
index 0000000..9bb702e
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/StateListDetector.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2011 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.resources.ResourceFolderType;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Issue;
+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 org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+
+import java.util.List;
+
+/**
+ * Checks for unreachable states in an Android state list definition
+ */
+public class StateListDetector extends ResourceXmlDetector {
+    /** The main issue discovered by this detector */
+    public static final Issue ISSUE = Issue.create(
+            "StateListReachable", //$NON-NLS-1$
+            "Looks for unreachable states in a <selector>",
+            "In a selector, only the last child in the state list should omit a " +
+            "state qualifier. If not, all subsequent items in the list will be ignored " +
+            "since the given item will match all.",
+            CATEGORY_CORRECTNESS, 7, Severity.WARNING, null);
+
+    /** Constructs a new {@link StateListDetector} */
+    public StateListDetector() {
+    };
+
+    @Override
+    public Issue[] getIssues() {
+        return new Issue[] { ISSUE };
+    }
+
+    @Override
+    public boolean appliesTo(ResourceFolderType folderType) {
+        return folderType == ResourceFolderType.DRAWABLE;
+    }
+
+    @Override
+    public Speed getSpeed() {
+        return Speed.FAST;
+    }
+
+    @Override
+    public Scope getScope() {
+        return Scope.SINGLE_FILE;
+    }
+
+    @Override
+    public void visitDocument(Context context, Document document) {
+        // TODO: Look for views that don't specify
+        // Display the error token somewhere so it can be suppressed
+        // Emit warning at the end "run with --help to learn how to suppress types of errors/checks";
+        // ("...and this message.")
+
+        Element root = document.getDocumentElement();
+        if (root != null && root.getTagName().equals("selector")) { //$NON-NLS-1$
+            List<Element> children = getChildren(root);
+            for (int i = 0; i < children.size() - 1; i++) {
+                Element child = children.get(i);
+                boolean hasState = false;
+                NamedNodeMap attributes = child.getAttributes();
+                for (int j = 0; j < attributes.getLength(); j++) {
+                    Attr attribute = (Attr) attributes.item(j);
+                    if (attribute.getLocalName().startsWith("state_")) {
+                        hasState = true;
+                        break;
+                    }
+                }
+                if (!hasState) {
+                    context.toolContext.report(ISSUE, context.getLocation(child),
+                            String.format("No android:state_ attribute found on <item> %1$d, later states not reachable",
+                                    i));
+                }
+            }
+        }
+    }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TooManyViewsDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TooManyViewsDetector.java
new file mode 100644
index 0000000..c95dbea
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TooManyViewsDetector.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2011 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.Context;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LayoutDetector;
+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 org.w3c.dom.Element;
+
+import java.util.Collection;
+
+/**
+ * Checks whether a root FrameLayout can be replaced with a {@code <merge>} tag.
+ */
+public class TooManyViewsDetector extends LayoutDetector {
+    /** Issue of having too many views in a single layout */
+    public static final Issue TOO_MANY = Issue.create(
+            "TooManyViews", //$NON-NLS-1$
+            "Checks whether a layout has too many views",
+            "Using too many views in a single layout in a layout is bad for " +
+            "performance. Consider using compound drawables or other tricks for " +
+            "reducing the number of views in this layout.\n\n" +
+            "The maximum view count defaults to 80 but can be configured with the " +
+            "environment variable ANDROID_LINT_MAX_VIEW_COUNT.",
+            CATEGORY_PERFORMANCE, 1, Severity.WARNING, null);
+
+    /** Issue of having too deep hierarchies in layouts */
+    public static final Issue TOO_DEEP = Issue.create(
+            "TooDeepLayout", //$NON-NLS-1$
+            "Checks whether a layout hierarchy is too deep",
+            "Layouts with too much nesting is bad for performance. " +
+            "Consider using a flatter layout (such as RelativeLayout or GridLayout)." +
+            "The default maximum depth is 10 but can be configured with the environment " +
+            "variable ANDROID_LINT_MAX_DEPTH.",
+            CATEGORY_PERFORMANCE, 1, Severity.WARNING, null);
+
+    private static final int MAX_VIEW_COUNT;
+    private static final int MAX_DEPTH;
+    static {
+        int maxViewCount = 0;
+        int maxDepth = 0;
+
+        String countValue = System.getenv("ANDROID_LINT_MAX_VIEW_COUNT"); //$NON-NLS-1$
+        if (countValue != null) {
+            try {
+                maxViewCount = Integer.parseInt(countValue);
+            } catch (NumberFormatException nufe) {
+            }
+        }
+        String depthValue = System.getenv("ANDROID_LINT_MAX_DEPTH"); //$NON-NLS-1$
+        if (depthValue != null) {
+            try {
+                maxDepth = Integer.parseInt(depthValue);
+            } catch (NumberFormatException nufe) {
+            }
+        }
+        if (maxViewCount == 0) {
+            maxViewCount = 80;
+        }
+        if (maxDepth == 0) {
+            maxDepth = 10;
+        }
+
+        MAX_VIEW_COUNT = maxViewCount;
+        MAX_DEPTH = maxDepth;
+    }
+
+    private int mViewCount;
+    private int mDepth;
+    private boolean mWarnedAboutDepth;
+
+    /** Constructs a new {@link TooManyViewsDetector} */
+    public TooManyViewsDetector() {
+    }
+
+    @Override
+    public Issue[] getIssues() {
+        return new Issue[] { TOO_DEEP, TOO_MANY };
+    }
+
+    @Override
+    public Speed getSpeed() {
+        return Speed.FAST;
+    }
+
+    @Override
+    public Scope getScope() {
+        return Scope.SINGLE_FILE;
+    }
+
+    @Override
+    public void beforeCheckFile(Context context) {
+        mViewCount = mDepth = 0;
+        mWarnedAboutDepth = false;
+    }
+
+    @Override
+    public Collection<String> getApplicableElements() {
+        return ResourceXmlDetector.ALL;
+    }
+
+    @Override
+    public void visitElement(Context context, Element element) {
+        mViewCount++;
+        mDepth++;
+
+        if (mDepth == MAX_DEPTH && !mWarnedAboutDepth) {
+            // Have to record whether or not we've warned since we could have many siblings
+            // at the max level and we'd warn for each one. No need to do the same thing
+            // for the view count error since we'll only have view count exactly equal the
+            // max just once.
+            mWarnedAboutDepth = true;
+            String msg = String.format("%1$s has more than %2$d levels, bad for performance",
+                    context.file.getName(), MAX_DEPTH);
+            context.toolContext.report(TOO_DEEP, context.getLocation(element), msg);
+        }
+        if (mViewCount == MAX_VIEW_COUNT) {
+            String msg = String.format("%1$s has more than %2$d views, bad for performance",
+                    context.file.getName(), MAX_VIEW_COUNT);
+            context.toolContext.report(TOO_MANY, context.getLocation(element), msg);
+        }
+    }
+
+    @Override
+    public void visitElementAfter(Context context, Element element) {
+        mDepth--;
+    }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/UseCompoundDrawableDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/UseCompoundDrawableDetector.java
new file mode 100644
index 0000000..650e339
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/UseCompoundDrawableDetector.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2011 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.Context;
+import com.android.tools.lint.detector.api.Issue;
+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 org.w3c.dom.Element;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Checks whether the current node can be replaced by a TextView using compound
+ * drawables.
+ */
+public class UseCompoundDrawableDetector extends LayoutDetector {
+    /** The main issue discovered by this detector */
+    public static final Issue ISSUE = Issue.create(
+            "UseCompoundDrawables", //$NON-NLS-1$
+            "Checks whether the current node can be replaced by a TextView using compound drawables.",
+            // TODO: OFFER MORE HELP!
+            "A LinearLayout which contains an ImageView and a TextView can be more efficiently " +
+            "handled as a compound drawable",
+            CATEGORY_PERFORMANCE, 6, Severity.WARNING, null);
+
+    /** Constructs a new {@link UseCompoundDrawableDetector} */
+    public UseCompoundDrawableDetector() {
+    }
+
+    @Override
+    public Issue[] getIssues() {
+        return new Issue[] { ISSUE };
+    }
+
+    @Override
+    public Speed getSpeed() {
+        return Speed.FAST;
+    }
+
+    @Override
+    public Scope getScope() {
+        return Scope.SINGLE_FILE;
+    }
+
+    @Override
+    public Collection<String> getApplicableElements() {
+        return Arrays.asList(new String[] {
+                LINEAR_LAYOUT
+        });
+    }
+
+    @Override
+    public void visitElement(Context context, Element element) {
+        int childCount = getChildCount(element);
+        if (childCount == 2) {
+            List<Element> children = getChildren(element);
+            Element first = children.get(0);
+            Element second = children.get(1);
+            if ((first.getTagName().equals(IMAGE_VIEW) &&
+                    second.getTagName().equals(TEXT_VIEW) &&
+                    !first.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WEIGHT)) ||
+                ((second.getTagName().equals(IMAGE_VIEW) &&
+                        first.getTagName().equals(TEXT_VIEW) &&
+                        !second.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WEIGHT)))) {
+                context.toolContext.report(ISSUE, context.getLocation(element),
+                        "This tag and its children can be replaced by one <TextView/> and " +
+                                "a compound drawable");
+            }
+        }
+    }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/UselessViewDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/UselessViewDetector.java
new file mode 100644
index 0000000..6de2264
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/UselessViewDetector.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2011 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.Context;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.Location;
+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 org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Checks whether the current node can be removed without affecting the layout.
+ */
+public class UselessViewDetector extends LayoutDetector {
+    /** Issue of including a parent that has no value on its own */
+    public static final Issue USELESS_PARENT = Issue.create(
+            "UselessParent", //$NON-NLS-1$
+            "Checks whether a parent layout can be removed.",
+            "A layout with children that has no siblings, is not a scrollview or " +
+            "a root layout, and does not have a background, can be removed and have " +
+            "its children moved directly into the parent for a flatter and more " +
+            "efficient layout hierarchy.",
+            CATEGORY_LAYOUT, 2, Severity.WARNING, null);
+
+    /** Issue of including a leaf that isn't shown */
+    public static final Issue USELESS_LEAF = Issue.create(
+            "UselessLeaf", //$NON-NLS-1$
+            "Checks whether a leaf layout can be removed.",
+            "A layout that has no children or no background can often be removed (since it " +
+            "is invisible) for a flatter and more efficient layout hierarchy.",
+            CATEGORY_LAYOUT, 2, Severity.WARNING, null);
+
+    /** Constructs a new {@link UselessViewDetector} */
+    public UselessViewDetector() {
+    }
+
+    @Override
+    public Issue[] getIssues() {
+        return new Issue[] { USELESS_PARENT, USELESS_LEAF };
+    }
+
+    @Override
+    public Speed getSpeed() {
+        return Speed.FAST;
+    }
+
+    @Override
+    public Scope getScope() {
+        return Scope.SINGLE_FILE;
+    }
+
+    private static final List<String> CONTAINERS = new ArrayList<String>(20);
+    static {
+        CONTAINERS.add("android.gesture.GestureOverlayView"); //$NON-NLS-1$
+        CONTAINERS.add("AbsoluteLayout");                     //$NON-NLS-1$
+        CONTAINERS.add(FRAME_LAYOUT);
+        CONTAINERS.add("GridLayout");                         //$NON-NLS-1$
+        CONTAINERS.add(GRID_VIEW);
+        CONTAINERS.add(HORIZONTAL_SCROLL_VIEW);
+        CONTAINERS.add("ImageSwitcher");                      //$NON-NLS-1$
+        CONTAINERS.add(LINEAR_LAYOUT);
+        CONTAINERS.add("RadioGroup");                         //$NON-NLS-1$
+        CONTAINERS.add("RelativeLayout");                     //$NON-NLS-1$
+        CONTAINERS.add(SCROLL_VIEW);
+        CONTAINERS.add("SlidingDrawer");                      //$NON-NLS-1$
+        CONTAINERS.add("StackView");                          //$NON-NLS-1$
+        CONTAINERS.add("TabHost");                            //$NON-NLS-1$
+        CONTAINERS.add("TableLayout");                        //$NON-NLS-1$
+        CONTAINERS.add("TableRow");                           //$NON-NLS-1$
+        CONTAINERS.add("TextSwitcher");                       //$NON-NLS-1$
+        CONTAINERS.add("ViewAnimator");                       //$NON-NLS-1$
+        CONTAINERS.add("ViewFlipper");                        //$NON-NLS-1$
+        CONTAINERS.add("ViewSwitcher");                       //$NON-NLS-1$
+        // Available ViewGroups that are not included by this check:
+        //  CONTAINERS.add("AdapterViewFlipper");
+        //  CONTAINERS.add("DialerFilter");
+        //  CONTAINERS.add("ExpandableListView");
+        //  CONTAINERS.add("ListView");
+        //  CONTAINERS.add("MediaController");
+        //  CONTAINERS.add("merge");
+        //  CONTAINERS.add("SearchView");
+        //  CONTAINERS.add("TabWidget");
+    }
+    @Override
+    public Collection<String> getApplicableElements() {
+        return CONTAINERS;
+    }
+
+    @Override
+    public void visitElement(Context context, Element element) {
+        int childCount = getChildCount(element);
+        if (childCount == 0) {
+            // Check to see if this is a leaf layout that can be removed
+            checkUselessLeaf(context, element);
+        } else {
+            // Check to see if this is a middle-man layout which can be removed
+            checkUselessMiddleLayout(context, element);
+        }
+    }
+
+    // This is the old UselessLayoutCheck from layoutopt
+    private void checkUselessMiddleLayout(Context context, Element element) {
+        // Conditions:
+        // - The node has children
+        // - The node does not have siblings
+        // - The node's parent is not a scroll view (horizontal or vertical)
+        // - The node does not have a background or its parent does not have a
+        //   background or neither the node and its parent have a background
+        // - The parent is not a <merge/>
+
+        Node parentNode = element.getParentNode();
+        if (parentNode.getNodeType() != Node.ELEMENT_NODE) {
+            // Can't remove root
+            return;
+        }
+
+        Element parent = (Element) parentNode;
+        String parentTag = parent.getTagName();
+        if (parentTag.equals(SCROLL_VIEW) || parentTag.equals(HORIZONTAL_SCROLL_VIEW) ||
+                parentTag.equals(MERGE)) {
+            // Can't remove if the parent is a scroll view or a merge
+            return;
+        }
+
+        // This method is only called when we've already ensured that it has children
+        assert getChildCount(element) > 0;
+
+        int parentChildCount = getChildCount(parent);
+        if (parentChildCount != 1) {
+            // Don't remove if the node has siblings
+            return;
+        }
+
+        boolean nodeHasBackground = element.hasAttributeNS(ANDROID_URI, ATTR_BACKGROUND);
+        boolean parentHasBackground = parent.hasAttributeNS(ANDROID_URI, ATTR_BACKGROUND);
+        // TODO: The logic on this has background stuff is a bit unclear to me; this is
+        // a literal translation of the Groovy code in layoutopt
+        // TODO: Get clarification on what the criteria are.
+        if (nodeHasBackground || parentHasBackground ||
+                (!nodeHasBackground && !parentHasBackground)) {
+            boolean hasId = element.hasAttributeNS(ANDROID_URI, ATTR_ID);
+            Location location = context.getLocation(element);
+            String tag = element.getTagName();
+            String format;
+            if (hasId) {
+                format = "This %1$s layout or its %2$s parent is possibly useless";
+            } else {
+                format = "This %1$s layout or its %2$s parent is useless";
+            }
+            String message = String.format(format, tag, parentTag);
+            context.toolContext.report(USELESS_PARENT, location, message);
+        }
+    }
+
+    // This is the old UselessView check from layoutopt
+    private void checkUselessLeaf(Context context, Element element) {
+        assert getChildCount(element) == 0;
+
+        // Conditions:
+        // - The node is a container view (LinearLayout, etc.)
+        // - The node has no id
+        // - The node has no background
+        // - The node has no children
+
+        if (element.hasAttributeNS(ANDROID_URI, ATTR_ID)) {
+            return;
+        }
+
+        if (element.hasAttributeNS(ANDROID_URI, ATTR_BACKGROUND)) {
+            return;
+        }
+
+        Location location = context.getLocation(element);
+        String tag = element.getTagName();
+        String message = String.format(
+                "This %1$s view is useless (no children, no background, no id)", tag);
+        context.toolContext.report(USELESS_LEAF, location, message);
+    }
+}
diff --git a/lint/libs/lint_checks/tests/.classpath b/lint/libs/lint_checks/tests/.classpath
new file mode 100644
index 0000000..73067c0
--- /dev/null
+++ b/lint/libs/lint_checks/tests/.classpath
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
+	<classpathentry kind="var" path="ANDROID_OUT_FRAMEWORK/easymock.jar"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/lint-api"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/lint-checks"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/lint-cli"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/lint/libs/lint_checks/tests/.project b/lint/libs/lint_checks/tests/.project
new file mode 100644
index 0000000..5713c07
--- /dev/null
+++ b/lint/libs/lint_checks/tests/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>lint_check-tests</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/lint/libs/lint_checks/tests/Android.mk b/lint/libs/lint_checks/tests/Android.mk
new file mode 100644
index 0000000..fda6d7f
--- /dev/null
+++ b/lint/libs/lint_checks/tests/Android.mk
@@ -0,0 +1,30 @@
+# Copyright (C) 2010 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Only compile source java files in this lib.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := lint_checks-tests
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_JAVA_LIBRARIES := lint_api lint_checks lint junit easymock
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+# Build all sub-directories
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AbstractCheckTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AbstractCheckTest.java
new file mode 100644
index 0000000..3cfc8e8
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AbstractCheckTest.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2011 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.PositionXmlParser;
+import com.android.tools.lint.api.DetectorRegistry;
+import com.android.tools.lint.api.IDomParser;
+import com.android.tools.lint.api.Lint;
+import com.android.tools.lint.api.ToolContext;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Position;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/** Common utility methods for the various lint check tests */
+abstract class AbstractCheckTest extends TestCase implements ToolContext {
+    protected abstract Detector getDetector();
+
+    private class CustomDetectorRegistry extends DetectorRegistry {
+        @Override
+        public List<? extends Detector> getDetectors() {
+            List<Detector> detectors = new ArrayList<Detector>(1);
+            detectors.add(AbstractCheckTest.this.getDetector());
+            return detectors;
+        }
+    }
+
+    protected String lint(String... relativePaths) throws Exception {
+        List<File> files = new ArrayList<File>();
+        for (String relativePath : relativePaths) {
+            File file = getTestfile(relativePath);
+            assertNotNull(file);
+            files.add(file);
+        }
+
+        mOutput = new StringBuilder();
+        Lint analyzer = new Lint(new CustomDetectorRegistry(), this, Scope.PROJECT);
+        analyzer.analyze(files);
+        if (mOutput.length() == 0) {
+            mOutput.append("No warnings.");
+        }
+
+        return mOutput.toString();
+    }
+
+    public void report(Issue issue, Location location, String message) {
+        if (mOutput.length() > 0) {
+            mOutput.append('\n');
+        }
+
+        mOutput.append(location.getFile().getName());
+        mOutput.append(':');
+
+        Position startPosition = location.getStart();
+        if (startPosition != null) {
+            int line = startPosition.getLine();
+            if (line >= 0) {
+                // line is 0-based, should display 1-based
+                mOutput.append(Integer.toString(line + 1));
+                mOutput.append(':');
+            }
+        }
+
+        mOutput.append(' ');
+        Severity severity = getSeverity(issue);
+        mOutput.append(severity.getDescription());
+        mOutput.append(": ");
+
+        mOutput.append(message);
+    }
+
+    private StringBuilder mOutput = null;
+
+    private static File sTempDir = null;
+    private File getTempDir() {
+        if (sTempDir == null) {
+            File base = new File(System.getProperty("java.io.tmpdir"));     //$NON-NLS-1$
+            String os = System.getProperty("os.name");          //$NON-NLS-1$
+            if (os.startsWith("Mac OS")) {                      //$NON-NLS-1$
+                base = new File("/tmp");
+            }
+            Calendar c = Calendar.getInstance();
+            String name = String.format("lintTests_%1$tF_%1$tT", c).replace(':', '-'); //$NON-NLS-1$
+            File tmpDir = new File(base, name);
+            if (!tmpDir.exists() && tmpDir.mkdir()) {
+                sTempDir = tmpDir;
+            } else {
+                sTempDir = base;
+            }
+        }
+
+        return sTempDir;
+    }
+
+    private File makeTestFile(String name, String relative,
+            String contents) throws IOException {
+        File dir = getTempDir();
+        if (relative != null) {
+            dir = new File(dir, relative);
+            if (!dir.exists()) {
+                boolean mkdir = dir.mkdirs();
+                assertTrue(dir.getPath(), mkdir);
+            }
+        }
+        File tempFile = new File(dir, name);
+        if (tempFile.exists()) {
+            tempFile.delete();
+        }
+
+        Writer writer = new BufferedWriter(new FileWriter(tempFile));
+        writer.write(contents);
+        writer.close();
+
+        return tempFile;
+    }
+
+    private File getTestfile(String relativePath) throws IOException {
+        String path = "data" + File.separator + relativePath; //$NON-NLS-1$
+        InputStream stream =
+            AbstractCheckTest.class.getResourceAsStream(path);
+        assertNotNull(relativePath + " does not exist", stream);
+        BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
+        String xml = readFile(reader);
+        assertNotNull(xml);
+        assertTrue(xml.length() > 0);
+        int index = relativePath.lastIndexOf('/');
+        String relative = null;
+        String name = relativePath;
+        if (index != -1) {
+            name = relativePath.substring(index + 1);
+            relative = relativePath.substring(0, index);
+        }
+        return makeTestFile(name, relative, xml);
+    }
+
+    private String readFile(Reader reader) throws IOException {
+        try {
+            StringBuilder sb = new StringBuilder();
+            while (true) {
+                int c = reader.read();
+                if (c == -1) {
+                    return sb.toString();
+                } else {
+                    sb.append((char)c);
+                }
+            }
+        } finally {
+            reader.close();
+        }
+    }
+
+    public void log(Throwable exception, String format, Object... args) {
+        exception.printStackTrace();
+        fail(exception.toString());
+    }
+
+    public IDomParser getParser() {
+        return new PositionXmlParser();
+    }
+
+    public boolean isEnabled(Issue issue) {
+        for (Issue detectorIssue : getDetector().getIssues()) {
+            if (issue == detectorIssue) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public boolean isSuppressed(Issue issue, Location range, String message, Severity severity) {
+        return false;
+    }
+
+    public Severity getSeverity(Issue issue) {
+        return issue.getDefaultSeverity();
+    }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AccessibilityDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AccessibilityDetectorTest.java
new file mode 100644
index 0000000..57754d6
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AccessibilityDetectorTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2011 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 AccessibilityDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new AccessibilityDetector();
+    }
+
+    public void testAccessibility() throws Exception {
+        assertEquals(
+                "accessibility.xml:5: Warning: [Accessibility] Missing contentDescription " +
+                        "attribute on image\n" +
+                "accessibility.xml:6: Warning: [Accessibility] Missing contentDescription " +
+                        "attribute on image",
+                lint("layout/accessibility.xml"));
+    }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ChildCountDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ChildCountDetectorTest.java
new file mode 100644
index 0000000..a31036c
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ChildCountDetectorTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2011 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 ChildCountDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new ChildCountDetector();
+    }
+
+    public void testChildCount() throws Exception {
+        assertEquals(
+                "has_children.xml:8: Warning: A list/grid should have no children declared " +
+                        "in XML",
+                lint("layout/has_children.xml"));
+    }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/DuplicateIdDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/DuplicateIdDetectorTest.java
new file mode 100644
index 0000000..b085fff
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/DuplicateIdDetectorTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2011 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 DuplicateIdDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new DuplicateIdDetector();
+    }
+
+    public void testDuplicate() throws Exception {
+        assertEquals(
+                "duplicate.xml:6: Warning: Duplicate id @+id/android_logo, already defined " +
+                        "earlier in this layout",
+                lint("layout/duplicate.xml"));
+    }
+
+    public void testDuplicateChains() throws Exception {
+        assertEquals(
+                "layout2.xml: Warning: Duplicate id @+id/button1, already defined in layout " +
+                        "layout4.xml which is included in this layout\n" +
+                "layout1.xml: Warning: Duplicate id @+id/button1, already defined in layout " +
+                    "layout2 which is included in this layout (layout1 => layout3 => layout2)\n" +
+                "layout1.xml: Warning: Duplicate id @+id/button2, already defined in layout " +
+                    "layout2 which is included in this layout (layout1 => layout4 => layout2)",
+                lint("layout/layout1.xml", "layout/layout2.xml",
+                        "layout/layout3.xml", "layout/layout4.xml"));
+    }
+
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/InefficientWeightDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/InefficientWeightDetectorTest.java
new file mode 100644
index 0000000..1ab7696
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/InefficientWeightDetectorTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2011 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 InefficientWeightDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new InefficientWeightDetector();
+    }
+
+    public void testWeights() throws Exception {
+        assertEquals(
+                "inefficient_weight.xml:13: Warning: Use a layout_width of 0dip instead of " +
+                        "match_parent for better performance\n" +
+                "inefficient_weight.xml:26: Warning: Use a layout_height of 0dip instead of " +
+                        "wrap_content for better performance",
+                lint("layout/inefficient_weight.xml"));
+    }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MergeRootFrameLayoutDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MergeRootFrameLayoutDetectorTest.java
new file mode 100644
index 0000000..423a5bc
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MergeRootFrameLayoutDetectorTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2011 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 MergeRootFrameLayoutDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new MergeRootFrameLayoutDetector();
+    }
+
+    public void testMerge() throws Exception {
+        assertEquals(
+                "simple.xml:8: Warning: This <FrameLayout> can be replaced with a <merge> tag",
+                lint("layout/simple.xml"));
+    }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/NestedScrollingWidgetDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/NestedScrollingWidgetDetectorTest.java
new file mode 100644
index 0000000..9338731
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/NestedScrollingWidgetDetectorTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2011 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 NestedScrollingWidgetDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new NestedScrollingWidgetDetector();
+    }
+
+    public void testNested() throws Exception {
+        assertEquals(
+                "scrolling.xml:16: Warning: The vertically scrolling ScrollView should not " +
+                        "contain another vertically scrolling widget (ListView)",
+                lint("layout/scrolling.xml"));
+    }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ScrollViewChildDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ScrollViewChildDetectorTest.java
new file mode 100644
index 0000000..2e763ea
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ScrollViewChildDetectorTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2011 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 ScrollViewChildDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new ScrollViewChildDetector();
+    }
+
+    public void testScrollView() throws Exception {
+        assertEquals(
+                "wrong_dimension.xml:12: Warning: This LinearLayout should use " +
+                        "android:layout_width=\"wrap_content\"",
+                lint("layout/wrong_dimension.xml"));
+    }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/StateListDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/StateListDetectorTest.java
new file mode 100644
index 0000000..e7dfc74
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/StateListDetectorTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2011 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 StateListDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new StateListDetector();
+    }
+
+    public void testStates() throws Exception {
+        assertEquals(
+                "states.xml:3: Warning: No android:state_ attribute found on <item> 0, " +
+                        "later states not reachable",
+                lint("drawable/states.xml"));
+    }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/TooManyViewsDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/TooManyViewsDetectorTest.java
new file mode 100644
index 0000000..8d1af2c
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/TooManyViewsDetectorTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2011 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 TooManyViewsDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new TooManyViewsDetector();
+    }
+
+    public void testTooMany() throws Exception {
+        assertEquals(
+                "too_many.xml:403: Warning: too_many.xml has more than 80 views, bad for " +
+                        "performance",
+                lint("layout/too_many.xml"));
+    }
+
+    public void testTooDeep() throws Exception {
+        assertEquals(
+                "too_deep.xml:49: Warning: too_deep.xml has more than 10 levels, bad for " +
+                        "performance",
+                lint("layout/too_deep.xml"));
+    }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/UseCompoundDrawableDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/UseCompoundDrawableDetectorTest.java
new file mode 100644
index 0000000..ce407e8
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/UseCompoundDrawableDetectorTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2011 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 UseCompoundDrawableDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new UseCompoundDrawableDetector();
+    }
+
+    public void testCompound() throws Exception {
+        assertEquals(
+                "compound.xml:8: Warning: This tag and its children can be replaced by one " +
+                        "<TextView/> and a compound drawable",
+                lint("layout/compound.xml"));
+    }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/UselessViewDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/UselessViewDetectorTest.java
new file mode 100644
index 0000000..d2ba0eb
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/UselessViewDetectorTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2011 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 UselessViewDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new UselessViewDetector();
+    }
+
+    public void testUseless1() throws Exception {
+        assertEquals(
+                "useless.xml:12: Warning: This LinearLayout layout or its FrameLayout parent " +
+                        "is useless",
+                lint("layout/useless.xml"));
+    }
+
+    // TODO: Test the other case as well
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/drawable/states.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/drawable/states.xml
new file mode 100644
index 0000000..3dedb64
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/drawable/states.xml
@@ -0,0 +1,7 @@
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item  android:color="#ff000000"/> <!-- WRONG, SHOULD BE LAST -->
+    <item android:state_pressed="true"
+          android:color="#ffff0000"/> <!-- pressed -->
+    <item android:state_focused="true"
+          android:color="#ff0000ff"/> <!-- focused -->
+</selector>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/accessibility.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/accessibility.xml
new file mode 100644
index 0000000..9a49772
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/accessibility.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/newlinear" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
+    <Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
+    <ImageView android:id="@+id/android_logo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android_button" android:focusable="false" android:clickable="false" android:layout_weight="1.0" />
+    <ImageButton android:id="@+id/android_logo2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android_button" android:focusable="false" android:clickable="false" android:layout_weight="1.0" />
+    <Button android:text="Button" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
+</LinearLayout>
+
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/compound.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/compound.xml
new file mode 100644
index 0000000..f7b28ef
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/compound.xml
@@ -0,0 +1,17 @@
+<?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">
+
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/duplicate.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/duplicate.xml
new file mode 100644
index 0000000..e142e9e
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/duplicate.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/newlinear" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
+    <Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
+    <ImageView android:id="@+id/android_logo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android_button" android:focusable="false" android:clickable="false" android:layout_weight="1.0" />
+    <ImageButton android:id="@+id/android_logo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android_button" android:focusable="false" android:clickable="false" android:layout_weight="1.0" />
+    <Button android:text="Button" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
+</LinearLayout>
+
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/has_children.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/has_children.xml
new file mode 100644
index 0000000..cac27d4
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/has_children.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<ListView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+	<ListView
+	    android:layout_width="match_parent"
+	    android:layout_height="match_parent" />
+
+</ListView>
\ No newline at end of file
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/inefficient_weight.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/inefficient_weight.xml
new file mode 100644
index 0000000..058fde1
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/inefficient_weight.xml
@@ -0,0 +1,44 @@
+<?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">
+
+	<Button
+	    android:layout_width="match_parent"
+	    android:layout_height="wrap_content"
+        android:layout_weight="1.0" />
+
+	<LinearLayout
+	    xmlns:android="http://schemas.android.com/apk/res/android"
+
+	    android:layout_width="match_parent"
+	    android:layout_height="match_parent"
+
+		android:orientation="vertical">
+
+		<Button
+		    android:layout_width="match_parent"
+		    android:layout_height="wrap_content"
+		    android:layout_weight="1.0" />
+
+	</LinearLayout>
+
+	<LinearLayout
+	    xmlns:android="http://schemas.android.com/apk/res/android"
+
+	    android:layout_width="match_parent"
+	    android:layout_height="match_parent"
+
+		android:orientation="vertical">
+
+		<Button
+		    android:layout_width="match_parent"
+		    android:layout_height="0dip"
+            android:layout_weight="1.0" />
+
+	</LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout1.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout1.xml
new file mode 100644
index 0000000..efd6be0
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout1.xml
@@ -0,0 +1,24 @@
+<?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" >
+
+    <include
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        layout="@layout/layout2" />
+
+    <Button
+        android:id="@+id/button1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Button" />
+
+    <Button
+        android:id="@+id/button2"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Button" />
+
+</LinearLayout>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout2.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout2.xml
new file mode 100644
index 0000000..9fc9c5f
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout2.xml
@@ -0,0 +1,23 @@
+<?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/radioButton1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="RadioButton" />
+
+    <include
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        layout="@layout/layout3" />
+
+    <include
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        layout="@layout/layout4" />
+
+</LinearLayout>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout3.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout3.xml
new file mode 100644
index 0000000..aa5a137
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout3.xml
@@ -0,0 +1,19 @@
+<?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" >
+
+    <Button
+        android:id="@+id/button1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Button" />
+
+    <CheckBox
+        android:id="@+id/checkBox1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="CheckBox" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout4.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout4.xml
new file mode 100644
index 0000000..442efd4
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout4.xml
@@ -0,0 +1,19 @@
+<?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" >
+
+    <Button
+        android:id="@+id/button1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Button" />
+
+    <Button
+        android:id="@+id/button2"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Button" />
+
+</LinearLayout>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/scrolling.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/scrolling.xml
new file mode 100644
index 0000000..0bed702
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/scrolling.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+	<LinearLayout
+	    android:layout_width="match_parent"
+	    android:layout_height="match_parent">
+
+		<ListView
+		    android:layout_width="match_parent"
+		    android:layout_height="match_parent" />
+
+	</LinearLayout>
+
+</ScrollView>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/simple.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/simple.xml
new file mode 100644
index 0000000..d462c69
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/simple.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" />
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/too_deep.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/too_deep.xml
new file mode 100644
index 0000000..7e92008
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/too_deep.xml
@@ -0,0 +1,85 @@
+<?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">
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Ok" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="match_parent">
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent">
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="match_parent">
+
+                        <LinearLayout
+                            android:layout_width="match_parent"
+                            android:layout_height="match_parent">
+
+                            <LinearLayout
+                                android:layout_width="match_parent"
+                                android:layout_height="match_parent">
+
+                                <LinearLayout
+                                    android:layout_width="match_parent"
+                                    android:layout_height="match_parent">
+
+                                    <LinearLayout
+                                        android:layout_width="match_parent"
+                                        android:layout_height="match_parent">
+
+                                        <LinearLayout
+                                            android:layout_width="match_parent"
+                                            android:layout_height="match_parent">
+
+                                            <LinearLayout
+                                                android:layout_width="match_parent"
+                                                android:layout_height="match_parent">
+
+                                                <Button
+                                                    android:layout_width="wrap_content"
+                                                    android:layout_height="wrap_content"
+                                                    android:text="Ok" />
+
+                                            </LinearLayout>
+
+                                        </LinearLayout>
+
+                                    </LinearLayout>
+
+                                </LinearLayout>
+
+                            </LinearLayout>
+
+                        </LinearLayout>
+
+                    </LinearLayout>
+
+                </LinearLayout>
+
+            </LinearLayout>
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/too_many.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/too_many.xml
new file mode 100644
index 0000000..e2dbd6b
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/too_many.xml
@@ -0,0 +1,413 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Ok" />
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Ok" />
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Ok" />
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Ok" />
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Ok" />
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Ok" />
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Ok" />
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Ok" />
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Ok" />
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Ok" />
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Ok" />
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Ok" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="match_parent">
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+            </LinearLayout>
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ok" />
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="match_parent">
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Ok" />
+
+            </LinearLayout>
+
+    </LinearLayout>
+
+</FrameLayout>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/useless.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/useless.xml
new file mode 100644
index 0000000..b6d5ee7
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/useless.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+	<LinearLayout
+		android:layout_width="match_parent"
+	    android:layout_height="match_parent">
+
+		<TextView
+			android:layout_width="wrap_content"
+		    android:layout_height="wrap_content" />
+
+	</LinearLayout>
+
+</FrameLayout>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/wrong_dimension.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/wrong_dimension.xml
new file mode 100644
index 0000000..79b922b
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/wrong_dimension.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<HorizontalScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+	<LinearLayout
+	    android:layout_width="match_parent"
+	    android:layout_height="match_parent" />
+
+</HorizontalScrollView>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/detector/api/ScopeTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/detector/api/ScopeTest.java
new file mode 100644
index 0000000..83c032f
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/detector/api/ScopeTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2011 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.detector.api;
+
+import junit.framework.TestCase;
+
+@SuppressWarnings("javadoc")
+public class ScopeTest extends TestCase {
+    public void testWithin() throws Exception {
+        assertTrue(Scope.SINGLE_FILE.within(Scope.SINGLE_FILE));
+        assertTrue(Scope.SINGLE_FILE.within(Scope.JAVA_CODE));
+        assertTrue(Scope.SINGLE_FILE.within(Scope.JAVA));
+        assertTrue(Scope.SINGLE_FILE.within(Scope.RESOURCES));
+        assertTrue(Scope.SINGLE_FILE.within(Scope.PROJECT));
+        assertTrue(Scope.RESOURCES.within(Scope.RESOURCES));
+        assertTrue(Scope.RESOURCES.within(Scope.PROJECT));
+        assertTrue(Scope.JAVA_CODE.within(Scope.JAVA));
+        assertTrue(Scope.JAVA_CODE.within(Scope.PROJECT));
+        assertTrue(Scope.JAVA.within(Scope.JAVA));
+        assertTrue(Scope.JAVA.within(Scope.PROJECT));
+        assertTrue(Scope.PROJECT.within(Scope.PROJECT));
+
+        assertFalse(Scope.PROJECT.within(Scope.SINGLE_FILE));
+        assertFalse(Scope.RESOURCES.within(Scope.SINGLE_FILE));
+        assertFalse(Scope.JAVA.within(Scope.SINGLE_FILE));
+        assertFalse(Scope.JAVA_CODE.within(Scope.SINGLE_FILE));
+        assertFalse(Scope.PROJECT.within(Scope.RESOURCES));
+        assertFalse(Scope.JAVA.within(Scope.RESOURCES));
+        assertFalse(Scope.JAVA_CODE.within(Scope.RESOURCES));
+        assertFalse(Scope.PROJECT.within(Scope.JAVA));
+        assertFalse(Scope.PROJECT.within(Scope.JAVA_CODE));
+        assertFalse(Scope.JAVA.within(Scope.JAVA_CODE));
+        assertFalse(Scope.JAVA.within(Scope.JAVA_CODE));
+    }
+}