Merge "Update SDK lint to the 2.0 release version" into emu-2.0-release
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
index e206d70..f758959 100644
--- a/.idea/encodings.xml
+++ b/.idea/encodings.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
-  <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
-</project>
-
+  <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false">
+    <file url="PROJECT" charset="UTF-8" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LintGradleClient.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LintGradleClient.java
index 34d2240..4e1876f 100644
--- a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LintGradleClient.java
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LintGradleClient.java
@@ -54,7 +54,7 @@
             @NonNull AndroidProject modelProject,
             @Nullable File sdkHome,
             @Nullable String variantName) {
-        super(flags);
+        super(flags, CLIENT_GRADLE);
         mGradleProject = gradleProject;
         mModelProject = modelProject;
         mVariantName = variantName;
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LintGradleProject.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LintGradleProject.java
index 8193a8e..f9ad361 100644
--- a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LintGradleProject.java
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LintGradleProject.java
@@ -424,7 +424,7 @@
 
         @NonNull
         @Override
-        public List<File> getJavaLibraries() {
+        public List<File> getJavaLibraries(boolean includeProvided) {
             if (mJavaLibraries == null) {
                 Collection<JavaLibrary> libs = mVariant.getMainArtifact().getDependencies().getJavaLibraries();
                 mJavaLibraries = Lists.newArrayListWithExpectedSize(libs.size());
@@ -619,7 +619,7 @@
 
         @NonNull
         @Override
-        public List<File> getJavaLibraries() {
+        public List<File> getJavaLibraries(boolean includeProvided) {
             if (mJavaLibraries == null) {
                 mJavaLibraries = Lists.newArrayList();
                 File jarFile = mLibrary.getJarFile();
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/ExtractAnnotationsDriver.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/ExtractAnnotationsDriver.java
index 78ec03d..389813b 100644
--- a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/ExtractAnnotationsDriver.java
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/ExtractAnnotationsDriver.java
@@ -392,9 +392,9 @@
         options.originalSourceLevel = options.sourceLevel;
         options.inlineJsrBytecode = true; // >= 1.5
 
-        INameEnvironment environment = EcjParser.parse(options, sourceUnits, classpath,
-                outputMap, null);
+        EcjParser.EcjResult result = EcjParser.parse(options, sourceUnits, classpath,
+                null);
         Collection<CompilationUnitDeclaration> parsedUnits = outputMap.values();
-        return Pair.of(parsedUnits, environment);
+        return Pair.of(parsedUnits, result.getNameEnvironment());
     }
 }
\ No newline at end of file
diff --git a/common/src/main/java/com/android/SdkConstants.java b/common/src/main/java/com/android/SdkConstants.java
index c48f098..41d1969 100644
--- a/common/src/main/java/com/android/SdkConstants.java
+++ b/common/src/main/java/com/android/SdkConstants.java
@@ -563,6 +563,7 @@
     public static final String CLASS_MANIFEST_PERMISSION = "android.Manifest$permission"; //$NON-NLS-1$
     public static final String CLASS_INTENT = "android.content.Intent"; //$NON-NLS-1$
     public static final String CLASS_CONTEXT = "android.content.Context"; //$NON-NLS-1$
+    public static final String CLASS_RESOURCES = "android.content.res.Resources"; //$NON-NLS-1$
     public static final String CLASS_VIEW = "android.view.View"; //$NON-NLS-1$
     public static final String CLASS_VIEWGROUP = "android.view.ViewGroup"; //$NON-NLS-1$
     public static final String CLASS_NAME_LAYOUTPARAMS = "LayoutParams"; //$NON-NLS-1$
@@ -586,6 +587,9 @@
      * no rendering in the graphical layout editor. */
     public static final String CLASS_MOCK_VIEW = "com.android.layoutlib.bridge.MockView"; //$NON-NLS-1$
     public static final String CLASS_LAYOUT_INFLATER = "android.view.LayoutInflater"; //$NON-NLS-1$
+    public static final String CLASS_DATA_BINDING_COMPONENT = "android.databinding.DataBindingComponent"; //$NON-NLS-1$
+    public static final String CLASS_NAME_DATA_BINDING_COMPONENT = "DataBindingComponent"; //$NON-NLS-1$
+    public static final String CLASS_DATA_BINDING_BASE_BINDING = "android.databinding.ViewDataBinding";
 
     /* Android Design Support Class Constants */
     public static final String CLASS_COORDINATOR_LAYOUT = "android.support.design.widget.CoordinatorLayout"; //$NON-NLS-1$
@@ -735,8 +739,12 @@
     /** Namespace used for auto-adjusting namespaces */
     public static final String AUTO_URI =
             "http://schemas.android.com/apk/res-auto";                 //$NON-NLS-1$
+    /** Namespace for xliff in string resources. */
+    public static final String XLIFF_URI = "urn:oasis:names:tc:xliff:document:1.2";
     /** Default prefix used for tools attributes */
     public static final String TOOLS_PREFIX = "tools";                 //$NON-NLS-1$
+    /** Default prefix used for xliff tags. */
+    public static final String XLIFF_PREFIX = "xliff";                 //$NON-NLS-1$
     public static final String R_CLASS = "R";                          //$NON-NLS-1$
     public static final String ANDROID_PKG = "android";                //$NON-NLS-1$
 
@@ -745,6 +753,8 @@
     public static final String TAG_PERMISSION = "permission";          //$NON-NLS-1$
     public static final String TAG_USES_FEATURE = "uses-feature";      //$NON-NLS-1$
     public static final String TAG_USES_PERMISSION = "uses-permission";//$NON-NLS-1$
+    public static final String TAG_USES_PERMISSION_SDK_23 = "uses-permission-sdk-23";//$NON-NLS-1$
+    public static final String TAG_USES_PERMISSION_SDK_M = "uses-permission-sdk-m";//$NON-NLS-1$
     public static final String TAG_USES_LIBRARY = "uses-library";      //$NON-NLS-1$
     public static final String TAG_APPLICATION = "application";        //$NON-NLS-1$
     public static final String TAG_INTENT_FILTER = "intent-filter";    //$NON-NLS-1$
@@ -875,6 +885,7 @@
     public static final String ATTR_WRITE_PERMISSION = "writePermission"; //$NON_NLS-1$
     public static final String ATTR_VERSION_CODE = "versionCode";      //$NON_NLS-1$
     public static final String ATTR_VERSION_NAME = "versionName";      //$NON_NLS-1$
+    public static final String ATTR_FULL_BACKUP_CONTENT = "fullBackupContent"; //$NON_NLS-1$
 
     // Attributes: Resources
     public static final String ATTR_NAME = "name";                     //$NON-NLS-1$
@@ -960,6 +971,14 @@
     public static final String ATTR_LIST_PREFERRED_ITEM_PADDING_END =
             "listPreferredItemPaddingEnd";                             //$NON-NLS-1$
     public static final String ATTR_INDEX = "index";                   //$NON-NLS-1$
+    public static final String ATTR_ACTION_BAR_NAV_MODE = "actionBarNavMode"; //$NON-NLS-1$
+    public static final String ATTR_MENU = "menu";                     //$NON-NLS-1$
+    public static final String ATTR_SHOW_IN = "showIn";                //$NON-NLS-1$
+
+    // Tools attributes for AdapterView inheritors
+    public static final String ATTR_LISTFOOTER = "listfooter";         //$NON-NLS-1$
+    public static final String ATTR_LISTHEADER = "listheader";         //$NON-NLS-1$
+    public static final String ATTR_LISTITEM = "listitem";             //$NON-NLS-1$
 
     // AbsoluteLayout layout params
     public static final String ATTR_LAYOUT_Y = "layout_y";             //$NON-NLS-1$
@@ -1077,6 +1096,7 @@
     public static final String DOT_JAR = ".jar";                       //$NON-NLS-1$
     public static final String DOT_GRADLE = ".gradle";                 //$NON-NLS-1$
     public static final String DOT_PROPERTIES = ".properties";         //$NON-NLS-1$
+    public static final String DOT_JSON = ".json";                     //$NON-NLS-1$
 
     /** Extension of the Application package Files, i.e. "apk". */
     public static final String EXT_ANDROID_PACKAGE = "apk"; //$NON-NLS-1$
@@ -1135,6 +1155,8 @@
     public static final String DOT_BC = DOT + EXT_BC;
     /** Dot-Extension of dependency files, i.e. ".d" */
     public static final String DOT_DEP = DOT + EXT_DEP;
+    /** Dot-Extension of native dynamic libraries, i.e. ".so" */
+    public static final String DOT_NATIVE_LIBS = DOT + EXT_NATIVE_LIB;
     /** Dot-Extension of dex files, i.e. ".dex" */
     public static final String DOT_DEX = DOT + EXT_DEX;
     /** Dot-Extension for temporary resource files, ie "ap_ */
@@ -1185,6 +1207,7 @@
     public static final String PREFIX_RESOURCE_REF = "@";               //$NON-NLS-1$
     public static final String PREFIX_THEME_REF = "?";                  //$NON-NLS-1$
     public static final String PREFIX_BINDING_EXPR = "@{";              //$NON-NLS-1$
+    public static final String PREFIX_TWOWAY_BINDING_EXPR = "@={";      //$NON-NLS-1$
     public static final String ANDROID_PREFIX = "@android:";            //$NON-NLS-1$
     public static final String ANDROID_THEME_PREFIX = "?android:";      //$NON-NLS-1$
     public static final String LAYOUT_RESOURCE_PREFIX = "@layout/";     //$NON-NLS-1$
@@ -1340,6 +1363,9 @@
     /** The android.webkit. package prefix */
     public static final String ANDROID_WEBKIT_PKG = ANDROID_PKG_PREFIX + "webkit."; //$NON-NLS-1$
 
+    /** The android.app. package prefix */
+    public static final String ANDROID_APP_PKG = ANDROID_PKG_PREFIX + "app."; //$NON-NLS-1$
+
     /** The LayoutParams inner-class name suffix, .LayoutParams */
     public static final String DOT_LAYOUT_PARAMS = ".LayoutParams"; //$NON-NLS-1$
 
@@ -1440,11 +1466,28 @@
     public static final String TYPE_DEF_VALUE_ATTRIBUTE = "value";
     public static final String TYPE_DEF_FLAG_ATTRIBUTE = "flag";
     public static final String FN_ANNOTATIONS_ZIP = "annotations.zip";
+    public static final String BINDING_ADAPTER_ANNOTATION = "android.databinding.BindingAdapter";
 
     // Data Binding MISC
     public static final String DATA_BINDING_LIB_ARTIFACT = "com.android.databinding:library";
+    public static final String DATA_BINDING_BASELIB_ARTIFACT = "com.android.databinding:baseLibrary";
+    public static final String DATA_BINDING_ANNOTATION_PROCESSOR_ARTIFACT =
+            "com.android.databinding:compiler";
+    public static final String DATA_BINDING_ADAPTER_LIB_ARTIFACT =
+            "com.android.databinding:adapters";
     public static final String[] TAGS_DATA_BINDING = new String[]{TAG_VARIABLE,
         TAG_IMPORT, TAG_LAYOUT, TAG_DATA};
     public static final String[] ATTRS_DATA_BINDING = new String[]{ATTR_NAME,
         ATTR_TYPE, ATTR_CLASS, ATTR_ALIAS};
+
+    /** Name of keep attribute in XML */
+    public static final String ATTR_KEEP = "keep";
+    /** Name of discard attribute in XML (to mark resources as not referenced, despite guesses) */
+    public static final String ATTR_DISCARD = "discard";
+    /** Name of attribute in XML to control whether we should guess resources to keep */
+    public static final String ATTR_SHRINK_MODE = "shrinkMode";
+    /** {@linkplain #ATTR_SHRINK_MODE} value to only shrink explicitly encountered resources */
+    public static final String VALUE_STRICT = "strict";
+    /** {@linkplain #ATTR_SHRINK_MODE} value to keep possibly referenced resources */
+    public static final String VALUE_SAFE = "safe";
 }
diff --git a/common/src/main/java/com/android/utils/SdkUtils.java b/common/src/main/java/com/android/utils/SdkUtils.java
index 68e7354..4ad7052 100644
--- a/common/src/main/java/com/android/utils/SdkUtils.java
+++ b/common/src/main/java/com/android/utils/SdkUtils.java
@@ -29,12 +29,14 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.StringWriter;
 import java.net.MalformedURLException;
 import java.net.URISyntaxException;
 import java.net.URL;
 import java.text.NumberFormat;
 import java.text.ParseException;
 import java.util.List;
+import java.util.Properties;
 
 import static com.android.SdkConstants.DOT_WEBP;
 import static com.android.SdkConstants.DOT_XML;
@@ -508,4 +510,39 @@
         }
         return false;
     }
+
+
+    /**
+     * Escapes the given property file value (right hand side of property assignment)
+     * as required by the property file format (e.g. escapes colons and backslashes)
+     *
+     * @param value the value to be escaped
+     * @return the escaped value
+     */
+    @NonNull
+    public static String escapePropertyValue(@NonNull String value) {
+        // Slow, stupid implementation, but is 100% compatible with Java's property file
+        // implementation
+        Properties properties = new Properties();
+        properties.setProperty("k", value); // key doesn't matter
+        StringWriter writer = new StringWriter();
+        try {
+            properties.store(writer, null);
+            String s = writer.toString();
+            int end = s.length();
+
+            // Writer inserts trailing newline
+            String lineSeparator = SdkUtils.getLineSeparator();
+            if (s.endsWith(lineSeparator)) {
+                end -= lineSeparator.length();
+            }
+
+            int start = s.indexOf('=');
+            assert start != -1 : s;
+            return s.substring(start + 1, end);
+        }
+        catch (IOException e) {
+            return value; // shouldn't happen; we're not going to disk
+        }
+    }
 }
diff --git a/lint/cli/lint-cli.iml b/lint/cli/lint-cli.iml
index 978b7a8..93aac33 100644
--- a/lint/cli/lint-cli.iml
+++ b/lint/cli/lint-cli.iml
@@ -16,11 +16,12 @@
     <orderEntry type="module" module-name="builder-model" exported="" />
     <orderEntry type="library" name="ecj" level="project" />
     <orderEntry type="library" scope="TEST" name="groovy" level="project" />
-    <orderEntry type="module" module-name="sdk-common-base" />
     <orderEntry type="module" module-name="lint-checks-base" />
     <orderEntry type="module" module-name="lint-api-base" />
     <orderEntry type="library" scope="TEST" name="intellij-annotations" level="project" />
     <orderEntry type="module" module-name="lint-tests" scope="TEST" />
     <orderEntry type="library" scope="TEST" name="mockito" level="project" />
+    <orderEntry type="module" module-name="common" />
+    <orderEntry type="module" module-name="sdklib-base" />
   </component>
 </module>
\ No newline at end of file
diff --git a/lint/cli/src/main/java/com/android/tools/lint/EcjParser.java b/lint/cli/src/main/java/com/android/tools/lint/EcjParser.java
index 090e3f1..b4a2dee 100644
--- a/lint/cli/src/main/java/com/android/tools/lint/EcjParser.java
+++ b/lint/cli/src/main/java/com/android/tools/lint/EcjParser.java
@@ -27,6 +27,8 @@
 import com.android.sdklib.IAndroidTarget;
 import com.android.tools.lint.client.api.JavaParser;
 import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.detector.api.ClassContext;
+import com.android.tools.lint.detector.api.DefaultPosition;
 import com.android.tools.lint.detector.api.JavaContext;
 import com.android.tools.lint.detector.api.Location;
 import com.android.tools.lint.detector.api.Project;
@@ -48,7 +50,9 @@
 import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
 import org.eclipse.jdt.internal.compiler.ast.Annotation;
+import org.eclipse.jdt.internal.compiler.ast.Argument;
 import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
+import org.eclipse.jdt.internal.compiler.ast.Block;
 import org.eclipse.jdt.internal.compiler.ast.CharLiteral;
 import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.DoubleLiteral;
@@ -67,10 +71,13 @@
 import org.eclipse.jdt.internal.compiler.ast.NameReference;
 import org.eclipse.jdt.internal.compiler.ast.NullLiteral;
 import org.eclipse.jdt.internal.compiler.ast.NumberLiteral;
+import org.eclipse.jdt.internal.compiler.ast.Statement;
 import org.eclipse.jdt.internal.compiler.ast.StringLiteral;
 import org.eclipse.jdt.internal.compiler.ast.TrueLiteral;
+import org.eclipse.jdt.internal.compiler.ast.TryStatement;
 import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.TypeReference;
+import org.eclipse.jdt.internal.compiler.ast.UnionTypeReference;
 import org.eclipse.jdt.internal.compiler.batch.CompilationUnit;
 import org.eclipse.jdt.internal.compiler.batch.FileSystem;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
@@ -89,19 +96,23 @@
 import org.eclipse.jdt.internal.compiler.impl.StringConstant;
 import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding;
 import org.eclipse.jdt.internal.compiler.lookup.Binding;
+import org.eclipse.jdt.internal.compiler.lookup.CatchParameterBinding;
 import org.eclipse.jdt.internal.compiler.lookup.ElementValuePair;
 import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
 import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
+import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
 import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
 import org.eclipse.jdt.internal.compiler.lookup.NestedTypeBinding;
 import org.eclipse.jdt.internal.compiler.lookup.PackageBinding;
 import org.eclipse.jdt.internal.compiler.lookup.ProblemBinding;
 import org.eclipse.jdt.internal.compiler.lookup.ProblemFieldBinding;
 import org.eclipse.jdt.internal.compiler.lookup.ProblemMethodBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ProblemPackageBinding;
 import org.eclipse.jdt.internal.compiler.lookup.ProblemReferenceBinding;
 import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
 import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
 import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
+import org.eclipse.jdt.internal.compiler.lookup.VariableBinding;
 import org.eclipse.jdt.internal.compiler.parser.Parser;
 import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
 import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
@@ -117,7 +128,13 @@
 import java.util.Map;
 import java.util.Set;
 
+import lombok.ast.Catch;
+import lombok.ast.Identifier;
+import lombok.ast.MethodDeclaration;
 import lombok.ast.Node;
+import lombok.ast.Position;
+import lombok.ast.StrictListAccessor;
+import lombok.ast.Try;
 import lombok.ast.VariableDeclaration;
 import lombok.ast.VariableDefinition;
 import lombok.ast.VariableDefinitionEntry;
@@ -129,13 +146,21 @@
 public class EcjParser extends JavaParser {
     private static final boolean DEBUG_DUMP_PARSE_ERRORS = false;
 
+    /**
+     * Whether we're going to keep the ECJ compiler mLookupEnvironment around between
+     * the parse phase and disposal. The lookup environment is important for type attribution.
+     * We should be able to inline this field to true, but making it optional now to allow
+     * people to revert this behavior in the field immediately if there's an unexpected
+     * problem.
+     */
+    private static final boolean KEEP_LOOKUP_ENVIRONMENT = !Boolean.getBoolean("lint.reset.ecj");
+
     private final LintClient mClient;
     private final Project mProject;
     private Map<File, ICompilationUnit> mSourceUnits;
-    private Map<ICompilationUnit, CompilationUnitDeclaration> mCompiled;
     private Map<String, TypeDeclaration> mTypeUnits;
     private Parser mParser;
-    private INameEnvironment mEnvironment;
+    protected EcjResult mEcjResult;
 
     public EcjParser(@NonNull LintCliClient client, @Nullable Project project) {
         mClient = client;
@@ -226,40 +251,115 @@
             mSourceUnits.put(file, unit);
         }
         List<String> classPath = computeClassPath(contexts);
-        mCompiled = Maps.newHashMapWithExpectedSize(mSourceUnits.size());
         try {
-            mEnvironment = parse(createCompilerOptions(), sources, classPath, mCompiled, mClient);
-        } catch (Throwable t) {
-            mClient.log(t, "ECJ compiler crashed");
-        }
+            mEcjResult = parse(createCompilerOptions(), sources, classPath, mClient);
 
-        if (DEBUG_DUMP_PARSE_ERRORS) {
-            for (CompilationUnitDeclaration unit : mCompiled.values()) {
-                // so maybe I don't need my map!!
-                CategorizedProblem[] problems = unit.compilationResult()
-                        .getAllProblems();
-                if (problems != null) {
-                    for (IProblem problem : problems) {
-                        if (problem == null || !problem.isError()) {
-                            continue;
+            if (DEBUG_DUMP_PARSE_ERRORS) {
+                for (CompilationUnitDeclaration unit : mEcjResult.getCompilationUnits()) {
+                    // so maybe I don't need my map!!
+                    CategorizedProblem[] problems = unit.compilationResult()
+                            .getAllProblems();
+                    if (problems != null) {
+                        for (IProblem problem : problems) {
+                            if (problem == null || !problem.isError()) {
+                                continue;
+                            }
+                            System.out.println(
+                                    new String(problem.getOriginatingFileName()) + ":"
+                                    + (problem.isError() ? "Error" : "Warning") + ": "
+                                    + problem.getSourceLineNumber() + ": " + problem.getMessage());
                         }
-                        System.out.println(
-                                new String(problem.getOriginatingFileName()) + ":"
-                                + (problem.isError() ? "Error" : "Warning") + ": "
-                                + problem.getSourceLineNumber() + ": " + problem.getMessage());
                     }
                 }
             }
+        } catch (Throwable t) {
+            mClient.log(t, "ECJ compiler crashed");
+        }
+    }
+
+    /**
+     * A result from an ECJ compilation. In addition to the {@link #compilationUnits} it also
+     * returns the {@link #mNameEnvironment} and {@link #mLookupEnvironment} which are sometimes
+     * needed after parsing to perform for example type attribution. <b>NOTE</b>: Clients are
+     * expected to dispose of the {@link #mNameEnvironment} when done with the compilation units!
+     */
+    public static class EcjResult {
+        @Nullable private final INameEnvironment mNameEnvironment;
+        @Nullable private final LookupEnvironment mLookupEnvironment;
+        @NonNull  private final Map<ICompilationUnit, CompilationUnitDeclaration> compilationUnits;
+
+        public EcjResult(@Nullable INameEnvironment nameEnvironment,
+                @Nullable LookupEnvironment lookupEnvironment,
+                @NonNull Map<ICompilationUnit, CompilationUnitDeclaration> compilationUnits) {
+            this.mNameEnvironment = nameEnvironment;
+            this.mLookupEnvironment = lookupEnvironment;
+            this.compilationUnits = compilationUnits;
+        }
+
+        /**
+         * Returns the collection of compilation units found by the parse task
+         *
+         * @return a read-only collection of compilation units
+         */
+        @NonNull
+        public Collection<CompilationUnitDeclaration> getCompilationUnits() {
+            return compilationUnits.values();
+        }
+
+        @Nullable
+        public INameEnvironment getNameEnvironment() {
+            return mNameEnvironment;
+        }
+
+        /**
+         * Returns the compilation unit parsed from the given source unit, if any
+         *
+         * @param sourceUnit the original source passed to ECJ
+         * @return the corresponding compilation unit, if created
+         */
+        @Nullable
+        public CompilationUnitDeclaration getCompilationUnit(
+                @NonNull ICompilationUnit sourceUnit) {
+            return compilationUnits.get(sourceUnit);
+        }
+
+        /**
+         * Removes the compilation unit for the given source unit, if any. Used when individual
+         * source units are disposed to allow memory to be freed up.
+         *
+         * @param sourceUnit the source unit
+         */
+        void removeCompilationUnit(@NonNull ICompilationUnit sourceUnit) {
+            compilationUnits.remove(sourceUnit);
+        }
+
+        /**
+         * Disposes this parser result, allowing various ECJ data structures to be freed up even if
+         * the parser instance hangs around.
+         */
+        public void dispose() {
+            if (mNameEnvironment != null) {
+                mNameEnvironment.cleanup();
+            }
+
+            if (mLookupEnvironment != null) {
+                mLookupEnvironment.reset();
+            }
+
+            compilationUnits.clear();
         }
     }
 
     /** Parse the given source units and class path and store it into the given output map */
-    public static INameEnvironment parse(
+    @NonNull
+    public static EcjResult parse(
             CompilerOptions options,
             @NonNull List<ICompilationUnit> sourceUnits,
             @NonNull List<String> classPath,
-            @NonNull Map<ICompilationUnit, CompilationUnitDeclaration> outputMap,
             @Nullable LintClient client) {
+        Map<ICompilationUnit, CompilationUnitDeclaration> outputMap =
+                Maps.newHashMapWithExpectedSize(sourceUnits.size());
+
         INameEnvironment environment = new FileSystem(
                 classPath.toArray(new String[classPath.size()]), new String[0],
                 options.defaultEncoding);
@@ -322,7 +422,8 @@
             environment = null;
         }
 
-        return environment;
+        LookupEnvironment lookupEnvironment = compiler != null ? compiler.lookupEnvironment : null;
+        return new EcjResult(environment, lookupEnvironment, outputMap);
     }
 
     @NonNull
@@ -340,12 +441,12 @@
 
         Set<File> libraries = Sets.newHashSet();
         Set<String> names = Sets.newHashSet();
-        for (File library : mProject.getJavaLibraries()) {
+        for (File library : mProject.getJavaLibraries(true)) {
             libraries.add(library);
             names.add(getLibraryName(library));
         }
         for (Project project : mProject.getAllLibraries()) {
-            for (File library : project.getJavaLibraries()) {
+            for (File library : project.getJavaLibraries(true)) {
                 String name = getLibraryName(library);
                 // Avoid pulling in android-support-v4.jar from libraries etc
                 // since we're pointing to the local copies rather than the real
@@ -438,10 +539,10 @@
             @NonNull JavaContext context,
             @NonNull String code) {
         ICompilationUnit sourceUnit = null;
-        if (mSourceUnits != null && mCompiled != null) {
+        if (mSourceUnits != null && mEcjResult != null) {
             sourceUnit = mSourceUnits.get(context.file);
             if (sourceUnit != null) {
-                CompilationUnitDeclaration unit = mCompiled.get(sourceUnit);
+                CompilationUnitDeclaration unit = mEcjResult.getCompilationUnit(sourceUnit);
                 if (unit != null) {
                     return unit;
                 }
@@ -467,35 +568,113 @@
     @Override
     public Location getLocation(@NonNull JavaContext context, @NonNull Node node) {
         lombok.ast.Position position = node.getPosition();
+
+        // Not all ECJ nodes have offsets; in particular, VariableDefinitionEntries
+        while (position == Position.UNPLACED) {
+            node = node.getParent();
+            //noinspection ConstantConditions
+            if (node == null) {
+                break;
+            }
+            position = node.getPosition();
+        }
         return Location.create(context.file, context.getContents(),
                 position.getStart(), position.getEnd());
     }
 
     @NonNull
     @Override
+    public Location getRangeLocation(
+            @NonNull JavaContext context,
+            @NonNull Node from,
+            int fromDelta,
+            @NonNull Node to,
+            int toDelta) {
+        String contents = context.getContents();
+        int start = Math.max(0, from.getPosition().getStart() + fromDelta);
+        int end = Math.min(contents == null ? Integer.MAX_VALUE : contents.length(),
+                to.getPosition().getEnd() + toDelta);
+        return Location.create(context.file, contents, start, end);
+    }
+
+    @Override
+    @NonNull
+    public Location getNameLocation(@NonNull JavaContext context, @NonNull Node node) {
+        // The range on method name identifiers is wrong in the ECJ nodes; just take start of
+        // name + length of name
+        if (node instanceof MethodDeclaration) {
+            MethodDeclaration declaration = (MethodDeclaration) node;
+            Identifier identifier = declaration.astMethodName();
+            Location location = getLocation(context, identifier);
+            com.android.tools.lint.detector.api.Position start = location.getStart();
+            com.android.tools.lint.detector.api.Position end = location.getEnd();
+            int methodNameLength = identifier.astValue().length();
+            if (start != null && end != null &&
+                    end.getOffset() - start.getOffset() > methodNameLength) {
+                end = new DefaultPosition(start.getLine(), start.getColumn() + methodNameLength,
+                        start.getOffset() + methodNameLength);
+                return Location.create(location.getFile(), start, end);
+            }
+            return location;
+        }
+        return super.getNameLocation(context, node);
+    }
+
+    @NonNull
+    @Override
     public
     Location.Handle createLocationHandle(@NonNull JavaContext context, @NonNull Node node) {
         return new LocationHandle(context.file, node);
     }
 
     @Override
-    public void dispose(@NonNull JavaContext context,
-            @NonNull Node compilationUnit) {
-        if (mSourceUnits != null && mCompiled != null) {
+    public void dispose(@NonNull JavaContext context, @NonNull Node compilationUnit) {
+        if (mSourceUnits != null) {
             ICompilationUnit sourceUnit = mSourceUnits.get(context.file);
             if (sourceUnit != null) {
                 mSourceUnits.remove(context.file);
-                mCompiled.remove(sourceUnit);
+                if (mEcjResult != null) {
+                    CompilationUnitDeclaration unit = mEcjResult.getCompilationUnit(sourceUnit);
+                    if (unit != null) {
+                        // See if this compilation unit defines any enum types; if so,
+                        // keep those around for the type map (see #findAnnotationDeclaration())
+                        if (unit.types != null) {
+                            for (TypeDeclaration type : unit.types) {
+                                if (isAnnotationType(type)) {
+                                    return;
+                                }
+                                if (type.memberTypes != null) {
+                                    for (TypeDeclaration member : type.memberTypes) {
+                                        if (isAnnotationType(member)) {
+                                            return;
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                        // Compilation unit is not defining an annotation type at the top two
+                        // levels: we can remove it now; findAnnotationDeclaration will not need
+                        // to go looking for it
+                        mEcjResult.removeCompilationUnit(sourceUnit);
+                    }
+                }
             }
         }
     }
 
+    private static boolean isAnnotationType(@NonNull TypeDeclaration type) {
+        return TypeDeclaration.kind(type.modifiers) == TypeDeclaration.ANNOTATION_TYPE_DECL;
+    }
+
     @Override
     public void dispose() {
-        if (mEnvironment != null) {
-            mEnvironment.cleanup();
-            mEnvironment = null;
+        if (mEcjResult != null) {
+            mEcjResult.dispose();
+            mEcjResult = null;
         }
+
+        mSourceUnits = null;
+        mTypeUnits = null;
     }
 
     @Nullable
@@ -505,6 +684,73 @@
             return nativeNode;
         }
 
+        // Special case the handling for variables: these are missing
+        // native nodes in Lombok, but we can generally reconstruct them
+        // by looking at the context and fishing into the ECJ hierarchy.
+        // For example, for a method parameter, we can look at the surrounding
+        // method declaration, which we do have an ECJ node for, and then
+        // iterate through its Argument nodes and match those up with the
+        // variable name.
+        if (node instanceof VariableDeclaration) {
+            node = ((VariableDeclaration)node).astDefinition();
+        }
+        if (node instanceof VariableDefinition) {
+            StrictListAccessor<VariableDefinitionEntry, VariableDefinition>
+                    variables = ((VariableDefinition)node).astVariables();
+            if (variables.size() == 1) {
+                node = variables.first();
+            }
+        }
+        if (node instanceof VariableDefinitionEntry) {
+            VariableDefinitionEntry entry = (VariableDefinitionEntry) node;
+            String name = entry.astName().astValue();
+
+            // Find the nearest surrounding native node
+            Node parent = node.getParent();
+            while (parent != null) {
+                Object parentNativeNode = parent.getNativeNode();
+                if (parentNativeNode != null) {
+                    if (parentNativeNode instanceof AbstractMethodDeclaration) {
+                        // Parameter in a method declaration?
+                        AbstractMethodDeclaration method =
+                                (AbstractMethodDeclaration) parentNativeNode;
+                        for (Argument argument : method.arguments) {
+                            if (sameChars(name, argument.name)) {
+                                return argument;
+                            }
+                        }
+                        for (Statement statement : method.statements) {
+                            if (statement instanceof LocalDeclaration) {
+                                LocalDeclaration declaration = (LocalDeclaration)statement;
+                                if (sameChars(name, declaration.name)) {
+                                    return declaration;
+                                }
+                            }
+                        }
+                    } else if (parentNativeNode instanceof TypeDeclaration) {
+                        TypeDeclaration typeDeclaration = (TypeDeclaration) parentNativeNode;
+                        for (FieldDeclaration fieldDeclaration : typeDeclaration.fields) {
+                            if (sameChars(name, fieldDeclaration.name)) {
+                                return fieldDeclaration;
+                            }
+                        }
+                    } else if (parentNativeNode instanceof Block) {
+                        Block block = (Block)parentNativeNode;
+                        for (Statement statement : block.statements) {
+                            if (statement instanceof LocalDeclaration) {
+                                LocalDeclaration declaration = (LocalDeclaration)statement;
+                                if (sameChars(name, declaration.name)) {
+                                    return declaration;
+                                }
+                            }
+                        }
+                    }
+                    break;
+                }
+                parent = parent.getParent();
+            }
+        }
+
         Node parent = node.getParent();
         // The ECJ native nodes are sometimes spotty; for example, for a
         // MethodInvocation node we can have a null native node, but its
@@ -516,20 +762,6 @@
             }
         }
 
-        if (node instanceof VariableDefinitionEntry) {
-            node = node.getParent().getParent();
-        }
-        if (node instanceof VariableDeclaration) {
-            VariableDeclaration declaration = (VariableDeclaration) node;
-            VariableDefinition definition = declaration.astDefinition();
-            if (definition != null) {
-                lombok.ast.TypeReference typeReference = definition.astTypeReference();
-                if (typeReference != null) {
-                    return typeReference.getNativeNode();
-                }
-            }
-        }
-
         return null;
     }
 
@@ -623,10 +855,11 @@
         return null;
     }
 
-    private TypeDeclaration findTypeDeclaration(@NonNull String signature) {
+    private TypeDeclaration findAnnotationDeclaration(@NonNull String signature) {
         if (mTypeUnits == null) {
-            mTypeUnits = Maps.newHashMapWithExpectedSize(mCompiled.size());
-            for (CompilationUnitDeclaration unit : mCompiled.values()) {
+            Collection<CompilationUnitDeclaration> units = mEcjResult.getCompilationUnits();
+            mTypeUnits = Maps.newHashMapWithExpectedSize(units.size());
+            for (CompilationUnitDeclaration unit : units) {
                 if (unit.types != null) {
                     for (TypeDeclaration typeDeclaration : unit.types) {
                         addTypeDeclaration(typeDeclaration);
@@ -692,6 +925,10 @@
             nativeNode = ((TypeDeclaration) nativeNode).binding;
         } else if (nativeNode instanceof AbstractMethodDeclaration) {
             nativeNode = ((AbstractMethodDeclaration) nativeNode).binding;
+        } else if (nativeNode instanceof FieldDeclaration) {
+            nativeNode = ((FieldDeclaration) nativeNode).binding;
+        } else if (nativeNode instanceof LocalDeclaration) {
+            nativeNode = ((LocalDeclaration) nativeNode).binding;
         }
 
         if (nativeNode instanceof Binding) {
@@ -723,20 +960,14 @@
     @Override
     public ResolvedClass findClass(@NonNull JavaContext context,
             @NonNull String fullyQualifiedName) {
-        Node compilationUnit = context.getCompilationUnit();
-        if (compilationUnit == null) {
-            return null;
-        }
-        Object nativeObj = getNativeNode(compilationUnit);
-        if (!(nativeObj instanceof CompilationUnitDeclaration)) {
-            return null;
-        }
-        CompilationUnitDeclaration ecjUnit = (CompilationUnitDeclaration) nativeObj;
+        // Inner classes must use $ as separators. Switch to internal name first
+        // to make it more likely that we handle this correctly:
+        String internal = ClassContext.getInternalName(fullyQualifiedName);
 
-        // Convert "foo.bar.Baz" into char[][] 'foo','bar','Baz' as required for
+        // Convert "foo/bar/Baz" into char[][] 'foo','bar','Baz' as required for
         // ECJ name lookup
         List<char[]> arrays = Lists.newArrayList();
-        for (String segment : Splitter.on('.').split(fullyQualifiedName)) {
+        for (String segment : Splitter.on('/').split(internal)) {
             arrays.add(segment.toCharArray());
         }
         char[][] compoundName = new char[arrays.size()][];
@@ -744,14 +975,63 @@
             compoundName[i] = arrays.get(i);
         }
 
-        Binding typeOrPackage = ecjUnit.scope.getTypeOrPackage(compoundName);
-        if (typeOrPackage instanceof TypeBinding && !(typeOrPackage instanceof ProblemReferenceBinding)) {
-            return new EcjResolvedClass((TypeBinding)typeOrPackage);
+        LookupEnvironment lookup = mEcjResult.mLookupEnvironment;
+        if (lookup != null) {
+            ReferenceBinding type = lookup.getType(compoundName);
+            if (type != null && !(type instanceof ProblemReferenceBinding)) {
+                return new EcjResolvedClass(type);
+            }
         }
 
         return null;
     }
 
+    @Override
+    public List<TypeDescriptor> getCatchTypes(@NonNull JavaContext context,
+            @NonNull Catch catchBlock) {
+        Try aTry = catchBlock.upToTry();
+        if (aTry != null) {
+            Object nativeNode = getNativeNode(aTry);
+            if (nativeNode instanceof TryStatement) {
+                TryStatement tryStatement = (TryStatement) nativeNode;
+                Argument[] catchArguments = tryStatement.catchArguments;
+                Argument argument = null;
+                if (catchArguments.length > 1) {
+                    int index = 0;
+                    for (Catch aCatch : aTry.astCatches()) {
+                        if (aCatch == catchBlock) {
+                            if (index < catchArguments.length) {
+                                argument = catchArguments[index];
+                                break;
+                            }
+                        }
+                        index++;
+                    }
+                } else {
+                    argument = catchArguments[0];
+                }
+                if (argument != null) {
+                    if (argument.type instanceof UnionTypeReference) {
+                        UnionTypeReference typeRef = (UnionTypeReference) argument.type;
+                        List<TypeDescriptor> types = Lists.newArrayListWithCapacity(typeRef.typeReferences.length);
+                        for (TypeReference typeReference : typeRef.typeReferences) {
+                            TypeBinding binding = typeReference.resolvedType;
+                            if (binding != null) {
+                                types.add(new EcjTypeDescriptor(binding));
+                            }
+                        }
+                        return types;
+                    } else if (argument.type.resolvedType != null) {
+                        TypeDescriptor t = new EcjTypeDescriptor(argument.type.resolvedType);
+                        return Collections.singletonList(t);
+                    }
+                }
+            }
+        }
+
+        return super.getCatchTypes(context, catchBlock);
+    }
+
     @Nullable
     private TypeDescriptor getTypeDescriptor(@Nullable TypeBinding resolvedType) {
         if (resolvedType == null) {
@@ -852,7 +1132,7 @@
 
         @Nullable
         CompilationUnitDeclaration getCurrentUnit() {
-            // Can't use lookupEnvironment.unitBeingCompleted directly; it gets nulled out
+            // Can't use mLookupEnvironment.unitBeingCompleted directly; it gets nulled out
             // as part of the exception catch handling in the compiler before this method
             // is called from lint -- therefore we stash a copy in our own mCurrentUnit field
             return mCurrentUnit;
@@ -889,6 +1169,24 @@
             unit.compilationResult.totalUnitsKnown = totalUnits;
             lookupEnvironment.unitBeingCompleted = null;
         }
+
+        @Override
+        public void reset() {
+            if (KEEP_LOOKUP_ENVIRONMENT) {
+                // Same as super.reset() in ECJ 4.4.2, but omits the following statement:
+                //  this.mLookupEnvironment.reset();
+                // because we need the lookup environment to stick around even after the
+                // parse phase is done: at that point we're going to use the parse trees
+                // from java detectors which may need to resolve types
+                this.parser.scanner.source = null;
+                this.unitsToProcess = null;
+                if (DebugRequestor != null) DebugRequestor.reset();
+                this.problemReporter.reset();
+
+            } else {
+                super.reset();
+            }
+        }
     }
 
     private class EcjTypeDescriptor extends TypeDescriptor {
@@ -914,12 +1212,64 @@
             return sameChars(signature, mBinding.readableName());
         }
 
+        @Override
+        public boolean isPrimitive() {
+            return mBinding.isPrimitiveType();
+        }
+
+        @Override
+        public boolean isArray() {
+            return mBinding.isArrayType();
+        }
+
         @NonNull
         @Override
         public String getSignature() {
             return getName();
         }
 
+        @NonNull
+        @Override
+        public String getSimpleName() {
+            if (mBinding instanceof ReferenceBinding) {
+                ReferenceBinding ref = (ReferenceBinding) mBinding;
+                char[][] name = ref.compoundName;
+                char[] lastSegment = name[name.length - 1];
+                StringBuilder sb = new StringBuilder(lastSegment.length);
+                for (char c : lastSegment) {
+                    if (c == '$') {
+                        c = '.';
+                    }
+                    sb.append(c);
+                }
+                return sb.toString();
+            }
+            return super.getSimpleName();
+        }
+
+        @NonNull
+        @Override
+        public String getInternalName() {
+            if (mBinding instanceof ReferenceBinding) {
+                ReferenceBinding ref = (ReferenceBinding) mBinding;
+                StringBuilder sb = new StringBuilder(100);
+                char[][] name = ref.compoundName;
+                if (name == null) {
+                    return super.getInternalName();
+                }
+                for (char[] segment : name) {
+                    if (sb.length() != 0) {
+                        sb.append('/');
+                    }
+                    for (char c : segment) {
+                        sb.append(c);
+                    }
+                }
+                return sb.toString();
+            }
+            return super.getInternalName();
+        }
+
         @Override
         @Nullable
         public ResolvedClass getTypeClass() {
@@ -1050,6 +1400,7 @@
                 binding = findSuperMethodBinding(binding);
             }
 
+            all = ensureUnique(all);
             return all;
         }
 
@@ -1085,6 +1436,7 @@
                 binding = findSuperMethodBinding(binding);
             }
 
+            all = ensureUnique(all);
             return all;
         }
 
@@ -1101,6 +1453,7 @@
         @Override
         public boolean isInPackage(@NonNull String pkgName, boolean includeSubPackages) {
             PackageBinding pkg = mBinding.declaringClass.getPackage();
+            //noinspection SimplifiableIfStatement
             if (pkg != null) {
                 return includeSubPackages ?
                         startsWithCompound(pkgName, pkg.compoundName) :
@@ -1134,6 +1487,44 @@
         }
     }
 
+    /**
+     * It is valid (and in fact encouraged by IntelliJ's inspections) to specify the same
+     * annotation on overriding methods and overriding parameters. However, we shouldn't
+     * return all these "duplicates" when you ask for the annotation on a given element.
+     * This method filters out duplicates.
+     */
+    @VisibleForTesting
+    @NonNull
+    static List<ResolvedAnnotation> ensureUnique(@NonNull List<ResolvedAnnotation> list) {
+        if (list.size() < 2) {
+            return list;
+        }
+
+        // The natural way to deduplicate would be to create a Set of seen names, iterate
+        // through the list and look to see if the current annotation's name is already in the
+        // set (if so, remove this annotation from the list, else add it to the set of seen names)
+        // but this involves creating the set and all the Map entry objects; that's not
+        // necessary here since these lists are always very short 2-5 elements.
+        // Instead we'll just do an O(n^2) iteration comparing each subsequent element with each
+        // previous element and removing if matches, which is fine for these tiny lists.
+        int n = list.size();
+        for (int i = 0; i < n - 1; i++) {
+            ResolvedAnnotation current = list.get(i);
+            String currentName = current.getName();
+            // Deleting duplicates at end reduces number of elements that have to be shifted
+            for (int j = n - 1; j > i; j--) {
+                ResolvedAnnotation later = list.get(j);
+                String laterName = later.getName();
+                if (currentName.equals(laterName)) {
+                    list.remove(j);
+                    n--;
+                }
+            }
+        }
+
+        return list;
+    }
+
     private class EcjResolvedClass extends ResolvedClass {
         protected final TypeBinding mBinding;
 
@@ -1146,17 +1537,15 @@
         public String getName() {
             String name = new String(mBinding.readableName());
             if (name.indexOf('.') == -1 && mBinding.enclosingType() != null) {
-                return new String(mBinding.enclosingType().readableName()) + '.' +
-                        name;
+                name = new String(mBinding.enclosingType().readableName()) + '.' + name;
             }
-
-            return name;
+            return stripTypeVariables(name);
         }
 
         @NonNull
         @Override
         public String getSimpleName() {
-            return new String(mBinding.shortReadableName());
+            return stripTypeVariables(new String(mBinding.sourceName()));
         }
 
         @Override
@@ -1167,17 +1556,28 @@
         @Nullable
         @Override
         public ResolvedClass getSuperClass() {
-            if (mBinding instanceof ReferenceBinding) {
-                ReferenceBinding refBinding = (ReferenceBinding) mBinding;
-                ReferenceBinding superClass = refBinding.superclass();
-                if (superClass != null) {
-                    return new EcjResolvedClass(superClass);
-                }
+            ReferenceBinding superClass = mBinding.superclass();
+            if (superClass != null) {
+                return new EcjResolvedClass(superClass);
             }
 
             return null;
         }
 
+        @Override
+        @NonNull
+        public Iterable<ResolvedClass> getInterfaces() {
+            ReferenceBinding[] interfaces = mBinding.superInterfaces();
+            if (interfaces.length == 0) {
+                return Collections.emptyList();
+            }
+            List<ResolvedClass> classes = Lists.newArrayListWithExpectedSize(interfaces.length);
+            for (ReferenceBinding binding : interfaces) {
+                classes.add(new EcjResolvedClass(binding));
+            }
+            return classes;
+        }
+
         @Nullable
         @Override
         public ResolvedClass getContainingClass() {
@@ -1193,16 +1593,40 @@
 
         @Override
         public boolean isSubclassOf(@NonNull String name, boolean strict) {
+            ReferenceBinding cls = (ReferenceBinding) mBinding;
+            if (strict) {
+                cls = cls.superclass();
+            }
+            for (; cls != null; cls = cls.superclass()) {
+                if (equalsCompound(name, cls.compoundName)) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        @Override
+        public boolean isImplementing(@NonNull String name, boolean strict) {
             if (mBinding instanceof ReferenceBinding) {
                 ReferenceBinding cls = (ReferenceBinding) mBinding;
                 if (strict) {
                     cls = cls.superclass();
                 }
-                for (; cls != null; cls = cls.superclass()) {
-                    if (sameChars(name, cls.readableName())) {
-                        return true;
-                    }
+                return isInheritor(cls, name);
+            }
+
+            return false;
+        }
+
+        @Override
+        public boolean isInheritingFrom(@NonNull String name, boolean strict) {
+            if (mBinding instanceof ReferenceBinding) {
+                ReferenceBinding cls = (ReferenceBinding) mBinding;
+                if (strict) {
+                    cls = cls.superclass();
                 }
+                return isInheritor(cls, name);
             }
 
             return false;
@@ -1337,6 +1761,7 @@
                 }
             }
 
+            all = ensureUnique(all);
             return all;
         }
 
@@ -1437,6 +1862,11 @@
         }
 
         @Override
+        public TypeDescriptor getType() {
+            return new EcjTypeDescriptor(mBinding);
+        }
+
+        @Override
         public String getSignature() {
             return getName();
         }
@@ -1444,6 +1874,7 @@
         @Override
         public boolean isInPackage(@NonNull String pkgName, boolean includeSubPackages) {
             PackageBinding pkg = mBinding.getPackage();
+            //noinspection SimplifiableIfStatement
             if (pkg != null) {
                 return includeSubPackages ?
                         startsWithCompound(pkgName, pkg.compoundName) :
@@ -1477,6 +1908,32 @@
         }
     }
 
+    @NonNull
+    private static String stripTypeVariables(String name) {
+        // Strip out type variables; there doesn't seem to be a way to
+        // do it from the ECJ APIs; it unconditionally includes this.
+        // (Converts for example
+        //     android.support.v7.widget.RecyclerView.Adapter<VH>
+        //  to
+        //     android.support.v7.widget.RecyclerView.Adapter
+        if (name.indexOf('<') != -1) {
+            StringBuilder sb = new StringBuilder(name.length());
+            int depth = 0;
+            for (int i = 0, n = name.length(); i < n; i++) {
+                char c = name.charAt(i);
+                if (c == '<') {
+                    depth++;
+                } else if (c == '>') {
+                    depth--;
+                } else if (depth == 0) {
+                    sb.append(c);
+                }
+            }
+            name = sb.toString();
+        }
+        return name;
+    }
+
     // "package-info" as a char
     private static final char[] PACKAGE_INFO_CHARS = new char[] {
             'p', 'a', 'c', 'k', 'a', 'g', 'e', '-', 'i', 'n', 'f', 'o'
@@ -1529,13 +1986,69 @@
                 all.addAll(external);
             }
 
+            all = ensureUnique(all);
             return all;
         }
 
         @Override
+        @Nullable
+        public ResolvedPackage getParentPackage() {
+            char[][] compoundName = mBinding.compoundName;
+            if (compoundName.length == 1) {
+                return null;
+            } else {
+                PackageBinding defaultPackage = mBinding.environment.defaultPackage;
+                PackageBinding packageBinding =
+                        (PackageBinding) defaultPackage.getTypeOrPackage(compoundName[0]);
+                if (packageBinding == null || packageBinding instanceof ProblemPackageBinding) {
+                    return null;
+                }
+
+                for (int i = 1, packageLength = compoundName.length - 1; i < packageLength; i++) {
+                    Binding next = packageBinding.getTypeOrPackage(compoundName[i]);
+                    if (next == null) {
+                        return null;
+                    }
+                    if (next instanceof PackageBinding) {
+                        if (next instanceof ProblemPackageBinding) {
+                            return null;
+                        }
+                        packageBinding = (PackageBinding) next;
+                    }
+                }
+
+                return new EcjResolvedPackage(packageBinding);
+            }
+        }
+
+        @Override
         public int getModifiers() {
             return 0;
         }
+
+        @SuppressWarnings("RedundantIfStatement")
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+
+            EcjResolvedPackage that = (EcjResolvedPackage) o;
+
+            if (mBinding != null ? !mBinding.equals(that.mBinding) : that.mBinding != null) {
+                return false;
+            }
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            return mBinding != null ? mBinding.hashCode() : 0;
+        }
     }
 
     private class EcjResolvedField extends ResolvedField {
@@ -1611,6 +2124,7 @@
         @Override
         public boolean isInPackage(@NonNull String pkgName, boolean includeSubPackages) {
             PackageBinding pkg = mBinding.declaringClass.getPackage();
+            //noinspection SimplifiableIfStatement
             if (pkg != null) {
                 return includeSubPackages ?
                         startsWithCompound(pkgName, pkg.compoundName) :
@@ -1645,9 +2159,9 @@
     }
 
     private class EcjResolvedVariable extends ResolvedVariable {
-        private LocalVariableBinding mBinding;
+        private VariableBinding mBinding;
 
-        private EcjResolvedVariable(LocalVariableBinding binding) {
+        private EcjResolvedVariable(VariableBinding binding) {
             mBinding = binding;
         }
 
@@ -1726,21 +2240,23 @@
     }
 
     private class EcjResolvedAnnotation extends ResolvedAnnotation {
-        private AnnotationBinding mBinding;
+        private final AnnotationBinding mBinding;
+        private final String mName;
 
         private EcjResolvedAnnotation(@NonNull final AnnotationBinding binding) {
             mBinding = binding;
+            mName = new String(mBinding.getAnnotationType().readableName());
         }
 
         @NonNull
         @Override
         public String getName() {
-            return new String(mBinding.getAnnotationType().readableName());
+            return mName;
         }
 
         @Override
         public boolean matches(@NonNull String name) {
-            return sameChars(name, mBinding.getAnnotationType().readableName());
+            return name.equals(mName);
         }
 
         @NonNull
@@ -1777,7 +2293,8 @@
                                 char[] readableName = annotation.getAnnotationType().readableName();
                                 if (sameChars(INT_DEF_ANNOTATION, readableName)
                                         || sameChars(STRING_DEF_ANNOTATION, readableName)) {
-                                    TypeDeclaration typeDeclaration = findTypeDeclaration(getName());
+                                    TypeDeclaration typeDeclaration =
+                                            findAnnotationDeclaration(getName());
                                     if (typeDeclaration != null && typeDeclaration.annotations != null) {
                                         Annotation astAnnotation = null;
                                         for (Annotation a : typeDeclaration.annotations) {
@@ -1793,6 +2310,12 @@
                                             result.add(new EcjAstAnnotation(annotation, astAnnotation));
                                             continue;
                                         }
+                                    } else {
+                                        // Don't record these typedef annotations; without
+                                        // finding the bindings, we'll get the literal values
+                                        // from the ECJ annotation, and that will lead to incorrect
+                                        // typedef warnings.
+                                        continue;
                                     }
                                 }
 
@@ -1804,7 +2327,6 @@
 
                     return Collections.emptyList();
                 }
-
             };
         }
 
@@ -1843,7 +2365,7 @@
 
         @Override
         public String getSignature() {
-            return new String(mBinding.getAnnotationType().readableName());
+            return mName;
         }
 
         @Override
@@ -2027,6 +2549,8 @@
             }
         } else if (value instanceof AnnotationBinding) {
             return new EcjResolvedAnnotation((AnnotationBinding) value);
+        } else if (value instanceof FieldBinding) {
+            return new EcjResolvedField((FieldBinding)value);
         }
 
         return value;
@@ -2061,11 +2585,15 @@
         int index = 0;
         for (int i = 0, n = compoundName.length; i < n; i++) {
             char[] o = compoundName[i];
+            //noinspection ForLoopReplaceableByForEach
             for (int j = 0, m = o.length; j < m; j++) {
                 if (index == length) {
                     return false; // Don't allow prefix in a compound name
                 }
-                if (name.charAt(index) != o[j]) {
+                if (name.charAt(index) != o[j]
+                        // Allow using . as an inner class separator whereas the
+                        // symbol table will always use $
+                        && !(o[j] == '$' && name.charAt(index) == '.')) {
                     return false;
                 }
                 index++;
@@ -2096,11 +2624,15 @@
         int index = 0;
         for (int i = 0, n = compoundName.length; i < n; i++) {
             char[] o = compoundName[i];
+            //noinspection ForLoopReplaceableByForEach
             for (int j = 0, m = o.length; j < m; j++) {
                 if (index == length) {
                     return false; // Don't allow prefix in a compound name
                 }
-                if (name.charAt(index) != o[j]) {
+                if (name.charAt(index) != o[j]
+                        // Allow using . as an inner class separator whereas the
+                        // symbol table will always use $
+                        && !(o[j] == '$' && name.charAt(index) == '.')) {
                     return false;
                 }
                 index++;
@@ -2121,4 +2653,22 @@
 
         return index == length;
     }
+
+    /** Checks whether the given class extends or implements a class with the given name */
+    private static boolean isInheritor(@Nullable ReferenceBinding cls, @NonNull String name) {
+        for (; cls != null; cls = cls.superclass()) {
+            ReferenceBinding[] interfaces = cls.superInterfaces();
+            for (ReferenceBinding binding : interfaces) {
+                if (isInheritor(binding, name)) {
+                    return true;
+                }
+            }
+
+            if (equalsCompound(name, cls.compoundName)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
 }
\ No newline at end of file
diff --git a/lint/cli/src/main/java/com/android/tools/lint/ExternalAnnotationRepository.java b/lint/cli/src/main/java/com/android/tools/lint/ExternalAnnotationRepository.java
index 0cf2eff..df17a3a 100644
--- a/lint/cli/src/main/java/com/android/tools/lint/ExternalAnnotationRepository.java
+++ b/lint/cli/src/main/java/com/android/tools/lint/ExternalAnnotationRepository.java
@@ -21,6 +21,7 @@
 import static com.android.SdkConstants.FN_ANNOTATIONS_ZIP;
 import static com.android.SdkConstants.VALUE_FALSE;
 import static com.android.SdkConstants.VALUE_TRUE;
+import static com.android.tools.lint.checks.SupportAnnotationDetector.CHECK_RESULT_ANNOTATION;
 import static com.android.tools.lint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION;
 
 import com.android.annotations.NonNull;
@@ -470,7 +471,7 @@
                 return m.parameterAnnotations.get(parameterIndex);
             }
 
-            return m.annotations;
+            return null;
         }
 
         @Nullable
@@ -772,7 +773,11 @@
 
         @Nullable
         private FieldInfo findField(@NonNull ResolvedField field) {
-            ClassInfo c = findClass(field.getContainingClass());
+            ResolvedClass containingClass = field.getContainingClass();
+            if (containingClass == null) {
+                return null;
+            }
+            ClassInfo c = findClass(containingClass);
             if (c == null) {
                 return null;
             }
@@ -981,8 +986,10 @@
             if (valueElements.isEmpty()
                     // Permission annotations are sometimes used as marker annotations (on
                     // parameters) but that shouldn't let us conclude that any future
-                    // permission annotations are
-                    && !name.startsWith(PERMISSION_ANNOTATION)) {
+                    // permission annotations are. Ditto for @CheckResult, where we sometimes
+                    // specify a suggestion.
+                    && !name.startsWith(PERMISSION_ANNOTATION)
+                    && !name.equals(CHECK_RESULT_ANNOTATION)) {
                 mMarkerAnnotations.put(name, annotation);
                 return annotation;
             }
@@ -1078,8 +1085,10 @@
             } else if (obj instanceof ResolvedField) {
                 ResolvedField field = (ResolvedField)obj;
                 if (mSignature.endsWith(field.getName())) {
-                    String signature = field.getContainingClass().getSignature() +
-                            "." + field.getName();
+                    ResolvedClass containingClass = field.getContainingClass();
+                    String signature = containingClass != null
+                            ? containingClass.getSignature() + "." + field.getName()
+                            : field.getName();
                     return mSignature.equals(signature);
                 }
                 return false;
@@ -1109,10 +1118,10 @@
             return new DefaultTypeDescriptor(mSignature);
         }
 
-        @NonNull
+        @Nullable
         @Override
         public ResolvedClass getContainingClass() {
-            throw new UnsupportedOperationException();
+            return null;
         }
 
         @Override
@@ -1164,7 +1173,6 @@
     /** For test usage only */
     @VisibleForTesting
     static synchronized void set(ExternalAnnotationRepository singleton) {
-        assert singleton == null || sSingleton == null;
         sSingleton = singleton;
     }
 }
diff --git a/lint/cli/src/main/java/com/android/tools/lint/HtmlReporter.java b/lint/cli/src/main/java/com/android/tools/lint/HtmlReporter.java
index c71b5a0..555b01d 100644
--- a/lint/cli/src/main/java/com/android/tools/lint/HtmlReporter.java
+++ b/lint/cli/src/main/java/com/android/tools/lint/HtmlReporter.java
@@ -354,7 +354,7 @@
                 String adt = "Eclipse/ADT";
                 String studio = "Android Studio/IntelliJ";
                 String tools = adtHasFix && studioHasFix
-                        ? (adt + " & " + studio) : studioHasFix ? studio : adt;
+                        ? (studio + " & " + adt) : studioHasFix ? studio : adt;
                 mWriter.write("Note: This issue has an associated quickfix operation in " + tools);
                 if (mFixUrl != null) {
                     mWriter.write("&nbsp;<img alt=\"Fix\" border=\"0\" align=\"top\" src=\""); //$NON-NLS-1$
diff --git a/lint/cli/src/main/java/com/android/tools/lint/LintCliClient.java b/lint/cli/src/main/java/com/android/tools/lint/LintCliClient.java
index 8bcbedf..91f0ebb 100644
--- a/lint/cli/src/main/java/com/android/tools/lint/LintCliClient.java
+++ b/lint/cli/src/main/java/com/android/tools/lint/LintCliClient.java
@@ -92,13 +92,16 @@
 
     /** Creates a CLI driver */
     public LintCliClient() {
+        super(CLIENT_CLI);
         mFlags = new LintCliFlags();
         TextReporter reporter = new TextReporter(this, mFlags, new PrintWriter(System.out, true),
                 false);
         mFlags.getReporters().add(reporter);
     }
 
-    public LintCliClient(LintCliFlags flags) {
+    /** Creates a CLI driver */
+    public LintCliClient(@NonNull LintCliFlags flags, @NonNull String clientName) {
+        super(clientName);
         mFlags = flags;
     }
 
@@ -381,10 +384,10 @@
                 classes = classPath.getClassFolders();
             }
             if (libraries == null) {
-                libraries = classPath.getLibraries();
+                libraries = classPath.getLibraries(true);
             }
 
-            info = new ClassPathInfo(sources, classes, libraries,
+            info = new ClassPathInfo(sources, classes, libraries, classPath.getLibraries(false),
                     classPath.getTestSourceFolders());
             mProjectInfo.put(project, info);
         }
diff --git a/lint/cli/src/main/java/com/android/tools/lint/Main.java b/lint/cli/src/main/java/com/android/tools/lint/Main.java
index 126d02f..fc3d857 100644
--- a/lint/cli/src/main/java/com/android/tools/lint/Main.java
+++ b/lint/cli/src/main/java/com/android/tools/lint/Main.java
@@ -32,6 +32,7 @@
 import com.android.tools.lint.checks.BuiltinIssueRegistry;
 import com.android.tools.lint.client.api.Configuration;
 import com.android.tools.lint.client.api.IssueRegistry;
+import com.android.tools.lint.client.api.LintClient;
 import com.android.tools.lint.client.api.LintDriver;
 import com.android.tools.lint.detector.api.Category;
 import com.android.tools.lint.detector.api.Context;
@@ -133,7 +134,7 @@
         // When running lint from the command line, warn if the project is a Gradle project
         // since those projects may have custom project configuration that the command line
         // runner won't know about.
-        LintCliClient client = new LintCliClient(mFlags) {
+        LintCliClient client = new LintCliClient(mFlags, LintClient.CLIENT_CLI) {
             @NonNull
             @Override
             protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
@@ -179,8 +180,9 @@
                                @Nullable Location location, @NonNull String message) {
                            // If you've deliberately ignored IssueRegistry.LINT_ERROR
                            // don't flag that one either
-                           if (issue == IssueRegistry.LINT_ERROR && new LintCliClient(mFlags).isSuppressed(
-                                   IssueRegistry.LINT_ERROR)) {
+                           if (issue == IssueRegistry.LINT_ERROR
+                                   && new LintCliClient(mFlags, LintClient.getClientName())
+                                   .isSuppressed(IssueRegistry.LINT_ERROR)) {
                                return true;
                            }
 
diff --git a/lint/cli/src/main/java/com/android/tools/lint/Reporter.java b/lint/cli/src/main/java/com/android/tools/lint/Reporter.java
index 8a0b92b..11ba13d 100644
--- a/lint/cli/src/main/java/com/android/tools/lint/Reporter.java
+++ b/lint/cli/src/main/java/com/android/tools/lint/Reporter.java
@@ -28,8 +28,12 @@
 import com.android.annotations.Nullable;
 import com.android.tools.lint.checks.AccessibilityDetector;
 import com.android.tools.lint.checks.AlwaysShowActionDetector;
+import com.android.tools.lint.checks.AndroidAutoDetector;
+import com.android.tools.lint.checks.AndroidTvDetector;
+import com.android.tools.lint.checks.AnnotationDetector;
 import com.android.tools.lint.checks.ApiDetector;
 import com.android.tools.lint.checks.AppCompatCallDetector;
+import com.android.tools.lint.checks.AppIndexingApiDetector;
 import com.android.tools.lint.checks.ByteOrderMarkDetector;
 import com.android.tools.lint.checks.CommentDetector;
 import com.android.tools.lint.checks.DetectMissingPrefix;
@@ -46,8 +50,11 @@
 import com.android.tools.lint.checks.MissingIdDetector;
 import com.android.tools.lint.checks.NamespaceDetector;
 import com.android.tools.lint.checks.ObsoleteLayoutParamsDetector;
+import com.android.tools.lint.checks.ParcelDetector;
 import com.android.tools.lint.checks.PropertyFileDetector;
 import com.android.tools.lint.checks.PxUsageDetector;
+import com.android.tools.lint.checks.ReadParcelableDetector;
+import com.android.tools.lint.checks.RtlDetector;
 import com.android.tools.lint.checks.ScrollViewChildDetector;
 import com.android.tools.lint.checks.SecurityDetector;
 import com.android.tools.lint.checks.SharedPrefsDetector;
@@ -455,9 +462,20 @@
                 sStudioFixes = Sets.newHashSet(
                         AccessibilityDetector.ISSUE,
                         AlwaysShowActionDetector.ISSUE,
+                        AndroidAutoDetector.INVALID_USES_TAG_ISSUE,
+                        AndroidTvDetector.MISSING_BANNER,
+                        AndroidTvDetector.MISSING_LEANBACK_SUPPORT,
+                        AndroidTvDetector.PERMISSION_IMPLIES_UNSUPPORTED_HARDWARE,
+                        AndroidTvDetector.UNSUPPORTED_TV_HARDWARE,
+                        AnnotationDetector.SWITCH_TYPE_DEF,
                         ApiDetector.INLINED,
+                        ApiDetector.OVERRIDE,
                         ApiDetector.UNSUPPORTED,
+                        ApiDetector.UNUSED,
                         AppCompatCallDetector.ISSUE,
+                        AppIndexingApiDetector.ISSUE_APP_INDEXING,
+                        AppIndexingApiDetector.ISSUE_APP_INDEXING_API,
+                        AppIndexingApiDetector.ISSUE_URL_ERROR,
                         ByteOrderMarkDetector.BOM,
                         CommentDetector.STOP_SHIP,
                         DetectMissingPrefix.MISSING_NAMESPACE,
@@ -465,6 +483,7 @@
                         GradleDetector.COMPATIBILITY,
                         GradleDetector.DEPENDENCY,
                         GradleDetector.DEPRECATED,
+                        GradleDetector.NOT_INTERPOLATED,
                         GradleDetector.PLUS,
                         GradleDetector.REMOTE_VERSION,
                         GradleDetector.STRING_INTEGER,
@@ -483,16 +502,20 @@
                         MissingIdDetector.ISSUE,
                         NamespaceDetector.RES_AUTO,
                         ObsoleteLayoutParamsDetector.ISSUE,
+                        ParcelDetector.ISSUE,
                         PropertyFileDetector.ESCAPE,
                         PropertyFileDetector.HTTP,
                         PxUsageDetector.DP_ISSUE,
                         PxUsageDetector.PX_ISSUE,
+                        ReadParcelableDetector.ISSUE,
+                        RtlDetector.COMPAT,
                         ScrollViewChildDetector.ISSUE,
                         SecurityDetector.EXPORTED_SERVICE,
                         SharedPrefsDetector.ISSUE,
                         SignatureOrSystemDetector.ISSUE,
                         SupportAnnotationDetector.CHECK_PERMISSION,
                         SupportAnnotationDetector.CHECK_RESULT,
+                        SupportAnnotationDetector.MISSING_PERMISSION,
                         TextFieldDetector.ISSUE,
                         TextViewDetector.SELECTABLE,
                         TitleDetector.ISSUE,
diff --git a/lint/cli/src/main/java/com/android/tools/lint/Warning.java b/lint/cli/src/main/java/com/android/tools/lint/Warning.java
index 0a37058..c2dbe30 100644
--- a/lint/cli/src/main/java/com/android/tools/lint/Warning.java
+++ b/lint/cli/src/main/java/com/android/tools/lint/Warning.java
@@ -195,7 +195,7 @@
             return false;
         }
 
-        // This handles the case where you have a huge XML document without hewlines,
+        // This handles the case where you have a huge XML document without newlines,
         // such that all the errors end up on the same line.
         //noinspection RedundantIfStatement
         if (location != null && warning.location != null &&
diff --git a/lint/cli/src/main/java/com/android/tools/lint/XmlReporter.java b/lint/cli/src/main/java/com/android/tools/lint/XmlReporter.java
index 1428d63..2d1ac37 100644
--- a/lint/cli/src/main/java/com/android/tools/lint/XmlReporter.java
+++ b/lint/cli/src/main/java/com/android/tools/lint/XmlReporter.java
@@ -22,6 +22,7 @@
 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.utils.SdkUtils;
 import com.google.common.annotations.Beta;
 import com.google.common.base.Charsets;
 import com.google.common.base.Joiner;
@@ -166,8 +167,8 @@
 
         if (!mClient.getFlags().isQuiet()
                 && (mDisplayEmpty || errorCount > 0 || warningCount > 0)) {
-            String path = mOutput.getAbsolutePath();
-            System.out.println(String.format("Wrote XML report to %1$s", path));
+            String url = SdkUtils.fileToUrlString(mOutput.getAbsoluteFile());
+            System.out.println(String.format("Wrote XML report to %1$s", url));
         }
     }
 
diff --git a/lint/libs/lint-api/lint-api-base.iml b/lint/libs/lint-api/lint-api-base.iml
index 1be0d2e..bb41d17 100644
--- a/lint/libs/lint-api/lint-api-base.iml
+++ b/lint/libs/lint-api/lint-api-base.iml
@@ -17,6 +17,5 @@
     <orderEntry type="module" module-name="common" />
     <orderEntry type="module" module-name="layoutlib-api-base" />
     <orderEntry type="module" module-name="sdk-common-base" />
-    <orderEntry type="module" module-name="sdklib-base" />
   </component>
 </module>
\ No newline at end of file
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/IssueRegistry.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/IssueRegistry.java
index 09e70dc..d7481f9 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/IssueRegistry.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/IssueRegistry.java
@@ -27,6 +27,7 @@
 import com.android.tools.lint.detector.api.Severity;
 import com.google.common.annotations.Beta;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -45,8 +46,8 @@
  */
 @Beta
 public abstract class IssueRegistry {
-    private static List<Category> sCategories;
-    private static Map<String, Issue> sIdToIssue;
+    private static volatile List<Category> sCategories;
+    private static volatile Map<String, Issue> sIdToIssue;
     private static Map<EnumSet<Scope>, List<Issue>> sScopeIssues = Maps.newHashMap();
 
     /**
@@ -272,19 +273,31 @@
      *
      * @return an iterator for all the categories, never null
      */
+    @SuppressWarnings("AssignmentToStaticFieldFromInstanceMethod")
     @NonNull
     public List<Category> getCategories() {
-        if (sCategories == null) {
-            final Set<Category> categories = new HashSet<Category>();
-            for (Issue issue : getIssues()) {
-                categories.add(issue.getCategory());
+        List<Category> categories = sCategories;
+        if (categories == null) {
+            synchronized (IssueRegistry.class) {
+                categories = sCategories;
+                if (categories == null) {
+                    sCategories = categories = Collections.unmodifiableList(createCategoryList());
+                }
             }
-            List<Category> sorted = new ArrayList<Category>(categories);
-            Collections.sort(sorted);
-            sCategories = Collections.unmodifiableList(sorted);
         }
 
-        return sCategories;
+        return categories;
+    }
+
+    @NonNull
+    private List<Category> createCategoryList() {
+        Set<Category> categorySet = Sets.newHashSetWithExpectedSize(20);
+        for (Issue issue : getIssues()) {
+            categorySet.add(issue.getCategory());
+        }
+        List<Category> sorted = new ArrayList<Category>(categorySet);
+        Collections.sort(sorted);
+        return sorted;
     }
 
     /**
@@ -293,27 +306,39 @@
      * @param id the id to be checked
      * @return the corresponding issue, or null
      */
+    @SuppressWarnings("AssignmentToStaticFieldFromInstanceMethod")
     @Nullable
     public final Issue getIssue(@NonNull String id) {
-        if (sIdToIssue == null) {
-            List<Issue> issues = getIssues();
-            sIdToIssue = new HashMap<String, Issue>(issues.size());
-            for (Issue issue : issues) {
-                sIdToIssue.put(issue.getId(), issue);
+        Map<String, Issue> map = sIdToIssue;
+        if (map == null) {
+            synchronized (IssueRegistry.class) {
+                map = sIdToIssue;
+                if (map == null) {
+                    map = createIdToIssueMap();
+                    sIdToIssue = map;
+                }
             }
-
-            sIdToIssue.put(PARSER_ERROR.getId(), PARSER_ERROR);
-            sIdToIssue.put(LINT_ERROR.getId(), LINT_ERROR);
         }
-        return sIdToIssue.get(id);
+
+        return map.get(id);
+    }
+
+    @NonNull
+    private Map<String, Issue> createIdToIssueMap() {
+        List<Issue> issues = getIssues();
+        Map<String, Issue> map = Maps.newHashMapWithExpectedSize(issues.size() + 2);
+        for (Issue issue : issues) {
+            map.put(issue.getId(), issue);
+        }
+
+        map.put(PARSER_ERROR.getId(), PARSER_ERROR);
+        map.put(LINT_ERROR.getId(), LINT_ERROR);
+        return map;
     }
 
     /**
      * Reset the registry such that it recomputes its available issues.
-     * <p>
-     * NOTE: This is only intended for testing purposes.
      */
-    @VisibleForTesting
     protected static void reset() {
         sIdToIssue = null;
         sCategories = null;
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JarFileIssueRegistry.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JarFileIssueRegistry.java
index 0262691..b39adec 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JarFileIssueRegistry.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JarFileIssueRegistry.java
@@ -15,6 +15,8 @@
  */
 package com.android.tools.lint.client.api;
 
+import static com.android.SdkConstants.DOT_CLASS;
+
 import com.android.annotations.NonNull;
 import com.android.tools.lint.detector.api.Issue;
 import com.android.tools.lint.detector.api.Severity;
@@ -22,8 +24,11 @@
 import com.google.common.collect.Lists;
 
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.lang.ref.SoftReference;
+import java.lang.reflect.Method;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.HashMap;
@@ -31,7 +36,9 @@
 import java.util.Map;
 import java.util.jar.Attributes;
 import java.util.jar.JarFile;
+import java.util.jar.JarInputStream;
 import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
 
 /**
  * <p> An {@link IssueRegistry} for a custom lint rule jar file. The rule jar should provide a
@@ -67,6 +74,9 @@
             }
         }
 
+        // Ensure that the scope-to-detector map doesn't return stale results
+        IssueRegistry.reset();
+
         JarFileIssueRegistry registry = new JarFileIssueRegistry(client, jarFile);
         sCache.put(jarFile, new SoftReference<JarFileIssueRegistry>(registry));
         return registry;
@@ -78,6 +88,7 @@
         myIssues = Lists.newArrayList();
         JarFile jarFile = null;
         try {
+            //noinspection IOResourceOpenedButNotSafelyClosed
             jarFile = new JarFile(file);
             Manifest manifest = jarFile.getManifest();
             Attributes attrs = manifest.getMainAttributes();
@@ -86,11 +97,15 @@
                 String className = (String) object;
                 // Make a class loader for this jar
                 URL url = SdkUtils.fileToUrl(file);
-                URLClassLoader loader = new URLClassLoader(new URL[]{url},
+                ClassLoader loader = client.createUrlClassLoader(new URL[]{url},
                         JarFileIssueRegistry.class.getClassLoader());
                 Class<?> registryClass = Class.forName(className, true, loader);
                 IssueRegistry registry = (IssueRegistry) registryClass.newInstance();
                 myIssues.addAll(registry.getIssues());
+
+                if (loader instanceof URLClassLoader) {
+                    loadAndCloseURLClassLoader(client, file, (URLClassLoader)loader);
+                }
             } else {
                 client.log(Severity.ERROR, null,
                     "Custom lint rule jar %1$s does not contain a valid registry manifest key " +
@@ -105,6 +120,79 @@
         }
     }
 
+    /**
+     * Work around http://bugs.java.com/bugdatabase/view_bug.do?bug_id=5041014 :
+     * URLClassLoader, on Windows, locks the .jar file forever.
+     * As of Java 7, there's a workaround: you can call close() when you're "done"
+     * with the file. We'll do that here. However, the whole point of the
+     * {@linkplain JarFileIssueRegistry} is that when lint is run over and over again
+     * as the user is editing in the IDE and we're background checking the code, we
+     * don't to keep loading the custom view classes over and over again: we want to
+     * cache them. Therefore, just closing the URLClassLoader right away isn't great
+     * either. However, it turns out it's safe to close the URLClassLoader once you've
+     * loaded the classes you need, since the URLClassLoader will continue to serve
+     * those classes even after its close() methods has been called.
+     * <p>
+     * Therefore, if we can call close() on this URLClassLoader, we'll proactively load
+     * all class files we find in the .jar file, then close it.
+     *
+     * @param client the client to report errors to
+     * @param file the .jar file
+     * @param loader the URLClassLoader we should close
+     */
+    private static void loadAndCloseURLClassLoader(
+            @NonNull LintClient client,
+            @NonNull File file,
+            @NonNull URLClassLoader loader) {
+        try {
+            // Proactively close out the .jar file. This is only available on Java 7.
+            Method closeMethod = loader.getClass().getDeclaredMethod("close");
+
+            // But first, proactively load all classes:
+            try {
+                InputStream inputStream = new FileInputStream(file);
+                try {
+                    JarInputStream jarInputStream = new JarInputStream(inputStream);
+                    try {
+                        ZipEntry entry = jarInputStream.getNextEntry();
+                        while (entry != null) {
+                            String name = entry.getName();
+                            // Load non-inner-classes
+                            if (name.endsWith(DOT_CLASS) && name.indexOf('$') == -1) {
+                                // Strip .class suffix and change .jar file path (/)
+                                // to class name (.'s).
+                                name = name.substring(0,
+                                        name.length() - DOT_CLASS.length());
+                                name = name.replace('/', '.');
+                                try {
+                                    Class.forName(name, true, loader);
+                                } catch (Throwable e) {
+                                    client.log(Severity.ERROR, e,
+                                            "Failed to prefetch " + name + " from " + file);
+                                }
+                            }
+                            entry = jarInputStream.getNextEntry();
+                        }
+                    } finally {
+                        jarInputStream.close();
+                    }
+                } finally {
+                    inputStream.close();
+                }
+            } catch (Throwable ignore) {
+            } finally {
+                // Finally close the URL class loader
+                try {
+                    closeMethod.invoke(loader);
+                } catch (Throwable ignore) {
+                    // Couldn't close. This is unlikely.
+                }
+            }
+        } catch (NoSuchMethodException ignore) {
+            // No close method - we're on 1.6
+        }
+    }
+
     @NonNull
     @Override
     public List<Issue> getIssues() {
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaParser.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaParser.java
index f150eb2..4ce10dc 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaParser.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaParser.java
@@ -20,20 +20,30 @@
 
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
+import com.android.tools.lint.detector.api.ClassContext;
 import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.DefaultPosition;
 import com.android.tools.lint.detector.api.JavaContext;
 import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Position;
 import com.google.common.annotations.Beta;
 import com.google.common.base.Splitter;
 
 import java.util.Collections;
 import java.util.List;
 
+import lombok.ast.Catch;
+import lombok.ast.For;
 import lombok.ast.Identifier;
+import lombok.ast.If;
 import lombok.ast.Node;
+import lombok.ast.Return;
 import lombok.ast.StrictListAccessor;
+import lombok.ast.Switch;
+import lombok.ast.Throw;
 import lombok.ast.TypeReference;
 import lombok.ast.TypeReferencePart;
+import lombok.ast.While;
 
 /**
  * A wrapper for a Java parser. This allows tools integrating lint to map directly
@@ -85,13 +95,70 @@
      * Returns a {@link Location} for the given node
      *
      * @param context information about the file being parsed
-     * @param node the node to create a location for
+     * @param node    the node to create a location for
      * @return a location for the given node
      */
     @NonNull
     public abstract Location getLocation(@NonNull JavaContext context, @NonNull Node node);
 
     /**
+     * Returns a {@link Location} for the given node range (from the starting offset of the first
+     * node to the ending offset of the second node).
+     *
+     * @param from      the AST node to get a starting location from
+     * @param fromDelta Offset delta to apply to the starting offset
+     * @param to        the AST node to get a ending location from
+     * @param toDelta   Offset delta to apply to the ending offset
+     * @return a location for the given node
+     */
+    @NonNull
+    public abstract Location getRangeLocation(
+            @NonNull JavaContext context,
+            @NonNull Node from,
+            int fromDelta,
+            @NonNull Node to,
+            int toDelta);
+
+    /**
+     * Returns a {@link Location} for the given node. This attempts to pick a shorter
+     * location range than the entire node; for a class or method for example, it picks
+     * the name node (if found). For statement constructs such as a {@code switch} statement
+     * it will highlight the keyword, etc.
+     *
+     * @param context information about the file being parsed
+     * @param node the node to create a location for
+     * @return a location for the given node
+     */
+    @NonNull
+    public Location getNameLocation(@NonNull JavaContext context, @NonNull Node node) {
+        Node nameNode = JavaContext.findNameNode(node);
+        if (nameNode != null) {
+            node = nameNode;
+        } else {
+            if (node instanceof Switch
+                    || node instanceof For
+                    || node instanceof If
+                    || node instanceof While
+                    || node instanceof Throw
+                    || node instanceof Return) {
+                // Lint doesn't want to highlight the entire statement/block associated
+                // with this node, it wants to just highlight the keyword.
+                Location location = getLocation(context, node);
+                Position start = location.getStart();
+                if (start != null) {
+                    // The Lombok classes happen to have the same length as the target keyword
+                    int length = node.getClass().getSimpleName().length();
+                    return Location.create(location.getFile(), start,
+                            new DefaultPosition(start.getLine(), start.getColumn() + length,
+                                    start.getOffset() + length));
+                }
+            }
+        }
+
+        return getLocation(context, node);
+    }
+
+    /**
      * Creates a light-weight handle to a location for the given node. It can be
      * turned into a full fledged location by
      * {@link com.android.tools.lint.detector.api.Location.Handle#resolve()}.
@@ -148,6 +215,19 @@
     }
 
     /**
+     * Returns the set of exception types handled by the given catch block.
+     * <p>
+     * This is a workaround for the fact that the Lombok AST API (and implementation)
+     * doesn't support multi-catch statements.
+     */
+    public List<TypeDescriptor> getCatchTypes(@NonNull JavaContext context,
+            @NonNull Catch catchBlock) {
+        TypeReference typeReference = catchBlock.astExceptionDeclaration().astTypeReference();
+        return Collections.<TypeDescriptor>singletonList(new DefaultTypeDescriptor(
+                typeReference.getTypeName()));
+    }
+
+    /**
      * Gets the type of the given node
      *
      * @param context information about the file being parsed
@@ -164,6 +244,19 @@
          * */
         @NonNull public abstract String getName();
 
+        /** Returns the simple name of this class */
+        @NonNull
+        public String getSimpleName() {
+            // This doesn't handle inner classes properly, so subclasses with more
+            // accurate type information will override to handle it correctly.
+            String name = getName();
+            int index = name.lastIndexOf('.');
+            if (index != -1) {
+                return name.substring(index + 1);
+            }
+            return name;
+        }
+
         /**
          * Returns the full signature of the type, which is normally the same as {@link #getName()}
          * but for arrays can include []'s, for generic methods can include generics parameters
@@ -171,8 +264,31 @@
          */
         @NonNull public abstract String getSignature();
 
+        /**
+         * Computes the internal class name of the given fully qualified class name.
+         * For example, it converts foo.bar.Foo.Bar into foo/bar/Foo$Bar.
+         * This should only be called for class types, not primitives.
+         *
+         * @return the internal class name
+         */
+        @NonNull public String getInternalName() {
+            return ClassContext.getInternalName(getName());
+        }
+
         public abstract boolean matchesName(@NonNull String name);
 
+        /**
+         * Returns true if the given TypeDescriptor represents an array
+         * @return true if this type represents an array
+         */
+        public abstract boolean isArray();
+
+        /**
+         * Returns true if the given TypeDescriptor represents a primitive
+         * @return true if this type represents a primitive
+         */
+        public abstract boolean isPrimitive();
+
         public abstract boolean matchesSignature(@NonNull String signature);
 
         @NonNull
@@ -194,6 +310,10 @@
         @Override
         public abstract boolean equals(Object o);
 
+        @Override
+        public String toString() {
+            return getName();
+        }
     }
 
     /** Convenience implementation of {@link TypeDescriptor} */
@@ -223,6 +343,16 @@
         }
 
         @Override
+        public boolean isArray() {
+            return mName.endsWith("[]");
+        }
+
+        @Override
+        public boolean isPrimitive() {
+            return mName.indexOf('.') != -1;
+        }
+
+        @Override
         public boolean matchesSignature(@NonNull String signature) {
             return matchesName(signature);
         }
@@ -306,6 +436,20 @@
         public boolean isInPackage(@NonNull String pkg, boolean includeSubPackages) {
             return getSignature().startsWith(pkg);
         }
+
+        /**
+         * Attempts to find the corresponding AST node, if possible. This won't work if for example
+         * the resolved node is from a binary (such as a compiled class in a .jar) or if the
+         * underlying parser doesn't support it.
+         * <p>
+         * Note that looking up the AST node can result in different instances for each lookup.
+         *
+         * @return an AST node, if possible.
+         */
+        @Nullable
+        public Node findAstNode() {
+            return null;
+        }
     }
 
     /** A resolved class declaration (class, interface, enumeration or annotation) */
@@ -315,7 +459,7 @@
         @NonNull
         public abstract String getName();
 
-        /** Returns the simple of this class */
+        /** Returns the simple name of this class */
         @NonNull
         public abstract String getSimpleName();
 
@@ -336,6 +480,9 @@
         @Nullable
         public abstract ResolvedClass getSuperClass();
 
+        @NonNull
+        public abstract Iterable<ResolvedClass> getInterfaces();
+
         @Nullable
         public abstract ResolvedClass getContainingClass();
 
@@ -346,6 +493,10 @@
         /**
          * Determines whether this class extends the given name. If strict is true,
          * it will not consider C extends C true.
+         * <p>
+         * The target must be a class; to check whether this class extends an interface,
+         * use {@link #isImplementing(String,boolean)} instead. If you're not sure, use
+         * {@link #isInheritingFrom(String, boolean)}.
          *
          * @param name the fully qualified class name
          * @param strict if true, do not consider a class to be extending itself
@@ -353,6 +504,33 @@
          */
         public abstract boolean isSubclassOf(@NonNull String name, boolean strict);
 
+        /**
+         * Determines whether this is implementing the given interface.
+         * <p>
+         * The target must be an interface; to check whether this class extends a class,
+         * use {@link #isSubclassOf(String, boolean)} instead. If you're not sure, use
+         * {@link #isInheritingFrom(String, boolean)}.
+         *
+         * @param name the fully qualified interface name
+         * @param strict if true, do not consider a class to be extending itself
+         * @return true if this class implements the given interface
+         */
+        public abstract boolean isImplementing(@NonNull String name, boolean strict);
+
+        /**
+         * Determines whether this class extends or implements the class of the given name.
+         * If strict is true, it will not consider C extends C true.
+         * <p>
+         * For performance reasons, if you know that the target is a class, consider using
+         * {@link #isSubclassOf(String, boolean)} instead, and if the target is an interface,
+         * consider using {@link #isImplementing(String,boolean)}.
+         *
+         * @param name the fully qualified class name
+         * @param strict if true, do not consider a class to be inheriting from itself
+         * @return true if this class extends or implements the given class
+         */
+        public abstract boolean isInheritingFrom(@NonNull String name, boolean strict);
+
         @NonNull
         public abstract Iterable<ResolvedMethod> getConstructors();
 
@@ -497,19 +675,26 @@
         @NonNull
         public abstract TypeDescriptor getType();
 
-        @NonNull
+        @Nullable
         public abstract ResolvedClass getContainingClass();
 
         @Nullable
         public abstract Object getValue();
 
+        @Nullable
         public String getContainingClassName() {
-            return getContainingClass().getName();
+            ResolvedClass containingClass = getContainingClass();
+            return containingClass != null ? containingClass.getName() : null;
         }
 
         @Override
         public boolean isInPackage(@NonNull String pkg, boolean includeSubPackages) {
-            String packageName = getContainingClass().getPackageName();
+            ResolvedClass containingClass = getContainingClass();
+            if (containingClass == null) {
+                return false;
+            }
+
+            String packageName = containingClass.getPackageName();
 
             //noinspection SimplifiableIfStatement
             if (pkg.equals(packageName)) {
@@ -579,6 +764,10 @@
 
     /** A package declaration */
     public abstract static class ResolvedPackage extends ResolvedNode {
+        /** Returns the parent package of this package, if any. */
+        @Nullable
+        public abstract ResolvedPackage getParentPackage();
+
         @NonNull
         @Override
         public Iterable<ResolvedAnnotation> getAnnotations() {
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaVisitor.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaVisitor.java
index fbce577..2621fc9 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaVisitor.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaVisitor.java
@@ -20,6 +20,8 @@
 import static com.android.SdkConstants.R_CLASS;
 
 import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
 import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
 import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
 import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
@@ -285,6 +287,16 @@
                 return;
             }
 
+            if (e.getClass().getSimpleName().equals("IndexNotReadyException")) {
+                // Attempting to access PSI during startup before indices are ready; ignore these.
+                // See http://b.android.com/176644 for an example.
+                return;
+            } else if (e.getClass().getSimpleName().equals("ProcessCanceledException")) {
+                // Cancelling inspections in the IDE
+                context.getDriver().cancel();
+                return;
+            }
+
             // Work around ECJ bugs; see https://code.google.com/p/android/issues/detail?id=172268
             // Don't allow lint bugs to take down the whole build. TRY to log this as a
             // lint error instead!
@@ -297,7 +309,7 @@
             int count = 0;
             for (StackTraceElement frame : stackTrace) {
                 if (count > 0) {
-                    sb.append("->");
+                    sb.append("<-");
                 }
 
                 String className = frame.getClassName();
@@ -322,6 +334,24 @@
         }
     }
 
+    /**
+     * For testing only: returns the number of exceptions thrown during Java AST analysis
+     *
+     * @return the number of internal errors found
+     */
+    @VisibleForTesting
+    public static int getCrashCount() {
+        return sExceptionCount;
+    }
+
+    /**
+     * For testing only: clears the crash counter
+     */
+    @VisibleForTesting
+    public static void clearCrashCount() {
+        sExceptionCount = 0;
+    }
+
     public void prepare(@NonNull List<JavaContext> contexts) {
         mParser.prepareJavaParse(contexts);
     }
@@ -330,6 +360,29 @@
         mParser.dispose();
     }
 
+    @Nullable
+    private static Set<String> getInterfaceNames(
+            @Nullable Set<String> addTo,
+            @NonNull ResolvedClass cls) {
+        Iterable<ResolvedClass> interfaces = cls.getInterfaces();
+        for (ResolvedClass resolvedInterface : interfaces) {
+            String name = resolvedInterface.getName();
+            if (addTo == null) {
+                addTo = Sets.newHashSet();
+            } else if (addTo.contains(name)) {
+                // Superclasses can explicitly implement the same interface,
+                // so keep track of visited interfaces as we traverse up the
+                // super class chain to avoid checking the same interface
+                // more than once.
+                continue;
+            }
+            addTo.add(name);
+            getInterfaceNames(addTo, resolvedInterface);
+        }
+
+        return addTo;
+    }
+
     private static class VisitingDetector {
         private AstVisitor mVisitor; // construct lazily, and clear out on context switch!
         private JavaContext mContext;
@@ -388,6 +441,7 @@
 
             ResolvedClass resolvedClass = (ResolvedClass) resolved;
             ResolvedClass cls = resolvedClass;
+            int depth = 0;
             while (cls != null) {
                 List<VisitingDetector> list = mSuperClassDetectors.get(cls.getName());
                 if (list != null) {
@@ -396,7 +450,28 @@
                     }
                 }
 
+                // Check interfaces too
+                Set<String> interfaceNames = getInterfaceNames(null, cls);
+                if (interfaceNames != null) {
+                    for (String name : interfaceNames) {
+                        list = mSuperClassDetectors.get(name);
+                        if (list != null) {
+                            for (VisitingDetector v : list) {
+                                v.getJavaScanner().checkClass(mContext, node, node,
+                                        resolvedClass);
+                            }
+                        }
+                    }
+                }
+
                 cls = cls.getSuperClass();
+                depth++;
+                if (depth == 500) {
+                    // Shouldn't happen in practice; this prevents the IDE from
+                    // hanging if the user has accidentally typed in an incorrect
+                    // super class which creates a cycle.
+                    break;
+                }
             }
 
             return false;
@@ -406,10 +481,7 @@
         public boolean visitConstructorInvocation(ConstructorInvocation node) {
             NormalTypeBody anonymous = node.astAnonymousClassBody();
             if (anonymous != null) {
-                ResolvedNode resolved = mContext.resolve(anonymous);
-                if (resolved instanceof ResolvedMethod) {
-                    resolved = ((ResolvedMethod) resolved).getContainingClass();
-                }
+                ResolvedNode resolved = mContext.resolve(node.astTypeReference());
                 if (!(resolved instanceof ResolvedClass)) {
                     return true;
                 }
@@ -425,6 +497,20 @@
                         }
                     }
 
+                    // Check interfaces too
+                    Set<String> interfaceNames = getInterfaceNames(null, cls);
+                    if (interfaceNames != null) {
+                        for (String name : interfaceNames) {
+                            list = mSuperClassDetectors.get(name);
+                            if (list != null) {
+                                for (VisitingDetector v : list) {
+                                    v.getJavaScanner().checkClass(mContext, null, anonymous,
+                                            resolvedClass);
+                                }
+                            }
+                        }
+                    }
+
                     cls = cls.getSuperClass();
                 }
             }
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintClient.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintClient.java
old mode 100755
new mode 100644
index 473c95e..0e413a1
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintClient.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintClient.java
@@ -19,6 +19,7 @@
 import static com.android.SdkConstants.CLASS_FOLDER;
 import static com.android.SdkConstants.DOT_AAR;
 import static com.android.SdkConstants.DOT_JAR;
+import static com.android.SdkConstants.FD_ASSETS;
 import static com.android.SdkConstants.GEN_FOLDER;
 import static com.android.SdkConstants.LIBS_FOLDER;
 import static com.android.SdkConstants.RES_FOLDER;
@@ -34,6 +35,7 @@
 import com.android.ide.common.res2.AbstractResourceRepository;
 import com.android.ide.common.res2.ResourceItem;
 import com.android.prefs.AndroidLocation;
+import com.android.sdklib.BuildToolInfo;
 import com.android.sdklib.IAndroidTarget;
 import com.android.sdklib.SdkVersionInfo;
 import com.android.sdklib.repository.local.LocalSdk;
@@ -60,6 +62,7 @@
 import java.io.IOException;
 import java.net.HttpURLConnection;
 import java.net.URL;
+import java.net.URLClassLoader;
 import java.net.URLConnection;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -81,6 +84,14 @@
 public abstract class LintClient {
     private static final String PROP_BIN_DIR  = "com.android.tools.lint.bindir";  //$NON-NLS-1$
 
+    protected LintClient(@NonNull String clientName) {
+        sClientName = clientName;
+    }
+
+    protected LintClient() {
+        sClientName = "unknown";
+    }
+
     /**
      * Returns a configuration for use by the given project. The configuration
      * provides information about which issues are enabled, any customizations
@@ -236,12 +247,15 @@
     /**
      * Returns the list of Java libraries
      *
-     * @param project the project to look up jar dependencies for
+     * @param project         the project to look up jar dependencies for
+     * @param includeProvided If true, included provided libraries too (libraries that are not
+     *                        packaged with the app, but are provided for compilation purposes and
+     *                        are assumed to be present in the running environment)
      * @return a list of jar dependencies containing .class files
      */
     @NonNull
-    public List<File> getJavaLibraries(@NonNull Project project) {
-        return getClassPath(project).getLibraries();
+    public List<File> getJavaLibraries(@NonNull Project project, boolean includeProvided) {
+        return getClassPath(project).getLibraries(includeProvided);
     }
 
     /**
@@ -272,6 +286,22 @@
     }
 
     /**
+     * Returns the asset folders.
+     *
+     * @param project the project to look up the asset folder for
+     * @return a list of files pointing to the asset folders, possibly empty
+     */
+    @NonNull
+    public List<File> getAssetFolders(@NonNull Project project) {
+        File assets = new File(project.getDir(), FD_ASSETS);
+        if (assets.exists()) {
+            return Collections.singletonList(assets);
+        }
+
+        return Collections.emptyList();
+    }
+
+    /**
      * Returns the {@link SdkInfo} to use for the given project.
      *
      * @param project the project to look up an {@link SdkInfo} for
@@ -418,16 +448,19 @@
         private final List<File> mClassFolders;
         private final List<File> mSourceFolders;
         private final List<File> mLibraries;
+        private final List<File> mNonProvidedLibraries;
         private final List<File> mTestFolders;
 
         public ClassPathInfo(
                 @NonNull List<File> sourceFolders,
                 @NonNull List<File> classFolders,
                 @NonNull List<File> libraries,
+                @NonNull List<File> nonProvidedLibraries,
                 @NonNull List<File> testFolders) {
             mSourceFolders = sourceFolders;
             mClassFolders = classFolders;
             mLibraries = libraries;
+            mNonProvidedLibraries = nonProvidedLibraries;
             mTestFolders = testFolders;
         }
 
@@ -442,8 +475,8 @@
         }
 
         @NonNull
-        public List<File> getLibraries() {
-            return mLibraries;
+        public List<File> getLibraries(boolean includeProvided) {
+            return includeProvided ? mLibraries : mNonProvidedLibraries;
         }
 
         public List<File> getTestSourceFolders() {
@@ -576,7 +609,7 @@
                 }
             }
 
-            info = new ClassPathInfo(sources, classes, libraries, tests);
+            info = new ClassPathInfo(sources, classes, libraries, libraries, tests);
             mProjectInfo.put(project, info);
         }
 
@@ -705,9 +738,9 @@
     @NonNull
     public IAndroidTarget[] getTargets() {
         if (mTargets == null) {
-            LocalSdk localSdk = getSdk();
-            if (localSdk != null) {
-                mTargets = localSdk.getTargets();
+            LocalSdk sdkHandler = getSdk();
+            if (sdkHandler != null) {
+                mTargets = sdkHandler.getTargets();
             } else {
                 mTargets = new IAndroidTarget[0];
             }
@@ -725,12 +758,12 @@
      */
     @Nullable
     public LocalSdk getSdk() {
-         if (mSdk == null) {
-             File sdkHome = getSdkHome();
-             if (sdkHome != null) {
-                 mSdk = new LocalSdk(sdkHome);
-             }
-         }
+        if (mSdk == null) {
+            File sdkHome = getSdkHome();
+            if (sdkHome != null) {
+                mSdk = new LocalSdk(sdkHome);
+            }
+        }
 
         return mSdk;
     }
@@ -777,6 +810,23 @@
     }
 
     /**
+     * Returns the specific version of the build tools being used for the given project, if known
+     *
+     * @param project the project in question
+     *
+     * @return the build tools version in use by the project, or null if not known
+     */
+    @Nullable
+    public BuildToolInfo getBuildTools(@NonNull Project project) {
+        LocalSdk sdk = getSdk();
+        // Build systems like Eclipse and ant just use the latest available
+        // build tools, regardless of project metadata. In Gradle, this
+        // method is overridden to use the actual build tools specified in the
+        // project.
+        return sdk != null ? sdk.getLatestBuildTool() : null;
+    }
+
+    /**
      * Returns the super class for the given class name, which should be in VM
      * format (e.g. java/lang/Integer, not java.lang.Integer, and using $ rather
      * than . for inner classes). If the super class is not known, returns null.
@@ -827,7 +877,7 @@
      */
     @NonNull
     public Map<String, String> createSuperClassMap(@NonNull Project project) {
-        List<File> libraries = project.getJavaLibraries();
+        List<File> libraries = project.getJavaLibraries(true);
         List<File> classFolders = project.getJavaClassFolders();
         List<ClassEntry> classEntries = ClassEntry.fromClassPath(this, classFolders, true);
         if (libraries.isEmpty()) {
@@ -1038,6 +1088,17 @@
     }
 
     /**
+     * Creates a {@link ClassLoader} which can load in a set of Jar files.
+     *
+     * @param urls the URLs
+     * @param parent the parent class loader
+     * @return a new class loader
+     */
+    public ClassLoader createUrlClassLoader(@NonNull URL[] urls, @NonNull ClassLoader parent) {
+        return new URLClassLoader(urls, parent);
+    }
+
+    /**
      * Returns true if this client supports project resource repository lookup via
      * {@link #getProjectResources(Project,boolean)}
      *
@@ -1085,4 +1146,63 @@
         }
         return mResourceVisibility;
     }
+
+    /**
+     * The client name returned by {@link #getClientName()} when running in
+     * Android Studio/IntelliJ IDEA
+     */
+    public static final String CLIENT_STUDIO = "studio";
+
+    /**
+     * The client name returned by {@link #getClientName()} when running in
+     * Gradle
+     */
+    public static final String CLIENT_GRADLE = "gradle";
+
+    /**
+     * The client name returned by {@link #getClientName()} when running in
+     * the CLI (command line interface) version of lint, {@code lint}
+     */
+    public static final String CLIENT_CLI = "cli";
+
+    /**
+     * The client name returned by {@link #getClientName()} when running in
+     * some unknown client
+     */
+    public static final String CLIENT_UNKNOWN = "unknown";
+
+    /** The client name. */
+    @NonNull
+    private static String sClientName = CLIENT_UNKNOWN;
+
+    /**
+     * Returns the name of the embedding client. It could be not just
+     * {@link #CLIENT_STUDIO}, {@link #CLIENT_GRADLE}, {@link #CLIENT_CLI}
+     * etc but other values too as lint is integrated in other embedding contexts.
+     *
+     * @return the name of the embedding client
+     */
+    @NonNull
+    public static String getClientName() {
+        return sClientName;
+    }
+
+    /**
+     * Returns true if the embedding client currently running lint is Android Studio
+     * (or IntelliJ IDEA)
+     *
+     * @return true if running in Android Studio / IntelliJ IDEA
+     */
+    public static boolean isStudio() {
+        return CLIENT_STUDIO.equals(sClientName);
+    }
+
+    /**
+     * Returns true if the embedding client currently running lint is Gradle
+     *
+     * @return true if running in Gradle
+     */
+    public static boolean isGradle() {
+        return CLIENT_GRADLE.equals(sClientName);
+    }
 }
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintDriver.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintDriver.java
index b30cdb8..4b6971c 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintDriver.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintDriver.java
@@ -35,9 +35,11 @@
 
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
+import com.android.ide.common.repository.ResourceVisibilityLookup;
 import com.android.ide.common.res2.AbstractResourceRepository;
 import com.android.ide.common.res2.ResourceItem;
 import com.android.resources.ResourceFolderType;
+import com.android.sdklib.BuildToolInfo;
 import com.android.sdklib.IAndroidTarget;
 import com.android.sdklib.repository.local.LocalSdk;
 import com.android.tools.lint.client.api.LintListener.EventType;
@@ -101,6 +103,7 @@
 
 import lombok.ast.Annotation;
 import lombok.ast.AnnotationElement;
+import lombok.ast.AnnotationMethodDeclaration;
 import lombok.ast.AnnotationValue;
 import lombok.ast.ArrayInitializer;
 import lombok.ast.ConstructorDeclaration;
@@ -641,6 +644,7 @@
             List<Detector> resourceFileDetectors = mScopeDetectors.get(Scope.RESOURCE_FILE);
             if (resourceFileDetectors != null) {
                 for (Detector detector : resourceFileDetectors) {
+                    // This is wrong; it should allow XmlScanner instead of ResourceXmlScanner!
                     assert detector instanceof ResourceXmlDetector : detector;
                 }
             }
@@ -1254,7 +1258,7 @@
         // the parent chains (such that for example for a virtual dispatch, we can
         // also check the super classes).
 
-        List<File> libraries = project.getJavaLibraries();
+        List<File> libraries = project.getJavaLibraries(false);
         List<ClassEntry> libraryEntries = ClassEntry.fromClassPath(mClient, libraries, true);
 
         List<File> classFolders = project.getJavaClassFolders();
@@ -1471,7 +1475,7 @@
             }
         }
         // Search in the libraries
-        for (File root : mClient.getJavaLibraries(project)) {
+        for (File root : mClient.getJavaLibraries(project, true)) {
             // TODO: Handle .jar files!
             //if (root.getPath().endsWith(DOT_JAR)) {
             //}
@@ -1717,7 +1721,8 @@
                             visitor.getParser());
                     fireEvent(EventType.SCANNING_FILE, context);
                     visitor.visitFile(context, file);
-                } else if (binaryChecks != null && LintUtils.isBitmapFile(file)) {
+                } else if (binaryChecks != null && (LintUtils.isBitmapFile(file) ||
+                            type == ResourceFolderType.RAW)) {
                     ResourceContext context = new ResourceContext(this, project, main, file, type);
                     fireEvent(EventType.SCANNING_FILE, context);
                     visitor.visitBinaryResource(context);
@@ -1830,6 +1835,7 @@
         private final LintClient mDelegate;
 
         public LintClientWrapper(@NonNull LintClient delegate) {
+            super(getClientName());
             mDelegate = delegate;
         }
 
@@ -1907,8 +1913,37 @@
 
         @NonNull
         @Override
-        public List<File> getJavaLibraries(@NonNull Project project) {
-            return mDelegate.getJavaLibraries(project);
+        public List<File> getJavaLibraries(@NonNull Project project, boolean includeProvided) {
+            return mDelegate.getJavaLibraries(project, includeProvided);
+        }
+
+        @NonNull
+        @Override
+        public List<File> getTestSourceFolders(@NonNull Project project) {
+            return mDelegate.getTestSourceFolders(project);
+        }
+
+        @Override
+        public Collection<Project> getKnownProjects() {
+            return mDelegate.getKnownProjects();
+        }
+
+        @Nullable
+        @Override
+        public BuildToolInfo getBuildTools(@NonNull Project project) {
+            return mDelegate.getBuildTools(project);
+        }
+
+        @NonNull
+        @Override
+        public Map<String, String> createSuperClassMap(@NonNull Project project) {
+            return mDelegate.createSuperClassMap(project);
+        }
+
+        @NonNull
+        @Override
+        public ResourceVisibilityLookup.Provider getResourceVisibilityProvider() {
+            return mDelegate.getResourceVisibilityProvider();
         }
 
         @Override
@@ -2058,6 +2093,17 @@
             return mDelegate.addCustomLintRules(registry);
         }
 
+        @NonNull
+        @Override
+        public List<File> getAssetFolders(@NonNull Project project) {
+            return mDelegate.getAssetFolders(project);
+        }
+
+        @Override
+        public ClassLoader createUrlClassLoader(@NonNull URL[] urls, @NonNull ClassLoader parent) {
+            return mDelegate.createUrlClassLoader(urls, parent);
+        }
+
         @Override
         public boolean checkForSuppressComments() {
             return mDelegate.checkForSuppressComments();
@@ -2402,6 +2448,12 @@
                 if (isSuppressed(issue, declaration.astModifiers())) {
                     return true;
                 }
+            } else if (type == AnnotationMethodDeclaration.class) {
+                // Look for annotations on the method
+                AnnotationMethodDeclaration declaration = (AnnotationMethodDeclaration) scope;
+                if (isSuppressed(issue, declaration.astModifiers())) {
+                    return true;
+                }
             }
 
             if (checkComments && context.isSuppressedWithComment(scope, issue)) {
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Category.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Category.java
index a950634..3800671 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Category.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Category.java
@@ -107,6 +107,35 @@
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        Category category = (Category) o;
+
+        //noinspection SimplifiableIfStatement
+        if (!mName.equals(category.mName)) {
+            return false;
+        }
+        return mParent != null ? mParent.equals(category.mParent) : category.mParent == null;
+
+    }
+
+    @Override
+    public String toString() {
+        return getFullName();
+    }
+
+    @Override
+    public int hashCode() {
+        return mName.hashCode();
+    }
+
+    @Override
     public int compareTo(Category other) {
         if (other.mPriority == mPriority) {
             if (mParent == other) {
@@ -115,7 +144,13 @@
                 return -1;
             }
         }
-        return other.mPriority - mPriority;
+
+        int delta = other.mPriority - mPriority;
+        if (delta != 0) {
+            return delta;
+        }
+
+        return mName.compareTo(other.mName);
     }
 
     /** Issues related to running lint itself */
@@ -139,9 +174,6 @@
     /** Issues related to internationalization */
     public static final Category I18N = create("Internationalization", 50);
 
-    /** Issues related to right to left and bi-directional text support */
-    public static final Category RTL = create("Bi-directional Text", 40);
-
     // Sub categories
 
     /** Issues related to icons */
@@ -152,4 +184,7 @@
 
     /** Issues related to messages/strings */
     public static final Category MESSAGES = create(CORRECTNESS, "Messages", 95);
+
+    /** Issues related to right to left and bidirectional text support */
+    public static final Category RTL = create(I18N, "Bidirectional Text", 40);
 }
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ConstantEvaluator.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ConstantEvaluator.java
index 4550cc5..23225e5 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ConstantEvaluator.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ConstantEvaluator.java
@@ -15,19 +15,35 @@
  */
 package com.android.tools.lint.detector.api;
 
+import static com.android.tools.lint.client.api.JavaParser.TYPE_BOOLEAN;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_BYTE;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_CHAR;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_DOUBLE;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_FLOAT;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_INT;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_LONG;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_OBJECT;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_SHORT;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_STRING;
 import static com.android.tools.lint.detector.api.JavaContext.getParentOfType;
 
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
 import com.android.tools.lint.client.api.JavaParser.ResolvedField;
 import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.google.common.collect.Lists;
 
+import java.lang.reflect.Array;
+import java.util.List;
 import java.util.ListIterator;
 
+import lombok.ast.ArrayCreation;
+import lombok.ast.ArrayInitializer;
 import lombok.ast.BinaryExpression;
 import lombok.ast.BinaryOperator;
 import lombok.ast.BooleanLiteral;
 import lombok.ast.Cast;
+import lombok.ast.CharLiteral;
 import lombok.ast.Expression;
 import lombok.ast.ExpressionStatement;
 import lombok.ast.FloatingPointLiteral;
@@ -37,7 +53,9 @@
 import lombok.ast.NullLiteral;
 import lombok.ast.Select;
 import lombok.ast.Statement;
+import lombok.ast.StrictListAccessor;
 import lombok.ast.StringLiteral;
+import lombok.ast.TypeReference;
 import lombok.ast.UnaryExpression;
 import lombok.ast.UnaryOperator;
 import lombok.ast.VariableDeclaration;
@@ -85,6 +103,8 @@
         } else if (node instanceof StringLiteral) {
             StringLiteral string = (StringLiteral) node;
             return string.astValue();
+        } else if (node instanceof CharLiteral) {
+            return ((CharLiteral)node).astValue();
         } else if (node instanceof IntegralLiteral) {
             IntegralLiteral literal = (IntegralLiteral) node;
             // Don't combine to ?: since that will promote astIntValue to a long
@@ -375,7 +395,26 @@
             ResolvedNode resolved = mContext.resolve(node);
             if (resolved instanceof ResolvedField) {
                 ResolvedField field = (ResolvedField) resolved;
-                return field.getValue();
+                Object value = field.getValue();
+                if (value != null) {
+                    return value;
+                }
+                Node astNode = field.findAstNode();
+                if (astNode instanceof VariableDeclaration) {
+                    VariableDeclaration declaration = (VariableDeclaration) astNode;
+                    VariableDefinition definition = declaration.astDefinition();
+                    if (definition != null && definition.astModifiers().isFinal()) {
+                        StrictListAccessor<VariableDefinitionEntry, VariableDefinition> variables =
+                                definition.astVariables();
+                        if (variables.size() == 1) {
+                            VariableDefinitionEntry first = variables.first();
+                            if (first.astInitializer() != null) {
+                                return evaluate(first.astInitializer());
+                            }
+                        }
+                    }
+                }
+                return null;
             } else if (node instanceof VariableReference) {
                 Statement statement = getParentOfType(node, Statement.class, false);
                 if (statement != null) {
@@ -417,6 +456,79 @@
                     }
                 }
             }
+        } else if (node instanceof ArrayCreation) {
+            ArrayCreation creation = (ArrayCreation) node;
+            ArrayInitializer initializer = creation.astInitializer();
+            if (initializer != null) {
+                TypeReference typeReference = creation.astComponentTypeReference();
+                StrictListAccessor<Expression, ArrayInitializer> expressions = initializer
+                        .astExpressions();
+                List<Object> values = Lists.newArrayListWithExpectedSize(expressions.size());
+                Class<?> commonType = null;
+                for (Expression expression : expressions) {
+                    Object value = evaluate(expression);
+                    if (value != null) {
+                        values.add(value);
+                        if (commonType == null) {
+                            commonType = value.getClass();
+                        } else {
+                            while (!commonType.isAssignableFrom(value.getClass())) {
+                                commonType = commonType.getSuperclass();
+                            }
+                        }
+                    } else if (!mAllowUnknown) {
+                        // Inconclusive
+                        return null;
+                    }
+                }
+                if (!values.isEmpty()) {
+                    Object o = Array.newInstance(commonType, values.size());
+                    return values.toArray((Object[]) o);
+                } else {
+                    ResolvedNode type = mContext.resolve(typeReference);
+                    System.out.println(type);
+                    // TODO: return new array of this type
+                }
+            } else {
+                // something like "new byte[3]" but with no initializer.
+                String type = creation.astComponentTypeReference().toString();
+                // TODO: Look up the size and only if small, use it. E.g. if it was byte[3]
+                // we could return a byte[3] array, but if it's say byte[1024*1024] we don't
+                // want to do that.
+                int size = 0;
+                if (TYPE_BYTE.equals(type)) {
+                    return new byte[size];
+                }
+                if (TYPE_BOOLEAN.equals(type)) {
+                    return new boolean[size];
+                }
+                if (TYPE_INT.equals(type)) {
+                    return new int[size];
+                }
+                if (TYPE_LONG.equals(type)) {
+                    return new long[size];
+                }
+                if (TYPE_CHAR.equals(type)) {
+                    return new char[size];
+                }
+                if (TYPE_FLOAT.equals(type)) {
+                    return new float[size];
+                }
+                if (TYPE_DOUBLE.equals(type)) {
+                    return new double[size];
+                }
+                if (TYPE_STRING.equals(type)) {
+                    //noinspection SSBasedInspection
+                    return new String[size];
+                }
+                if (TYPE_SHORT.equals(type)) {
+                    return new short[size];
+                }
+                if (TYPE_OBJECT.equals(type)) {
+                    //noinspection SSBasedInspection
+                    return new Object[size];
+                }
+            }
         }
 
         // TODO: Check for MethodInvocation and perform some common operations -
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Detector.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Detector.java
index 04e1937..168c3a3 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Detector.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Detector.java
@@ -252,6 +252,7 @@
          * @param node the class declaration node or the anonymous class construction node
          * @param resolvedClass the resolved class
          */
+        // TODO: Change signature to pass in the NormalTypeBody instead of the plain Node?
         void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration declaration,
                 @NonNull Node node, @NonNull ResolvedClass resolvedClass);
     }
@@ -360,7 +361,10 @@
                 @NonNull MethodNode method, @NonNull MethodInsnNode call);
     }
 
-    /** Specialized interface for detectors that scan binary resource files */
+    /**
+     * Specialized interface for detectors that scan binary resource files
+     * (typically bitmaps but also files in res/raw)
+     */
     public interface BinaryResourceScanner {
         /**
          * Called for each resource folder
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/JavaContext.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/JavaContext.java
index 58f66be..b6009b9 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/JavaContext.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/JavaContext.java
@@ -30,15 +30,21 @@
 import java.io.File;
 import java.util.Iterator;
 
+import lombok.ast.AnnotationElement;
+import lombok.ast.AnnotationMethodDeclaration;
 import lombok.ast.ClassDeclaration;
 import lombok.ast.ConstructorDeclaration;
 import lombok.ast.ConstructorInvocation;
 import lombok.ast.EnumConstant;
 import lombok.ast.Expression;
+import lombok.ast.LabelledStatement;
 import lombok.ast.MethodDeclaration;
 import lombok.ast.MethodInvocation;
 import lombok.ast.Node;
 import lombok.ast.Position;
+import lombok.ast.TypeDeclaration;
+import lombok.ast.VariableDeclaration;
+import lombok.ast.VariableReference;
 
 /**
  * A {@link Context} used when checking Java files.
@@ -90,6 +96,39 @@
         return mParser.getLocation(this, node);
     }
 
+    /**
+     * Returns a location for the given node range (from the starting offset of the first node to
+     * the ending offset of the second node).
+     *
+     * @param from      the AST node to get a starting location from
+     * @param fromDelta Offset delta to apply to the starting offset
+     * @param to        the AST node to get a ending location from
+     * @param toDelta   Offset delta to apply to the ending offset
+     * @return a location for the given node
+     */
+    @NonNull
+    public Location getRangeLocation(
+            @NonNull Node from,
+            int fromDelta,
+            @NonNull Node to,
+            int toDelta) {
+        return mParser.getRangeLocation(this, from, fromDelta, to, toDelta);
+    }
+
+    /**
+     * Returns a {@link Location} for the given node. This attempts to pick a shorter
+     * location range than the entire node; for a class or method for example, it picks
+     * the name node (if found). For statement constructs such as a {@code switch} statement
+     * it will highlight the keyword, etc.
+     *
+     * @param node the AST node to create a location for
+     * @return a location for the given node
+     */
+    @NonNull
+    public Location getNameLocation(@NonNull Node node) {
+        return mParser.getNameLocation(this, node);
+    }
+
     @NonNull
     public JavaParser getParser() {
         return mParser;
@@ -244,6 +283,38 @@
         }
     }
 
+    /**
+     * Searches for a name node corresponding to the given node
+     * @return the name node to use, if applicable
+     */
+    @Nullable
+    public static Node findNameNode(@NonNull Node node) {
+        if (node instanceof TypeDeclaration) {
+            // ClassDeclaration, AnnotationDeclaration, EnumDeclaration, InterfaceDeclaration
+            return ((TypeDeclaration) node).astName();
+        } else if (node instanceof MethodDeclaration) {
+            return ((MethodDeclaration)node).astMethodName();
+        } else if (node instanceof ConstructorDeclaration) {
+            return ((ConstructorDeclaration)node).astTypeName();
+        } else if (node instanceof MethodInvocation) {
+            return ((MethodInvocation)node).astName();
+        } else if (node instanceof ConstructorInvocation) {
+            return ((ConstructorInvocation)node).astTypeReference();
+        } else if (node instanceof EnumConstant) {
+            return ((EnumConstant)node).astName();
+        } else if (node instanceof AnnotationElement) {
+            return ((AnnotationElement)node).astName();
+        } else if (node instanceof AnnotationMethodDeclaration) {
+            return ((AnnotationMethodDeclaration)node).astMethodName();
+        } else if (node instanceof VariableReference) {
+            return ((VariableReference)node).astIdentifier();
+        } else if (node instanceof LabelledStatement) {
+            return ((LabelledStatement)node).astLabel();
+        }
+
+        return null;
+    }
+
     @NonNull
     public static Iterator<Expression> getParameters(@NonNull Node call) {
         if (call instanceof MethodInvocation) {
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/LintUtils.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/LintUtils.java
old mode 100755
new mode 100644
index b233546..1195a2d
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/LintUtils.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/LintUtils.java
@@ -27,6 +27,7 @@
 import static com.android.SdkConstants.DOT_PNG;
 import static com.android.SdkConstants.DOT_WEBP;
 import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.FN_BUILD_GRADLE;
 import static com.android.SdkConstants.ID_PREFIX;
 import static com.android.SdkConstants.NEW_ID_PREFIX;
 import static com.android.SdkConstants.TOOLS_URI;
@@ -41,6 +42,7 @@
 import com.android.ide.common.rendering.api.ItemResourceValue;
 import com.android.ide.common.rendering.api.ResourceValue;
 import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.ide.common.repository.GradleVersion;
 import com.android.ide.common.res2.AbstractResourceRepository;
 import com.android.ide.common.res2.ResourceItem;
 import com.android.ide.common.resources.ResourceUrl;
@@ -52,7 +54,6 @@
 import com.android.sdklib.AndroidVersion;
 import com.android.sdklib.IAndroidTarget;
 import com.android.sdklib.SdkVersionInfo;
-import com.android.sdklib.repository.FullRevision;
 import com.android.tools.lint.client.api.LintClient;
 import com.android.utils.PositionXmlParser;
 import com.android.utils.SdkUtils;
@@ -72,13 +73,11 @@
 
 import java.io.File;
 import java.io.IOException;
-import java.io.StringWriter;
 import java.io.UnsupportedEncodingException;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Properties;
 import java.util.Queue;
 import java.util.Set;
 import java.util.regex.Matcher;
@@ -262,6 +261,22 @@
     }
 
     /**
+     * Returns the corresponding R field name for the given XML resource name
+     * @param styleName the XML name
+     * @return the corresponding R field name
+     */
+    public static String getFieldName(@NonNull String styleName) {
+        for (int i = 0, n = styleName.length(); i < n; i++) {
+            char c = styleName.charAt(i);
+            if (c == '.' || c == '-' || c == ':') {
+                return styleName.replace('.', '_').replace('-', '_').replace(':', '_');
+            }
+        }
+
+        return styleName;
+    }
+
+    /**
      * Returns the given id without an {@code @id/} or {@code @+id} prefix
      *
      * @param id the id to strip
@@ -582,7 +597,7 @@
             text = new String(data, offset, length, charset);
         } catch (UnsupportedEncodingException e) {
             try {
-                if (charset != defaultCharset) {
+                if (!charset.equals(defaultCharset)) {
                     text = new String(data, offset, length, defaultCharset);
                 }
             } catch (UnsupportedEncodingException u) {
@@ -1071,25 +1086,23 @@
     /**
      * Returns true if the given Gradle model is older than the given version number
      */
-    public static boolean isModelOlderThan(@Nullable AndroidProject project,
+    /**
+     * Returns true if the given Gradle model is older than the given version number
+     */
+    public static boolean isModelOlderThan(@NonNull Project project,
             int major, int minor, int micro) {
-        if (project != null) {
-            String modelVersion = project.getModelVersion();
-            try {
-                FullRevision version = FullRevision.parseRevision(modelVersion);
-                if (version.getMajor() != major) {
-                    return version.getMajor() < major;
-                }
-                if (version.getMinor() != minor) {
-                    return version.getMinor() < minor;
-                }
-                return version.getMicro() < micro;
-            } catch (NumberFormatException e) {
-                // ignore
-            }
+        GradleVersion version = project.getGradleModelVersion();
+        if (version == null) {
+            return false;
         }
 
-        return false;
+        if (version.getMajor() != major) {
+            return version.getMajor() < major;
+        }
+        if (version.getMinor() != minor) {
+            return version.getMinor() < minor;
+        }
+        return version.getMicro() < micro;
     }
 
     /**
@@ -1177,40 +1190,6 @@
     }
 
     /**
-     * Escapes the given property file value (right hand side of property assignment)
-     * as required by the property file format (e.g. escapes colons and backslashes)
-     *
-     * @param value the value to be escaped
-     * @return the escaped value
-     */
-    @NonNull
-    public static String escapePropertyValue(@NonNull String value) {
-        // Slow, stupid implementation, but is 100% compatible with Java's property file
-        // implementation
-        Properties properties = new Properties();
-        properties.setProperty("k", value); // key doesn't matter
-        StringWriter writer = new StringWriter();
-        try {
-            properties.store(writer, null);
-            String s = writer.toString();
-            int end = s.length();
-
-            // Writer inserts trailing newline
-            String lineSeparator = SdkUtils.getLineSeparator();
-            if (s.endsWith(lineSeparator)) {
-                end -= lineSeparator.length();
-            }
-
-            int start = s.indexOf('=');
-            assert start != -1 : s;
-            return s.substring(start + 1, end);
-        }
-        catch (IOException e) {
-            return value; // shouldn't happen; we're not going to disk
-        }
-    }
-
-    /**
      * Returns the locale for the given parent folder.
      *
      * @param parent the name of the parent folder
@@ -1262,4 +1241,24 @@
             return "en".equals(locale.getLanguage());  //$NON-NLS-1$
         }
     }
+
+    /**
+     * Create a {@link Location} for an error in the top level build.gradle file.
+     * This is necessary when we're doing an analysis based on the Gradle interpreted model,
+     * not from parsing Gradle files - and the model doesn't provide source positions.
+     * @param project the project containing the gradle file being analyzed
+     * @return location for the top level gradle file if it exists, otherwise fall back to
+     *     the project directory.
+     */
+    public static Location guessGradleLocation(@NonNull Project project) {
+        File dir = project.getDir();
+        Location location;
+        File topLevel = new File(dir, FN_BUILD_GRADLE);
+        if (topLevel.exists()) {
+            location = Location.create(topLevel);
+        } else {
+            location = Location.create(dir);
+        }
+        return location;
+    }
 }
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Location.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Location.java
index 284e41b..9e1b01f 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Location.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Location.java
@@ -355,7 +355,14 @@
                     index = findNextMatch(contents, offset, patternStart, hints);
                     line = adjustLine(contents, line, offset, index);
                 } else {
-                    assert direction == SearchDirection.NEAREST;
+                    assert direction == SearchDirection.NEAREST ||
+                            direction == SearchDirection.EOL_NEAREST;
+
+                    int lineEnd = contents.indexOf('\n', offset);
+                    if (lineEnd == -1) {
+                        lineEnd = contents.length();
+                    }
+                    offset = lineEnd;
 
                     int before = findPreviousMatch(contents, offset, patternStart, hints);
                     int after = findNextMatch(contents, offset, patternStart, hints);
@@ -366,12 +373,27 @@
                     } else if (after == -1) {
                         index = before;
                         line = adjustLine(contents, line, offset, index);
-                    } else if (offset - before < after - offset) {
-                        index = before;
-                        line = adjustLine(contents, line, offset, index);
                     } else {
-                        index = after;
-                        line = adjustLine(contents, line, offset, index);
+                        int newLinesBefore = 0;
+                        for (int i = before; i < offset; i++) {
+                            if (contents.charAt(i) == '\n') {
+                                newLinesBefore++;
+                            }
+                        }
+                        int newLinesAfter = 0;
+                        for (int i = offset; i < after; i++) {
+                            if (contents.charAt(i) == '\n') {
+                                newLinesAfter++;
+                            }
+                        }
+                        if (newLinesBefore < newLinesAfter || newLinesBefore == newLinesAfter
+                                && offset - before < after - offset) {
+                            index = before;
+                            line = adjustLine(contents, line, offset, index);
+                        } else {
+                            index = after;
+                            line = adjustLine(contents, line, offset, index);
+                        }
                     }
                 }
 
@@ -674,6 +696,12 @@
          * the match that is closest
          */
         NEAREST,
+
+        /**
+         * Search both forwards and backwards from the end of the given line, and prefer
+         * the match that is closest
+         */
+        EOL_NEAREST,
     }
 
     /**
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Project.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Project.java
old mode 100755
new mode 100644
index 3831284..de3186c
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Project.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Project.java
@@ -46,10 +46,12 @@
 import com.android.builder.model.ProductFlavor;
 import com.android.builder.model.ProductFlavorContainer;
 import com.android.builder.model.Variant;
+import com.android.ide.common.repository.GradleVersion;
 import com.android.ide.common.repository.ResourceVisibilityLookup;
 import com.android.resources.Density;
 import com.android.resources.ResourceFolderType;
 import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.BuildToolInfo;
 import com.android.sdklib.IAndroidTarget;
 import com.android.sdklib.SdkVersionInfo;
 import com.android.tools.lint.client.api.CircularDependencyException;
@@ -122,15 +124,18 @@
     protected List<File> mManifestFiles;
     protected List<File> mJavaSourceFolders;
     protected List<File> mJavaClassFolders;
+    protected List<File> mNonProvidedJavaLibraries;
     protected List<File> mJavaLibraries;
     protected List<File> mTestSourceFolders;
     protected List<File> mResourceFolders;
+    protected List<File> mAssetFolders;
     protected List<Project> mDirectLibraries;
     protected List<Project> mAllLibraries;
     protected boolean mReportIssues = true;
     protected Boolean mGradleProject;
     protected Boolean mSupportLib;
     protected Boolean mAppCompat;
+    protected GradleVersion mGradleVersion;
     private Map<String, String> mSuperClassMap;
     private ResourceVisibilityLookup mResourceVisibility;
 
@@ -186,6 +191,24 @@
     }
 
     /**
+     * If this is a Gradle project with a valid Gradle model, return the version
+     * of the model/plugin.
+     *
+     * @return the Gradle plugin version, or null if invalid or not a Gradle project
+     */
+    @Nullable
+    public GradleVersion getGradleModelVersion() {
+        if (mGradleVersion == null && isGradleProject()) {
+            AndroidProject gradleProjectModel = getGradleProjectModel();
+            if (gradleProjectModel != null) {
+                mGradleVersion = GradleVersion.tryParse(gradleProjectModel.getModelVersion());
+            }
+        }
+
+        return mGradleVersion;
+    }
+
+    /**
      * Returns the project model for this project if it corresponds to
      * a Gradle library. This is the case if both
      * {@link #isGradleProject()} and {@link #isLibrary()} return true.
@@ -435,20 +458,29 @@
      * library projects which are processed in a separate pass with their own
      * source and class folders.
      *
+     * @param includeProvided If true, included provided libraries too (libraries
+     *                        that are not packaged with the app, but are provided
+     *                        for compilation purposes and are assumed to be present
+     *                        in the running environment)
      * @return a list of .jar files (or class folders) that this project depends
      *         on.
      */
     @NonNull
-    public List<File> getJavaLibraries() {
-        if (mJavaLibraries == null) {
-            // AOSP builds already merge libraries and class folders into
-            // the single classes.jar file, so these have already been processed
-            // in getJavaClassFolders.
-
-            mJavaLibraries = mClient.getJavaLibraries(this);
+    public List<File> getJavaLibraries(boolean includeProvided) {
+        if (includeProvided) {
+            if (mJavaLibraries == null) {
+                // AOSP builds already merge libraries and class folders into
+                // the single classes.jar file, so these have already been processed
+                // in getJavaClassFolders.
+                mJavaLibraries = mClient.getJavaLibraries(this, true);
+            }
+            return mJavaLibraries;
+        } else {
+            if (mNonProvidedJavaLibraries == null) {
+                mNonProvidedJavaLibraries = mClient.getJavaLibraries(this, false);
+            }
+            return mNonProvidedJavaLibraries;
         }
-
-        return mJavaLibraries;
     }
 
     /**
@@ -466,10 +498,10 @@
     }
 
     /**
-     * Returns the resource folder.
+     * Returns the resource folders.
      *
-     * @return a file pointing to the resource folder, or null if the project
-     *         does not contain any resources
+     * @return a list of files pointing to the resource folders, which might be empty if the project
+     * does not provide any resources.
      */
     @NonNull
     public List<File> getResourceFolders() {
@@ -489,7 +521,21 @@
         }
 
         return mResourceFolders;
+    }
 
+    /**
+     * Returns the asset folders.
+     *
+     * @return a list of files pointing to the asset folders, which might be empty if the project
+     * does not provide any resources.
+     */
+    @NonNull
+    public List<File> getAssetFolders() {
+        if (mAssetFolders == null) {
+            mAssetFolders = mClient.getAssetFolders(this);
+        }
+
+        return mAssetFolders;
     }
 
     /**
@@ -583,9 +629,6 @@
      */
     @Nullable
     public String getPackage() {
-        //assert !mLibrary; // Should call getPackage on the master project, not the library
-        // Assertion disabled because you might be running lint on a standalone library project.
-
         return mPackage;
     }
 
@@ -644,6 +687,16 @@
     }
 
     /**
+     * Returns the specific version of the build tools being used, if known
+     *
+     * @return the build tools version in use, or null if not known
+     */
+    @Nullable
+    public BuildToolInfo getBuildTools() {
+        return mClient.getBuildTools(this);
+    }
+
+    /**
      * Returns the target used to build the project, or null if not known
      *
      * @return the build target, or null
@@ -1167,7 +1220,7 @@
     public Boolean dependsOn(@NonNull String artifact) {
         if (SUPPORT_LIB_ARTIFACT.equals(artifact)) {
             if (mSupportLib == null) {
-                for (File file : getJavaLibraries()) {
+                for (File file : getJavaLibraries(true)) {
                     String name = file.getName();
                     if (name.equals("android-support-v4.jar")      //$NON-NLS-1$
                             || name.startsWith("support-v4-")) {   //$NON-NLS-1$
@@ -1192,7 +1245,7 @@
             return mSupportLib;
         } else if (APPCOMPAT_LIB_ARTIFACT.equals(artifact)) {
             if (mAppCompat == null) {
-                for (File file : getJavaLibraries()) {
+                for (File file : getJavaLibraries(true)) {
                     String name = file.getName();
                     if (name.startsWith("appcompat-v7-")) { //$NON-NLS-1$
                         mAppCompat = true;
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Scope.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Scope.java
index 55dd778..8d3f2a2 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Scope.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Scope.java
@@ -62,7 +62,7 @@
     BINARY_RESOURCE_FILE,
 
     /**
-     * The analysis considers the resource folders
+     * The analysis considers the resource folders (which also includes asset folders)
      */
     RESOURCE_FOLDER,
 
@@ -112,7 +112,8 @@
 
     /**
      * The analysis considers classes in the libraries for this project. These
-     * will be analyzed before the classes themselves.
+     * will be analyzed before the classes themselves. NOTE: This excludes
+     * provided libraries.
      */
     JAVA_LIBRARIES,
 
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/TextFormat.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/TextFormat.java
index a66b326..9012411 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/TextFormat.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/TextFormat.java
@@ -56,7 +56,16 @@
     /**
      * HTML formatted output (note: does not include surrounding {@code <html></html>} tags)
      */
-    HTML;
+    HTML,
+
+    /**
+     * HTML formatted output (note: does not include surrounding {@code <html></html>} tags).
+     * This is like {@link #HTML}, but it does not escape unicode characters with entities.
+     * <p>
+     * (This is used for example in the IDE, where some partial HTML support in some
+     * label widgets support some HTML markup, but not numeric code character entities.)
+     */
+    HTML_WITH_UNICODE;
 
     /**
      * Converts the given text to HTML
@@ -102,6 +111,7 @@
                         return message;
                     case TEXT:
                     case HTML:
+                    case HTML_WITH_UNICODE:
                         return to.fromRaw(message);
                 }
             }
@@ -111,6 +121,7 @@
                     case RAW:
                         return message;
                     case HTML:
+                    case HTML_WITH_UNICODE:
                         return XmlUtils.toXmlTextValue(message);
                 }
             }
@@ -118,6 +129,20 @@
                 switch (to) {
                     case HTML:
                         return message;
+                    case HTML_WITH_UNICODE:
+                        return removeNumericEntities(message);
+                    case RAW:
+                    case TEXT: {
+                        return to.fromHtml(message);
+
+                    }
+                }
+            }
+            case HTML_WITH_UNICODE: {
+                switch (to) {
+                    case HTML:
+                    case HTML_WITH_UNICODE:
+                        return message;
                     case RAW:
                     case TEXT: {
                         return to.fromHtml(message);
@@ -194,9 +219,10 @@
     /** Converts to this output format from the given raw-format text */
     @NonNull
     private String fromRaw(@NonNull String text) {
-        assert this == HTML || this == TEXT : this;
+        assert this == HTML || this == HTML_WITH_UNICODE || this == TEXT : this;
         StringBuilder sb = new StringBuilder(3 * text.length() / 2);
-        boolean html = this == HTML;
+        boolean html = this == HTML || this == HTML_WITH_UNICODE;
+        boolean escapeUnicode = this == HTML;
 
         char prev = 0;
         int flushIndex = 0;
@@ -212,15 +238,15 @@
                     int end = text.indexOf(c, i + 1);
                     if (end != -1 && (end == n - 1 || !Character.isLetter(text.charAt(end + 1)))) {
                         if (i > flushIndex) {
-                            appendEscapedText(sb, text, html, flushIndex, i);
+                            appendEscapedText(sb, text, html, flushIndex, i, escapeUnicode);
                         }
                         if (html) {
                             String tag = c == '*' ? "b" : "code"; //$NON-NLS-1$ //$NON-NLS-2$
                             sb.append('<').append(tag).append('>');
-                            appendEscapedText(sb, text, html, i + 1, end);
+                            appendEscapedText(sb, text, html, i + 1, end, escapeUnicode);
                             sb.append('<').append('/').append(tag).append('>');
                         } else {
-                            appendEscapedText(sb, text, html, i + 1, end);
+                            appendEscapedText(sb, text, html, i + 1, end, escapeUnicode);
                         }
                         flushIndex = end + 1;
                         i = flushIndex - 1; // -1: account for the i++ in the loop
@@ -243,7 +269,7 @@
                 }
                 if (end > i + HTTP_PREFIX.length()) {
                     if (i > flushIndex) {
-                        appendEscapedText(sb, text, html, flushIndex, i);
+                        appendEscapedText(sb, text, html, flushIndex, i, escapeUnicode);
                     }
 
                     String url = text.substring(i, end);
@@ -261,14 +287,42 @@
         }
 
         if (flushIndex < n) {
-            appendEscapedText(sb, text, html, flushIndex, n);
+            appendEscapedText(sb, text, html, flushIndex, n, escapeUnicode);
+        }
+
+        return sb.toString();
+    }
+
+    private static String removeNumericEntities(@NonNull String html) {
+        if (!html.contains("&#")) {
+            return html;
+        }
+
+        StringBuilder sb = new StringBuilder(html.length());
+        for (int i = 0, n = html.length(); i < n; i++) {
+            char c = html.charAt(i);
+            if (c == '&' && i < n - 1 && html.charAt(i + 1) == '#') {
+                int end = html.indexOf(';', i + 2);
+                if (end != -1) {
+                    String decimal = html.substring(i + 2, end);
+                    try {
+                        c = (char)Integer.parseInt(decimal);
+                        sb.append(c);
+                        i = end;
+                        continue;
+                    } catch (NumberFormatException ignore) {
+                        // fall through to not escape this
+                    }
+                }
+            }
+            sb.append(c);
         }
 
         return sb.toString();
     }
 
     private static void appendEscapedText(@NonNull StringBuilder sb, @NonNull String text,
-            boolean html, int start, int end) {
+            boolean html, int start, int end, boolean escapeUnicode) {
         if (html) {
             for (int i = start; i < end; i++) {
                 char c = text.charAt(i);
@@ -279,7 +333,7 @@
                 } else if (c == '\n') {
                     sb.append("<br/>\n");
                 } else {
-                    if (c > 255) {
+                    if (c > 255 && escapeUnicode) {
                         sb.append("&#");                                 //$NON-NLS-1$
                         sb.append(Integer.toString(c));
                         sb.append(';');
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/TypeEvaluator.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/TypeEvaluator.java
new file mode 100644
index 0000000..bbafced
--- /dev/null
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/TypeEvaluator.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2015 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 static com.android.tools.lint.client.api.JavaParser.TYPE_BOOLEAN;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_CHAR;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_DOUBLE;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_FLOAT;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_INT;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_LONG;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_STRING;
+import static com.android.tools.lint.detector.api.JavaContext.getParentOfType;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.DefaultTypeDescriptor;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedField;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.client.api.JavaParser.ResolvedVariable;
+import com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
+
+import java.util.List;
+import java.util.ListIterator;
+
+import lombok.ast.BinaryExpression;
+import lombok.ast.BinaryOperator;
+import lombok.ast.BooleanLiteral;
+import lombok.ast.Cast;
+import lombok.ast.Catch;
+import lombok.ast.CharLiteral;
+import lombok.ast.Expression;
+import lombok.ast.ExpressionStatement;
+import lombok.ast.FloatingPointLiteral;
+import lombok.ast.InlineIfExpression;
+import lombok.ast.IntegralLiteral;
+import lombok.ast.Literal;
+import lombok.ast.Node;
+import lombok.ast.NullLiteral;
+import lombok.ast.Statement;
+import lombok.ast.StringLiteral;
+import lombok.ast.UnaryExpression;
+import lombok.ast.VariableDeclaration;
+import lombok.ast.VariableDefinition;
+import lombok.ast.VariableDefinitionEntry;
+import lombok.ast.VariableReference;
+
+/**
+ * Evaluates the types of nodes. This goes deeper than
+ * {@link JavaContext#getType(Node)} in that it analyzes the
+ * flow and for example figures out that if you ask for the type of {@code var}
+ * in this code snippet:
+ * <pre>
+ *     Object o = new StringBuilder();
+ *     Object var = o;
+ * </pre>
+ * it will return "java.lang.StringBuilder".
+ * <p>
+ * <b>NOTE:</b> This type evaluator does not (yet) compute the correct
+ * types when involving implicit type conversions, so be careful
+ * if using this for primitives; e.g. for "int * long" it might return
+ * the type "int".
+ */
+public class TypeEvaluator {
+    private final JavaContext mContext;
+
+    /**
+     * Creates a new constant evaluator
+     *
+     * @param context the context to use to resolve field references, if any
+     */
+    public TypeEvaluator(@Nullable JavaContext context) {
+        mContext = context;
+    }
+
+
+    /**
+     * Returns true if the node evaluates to an instance of type SecureRandom
+     */
+    @Nullable
+    public TypeDescriptor evaluate(@NonNull Node node) {
+        ResolvedNode resolved = null;
+        if (mContext != null) {
+            resolved = mContext.resolve(node);
+        }
+        if (resolved instanceof ResolvedMethod) {
+            TypeDescriptor type;
+            ResolvedMethod method = (ResolvedMethod) resolved;
+            if (method.isConstructor()) {
+                ResolvedClass containingClass = method.getContainingClass();
+                type = containingClass.getType();
+            } else {
+                type = method.getReturnType();
+            }
+            return type;
+        }
+        if (resolved instanceof ResolvedField) {
+            ResolvedField field = (ResolvedField) resolved;
+            Node astNode = field.findAstNode();
+            if (astNode instanceof VariableDeclaration) {
+                VariableDeclaration declaration = (VariableDeclaration)astNode;
+                VariableDefinition definition = declaration.astDefinition();
+                if (definition != null) {
+                    VariableDefinitionEntry first = definition.astVariables().first();
+                    if (first != null) {
+                        Expression initializer = first.astInitializer();
+                        if (initializer != null) {
+                            TypeDescriptor type = evaluate(initializer);
+                            if (type != null) {
+                                return type;
+                            }
+                        }
+                    }
+                }
+            }
+            return field.getType();
+        }
+
+        if (node instanceof VariableReference) {
+            Statement statement = getParentOfType(node, Statement.class, false);
+            if (statement != null) {
+                ListIterator<Node> iterator = statement.getParent().getChildren().listIterator();
+                while (iterator.hasNext()) {
+                    if (iterator.next() == statement) {
+                        if (iterator.hasPrevious()) { // should always be true
+                            iterator.previous();
+                        }
+                        break;
+                    }
+                }
+
+                String targetName = ((VariableReference) node).astIdentifier().astValue();
+                while (iterator.hasPrevious()) {
+                    Node previous = iterator.previous();
+                    if (previous instanceof VariableDeclaration) {
+                        VariableDeclaration declaration = (VariableDeclaration) previous;
+                        VariableDefinition definition = declaration.astDefinition();
+                        for (VariableDefinitionEntry entry : definition.astVariables()) {
+                            if (entry.astInitializer() != null && entry.astName().astValue()
+                                    .equals(targetName)) {
+                                return evaluate(entry.astInitializer());
+                            }
+                        }
+                    } else if (previous instanceof ExpressionStatement) {
+                        ExpressionStatement expressionStatement = (ExpressionStatement) previous;
+                        Expression expression = expressionStatement.astExpression();
+                        if (expression instanceof BinaryExpression &&
+                                ((BinaryExpression) expression).astOperator()
+                                        == BinaryOperator.ASSIGN) {
+                            BinaryExpression binaryExpression = (BinaryExpression) expression;
+                            if (targetName.equals(binaryExpression.astLeft().toString())) {
+                                return evaluate(binaryExpression.astRight());
+                            }
+                        }
+                    }
+                }
+            }
+        } else if (node instanceof Cast) {
+            Cast cast = (Cast) node;
+            if (mContext != null) {
+                ResolvedNode typeReference = mContext.resolve(cast.astTypeReference());
+                if (typeReference instanceof ResolvedClass) {
+                    return ((ResolvedClass) typeReference).getType();
+                }
+            }
+            TypeDescriptor viewType = evaluate(cast.astOperand());
+            if (viewType != null) {
+                return viewType;
+            }
+        } else if (node instanceof Literal) {
+            if (node instanceof NullLiteral) {
+                return null;
+            } else if (node instanceof BooleanLiteral) {
+                return new DefaultTypeDescriptor(TYPE_BOOLEAN);
+            } else if (node instanceof StringLiteral) {
+                return new DefaultTypeDescriptor(TYPE_STRING);
+            } else if (node instanceof CharLiteral) {
+                return new DefaultTypeDescriptor(TYPE_CHAR);
+            } else if (node instanceof IntegralLiteral) {
+                IntegralLiteral literal = (IntegralLiteral) node;
+                // Don't combine to ?: since that will promote astIntValue to a long
+                if (literal.astMarkedAsLong()) {
+                    return new DefaultTypeDescriptor(TYPE_LONG);
+                } else {
+                    return new DefaultTypeDescriptor(TYPE_INT);
+                }
+            } else if (node instanceof FloatingPointLiteral) {
+                FloatingPointLiteral literal = (FloatingPointLiteral) node;
+                // Don't combine to ?: since that will promote astFloatValue to a double
+                if (literal.astMarkedAsFloat()) {
+                    return new DefaultTypeDescriptor(TYPE_FLOAT);
+                } else {
+                    return new DefaultTypeDescriptor(TYPE_DOUBLE);
+                }
+            }
+        } else if (node instanceof UnaryExpression) {
+            return evaluate(((UnaryExpression) node).astOperand());
+        } else if (node instanceof InlineIfExpression) {
+            InlineIfExpression expression = (InlineIfExpression) node;
+            if (expression.astIfTrue() != null) {
+                return evaluate(expression.astIfTrue());
+            } else if (expression.astIfFalse() != null) {
+                return evaluate(expression.astIfFalse());
+            }
+        } else if (node instanceof BinaryExpression) {
+            BinaryExpression expression = (BinaryExpression) node;
+            BinaryOperator operator = expression.astOperator();
+            switch (operator) {
+                case LOGICAL_OR:
+                case LOGICAL_AND:
+                case EQUALS:
+                case NOT_EQUALS:
+                case GREATER:
+                case GREATER_OR_EQUAL:
+                case LESS:
+                case LESS_OR_EQUAL:
+                    return new DefaultTypeDescriptor(TYPE_BOOLEAN);
+            }
+
+            TypeDescriptor type = evaluate(expression.astLeft());
+            if (type != null) {
+                return type;
+            }
+            return evaluate(expression.astRight());
+        }
+
+        if (resolved instanceof ResolvedVariable) {
+            ResolvedVariable variable = (ResolvedVariable) resolved;
+            return variable.getType();
+        }
+
+        return null;
+    }
+
+    /**
+     * Evaluates the given node and returns the likely type of the instance. Convenience
+     * wrapper which creates a new {@linkplain TypeEvaluator}, evaluates the node and returns
+     * the result.
+     *
+     * @param context the context to use to resolve field references, if any
+     * @param node    the node to compute the type for
+     * @return the corresponding type descriptor, if found
+     */
+    @Nullable
+    public static TypeDescriptor evaluate(@NonNull JavaContext context, @NonNull Node node) {
+        return new TypeEvaluator(context).evaluate(node);
+    }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AllowAllHostnameVerifierDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AllowAllHostnameVerifierDetector.java
new file mode 100644
index 0000000..e11a04f
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AllowAllHostnameVerifierDetector.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2015 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.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.JavaParser.ResolvedField;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+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 java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.ConstructorInvocation;
+import lombok.ast.Expression;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.Identifier;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+
+public class AllowAllHostnameVerifierDetector extends Detector implements JavaScanner {
+
+    @SuppressWarnings("unchecked")
+    private static final Implementation IMPLEMENTATION =
+            new Implementation(AllowAllHostnameVerifierDetector.class,
+                    Scope.JAVA_FILE_SCOPE);
+
+    public static final Issue ISSUE = Issue.create("AllowAllHostnameVerifier",
+            "Insecure HostnameVerifier",
+            "This check looks for use of HostnameVerifier implementations " +
+            "whose `verify` method always returns true (thus trusting any hostname) " +
+            "which could result in insecure network traffic caused by trusting arbitrary " +
+            "hostnames in TLS/SSL certificates presented by peers.",
+            Category.SECURITY,
+            6,
+            Severity.WARNING,
+            IMPLEMENTATION);
+
+    // ---- Implements JavaScanner ----
+
+    @Override
+    @Nullable @SuppressWarnings("javadoc")
+    public List<String> getApplicableConstructorTypes() {
+        return Collections.singletonList("org.apache.http.conn.ssl.AllowAllHostnameVerifier");
+    }
+
+    @Override
+    public void visitConstructor(
+            @NonNull JavaContext context,
+            @Nullable AstVisitor visitor,
+            @NonNull ConstructorInvocation node,
+            @NonNull ResolvedMethod constructor) {
+        Location location = context.getLocation(node);
+        context.report(ISSUE, node, location,
+                "Using the AllowAllHostnameVerifier HostnameVerifier is unsafe " +
+                        "because it always returns true, which could cause insecure network " +
+                        "traffic due to trusting TLS/SSL server certificates for wrong " +
+                        "hostnames");
+    }
+
+    @Override
+    public List<String> getApplicableMethodNames() {
+        return Arrays.asList("setHostnameVerifier", "setDefaultHostnameVerifier");
+    }
+
+    @Override
+    public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+            @NonNull MethodInvocation node) {
+        ResolvedNode resolved = context.resolve(node);
+        if (resolved instanceof ResolvedMethod) {
+            ResolvedMethod method = (ResolvedMethod) resolved;
+            if (method.getArgumentCount() == 1 &&
+                    node.astArguments().size() == 1 &&
+                    method.getArgumentType(0).matchesName("javax.net.ssl.HostnameVerifier")) {
+                Expression argument = node.astArguments().first();
+                ResolvedNode resolvedArgument = context.resolve(argument);
+                if (resolvedArgument instanceof ResolvedField) {
+                    ResolvedField field = (ResolvedField) resolvedArgument;
+                    if (field.getName().equals("ALLOW_ALL_HOSTNAME_VERIFIER")) {
+                        Location location = context.getLocation(argument);
+                        String message = "Using the ALLOW_ALL_HOSTNAME_VERIFIER HostnameVerifier "
+                                + "is unsafe because it always returns true, which could cause "
+                                + "insecure network traffic due to trusting TLS/SSL server "
+                                + "certificates for wrong hostnames";
+                        context.report(ISSUE, argument, location, message);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AndroidAutoDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AndroidAutoDetector.java
new file mode 100644
index 0000000..af50f3e
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AndroidAutoDetector.java
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.TAG_INTENT_FILTER;
+import static com.android.SdkConstants.TAG_SERVICE;
+import static com.android.xml.AndroidManifest.NODE_ACTION;
+import static com.android.xml.AndroidManifest.NODE_APPLICATION;
+import static com.android.xml.AndroidManifest.NODE_METADATA;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.resources.ResourceFolderType;
+import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.LintUtils;
+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 com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.XmlContext;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+
+import lombok.ast.ClassDeclaration;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.Node;
+
+/**
+ * Detector for Android Auto issues.
+ * <p> Uses a {@code <meta-data>} tag with a {@code name="com.google.android.gms.car.application"}
+ * as a trigger for validating Automotive specific issues.
+ */
+public class AndroidAutoDetector extends ResourceXmlDetector
+        implements Detector.XmlScanner, Detector.JavaScanner {
+
+    public static final Implementation IMPL = new Implementation(
+            AndroidAutoDetector.class,
+            EnumSet.of(Scope.RESOURCE_FILE, Scope.MANIFEST, Scope.JAVA_FILE),
+            Scope.RESOURCE_FILE_SCOPE);
+
+    /** Invalid attribute for uses tag.*/
+    public static final Issue INVALID_USES_TAG_ISSUE = Issue.create(
+            "InvalidUsesTagAttribute", //$NON-NLS-1$
+            "Invalid `name` attribute for `uses` element.",
+            "The <uses> element in `<automotiveApp>` should contain a " +
+            "valid value for the `name` attribute.\n" +
+            "Valid values are `media` or `notification`.",
+            Category.CORRECTNESS,
+            6,
+            Severity.ERROR,
+            IMPL).addMoreInfo(
+            "https://developer.android.com/training/auto/start/index.html#auto-metadata");
+
+    /** Missing MediaBrowserService action */
+    public static final Issue MISSING_MEDIA_BROWSER_SERVICE_ACTION_ISSUE = Issue.create(
+            "MissingMediaBrowserServiceIntentFilter", //$NON-NLS-1$
+            "Missing intent-filter with action `android.media.browse.MediaBrowserService`.",
+            "An Automotive Media App requires an exported service that extends " +
+            "`android.service.media.MediaBrowserService` with an " +
+            "`intent-filter` for the action `android.media.browse.MediaBrowserService` " +
+            "to be able to browse and play media.\n" +
+            "To do this, add\n" +
+            "`<intent-filter>`\n" +
+            "    `<action android:name=\"android.media.browse.MediaBrowserService\" />`\n" +
+            "`</intent-filter>`\n to the service that extends " +
+            "`android.service.media.MediaBrowserService`",
+            Category.CORRECTNESS,
+            6,
+            Severity.ERROR,
+            IMPL).addMoreInfo(
+            "https://developer.android.com/training/auto/audio/index.html#config_manifest");
+
+    /** Missing intent-filter for Media Search. */
+    public static final Issue MISSING_INTENT_FILTER_FOR_MEDIA_SEARCH = Issue.create(
+            "MissingIntentFilterForMediaSearch", //$NON-NLS-1$
+            "Missing intent-filter with action `android.media.action.MEDIA_PLAY_FROM_SEARCH`",
+            "To support voice searches on Android Auto, you should also register an " +
+            "`intent-filter` for the action `android.media.action.MEDIA_PLAY_FROM_SEARCH`" +
+            ".\nTo do this, add\n" +
+            "`<intent-filter>`\n" +
+            "    `<action android:name=\"android.media.action.MEDIA_PLAY_FROM_SEARCH\" />`\n" +
+            "`</intent-filter>`\n" +
+            "to your `<activity>` or `<service>`.",
+            Category.CORRECTNESS,
+            6,
+            Severity.ERROR,
+            IMPL).addMoreInfo(
+            "https://developer.android.com/training/auto/audio/index.html#support_voice");
+
+    /** Missing implementation of MediaSession.Callback#onPlayFromSearch*/
+    public static final Issue MISSING_ON_PLAY_FROM_SEARCH = Issue.create(
+            "MissingOnPlayFromSearch", //$NON-NLS-1$
+            "Missing `onPlayFromSearch`.",
+            "To support voice searches on Android Auto, in addition to adding an " +
+            "`intent-filter` for the action `onPlayFromSearch`," +
+            " you also need to override and implement " +
+            "`onPlayFromSearch(String query, Bundle bundle)`",
+            Category.CORRECTNESS,
+            6,
+            Severity.ERROR,
+            IMPL).addMoreInfo(
+            "https://developer.android.com/training/auto/audio/index.html#support_voice");
+
+    private static final String CAR_APPLICATION_METADATA_NAME =
+            "com.google.android.gms.car.application"; //$NON-NLS-1$
+    private static final String VAL_NAME_MEDIA = "media"; //$NON-NLS-1$
+    private static final String VAL_NAME_NOTIFICATION = "notification"; //$NON-NLS-1$
+    private static final String TAG_AUTOMOTIVE_APP = "automotiveApp"; //$NON-NLS-1$
+    private static final String ATTR_RESOURCE = "resource"; //$NON-NLS-1$
+    private static final String TAG_USES = "uses"; //$NON-NLS-1$
+    private static final String ACTION_MEDIA_BROWSER_SERVICE =
+            "android.media.browse.MediaBrowserService"; //$NON-NLS-1$
+    private static final String ACTION_MEDIA_PLAY_FROM_SEARCH =
+            "android.media.action.MEDIA_PLAY_FROM_SEARCH"; //$NON-NLS-1$
+    private static final String CLASS_MEDIA_SESSION_CALLBACK =
+            "android.media.session.MediaSession.Callback"; //$NON-NLS-1$
+    private static final String CLASS_V4MEDIA_SESSION_COMPAT_CALLBACK =
+            "android.support.v4.media.session.MediaSessionCompat.Callback"; //$NON-NLS-1$
+    private static final String METHOD_MEDIA_SESSION_PLAY_FROM_SEARCH =
+            "onPlayFromSearch"; //$NON-NLS-1$
+    private static final String STRING_ARG = "java.lang.String"; //$NON-NLS-1$
+    private static final String BUNDLE_ARG = "android.os.Bundle"; //$NON-NLS-1$
+
+    /**
+     * Indicates whether we identified that the current app is an automotive app and
+     * that we should validate all the automotive specific issues.
+     */
+    private boolean mDoAutomotiveAppCheck;
+
+    /** Indicates that a {@link #ACTION_MEDIA_BROWSER_SERVICE} intent-filter action was found. */
+    private boolean mMediaIntentFilterFound;
+
+    /** Indicates that a {@link #ACTION_MEDIA_PLAY_FROM_SEARCH} intent-filter action was found. */
+    private boolean mMediaSearchIntentFilterFound;
+
+    /** The resource file name deduced by the meta-data resource value */
+    private String mAutomotiveResourceFileName;
+
+    /** Indicates whether this app is an automotive Media App. */
+    private boolean mIsAutomotiveMediaApp;
+
+    /** {@link Location.Handle} to the application element */
+    private Location.Handle mMainApplicationHandle;
+
+    /** Constructs a new {@link AndroidAutoDetector} check */
+    public AndroidAutoDetector() {
+    }
+
+    @Override
+    public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+        // We only need to check the meta data resource file in res/xml if any.
+        return folderType == ResourceFolderType.XML;
+    }
+
+    @Override
+    public Collection<String> getApplicableElements() {
+        return Arrays.asList(
+                TAG_AUTOMOTIVE_APP, // Root element of a declared automotive descriptor.
+                NODE_METADATA,      // meta-data from AndroidManifest.xml
+                TAG_SERVICE,        // service from AndroidManifest.xml
+                TAG_INTENT_FILTER,  // Any declared intent-filter from AndroidManifest.xml
+                NODE_APPLICATION    // Used for storing the application element/location.
+        );
+    }
+
+    @Override
+    public void beforeCheckProject(@NonNull Context context) {
+        mIsAutomotiveMediaApp = false;
+        mAutomotiveResourceFileName = null;
+        mMediaIntentFilterFound = false;
+        mMediaSearchIntentFilterFound = false;
+    }
+
+    @Override
+    @NonNull
+    public Speed getSpeed() {
+        return Speed.FAST;
+    }
+
+    @Override
+    public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+        String tagName = element.getTagName();
+        if (NODE_METADATA.equals(tagName) && !mDoAutomotiveAppCheck) {
+            checkAutoMetadataTag(element);
+        } else if (TAG_AUTOMOTIVE_APP.equals(tagName)) {
+            checkAutomotiveAppElement(context, element);
+        } else if (NODE_APPLICATION.equals(tagName)) {
+            // Disable reporting the error if the Issue was suppressed at
+            // the application level.
+            if (context.getMainProject() == context.getProject()
+                    && !context.getProject().isLibrary()) {
+                mMainApplicationHandle = context.createLocationHandle(element);
+                mMainApplicationHandle.setClientData(element);
+            }
+        } else if (TAG_SERVICE.equals(tagName)) {
+            checkServiceForBrowserServiceIntentFilter(element);
+        } else if (TAG_INTENT_FILTER.equals(tagName)) {
+            checkForMediaSearchIntentFilter(element);
+        }
+    }
+
+    private void checkAutoMetadataTag(Element element) {
+        String name = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
+
+        if (CAR_APPLICATION_METADATA_NAME.equals(name)) {
+            String autoFileName = element.getAttributeNS(ANDROID_URI, ATTR_RESOURCE);
+
+            if (autoFileName != null && autoFileName.startsWith("@xml/")) { //$NON-NLS-1$
+                // Store the fact that we need to check all the auto issues.
+                mDoAutomotiveAppCheck = true;
+                mAutomotiveResourceFileName =
+                        autoFileName.substring("@xml/".length()) + DOT_XML; //$NON-NLS-1$
+            }
+        }
+    }
+
+    private void checkAutomotiveAppElement(XmlContext context, Element element) {
+        // Indicates whether the current file matches the resource that was registered
+        // in AndroidManifest.xml.
+        boolean isMetadataResource =
+                mAutomotiveResourceFileName != null
+                && mAutomotiveResourceFileName.equals(context.file.getName());
+
+        for (Element child : LintUtils.getChildren(element)) {
+
+            if (TAG_USES.equals(child.getTagName())) {
+                String attrValue = child.getAttribute(ATTR_NAME);
+                if (VAL_NAME_MEDIA.equals(attrValue)) {
+                    mIsAutomotiveMediaApp |= isMetadataResource;
+                } else if (!VAL_NAME_NOTIFICATION.equals(attrValue)
+                        && context.isEnabled(INVALID_USES_TAG_ISSUE)) {
+                    // Error invalid value for attribute.
+                    Attr node = child.getAttributeNode(ATTR_NAME);
+                    if (node == null) {
+                        // no name specified
+                        continue;
+                    }
+                    context.report(INVALID_USES_TAG_ISSUE, node,
+                            context.getLocation(node),
+                            "Expecting one of `" + VAL_NAME_MEDIA + "` or `" +
+                            VAL_NAME_NOTIFICATION + "` for the name " +
+                            "attribute in " + TAG_USES + " tag.");
+                }
+            }
+        }
+        // Report any errors that we have collected that can be shown to the user
+        // once we determine that this is an Automotive Media App.
+        if (mIsAutomotiveMediaApp
+                && !context.getProject().isLibrary()
+                && mMainApplicationHandle != null
+                && mDoAutomotiveAppCheck) {
+
+            Element node = (Element) mMainApplicationHandle.getClientData();
+
+            if (!mMediaIntentFilterFound
+                    && context.isEnabled(MISSING_MEDIA_BROWSER_SERVICE_ACTION_ISSUE)) {
+                context.report(MISSING_MEDIA_BROWSER_SERVICE_ACTION_ISSUE, node,
+                        mMainApplicationHandle.resolve(),
+                        "Missing `intent-filter` for action " +
+                                "`android.media.browse.MediaBrowserService` that is required for " +
+                                "android auto support");
+            }
+            if (!mMediaSearchIntentFilterFound
+                    && context.isEnabled(MISSING_INTENT_FILTER_FOR_MEDIA_SEARCH)) {
+                context.report(MISSING_INTENT_FILTER_FOR_MEDIA_SEARCH, node,
+                        mMainApplicationHandle.resolve(),
+                        "Missing `intent-filter` for action " +
+                                "`android.media.action.MEDIA_PLAY_FROM_SEARCH`.");
+            }
+        }
+    }
+
+    private void checkServiceForBrowserServiceIntentFilter(Element element) {
+        if (TAG_SERVICE.equals(element.getTagName())
+                && !mMediaIntentFilterFound) {
+
+            for (Element child : LintUtils.getChildren(element)) {
+                String tagName = child.getTagName();
+                if (TAG_INTENT_FILTER.equals(tagName)) {
+                    for (Element filterChild : LintUtils.getChildren(child)) {
+                        if (NODE_ACTION.equals(filterChild.getTagName())) {
+                            String actionValue = filterChild.getAttributeNS(ANDROID_URI, ATTR_NAME);
+                            if (ACTION_MEDIA_BROWSER_SERVICE.equals(actionValue)) {
+                                mMediaIntentFilterFound = true;
+                                return;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private void checkForMediaSearchIntentFilter(Element element) {
+        if (!mMediaSearchIntentFilterFound) {
+
+            for (Element filterChild : LintUtils.getChildren(element)) {
+                if (NODE_ACTION.equals(filterChild.getTagName())) {
+                    String actionValue = filterChild.getAttributeNS(ANDROID_URI, ATTR_NAME);
+                    if (ACTION_MEDIA_PLAY_FROM_SEARCH.equals(actionValue)) {
+                        mMediaSearchIntentFilterFound = true;
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    // Implementation of the JavaScanner
+
+    @Override
+    @Nullable
+    public List<String> applicableSuperClasses() {
+        // We currently enable scanning only for media apps.
+        return mIsAutomotiveMediaApp ?
+                Arrays.asList(CLASS_MEDIA_SESSION_CALLBACK,
+                        CLASS_V4MEDIA_SESSION_COMPAT_CALLBACK)
+                : null;
+    }
+
+    @Override
+    public void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration declaration,
+            @NonNull Node node, @NonNull JavaParser.ResolvedClass resolvedClass) {
+        // Only check classes that are not declared abstract.
+        if (declaration != null && (resolvedClass.getModifiers() & Modifier.ABSTRACT) == 0) {
+            MediaSessionCallbackVisitor visitor = new MediaSessionCallbackVisitor(context);
+            declaration.accept(visitor);
+            if (!visitor.isPlayFromSearchMethodFound()
+                    && context.isEnabled(MISSING_ON_PLAY_FROM_SEARCH)) {
+
+                context.report(MISSING_ON_PLAY_FROM_SEARCH, declaration.astName(),
+                        context.getLocation(declaration.astName()),
+                        "This class does not override `" +
+                        METHOD_MEDIA_SESSION_PLAY_FROM_SEARCH + "` from `MediaSession.Callback`" +
+                        " The method should be overridden and implemented to support " +
+                        "Voice search on Android Auto.");
+            }
+        }
+    }
+
+    @Override
+    @Nullable
+    public List<String> getApplicableMethodNames() {
+        return Collections.singletonList(METHOD_MEDIA_SESSION_PLAY_FROM_SEARCH);
+    }
+
+    /**
+     * A Visitor class to search for {@code MediaSession.Callback#onPlayFromSearch(..)}
+     * method declaration.
+     */
+    private static class MediaSessionCallbackVisitor extends ForwardingAstVisitor {
+
+        private final JavaContext mContext;
+
+        private boolean mOnPlayFromSearchFound;
+
+        public MediaSessionCallbackVisitor(JavaContext context) {
+            this.mContext = context;
+        }
+
+        public boolean isPlayFromSearchMethodFound() {
+            return mOnPlayFromSearchFound;
+        }
+
+        @Override
+        public boolean visitMethodDeclaration(MethodDeclaration node) {
+            JavaParser.ResolvedNode result = mContext.resolve(node);
+            if (result != null
+                    && METHOD_MEDIA_SESSION_PLAY_FROM_SEARCH.equals(result.getName())
+                    && result instanceof JavaParser.ResolvedMethod) {
+                JavaParser.ResolvedMethod method = (JavaParser.ResolvedMethod) result;
+                if (method.getArgumentCount() == 2) {
+                    JavaParser.TypeDescriptor firstArg = method.getArgumentType(0);
+                    JavaParser.TypeDescriptor secondArg = method.getArgumentType(1);
+                    if (firstArg.getTypeClass() != null
+                            && firstArg.getTypeClass().matches(STRING_ARG)
+                            && secondArg.getTypeClass() != null
+                            && secondArg.getTypeClass().matches(BUNDLE_ARG)) {
+                        mOnPlayFromSearchFound = true;
+                    }
+                }
+            }
+            return super.visitMethodDeclaration(node);
+        }
+    }
+
+    // Used by AS to show errors.
+    @NonNull
+    public static String[] getAllowedAutomotiveAppTypes() {
+        return new String[]{VAL_NAME_MEDIA, VAL_NAME_NOTIFICATION};
+    }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AndroidTvDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AndroidTvDetector.java
new file mode 100644
index 0000000..a1d175b
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AndroidTvDetector.java
@@ -0,0 +1,553 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.TAG_USES_FEATURE;
+import static com.android.SdkConstants.TAG_USES_PERMISSION;
+import static com.android.tools.lint.detector.api.TextFormat.RAW;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_REQUIRED;
+import static com.android.xml.AndroidManifest.NODE_ACTIVITY;
+import static com.android.xml.AndroidManifest.NODE_APPLICATION;
+import static com.android.xml.AndroidManifest.NODE_CATEGORY;
+import static com.android.xml.AndroidManifest.NODE_INTENT;
+import static com.android.xml.AndroidManifest.NODE_USES_FEATURE;
+import static com.android.xml.AndroidManifest.NODE_USES_PERMISSION;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.TextFormat;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Detects various issues for Android TV.
+ */
+public class AndroidTvDetector extends Detector implements Detector.XmlScanner {
+    private static final Implementation IMPLEMENTATION = new Implementation(
+            AndroidTvDetector.class,
+            Scope.MANIFEST_SCOPE
+    );
+
+    /** Using hardware unsupported by TV */
+    public static final Issue UNSUPPORTED_TV_HARDWARE = Issue.create(
+            "UnsupportedTvHardware", //$NON-NLS-1$
+            "Unsupported TV Hardware Feature",
+            "The <uses-feature> element should not require this unsupported TV hardware feature. " +
+            "Any uses-feature not explicitly marked with required=\"false\" is necessary on the " +
+            "device to be installed on. " +
+            "Ensure that any features that might prevent it from being installed on a TV device " +
+            "are reviewed and marked as not required in the manifest.",
+            Category.CORRECTNESS,
+            6,
+            Severity.ERROR,
+            IMPLEMENTATION).addMoreInfo(
+            "https://developer.android.com/training/tv/start/hardware.html#unsupported-features");
+
+    /** Missing leanback launcher intent filter */
+    public static final Issue MISSING_LEANBACK_LAUNCHER = Issue.create(
+            "MissingLeanbackLauncher", //$NON-NLS-1$
+            "Missing Leanback Launcher Intent Filter.",
+            "An application intended to run on TV devices must declare a launcher activity " +
+            "for TV in its manifest using a `android.intent.category.LEANBACK_LAUNCHER` " +
+            "intent filter.",
+            Category.CORRECTNESS,
+            8,
+            Severity.ERROR,
+            IMPLEMENTATION)
+            .addMoreInfo("https://developer.android.com/training/tv/start/start.html#tv-activity");
+
+    /** Missing leanback support */
+    public static final Issue MISSING_LEANBACK_SUPPORT = Issue.create(
+            "MissingLeanbackSupport", //$NON-NLS-1$
+            "Missing Leanback Support.",
+            "The manifest should declare the use of the Leanback user interface required " +
+            "by Android TV.\n" +
+            "To fix this, add\n" +
+            "`<uses-feature android:name=\"android.software.leanback\" " +
+            "  android:required=\"false\" />`\n" +
+            "to your manifest.",
+            Category.CORRECTNESS,
+            6,
+            Severity.ERROR,
+            IMPLEMENTATION)
+            .addMoreInfo("https://developer.android.com/training/tv/start/start.html#leanback-req");
+
+    /** Permission implies required hardware unsupported by TV */
+    public static final Issue PERMISSION_IMPLIES_UNSUPPORTED_HARDWARE = Issue.create(
+            "PermissionImpliesUnsupportedHardware", //$NON-NLS-1$
+            "Permission Implies Unsupported Hardware",
+
+            "The <uses-permission> element should not require a permission that implies an " +
+            "unsupported TV hardware feature. Google Play assumes that certain hardware related " +
+            "permissions indicate that the underlying hardware features are required by default. " +
+            "To fix the issue, consider declaring the corresponding uses-feature element with " +
+            "required=\"false\" attribute.",
+            Category.CORRECTNESS,
+            3,
+            Severity.WARNING,
+            IMPLEMENTATION).addMoreInfo(
+            "http://developer.android.com/guide/topics/manifest/uses-feature-element.html#permissions");
+
+    /** Missing banner attibute */
+    public static final Issue MISSING_BANNER = Issue.create(
+            "MissingTvBanner", //$NON-NLS-1$
+            "TV Missing Banner",
+            "A TV application must provide a home screen banner for each localization if it " +
+            "includes a Leanback launcher intent filter. The banner is the app launch point that " +
+            "appears on the home screen in the apps and games rows.",
+            Category.CORRECTNESS,
+            5,
+            Severity.WARNING,
+            IMPLEMENTATION)
+            .addMoreInfo("http://developer.android.com/training/tv/start/start.html#banner");
+
+    public static final String SOFTWARE_FEATURE_LEANBACK =
+            "android.software.leanback"; //$NON-NLS-1$
+
+    private static final String LEANBACK_LIB_ARTIFACT =
+            "com.android.support:leanback-v17"; //$NON-NLS-1$
+
+    private static final String CATEGORY_LEANBACK_LAUNCHER =
+            "android.intent.category.LEANBACK_LAUNCHER"; //$NON-NLS-1$
+
+    private static final String HARDWARE_FEATURE_CAMERA = "android.hardware.camera"; //$NON-NLS-1$
+
+    private static final String HARDWARE_FEATURE_LOCATION_GPS =
+            "android.hardware.location.gps"; //$NON-NLS-1$
+
+    private static final String ANDROID_HARDWARE_TELEPHONY =
+            "android.hardware.telephony"; //$NON-NLS-1$
+
+    private static final String ANDROID_HARDWARE_BLUETOOTH =
+            "android.hardware.bluetooth"; //$NON-NLS-1$
+
+    private static final String ATTR_BANNER = "banner"; //$NON-NLS-1$
+
+    private static final String ANDROID_HARDWARE_MICROPHONE =
+            "android.hardware.microphone"; //$NON-NLS-1$
+
+    // https://developer.android.com/training/tv/start/hardware.html
+    private static final Set<String> UNSUPPORTED_HARDWARE_FEATURES =
+            ImmutableSet.<String>builder()
+                    .add("android.hardware.touchscreen") //$NON-NLS-1$
+                    .add("android.hardware.faketouch") //$NON-NLS-1$
+                    .add(ANDROID_HARDWARE_TELEPHONY)
+                    .add(HARDWARE_FEATURE_CAMERA)
+                    .add(ANDROID_HARDWARE_BLUETOOTH)
+                    .add("android.hardware.nfc") //$NON-NLS-1$
+                    .add(HARDWARE_FEATURE_LOCATION_GPS) //$NON-NLS-1$
+                    .add(ANDROID_HARDWARE_MICROPHONE) //$NON-NLS-1$
+                    .add("android.hardware.sensors") //$NON-NLS-1$
+                    .build();
+
+    private static final Map<String, String> PERMISSIONS_TO_IMPLIED_UNSUPPORTED_HARDWARE =
+            ImmutableMap.<String, String>builder()
+                    .put("android.permission.BLUETOOTH", //$NON-NLS-1$
+                            ANDROID_HARDWARE_BLUETOOTH)
+                    .put("android.permission.BLUETOOTH_ADMIN", //$NON-NLS-1$
+                            ANDROID_HARDWARE_BLUETOOTH)
+                    .put("android.permission.CAMERA", //$NON-NLS-1$
+                            HARDWARE_FEATURE_CAMERA)
+                    .put("android.permission.RECORD_AUDIO", //$NON-NLS-1$
+                            ANDROID_HARDWARE_MICROPHONE)
+                    .put("android.permission.ACCESS_FINE_LOCATION", //$NON-NLS-1$
+                            HARDWARE_FEATURE_LOCATION_GPS)
+                    .put("android.permission.CALL_PHONE", //$NON-NLS-1$
+                            ANDROID_HARDWARE_TELEPHONY)
+                    .put("android.permission.CALL_PRIVILEGED", //$NON-NLS-1$
+                            ANDROID_HARDWARE_TELEPHONY)
+                    .put("android.permission.PROCESS_OUTGOING_CALLS", //$NON-NLS-1$
+                            ANDROID_HARDWARE_TELEPHONY)
+                    .put("android.permission.READ_SMS", //$NON-NLS-1$
+                            ANDROID_HARDWARE_TELEPHONY)
+                    .put("android.permission.RECEIVE_SMS", //$NON-NLS-1$
+                            ANDROID_HARDWARE_TELEPHONY)
+                    .put("android.permission.RECEIVE_MMS", //$NON-NLS-1$
+                            ANDROID_HARDWARE_TELEPHONY)
+                    .put("android.permission.RECEIVE_WAP_PUSH", //$NON-NLS-1$
+                            ANDROID_HARDWARE_TELEPHONY)
+                    .put("android.permission.SEND_SMS", //$NON-NLS-1$
+                            ANDROID_HARDWARE_TELEPHONY)
+                    .put("android.permission.WRITE_APN_SETTINGS", //$NON-NLS-1$
+                            ANDROID_HARDWARE_TELEPHONY)
+                    .put("android.permission.WRITE_SMS", //$NON-NLS-1$
+                            ANDROID_HARDWARE_TELEPHONY)
+                    .build();
+
+    /**
+     * If you change number of parameters or order, update
+     * {@link #getHardwareFeature(String, TextFormat)}
+     */
+    private static final String USES_HARDWARE_ERROR_MESSAGE_FORMAT =
+            "Permission exists without corresponding hardware `<uses-feature "
+                    + "android:name=\"%1$s\" required=\"false\">` tag.";
+
+    /** Constructs a new {@link AndroidTvDetector} check */
+    public AndroidTvDetector() {
+    }
+
+    /** Used for {@link #MISSING_LEANBACK_LAUNCHER} */
+    private boolean mHasLeanbackLauncherActivity;
+
+    /** Used for {@link #MISSING_LEANBACK_SUPPORT} */
+    private boolean mHasLeanbackSupport;
+
+    /** Whether the app has a leanback-v7 dependency */
+    private boolean mHasLeanbackDependency;
+
+    /** Used for {@link #MISSING_BANNER} */
+    private boolean mHasApplicationBanner;
+
+    /** No. of activities that have the leanback intent but
+     * dont declare banners */
+    private int mLeanbackActivitiesWithoutBanners;
+
+    /** All permissions that imply unsupported tv hardware. */
+    private List<String> mUnsupportedHardwareImpliedPermissions;
+
+    /** All Unsupported TV uses features in use by the current manifest.*/
+    private Set<String> mAllUnsupportedTvUsesFeatures;
+
+    /** Set containing unsupported TV uses-features elements without required="false" */
+    private Set<String> mUnsupportedTvUsesFeatures;
+
+    @NonNull
+    @Override
+    public Speed getSpeed() {
+        return Speed.FAST;
+    }
+
+    @Override
+    public Collection<String> getApplicableElements() {
+        return Arrays.asList(
+                NODE_APPLICATION,
+                NODE_ACTIVITY,
+                NODE_USES_FEATURE,
+                NODE_USES_PERMISSION
+        );
+    }
+
+    @Override
+    public void beforeCheckFile(@NonNull Context context) {
+        mHasLeanbackLauncherActivity = false;
+        mHasLeanbackSupport = false;
+        mHasApplicationBanner = false;
+        mLeanbackActivitiesWithoutBanners = 0;
+        mUnsupportedHardwareImpliedPermissions = Lists.newArrayListWithExpectedSize(2);
+        mUnsupportedTvUsesFeatures = Sets.newHashSetWithExpectedSize(2);
+        mAllUnsupportedTvUsesFeatures = Sets.newHashSetWithExpectedSize(2);
+
+        // Check gradle dependency
+        Project mainProject = context.getMainProject();
+        mHasLeanbackDependency = (mainProject.isGradleProject()
+                && Boolean.TRUE.equals(mainProject.dependsOn(LEANBACK_LIB_ARTIFACT)));
+    }
+
+    @Override
+    public void afterCheckFile(@NonNull Context context) {
+        boolean isTvApp = mHasLeanbackSupport
+                || mHasLeanbackDependency
+                || mHasLeanbackLauncherActivity;
+
+        if (!context.getMainProject().isLibrary() && isTvApp) {
+            XmlContext xmlContext = (XmlContext) context;
+            // Report an error if there's not at least one leanback launcher intent filter activity
+            if (!mHasLeanbackLauncherActivity
+                    && xmlContext.isEnabled(MISSING_LEANBACK_LAUNCHER)) {
+                // No launch activity
+                Node manifestNode = xmlContext.document.getDocumentElement();
+                if (manifestNode != null) {
+                    xmlContext.report(MISSING_LEANBACK_LAUNCHER, manifestNode,
+                            xmlContext.getLocation(manifestNode),
+                            "Expecting an activity to have `" + CATEGORY_LEANBACK_LAUNCHER +
+                                    "` intent filter.");
+                }
+            }
+
+            // Report an issue if there is no leanback <uses-feature> tag.
+            if (!mHasLeanbackSupport
+                    && xmlContext.isEnabled(MISSING_LEANBACK_SUPPORT)) {
+                Node manifestNode = xmlContext.document.getDocumentElement();
+                if (manifestNode != null) {
+                    xmlContext.report(MISSING_LEANBACK_SUPPORT, manifestNode,
+                            xmlContext.getLocation(manifestNode),
+                            "Expecting <uses-feature android:name=\"android.software.leanback\" " +
+                                    "android:required=\"false\" /> tag.");
+                }
+            }
+
+            // Report missing banners
+            if (!mHasApplicationBanner // no application banner
+                    && mLeanbackActivitiesWithoutBanners > 0 // leanback activity without banner
+                    && xmlContext.isEnabled(MISSING_BANNER)) {
+                Node applicationElement = getApplicationElement(xmlContext.document);
+                if (applicationElement != null) {
+                    xmlContext.report(MISSING_BANNER, applicationElement,
+                            xmlContext.getLocation(applicationElement),
+                            "Expecting `android:banner` with the `<application>` tag or each "
+                                    + "Leanback launcher activity.");
+                }
+            }
+
+            // Report all unsupported TV hardware uses-feature.
+            // These point to all unsupported tv uses features that have not be marked
+            // required = false;
+            if (!mUnsupportedTvUsesFeatures.isEmpty()
+                    && xmlContext.isEnabled(UNSUPPORTED_TV_HARDWARE)) {
+                List<Element> usesFeatureElements =
+                        findUsesFeatureElements(mUnsupportedTvUsesFeatures, xmlContext.document);
+                for (Element element : usesFeatureElements) {
+                    Attr attrRequired = element.getAttributeNodeNS(ANDROID_URI, ATTRIBUTE_REQUIRED);
+                    Node location = attrRequired == null ? element : attrRequired;
+                    xmlContext.report(UNSUPPORTED_TV_HARDWARE, location,
+                            xmlContext.getLocation(location),
+                            "Expecting `android:required=\"false\"` for this hardware "
+                                    + "feature that may not be supported by all Android TVs.");
+                }
+            }
+
+            // Report permissions implying unsupported hardware
+            if (!mUnsupportedHardwareImpliedPermissions.isEmpty()
+                    && xmlContext.isEnabled(PERMISSION_IMPLIES_UNSUPPORTED_HARDWARE)) {
+
+                Collection<String> filteredPermissions = Collections2.filter(
+                        mUnsupportedHardwareImpliedPermissions,
+                        new Predicate<String>() {
+                            @Override
+                            public boolean apply(String input) {
+                                // Filter out all permissions that already have their
+                                // corresponding implied hardware declared in
+                                // the AndroidManifest.xml
+                                String usesFeature =
+                                        PERMISSIONS_TO_IMPLIED_UNSUPPORTED_HARDWARE.get(input);
+                                return usesFeature != null
+                                        && !mAllUnsupportedTvUsesFeatures.contains(usesFeature);
+                            }
+                        });
+
+                List<Element> permissionsWithoutUsesFeatures =
+                        findPermissionElements(filteredPermissions, xmlContext.document);
+
+                for (Element permissionElement : permissionsWithoutUsesFeatures) {
+                    String name = permissionElement.getAttributeNS(ANDROID_URI, ATTR_NAME);
+                    String unsupportedHardwareName =
+                            PERMISSIONS_TO_IMPLIED_UNSUPPORTED_HARDWARE.get(name);
+
+                    if (unsupportedHardwareName != null) {
+                        String message = String.format(
+                                USES_HARDWARE_ERROR_MESSAGE_FORMAT, unsupportedHardwareName);
+                        xmlContext.report(PERMISSION_IMPLIES_UNSUPPORTED_HARDWARE,
+                                permissionElement,
+                                xmlContext.getLocation(permissionElement), message);
+                    }
+                }
+            }
+        }
+    }
+
+    private static List<Element> findPermissionElements(Collection<String> permissions,
+            Document document) {
+        Node manifestElement = document.getDocumentElement();
+        if (manifestElement == null) {
+            return Collections.emptyList();
+        }
+        List<Element> nodes = new ArrayList<Element>(permissions.size());
+        for (Element child : LintUtils.getChildren(manifestElement)) {
+            if (TAG_USES_PERMISSION.equals(child.getTagName())
+                    && permissions.contains(child.getAttributeNS(ANDROID_URI, ATTR_NAME))) {
+                nodes.add(child);
+            }
+        }
+        return nodes;
+    }
+
+    /**
+     * Method to find all matching uses-feature elements in one go.
+     * Rather than iterating over the entire list of child nodes only to return the one that
+     * match a particular featureName, we use this method to iterate and return all the
+     * uses-feature elements of interest in a single iteration of the manifest element's children.
+     *
+     * @param featureNames The set of all features to look for inside the
+     *                     <code>&lt;manifest&gt;</code> node of the document.
+     * @param document The document/root node to use for iterating.
+     * @return A list of all <code>&lt;uses-feature&gt;</code> elements that match the featureNames.
+     */
+    private static List<Element> findUsesFeatureElements(@NonNull Set<String> featureNames,
+            @NonNull Document document) {
+        Node manifestElement = document.getDocumentElement();
+        if (manifestElement == null) {
+            return Collections.emptyList();
+        }
+        List<Element> nodes = new ArrayList<Element>(featureNames.size());
+        for (Element child : LintUtils.getChildren(manifestElement)) {
+            if (TAG_USES_FEATURE.equals(child.getTagName())
+                    && featureNames.contains(child.getAttributeNS(ANDROID_URI, ATTR_NAME))) {
+                nodes.add(child);
+            }
+        }
+        return nodes;
+    }
+
+    /**
+     * @param document The root of the document.
+     * @return The Node pointing to the {@link com.android.xml.AndroidManifest#NODE_APPLICATION}
+     *         of the document.
+     */
+    private static Node getApplicationElement(Document document) {
+        Node manifestNode = document.getDocumentElement();
+        if (manifestNode != null) {
+            return getElementWithTagName(NODE_APPLICATION, manifestNode);
+        }
+        return null;
+    }
+
+    @Override
+    public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+        String elementName = element.getTagName();
+
+        if (NODE_APPLICATION.equals(elementName)) {
+            mHasApplicationBanner = element.hasAttributeNS(ANDROID_URI, ATTR_BANNER);
+        } else if (NODE_USES_FEATURE.equals(elementName)) {
+            // Ensures that unsupported hardware features aren't required.
+            Attr name = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
+            if (name != null) {
+                String featureName = name.getValue();
+                if (isUnsupportedHardwareFeature(featureName)) {
+                    mAllUnsupportedTvUsesFeatures.add(featureName);
+                    Attr required =
+                            element.getAttributeNodeNS(ANDROID_URI, ATTRIBUTE_REQUIRED);
+                    if (required == null || Boolean.parseBoolean(required.getValue())) {
+                        mUnsupportedTvUsesFeatures.add(featureName);
+                    }
+                }
+            }
+
+            if (!mHasLeanbackSupport && hasLeanbackSupport(element)) {
+                mHasLeanbackSupport = true;
+            }
+        } else if (NODE_ACTIVITY.equals(elementName) && hasLeanbackIntentFilter(element)) {
+            mHasLeanbackLauncherActivity = true;
+            // Since this activity has a leanback launcher intent filter,
+            // Make sure it has a home screen banner
+            if (!element.hasAttributeNS(ANDROID_URI, ATTR_BANNER)) {
+                mLeanbackActivitiesWithoutBanners++;
+            }
+        } else if (NODE_USES_PERMISSION.equals(elementName)) {
+
+            // Store all <uses-permission> tags that imply unsupported hardware)
+            String permissionName = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
+            if (PERMISSIONS_TO_IMPLIED_UNSUPPORTED_HARDWARE.containsKey(permissionName)) {
+                mUnsupportedHardwareImpliedPermissions.add(permissionName);
+            }
+        }
+    }
+
+    private static boolean hasLeanbackSupport(Element element) {
+        assert NODE_USES_FEATURE.equals(element.getTagName()) : element.getTagName();
+        return SOFTWARE_FEATURE_LEANBACK.equals(element.getAttributeNS(ANDROID_URI, ATTR_NAME));
+    }
+
+    private static boolean isUnsupportedHardwareFeature(@NonNull String featureName) {
+        for (String prefix : UNSUPPORTED_HARDWARE_FEATURES) {
+            if (featureName.startsWith(prefix)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean hasLeanbackIntentFilter(@NonNull Node activityNode) {
+        assert NODE_ACTIVITY.equals(activityNode.getNodeName()) : activityNode.getNodeName();
+        // Visit every intent filter
+        for (Element activityChild : LintUtils.getChildren(activityNode)) {
+            if (NODE_INTENT.equals(activityChild.getNodeName())) {
+                for (Element intentFilterChild : LintUtils.getChildren(activityChild)) {
+                    // Check to see if the category is the leanback launcher
+                    String attrName = intentFilterChild.getAttributeNS(ANDROID_URI, ATTR_NAME);
+                    if (NODE_CATEGORY.equals(intentFilterChild.getNodeName())
+                            && CATEGORY_LEANBACK_LAUNCHER.equals(attrName)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Assumes that the node is a direct child of the given Node.
+     */
+    private static Node getElementWithTagName(@NonNull String tagName, @NonNull Node node) {
+        for (Element child : LintUtils.getChildren(node)) {
+            if (tagName.equals(child.getTagName())) {
+                return child;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Given an error message created by this lint check, return the corresponding featureName
+     * that it suggests should be added.
+     * (Intended to support quickfix implementations for this lint check.)
+     *
+     * @param errorMessage The error message originally produced by this detector.
+     * @param format The format of the error message.
+     * @return the corresponding featureName, or null if not recognized
+     */
+    @Nullable
+    public static String getHardwareFeature(@NonNull String errorMessage,
+            @NonNull TextFormat format) {
+        List<String> parameters = LintUtils.getFormattedParameters(
+                RAW.convertTo(USES_HARDWARE_ERROR_MESSAGE_FORMAT, format),
+                errorMessage);
+        if (parameters.size() == 1) {
+            return parameters.get(0);
+        }
+        return null;
+    }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AnnotationDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AnnotationDetector.java
index 9cd147f..6d418e4 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AnnotationDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AnnotationDetector.java
@@ -21,14 +21,20 @@
 import static com.android.SdkConstants.INT_DEF_ANNOTATION;
 import static com.android.SdkConstants.SUPPRESS_LINT;
 import static com.android.SdkConstants.TYPE_DEF_FLAG_ATTRIBUTE;
+import static com.android.tools.lint.checks.SupportAnnotationDetector.filterRelevantAnnotations;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_INT;
 import static com.android.tools.lint.detector.api.JavaContext.findSurroundingClass;
 import static com.android.tools.lint.detector.api.JavaContext.getParentOfType;
+import static com.android.tools.lint.detector.api.LintUtils.findSubstring;
 
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
 import com.android.tools.lint.client.api.IssueRegistry;
 import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedField;
 import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
 import com.android.tools.lint.detector.api.Category;
 import com.android.tools.lint.detector.api.Context;
 import com.android.tools.lint.detector.api.Detector;
@@ -39,13 +45,21 @@
 import com.android.tools.lint.detector.api.Scope;
 import com.android.tools.lint.detector.api.Severity;
 import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.TextFormat;
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+import com.google.common.base.Splitter;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 
 import java.io.File;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
+import java.util.Set;
 
 import lombok.ast.Annotation;
 import lombok.ast.AnnotationDeclaration;
@@ -53,18 +67,27 @@
 import lombok.ast.AnnotationValue;
 import lombok.ast.ArrayInitializer;
 import lombok.ast.AstVisitor;
+import lombok.ast.BinaryExpression;
+import lombok.ast.BinaryOperator;
 import lombok.ast.Block;
+import lombok.ast.Case;
+import lombok.ast.Cast;
 import lombok.ast.ClassDeclaration;
 import lombok.ast.ConstructorDeclaration;
 import lombok.ast.Expression;
+import lombok.ast.ExpressionStatement;
 import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.InlineIfExpression;
 import lombok.ast.IntegralLiteral;
 import lombok.ast.MethodDeclaration;
+import lombok.ast.MethodInvocation;
 import lombok.ast.Modifiers;
 import lombok.ast.Node;
 import lombok.ast.Select;
+import lombok.ast.Statement;
 import lombok.ast.StrictListAccessor;
 import lombok.ast.StringLiteral;
+import lombok.ast.Switch;
 import lombok.ast.TypeBody;
 import lombok.ast.TypeMember;
 import lombok.ast.VariableDeclaration;
@@ -134,6 +157,19 @@
             Severity.WARNING,
             IMPLEMENTATION);
 
+    /** All IntDef constants should be included in switch */
+    public static final Issue SWITCH_TYPE_DEF = Issue.create(
+            "SwitchIntDef", //$NON-NLS-1$
+            "Missing @IntDef in Switch",
+
+            "This check warns if a `switch` statement does not explicitly include all " +
+            "the values declared by the typedef `@IntDef` declaration.",
+
+            Category.CORRECTNESS,
+            3,
+            Severity.WARNING,
+            IMPLEMENTATION);
+
     /** Constructs a new {@link AnnotationDetector} check */
     public AnnotationDetector() {
     }
@@ -153,7 +189,8 @@
 
     @Override
     public List<Class<? extends Node>> getApplicableNodeTypes() {
-        return Collections.<Class<? extends Node>>singletonList(Annotation.class);
+        //noinspection unchecked
+        return Arrays.<Class<? extends Node>>asList(Annotation.class, Switch.class);
     }
 
     @Override
@@ -217,8 +254,192 @@
             return super.visitAnnotation(node);
         }
 
+        @Override
+        public boolean visitSwitch(Switch node) {
+            Expression condition = node.astCondition();
+            TypeDescriptor type = mContext.getType(condition);
+            if (type != null && type.matchesName(TYPE_INT)) {
+                ResolvedAnnotation annotation = findIntDef(condition);
+                if (annotation != null) {
+                    checkSwitch(node, annotation);
+                }
+            }
+
+            return super.visitSwitch(node);
+        }
+
+        /**
+         * Searches for the corresponding @IntDef annotation definition associated
+         * with a given node
+         */
+        @Nullable
+        private ResolvedAnnotation findIntDef(@NonNull Node node) {
+            if ((node instanceof VariableReference || node instanceof Select)) {
+                ResolvedNode resolved = mContext.resolve(node);
+                if (resolved == null) {
+                    return null;
+                }
+
+                ResolvedAnnotation annotation = SupportAnnotationDetector.findIntDef(
+                        filterRelevantAnnotations(resolved.getAnnotations()));
+                if (annotation != null) {
+                    return annotation;
+                }
+
+                if (node instanceof VariableReference) {
+                    Statement statement = getParentOfType(node, Statement.class, false);
+                    if (statement != null) {
+                        ListIterator<Node> iterator =
+                                statement.getParent().getChildren().listIterator();
+                        while (iterator.hasNext()) {
+                            if (iterator.next() == statement) {
+                                if (iterator.hasPrevious()) { // should always be true
+                                    iterator.previous();
+                                }
+                                break;
+                            }
+                        }
+
+                        String targetName = ((VariableReference) node).astIdentifier().astValue();
+                        while (iterator.hasPrevious()) {
+                            Node previous = iterator.previous();
+                            if (previous instanceof VariableDeclaration) {
+                                VariableDeclaration declaration = (VariableDeclaration) previous;
+                                VariableDefinition definition = declaration.astDefinition();
+                                for (VariableDefinitionEntry entry : definition
+                                        .astVariables()) {
+                                    if (entry.astInitializer() != null
+                                            && entry.astName().astValue().equals(targetName)) {
+                                        return findIntDef(entry.astInitializer());
+                                    }
+                                }
+                            } else if (previous instanceof ExpressionStatement) {
+                                ExpressionStatement expressionStatement =
+                                        (ExpressionStatement) previous;
+                                Expression expression = expressionStatement.astExpression();
+                                if (expression instanceof BinaryExpression &&
+                                        ((BinaryExpression) expression).astOperator()
+                                                == BinaryOperator.ASSIGN) {
+                                    BinaryExpression binaryExpression
+                                            = (BinaryExpression) expression;
+                                    if (targetName.equals(binaryExpression.astLeft().toString())) {
+                                        return findIntDef(binaryExpression.astRight());
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            } else if (node instanceof MethodInvocation) {
+                ResolvedNode resolved = mContext.resolve(node);
+                if (resolved != null) {
+                    ResolvedAnnotation annotation = SupportAnnotationDetector
+                            .findIntDef(filterRelevantAnnotations(resolved.getAnnotations()));
+                    if (annotation != null) {
+                        return annotation;
+                    }
+                }
+            } else if (node instanceof InlineIfExpression) {
+                InlineIfExpression expression = (InlineIfExpression) node;
+                if (expression.astIfTrue() != null) {
+                    ResolvedAnnotation result = findIntDef(expression.astIfTrue());
+                    if (result != null) {
+                        return result;
+                    }
+                }
+                if (expression.astIfFalse() != null) {
+                    ResolvedAnnotation result = findIntDef(expression.astIfFalse());
+                    if (result != null) {
+                        return result;
+                    }
+                }
+            } else if (node instanceof Cast) {
+                Cast cast = (Cast) node;
+                return findIntDef(cast.astOperand());
+            }
+
+            return null;
+        }
+
+        private void checkSwitch(@NonNull Switch node, @NonNull ResolvedAnnotation annotation) {
+            Block block = node.astBody();
+            if (block == null) {
+                return;
+            }
+
+            Object allowed = annotation.getValue();
+            if (!(allowed instanceof Object[])) {
+                return;
+            }
+            Object[] allowedValues = (Object[]) allowed;
+            List<ResolvedField> fields = Lists.newArrayListWithCapacity(allowedValues.length);
+            for (Object o : allowedValues) {
+                if (o instanceof ResolvedField) {
+                    fields.add((ResolvedField) o);
+                }
+            }
+
+            // Empty switch: arguably we could skip these (since the IDE already warns about
+            // empty switches) but it's useful since the quickfix will kick in and offer all
+            // the missing ones when you're editing.
+            //   if (block.astContents().isEmpty()) { return; }
+
+            for (Statement statement : block.astContents()) {
+                if (statement instanceof Case) {
+                    Case caseStatement = (Case) statement;
+                    Expression expression = caseStatement.astCondition();
+                    if (expression instanceof IntegralLiteral) {
+                        // Report warnings if you specify hardcoded constants.
+                        // It's the wrong thing to do.
+                        List<String> list = computeFieldNames(node, Arrays.asList(allowedValues));
+                        // Keep error message in sync with {@link #getMissingCases}
+                        String message = "Don't use a constant here; expected one of: " + Joiner
+                                .on(", ").join(list);
+                        mContext.report(SWITCH_TYPE_DEF, expression,
+                                mContext.getLocation(expression), message);
+                        return; // Don't look for other missing typedef constants since you might
+                        // have aliased with value
+                    } else if (expression != null) { // default case can have null expression
+                        ResolvedNode resolved = mContext.resolve(expression);
+                        if (resolved == null) {
+                            // If there are compilation issues (e.g. user is editing code) we
+                            // can't be certain, so don't flag anything.
+                            return;
+                        }
+                        if (resolved instanceof ResolvedField) {
+                            // We can't just do
+                            //    fields.remove(resolved);
+                            // since the fields list contains instances of potentially
+                            // different types with different hash codes. The
+                            // equals method on ResolvedExternalField deliberately handles
+                            // this (but it can't make its hash code match what
+                            // the ECJ fields do, which is tied to the ECJ binding hash code.)
+                            // So instead, manually check for equals. These lists tend to
+                            // be very short anyway.
+                            ListIterator<ResolvedField> iterator = fields.listIterator();
+                            while (iterator.hasNext()) {
+                                ResolvedField field = iterator.next();
+                                if (field.equals(resolved)) {
+                                    iterator.remove();
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            if (!fields.isEmpty()) {
+                List<String> list = computeFieldNames(node, fields);
+                // Keep error message in sync with {@link #getMissingCases}
+                String message = "Switch statement on an `int` with known associated constant "
+                        + "missing case " + Joiner.on(", ").join(list);
+                Location location = mContext.getNameLocation(node);
+                mContext.report(SWITCH_TYPE_DEF, node, location, message);
+            }
+        }
+
         private void ensureUniqueValues(@NonNull ResolvedAnnotation annotation,
-                @NonNull Annotation node) {
+                                        @NonNull Annotation node) {
             Object allowed = annotation.getValue();
             if (allowed instanceof Object[]) {
                 Object[] allowedValues = (Object[]) allowed;
@@ -410,6 +631,59 @@
 
             return true;
         }
+
+        @NonNull
+        private List<String> computeFieldNames(@NonNull Switch node, Iterable allowedValues) {
+            List<String> list = Lists.newArrayList();
+            for (Object o : allowedValues) {
+                if (o instanceof ResolvedField) {
+                    ResolvedField field = (ResolvedField) o;
+                    // Only include class name if necessary
+                    String name = field.getName();
+                    ClassDeclaration clz = findSurroundingClass(node);
+                    if (clz != null) {
+                        ResolvedNode resolved = mContext.resolve(clz);
+                        ResolvedClass containingClass = field.getContainingClass();
+                        if (containingClass != null && !containingClass.equals(resolved)
+                                && resolved instanceof ResolvedClass) {
+                            if (Objects.equal(containingClass.getPackage(),
+                                    ((ResolvedClass) resolved).getPackage())) {
+                                name = containingClass.getSimpleName() + '.' + field.getName();
+                            } else {
+                                name = containingClass.getName() + '.' + field.getName();
+                            }
+                        }
+                    }
+                    list.add('`' + name + '`');
+                }
+            }
+            Collections.sort(list);
+            return list;
+        }
+    }
+
+    /**
+     * Given an error message produced by this lint detector for the {@link #SWITCH_TYPE_DEF} issue
+     * type, returns the list of missing enum cases. <p> Intended for IDE quickfix implementations.
+     *
+     * @param errorMessage the error message associated with the error
+     * @param format       the format of the error message
+     * @return the list of enum cases, or null if not recognized
+     */
+    @Nullable
+    public static List<String> getMissingCases(@NonNull String errorMessage,
+            @NonNull TextFormat format) {
+        errorMessage = format.toText(errorMessage);
+
+        String substring = findSubstring(errorMessage, " missing case ", null);
+        if (substring == null) {
+            substring = findSubstring(errorMessage, "expected one of: ", null);
+        }
+        if (substring != null) {
+            return Splitter.on(",").trimResults().splitToList(substring);
+        }
+
+        return null;
     }
 
     /**
@@ -420,7 +694,6 @@
      */
     @NonNull
     private static Node getAnnotationScope(@NonNull Annotation node) {
-        //
         Node scope = getParentOfType(node,
               AnnotationDeclaration.class, true);
         if (scope == null) {
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/Api.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/Api.java
index ca84b27..8b08290 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/Api.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/Api.java
@@ -17,11 +17,14 @@
 package com.android.tools.lint.checks;
 
 
+import com.android.annotations.NonNull;
+
 import org.xml.sax.SAXException;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -44,14 +47,25 @@
      * @return a new ApiInfo
      */
     public static Api parseApi(File apiFile) {
-        FileInputStream fileInputStream = null;
+        InputStream inputStream = null;
         try {
-            fileInputStream = new FileInputStream(apiFile);
+            inputStream = new FileInputStream(apiFile);
             SAXParserFactory parserFactory = SAXParserFactory.newInstance();
             SAXParser parser = parserFactory.newSAXParser();
             ApiParser apiParser = new ApiParser();
-            parser.parse(fileInputStream, apiParser);
-            return new Api(apiParser.getClasses());
+            parser.parse(inputStream, apiParser);
+            inputStream.close();
+
+            // Also read in API (unless regenerating the map for newer libraries)
+            //noinspection PointlessBooleanExpression
+            if (!ApiLookup.DEBUG_FORCE_REGENERATE_BINARY) {
+                inputStream = Api.class.getResourceAsStream("api-versions-support-library.xml");
+                if (inputStream != null) {
+                    parser.parse(inputStream, apiParser);
+                }
+            }
+
+            return new Api(apiParser.getClasses(), apiParser.getPackages());
         } catch (ParserConfigurationException e) {
             e.printStackTrace();
         } catch (SAXException e) {
@@ -59,9 +73,9 @@
         } catch (IOException e) {
             e.printStackTrace();
         } finally {
-            if (fileInputStream != null) {
+            if (inputStream != null) {
                 try {
-                    fileInputStream.close();
+                    inputStream.close();
                 } catch (IOException e) {
                     // ignore
                 }
@@ -72,9 +86,13 @@
     }
 
     private final Map<String, ApiClass> mClasses;
+    private final Map<String, ApiPackage> mPackages;
 
-    private Api(Map<String, ApiClass> classes) {
+    private Api(
+            @NonNull Map<String, ApiClass> classes,
+            @NonNull Map<String, ApiPackage> packages) {
         mClasses = new HashMap<String, ApiClass>(classes);
+        mPackages = new HashMap<String, ApiPackage>(packages);
     }
 
     ApiClass getClass(String fqcn) {
@@ -84,4 +102,8 @@
     Map<String, ApiClass> getClasses() {
         return Collections.unmodifiableMap(mClasses);
     }
+
+    Map<String, ApiPackage> getPackages() {
+        return Collections.unmodifiableMap(mPackages);
+    }
 }
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiClass.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiClass.java
index c5946b9..64478f2 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiClass.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiClass.java
@@ -18,6 +18,7 @@
 
 import static com.android.SdkConstants.CONSTRUCTOR_NAME;
 
+import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
 import com.android.utils.Pair;
 import com.google.common.collect.Lists;
@@ -36,20 +37,31 @@
  * {@link #getMethod} returns when the method was introduced.
  * {@link #getField} returns when the field was introduced.
  */
-public class ApiClass {
-
+public class ApiClass implements Comparable<ApiClass> {
     private final String mName;
     private final int mSince;
+    private int mDeprecatedIn;
 
     private final List<Pair<String, Integer>> mSuperClasses = Lists.newArrayList();
     private final List<Pair<String, Integer>> mInterfaces = Lists.newArrayList();
 
     private final Map<String, Integer> mFields = new HashMap<String, Integer>();
     private final Map<String, Integer> mMethods = new HashMap<String, Integer>();
+    private final Map<String, Integer> mDeprecatedMembersIn = new HashMap<String, Integer>();
 
-    ApiClass(String name, int since) {
+    // Persistence data: Used when writing out binary data in ApiLookup
+    List<String> members;
+    int index;               // class number, e.g. entry in index where the pointer can be found
+    int indexOffset;         // offset of the class entry
+    int memberOffsetBegin;   // offset of the first member entry in the class
+    int memberOffsetEnd;     // offset after the last member entry in the class
+    int memberIndexStart;    // entry in index for first member
+    int memberIndexLength;   // number of entries
+
+    ApiClass(String name, int since, int deprecatedIn) {
         mName = name;
         mSince = since;
+        mDeprecatedIn = deprecatedIn;
     }
 
     /**
@@ -69,11 +81,20 @@
     }
 
     /**
-     * Returns when a field was added, or null if it doesn't exist.
+     * Returns the API level a method was deprecated in, or 0 if the method is not deprecated
+     *
+     * @return the API level a method was deprecated in, or 0 if the method is not deprecated
+     */
+    int getDeprecatedIn() {
+        return mDeprecatedIn;
+    }
+
+    /**
+     * Returns when a field was added, or Integer.MAX_VALUE if it doesn't exist.
      * @param name the name of the field.
      * @param info the corresponding info
      */
-    Integer getField(String name, Api info) {
+    int getField(String name, Api info) {
         // The field can come from this class or from a super class or an interface
         // The value can never be lower than this introduction of this class.
         // When looking at super classes and interfaces, it can never be lower than when the
@@ -98,11 +119,9 @@
             ApiClass superClass = info.getClass(superClassPair.getFirst());
             if (superClass != null) {
                 i = superClass.getField(name, info);
-                if (i != null) {
-                    int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i;
-                    if (tmp < min) {
-                        min = tmp;
-                    }
+                int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i;
+                if (tmp < min) {
+                    min = tmp;
                 }
             }
         }
@@ -112,11 +131,9 @@
             ApiClass superClass = info.getClass(superClassPair.getFirst());
             if (superClass != null) {
                 i = superClass.getField(name, info);
-                if (i != null) {
-                    int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i;
-                    if (tmp < min) {
-                        min = tmp;
-                    }
+                int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i;
+                if (tmp < min) {
+                    min = tmp;
                 }
             }
         }
@@ -125,8 +142,55 @@
     }
 
     /**
-     * Returns when a method was added, or null if it doesn't exist. This goes through the super
-     * class to find method only present there.
+     * Returns when a field was deprecated, or 0 if it's not deprecated
+     *
+     * @param name the name of the field.
+     * @param info the corresponding info
+     */
+    int getMemberDeprecatedIn(String name, Api info) {
+        int deprecatedIn = findMemberDeprecatedIn(name, info);
+        return deprecatedIn < Integer.MAX_VALUE ? deprecatedIn : 0;
+    }
+
+    private int findMemberDeprecatedIn(String name, Api info) {
+        // This follows the same logic as getField/getMethod.
+        // However, it also incorporates deprecation versions from the class.
+        int min = Integer.MAX_VALUE;
+        Integer i = mDeprecatedMembersIn.get(name);
+        if (i != null) {
+            min = i;
+        }
+
+        // now look at the super classes
+        for (Pair<String, Integer> superClassPair : mSuperClasses) {
+            ApiClass superClass = info.getClass(superClassPair.getFirst());
+            if (superClass != null) {
+                i = superClass.findMemberDeprecatedIn(name, info);
+                int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i;
+                if (tmp < min) {
+                    min = tmp;
+                }
+            }
+        }
+
+        // now look at the interfaces
+        for (Pair<String, Integer> superClassPair : mInterfaces) {
+            ApiClass superClass = info.getClass(superClassPair.getFirst());
+            if (superClass != null) {
+                i = superClass.findMemberDeprecatedIn(name, info);
+                int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i;
+                if (tmp < min) {
+                    min = tmp;
+                }
+            }
+        }
+
+        return min;
+    }
+
+    /**
+     * Returns when a method was added, or Integer.MAX_VALUE if it doesn't exist.
+     * This goes through the super class to find method only present there.
      * @param methodSignature the method signature
      */
     int getMethod(String methodSignature, Api info) {
@@ -159,11 +223,9 @@
             ApiClass superClass = info.getClass(superClassPair.getFirst());
             if (superClass != null) {
                 i = superClass.getMethod(methodSignature, info);
-                if (i != null) {
-                    int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i;
-                    if (tmp < min) {
-                        min = tmp;
-                    }
+                int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i;
+                if (tmp < min) {
+                    min = tmp;
                 }
             }
         }
@@ -173,11 +235,9 @@
             ApiClass superClass = info.getClass(interfacePair.getFirst());
             if (superClass != null) {
                 i = superClass.getMethod(methodSignature, info);
-                if (i != null) {
-                    int tmp = interfacePair.getSecond() > i ? interfacePair.getSecond() : i;
-                    if (tmp < min) {
-                        min = tmp;
-                    }
+                int tmp = interfacePair.getSecond() > i ? interfacePair.getSecond() : i;
+                if (tmp < min) {
+                    min = tmp;
                 }
             }
         }
@@ -185,14 +245,16 @@
         return min;
     }
 
-    void addField(String name, int since) {
+    void addField(String name, int since, int deprecatedIn) {
         Integer i = mFields.get(name);
-        if (i == null || i.intValue() > since) {
-            mFields.put(name, Integer.valueOf(since));
+        assert i == null;
+        mFields.put(name, since);
+        if (deprecatedIn > 0) {
+            mDeprecatedMembersIn.put(name, deprecatedIn);
         }
     }
 
-    void addMethod(String name, int since) {
+    void addMethod(String name, int since, int deprecatedIn) {
         // Strip off the method type at the end to ensure that the code which
         // produces inherited methods doesn't get confused and end up multiple entries.
         // For example, java/nio/Buffer has the method "array()Ljava/lang/Object;",
@@ -205,8 +267,10 @@
         }
 
         Integer i = mMethods.get(name);
-        if (i == null || i.intValue() > since) {
-            mMethods.put(name, Integer.valueOf(since));
+        assert i == null || i == since : i;
+        mMethods.put(name, since);
+        if (deprecatedIn > 0) {
+            mDeprecatedMembersIn.put(name, deprecatedIn);
         }
     }
 
@@ -222,11 +286,12 @@
         // check if we already have that name (at a lower level)
         for (Pair<String, Integer> pair : list) {
             if (name.equals(pair.getFirst())) {
+                assert false;
                 return;
             }
         }
 
-        list.add(Pair.of(name, Integer.valueOf(value)));
+        list.add(Pair.of(name, value));
 
     }
 
@@ -240,6 +305,16 @@
         return null;
     }
 
+    @NonNull
+    public String getSimpleName() {
+        int index = mName.lastIndexOf('/');
+        if (index != -1) {
+            return mName.substring(index + 1);
+        }
+
+        return mName;
+    }
+
     @Override
     public String toString() {
         return mName;
@@ -259,6 +334,14 @@
         return members;
     }
 
+    List<Pair<String, Integer>> getInterfaces() {
+        return mInterfaces;
+    }
+
+    List<Pair<String, Integer>> getSuperClasses() {
+        return mSuperClasses;
+    }
+
     private void addAllMethods(Api info, Set<String> set, boolean includeConstructors) {
         if (!includeConstructors) {
             for (String method : mMethods.keySet()) {
@@ -274,7 +357,6 @@
 
         for (Pair<String, Integer> superClass : mSuperClasses) {
             ApiClass clz = info.getClass(superClass.getFirst());
-            assert clz != null : superClass.getSecond();
             if (clz != null) {
                 clz.addAllMethods(info, set, false);
             }
@@ -283,7 +365,6 @@
         // Get methods from implemented interfaces as well;
         for (Pair<String, Integer> superClass : mInterfaces) {
             ApiClass clz = info.getClass(superClass.getFirst());
-            assert clz != null : superClass.getSecond();
             if (clz != null) {
                 clz.addAllMethods(info, set, false);
             }
@@ -312,21 +393,22 @@
         for (Pair<String, Integer> superClass : mSuperClasses) {
             ApiClass clz = info.getClass(superClass.getFirst());
             assert clz != null : superClass.getSecond();
-            if (clz != null) {
-                clz.addAllFields(info, set);
-            }
+            clz.addAllFields(info, set);
         }
 
         // Get methods from implemented interfaces as well;
         for (Pair<String, Integer> superClass : mInterfaces) {
             ApiClass clz = info.getClass(superClass.getFirst());
             assert clz != null : superClass.getSecond();
-            if (clz != null) {
-                clz.addAllFields(info, set);
-            }
+            clz.addAllFields(info, set);
         }
     }
 
+    @Override
+    public int compareTo(@NonNull ApiClass other) {
+        return mName.compareTo(other.mName);
+    }
+
     /* This code can be used to scan through all the fields and look for fields
        that have moved to a higher class:
             Field android/view/MotionEvent#CREATOR has api=1 but parent android/view/InputEvent provides it as 9
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java
old mode 100755
new mode 100644
index 482eb44..4678bc0
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java
@@ -20,6 +20,7 @@
 import static com.android.SdkConstants.ANDROID_THEME_PREFIX;
 import static com.android.SdkConstants.ANDROID_URI;
 import static com.android.SdkConstants.ATTR_CLASS;
+import static com.android.SdkConstants.ATTR_FULL_BACKUP_CONTENT;
 import static com.android.SdkConstants.ATTR_ID;
 import static com.android.SdkConstants.ATTR_LABEL_FOR;
 import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
@@ -42,24 +43,35 @@
 import static com.android.SdkConstants.TARGET_API;
 import static com.android.SdkConstants.TOOLS_URI;
 import static com.android.SdkConstants.VIEW_TAG;
+import static com.android.tools.lint.checks.RtlDetector.ATTR_SUPPORTS_RTL;
 import static com.android.tools.lint.detector.api.ClassContext.getFqcn;
 import static com.android.tools.lint.detector.api.ClassContext.getInternalName;
 import static com.android.tools.lint.detector.api.LintUtils.getNextInstruction;
 import static com.android.tools.lint.detector.api.Location.SearchDirection.BACKWARD;
+import static com.android.tools.lint.detector.api.Location.SearchDirection.EOL_NEAREST;
 import static com.android.tools.lint.detector.api.Location.SearchDirection.FORWARD;
 import static com.android.tools.lint.detector.api.Location.SearchDirection.NEAREST;
 import static com.android.utils.SdkUtils.getResourceFieldName;
 
-import com.android.SdkConstants;
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
 import com.android.builder.model.AndroidProject;
+import com.android.ide.common.repository.GradleVersion;
 import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
 import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.BuildToolInfo;
 import com.android.sdklib.SdkVersionInfo;
 import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.PreciseRevision;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgType;
+import com.android.sdklib.repository.local.LocalPkgInfo;
+import com.android.sdklib.repository.local.LocalSdk;
 import com.android.tools.lint.client.api.IssueRegistry;
-import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
 import com.android.tools.lint.client.api.LintDriver;
 import com.android.tools.lint.detector.api.Category;
 import com.android.tools.lint.detector.api.ClassContext;
@@ -94,17 +106,20 @@
 import org.objectweb.asm.tree.InsnList;
 import org.objectweb.asm.tree.IntInsnNode;
 import org.objectweb.asm.tree.JumpInsnNode;
+import org.objectweb.asm.tree.LabelNode;
 import org.objectweb.asm.tree.LdcInsnNode;
 import org.objectweb.asm.tree.LocalVariableNode;
 import org.objectweb.asm.tree.LookupSwitchInsnNode;
 import org.objectweb.asm.tree.MethodInsnNode;
 import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.tree.TryCatchBlockNode;
 import org.objectweb.asm.tree.analysis.AnalyzerException;
 import org.w3c.dom.Attr;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 
+import java.io.File;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -117,12 +132,13 @@
 
 import lombok.ast.Annotation;
 import lombok.ast.AnnotationElement;
+import lombok.ast.AnnotationMethodDeclaration;
 import lombok.ast.AnnotationValue;
 import lombok.ast.AstVisitor;
 import lombok.ast.BinaryExpression;
+import lombok.ast.BinaryOperator;
 import lombok.ast.Case;
-import lombok.ast.Catch;
-import lombok.ast.ClassDeclaration;
+import lombok.ast.Cast;
 import lombok.ast.ConstructorDeclaration;
 import lombok.ast.ConstructorInvocation;
 import lombok.ast.Expression;
@@ -140,6 +156,7 @@
 import lombok.ast.SuperConstructorInvocation;
 import lombok.ast.Switch;
 import lombok.ast.Try;
+import lombok.ast.TypeDeclaration;
 import lombok.ast.TypeReference;
 import lombok.ast.VariableDefinition;
 import lombok.ast.VariableDefinitionEntry;
@@ -315,16 +332,69 @@
 
     @Override
     public void beforeCheckProject(@NonNull Context context) {
-        mApiDatabase = ApiLookup.get(context.getClient());
-        // We can't look up the minimum API required by the project here:
-        // The manifest file hasn't been processed yet in the -before- project hook.
-        // For now it's initialized lazily in getMinSdk(Context), but the
-        // lint infrastructure should be fixed to parse manifest file up front.
+        if (mApiDatabase == null) {
+            mApiDatabase = ApiLookup.get(context.getClient());
+            // We can't look up the minimum API required by the project here:
+            // The manifest file hasn't been processed yet in the -before- project hook.
+            // For now it's initialized lazily in getMinSdk(Context), but the
+            // lint infrastructure should be fixed to parse manifest file up front.
 
-        if (mApiDatabase == null && !mWarnedMissingDb) {
-            mWarnedMissingDb = true;
-            context.report(IssueRegistry.LINT_ERROR, Location.create(context.file),
+            if (mApiDatabase == null && !mWarnedMissingDb) {
+                mWarnedMissingDb = true;
+                context.report(IssueRegistry.LINT_ERROR, Location.create(context.file),
                         "Can't find API database; API check not performed");
+            } else {
+                // See if you don't have at least version 23.0.1 of platform tools installed
+                // See if you don't have at least version 23.0.1 of platform tools installed
+                LocalSdk sdk = context.getClient().getSdk();
+                if (sdk == null) {
+                    return;
+                }
+                LocalPkgInfo pkgInfo = sdk.getPkgInfo(PkgType.PKG_PLATFORM_TOOLS);
+                if (pkgInfo == null) {
+                    return;
+                }
+                IPkgDesc desc = pkgInfo.getDesc();
+                PreciseRevision revision = desc.getPreciseRevision();
+
+                // The platform tools must be at at least the same revision
+                // as the compileSdkVersion!
+                // And as a special case, for 23, they must be at 23.0.1
+                // because 23.0.0 accidentally shipped without Android M APIs.
+                int compileSdkVersion = context.getProject().getBuildSdk();
+                if (compileSdkVersion == 23) {
+                    if (revision.getMajor() > 23 || revision.getMajor() == 23
+                      && (revision.getMinor() > 0 || revision.getMicro() > 0)) {
+                        return;
+                    }
+                } else if (compileSdkVersion <= revision.getMajor()) {
+                    return;
+                }
+
+                // Pick a location: when incrementally linting in the IDE, tie
+                // it to the current file
+                List<File> currentFiles = context.getProject().getSubset();
+                Location location;
+                if (currentFiles != null && currentFiles.size() == 1) {
+                    File file = currentFiles.get(0);
+                    String contents = context.getClient().readFile(file);
+                    int firstLineEnd = contents.indexOf('\n');
+                    if (firstLineEnd == -1) {
+                        firstLineEnd = contents.length();
+                    }
+                    location = Location.create(file,
+                        new DefaultPosition(0, 0, 0), new
+                        DefaultPosition(0, firstLineEnd, firstLineEnd));
+                } else {
+                    location = Location.create(context.file);
+                }
+                context.report(UNSUPPORTED,
+                        location,
+                        String.format("The SDK platform-tools version (%1$s) is too old "
+                                        + " to check APIs compiled with API %2$d; please update",
+                                revision.toShortString(),
+                                compileSdkVersion));
+            }
         }
     }
 
@@ -363,7 +433,7 @@
                         && attributeApiLevel > getLocalMinSdk(attribute.getOwnerElement())
                         && !isBenignUnusedAttribute(name)
                         && !isAlreadyWarnedDrawableFile(context, attribute, attributeApiLevel)) {
-                    if (RtlDetector.isRtlAttributeName(name)) {
+                    if (RtlDetector.isRtlAttributeName(name) || ATTR_SUPPORTS_RTL.equals(name)) {
                         // No need to warn for example that
                         //  "layout_alignParentEnd will only be used in API level 17 and higher"
                         // since we have a dedicated RTL lint rule dealing with those attributes
@@ -372,8 +442,19 @@
                         // when used on TextViews (and subclasses of TextViews), on some
                         // devices, because vendor specific attributes conflict with the
                         // later-added framework resources, and these are apparently read
-                        // by the text views:
+                        // by the text views.
+                        //
+                        // However, as of build tools 23.0.1 aapt works around this by packaging
+                        // the resources differently.
+
+                        BuildToolInfo buildToolInfo = context.getProject().getBuildTools();
+                        FullRevision buildTools = buildToolInfo != null
+                                ? buildToolInfo.getRevision() : null;
+                        boolean isOldBuildTools = buildTools != null &&
+                                (buildTools.getMajor() < 23 || buildTools.getMajor() == 23
+                                 && buildTools.getMinor() == 0 && buildTools.getMicro() == 0);
                         if (name.equals(ATTR_PADDING_START) &&
+                                (buildTools == null || isOldBuildTools) &&
                                 viewMayExtendTextView(attribute.getOwnerElement())) {
                             Location location = context.getLocation(attribute);
                             String message = String.format(
@@ -381,6 +462,14 @@
                                             + "some specific devices older than API %2$d "
                                             + "(current min is %3$d)",
                                     attribute.getLocalName(), attributeApiLevel, minSdk);
+                            //noinspection VariableNotUsedInsideIf
+                            if (buildTools != null) {
+                                message = String.format("Upgrade `buildToolsVersion` from "
+                                        + "`%1$s` to at least `23.0.1`; if not, ",
+                                            buildTools.toShortString())
+                                        + Character.toLowerCase(message.charAt(0))
+                                        + message.substring(1);
+                            }
                             context.report(UNSUPPORTED, attribute, location, message);
                         }
                     } else {
@@ -484,7 +573,7 @@
      */
     private static boolean viewMayExtendTextView(@NonNull Element element) {
         String tag = element.getTagName();
-        if (tag.equals(SdkConstants.VIEW_TAG)) {
+        if (tag.equals(VIEW_TAG)) {
             tag = element.getAttribute(ATTR_CLASS);
             if (tag == null || tag.isEmpty()) {
                 return false;
@@ -536,8 +625,9 @@
      * on older platforms.
      */
     public static boolean isBenignUnusedAttribute(@NonNull String name) {
-        return ATTR_LABEL_FOR.equals(name) || ATTR_TEXT_IS_SELECTABLE.equals(name);
-
+        return ATTR_LABEL_FOR.equals(name)
+               || ATTR_TEXT_IS_SELECTABLE.equals(name)
+               || ATTR_FULL_BACKUP_CONTENT.equals(name);
     }
 
     @Override
@@ -551,12 +641,10 @@
         ResourceFolderType folderType = context.getResourceFolderType();
         if (folderType != ResourceFolderType.LAYOUT) {
             if (folderType == ResourceFolderType.DRAWABLE) {
-                if (!projectSupportsVectorDrawables(context)) {
-                    checkElement(context, element, TAG_VECTOR, 21, UNSUPPORTED);
-                }
-                checkElement(context, element, TAG_RIPPLE, 21, UNSUPPORTED);
-                checkElement(context, element, TAG_ANIMATED_SELECTOR, 21, UNSUPPORTED);
-                checkElement(context, element, TAG_ANIMATED_VECTOR, 21, UNSUPPORTED);
+                checkElement(context, element, TAG_VECTOR, 21, "1.4", UNSUPPORTED);
+                checkElement(context, element, TAG_RIPPLE, 21, null, UNSUPPORTED);
+                checkElement(context, element, TAG_ANIMATED_SELECTOR, 21, null, UNSUPPORTED);
+                checkElement(context, element, TAG_ANIMATED_VECTOR, 21, null, UNSUPPORTED);
             }
             if (element.getParentNode().getNodeType() != Node.ELEMENT_NODE) {
                 // Root node
@@ -572,18 +660,20 @@
                         // Convert @android:type/foo into android/R$type and "foo"
                         int index = text.indexOf('/', ANDROID_PREFIX.length());
                         if (index != -1) {
-                            String owner = "android/R$"    //$NON-NLS-1$
-                                    + text.substring(ANDROID_PREFIX.length(), index);
-                            String name = getResourceFieldName(text.substring(index + 1));
-                            int api = mApiDatabase.getFieldVersion(owner, name);
-                            int minSdk = getMinSdk(context);
-                            if (api > minSdk && api > context.getFolderVersion()
-                                    && api > getLocalMinSdk(element)) {
-                                Location location = context.getLocation(textNode);
-                                String message = String.format(
-                                        "`%1$s` requires API level %2$d (current min is %3$d)",
-                                        text, api, minSdk);
-                                context.report(UNSUPPORTED, element, location, message);
+                            String typeString = text.substring(ANDROID_PREFIX.length(), index);
+                            if (ResourceType.getEnum(typeString) != null) {
+                                String owner = "android/R$" + typeString;
+                                String name = getResourceFieldName(text.substring(index + 1));
+                                int api = mApiDatabase.getFieldVersion(owner, name);
+                                int minSdk = getMinSdk(context);
+                                if (api > minSdk && api > context.getFolderVersion()
+                                        && api > getLocalMinSdk(element)) {
+                                    Location location = context.getLocation(textNode);
+                                    String message = String.format(
+                                            "`%1$s` requires API level %2$d (current min is %3$d)",
+                                            text, api, minSdk);
+                                    context.report(UNSUPPORTED, element, location, message);
+                                }
                             }
                         }
                     }
@@ -597,7 +687,7 @@
                 }
             } else {
                 // TODO: Complain if <tag> is used at the root level!
-                checkElement(context, element, TAG, 21, UNUSED);
+                checkElement(context, element, TAG, 21, null, UNUSED);
             }
 
             // Check widgets to make sure they're available in this version of the SDK.
@@ -626,17 +716,24 @@
     /** Checks whether the given element is the given tag, and if so, whether it satisfied
      * the minimum version that the given tag is supported in */
     private void checkElement(@NonNull XmlContext context, @NonNull Element element,
-            @NonNull String tag, int api, @NonNull Issue issue) {
+            @NonNull String tag, int api, @Nullable String gradleVersion, @NonNull Issue issue) {
         if (tag.equals(element.getTagName())) {
             int minSdk = getMinSdk(context);
-            if (api > minSdk && api > context.getFolderVersion()
-                    && api > getLocalMinSdk(element)) {
+            if (api > minSdk
+                    && api > context.getFolderVersion()
+                    && api > getLocalMinSdk(element)
+                    && !featureProvidedByGradle(context, gradleVersion)) {
                 Location location = context.getLocation(element);
                 String message;
                 if (issue == UNSUPPORTED) {
                     message = String.format(
                             "`<%1$s>` requires API level %2$d (current min is %3$d)", tag, api,
                             minSdk);
+                    if (gradleVersion != null) {
+                        message += String.format(
+                                " or building with Android Gradle plugin %1$s or higher",
+                                gradleVersion);
+                    }
                 } else {
                     assert issue == UNUSED : issue;
                     message = String.format(
@@ -743,9 +840,9 @@
                     String fqcn;
                     String owner = classNode.name;
                     if (CONSTRUCTOR_NAME.equals(name)) {
-                        fqcn = "new " + ClassContext.getFqcn(owner); //$NON-NLS-1$
+                        fqcn = "new " + getFqcn(owner); //$NON-NLS-1$
                     } else {
-                        fqcn = ClassContext.getFqcn(owner) + '#' + name;
+                        fqcn = getFqcn(owner) + '#' + name;
                     }
                     String message = String.format(
                             "This method is not overriding anything with the current build " +
@@ -761,6 +858,32 @@
                 continue;
             }
 
+            List tryCatchBlocks = method.tryCatchBlocks;
+            if (!tryCatchBlocks.isEmpty()) {
+                List<String> checked = Lists.newArrayList();
+                for (Object o : tryCatchBlocks) {
+                    TryCatchBlockNode tryCatchBlock = (TryCatchBlockNode) o;
+                    String className = tryCatchBlock.type;
+                    if (className == null || checked.contains(className)) {
+                        continue;
+                    }
+
+                    int api = mApiDatabase.getClassVersion(className);
+                    if (api > minSdk) {
+                        // Find instruction node
+                        LabelNode label = tryCatchBlock.handler;
+                        String fqcn = getFqcn(className);
+                        String message = String.format(
+                                "Class requires API level %1$d (current min is %2$d): `%3$s`",
+                                api, minSdk, fqcn);
+                        report(context, message, label, method,
+                                className.substring(className.lastIndexOf('/') + 1), null,
+                                SearchHints.create(EOL_NEAREST).matchJavaSymbol());
+                    }
+                }
+            }
+
+
             if (CHECK_DECLARATIONS) {
                 // Check types in parameter list and types of local variables
                 List localVariables = method.localVariables;
@@ -773,7 +896,7 @@
                             String className = desc.substring(1, desc.length() - 1);
                             int api = mApiDatabase.getClassVersion(className);
                             if (api > minSdk) {
-                                String fqcn = ClassContext.getFqcn(className);
+                                String fqcn = getFqcn(className);
                                 String message = String.format(
                                     "Class requires API level %1$d (current min is %2$d): `%3$s`",
                                     api, minSdk, fqcn);
@@ -796,7 +919,7 @@
                         String type = signature.substring(args + 2, signature.length() - 1);
                         int api = mApiDatabase.getClassVersion(type);
                         if (api > minSdk) {
-                            String fqcn = ClassContext.getFqcn(type);
+                            String fqcn = getFqcn(type);
                             String message = String.format(
                                 "Class requires API level %1$d (current min is %2$d): `%3$s`",
                                 api, minSdk, fqcn);
@@ -846,9 +969,9 @@
 
                             String fqcn;
                             if (CONSTRUCTOR_NAME.equals(name)) {
-                                fqcn = "new " + ClassContext.getFqcn(owner); //$NON-NLS-1$
+                                fqcn = "new " + getFqcn(owner); //$NON-NLS-1$
                             } else {
-                                fqcn = ClassContext.getFqcn(owner) + '#' + name;
+                                fqcn = getFqcn(owner) + '#' + name;
                             }
                             String message = String.format(
                                     "Call requires API level %1$d (current min is %2$d): `%3$s`",
@@ -863,7 +986,7 @@
                                 message = String.format(
                                     "Enum for switch requires API level %1$d " +
                                     "(current min is %2$d): `%3$s`",
-                                    api, minSdk, ClassContext.getFqcn(owner));
+                                    api, minSdk, getFqcn(owner));
                             }
 
                             // If you're simply calling super.X from method X, even if method X
@@ -887,6 +1010,27 @@
                                 break;
                             }
 
+                            if (api == 19
+                                    && owner.equals("java/lang/ReflectiveOperationException")
+                                    && !method.tryCatchBlocks.isEmpty()) {
+                                boolean direct = false;
+                                for (Object o : method.tryCatchBlocks) {
+                                    if (((TryCatchBlockNode)o).type.equals("java/lang/ReflectiveOperationException")) {
+                                        direct = true;
+                                        break;
+                                    }
+                                }
+                                if (!direct) {
+                                    message = String.format("Multi-catch with these reflection "
+                                            + "exceptions requires API level 19 (current min is"
+                                            + " %2$d) because they get compiled to the common but "
+                                            + "new super type `ReflectiveOperationException`. "
+                                            + "As a workaround either create individual catch "
+                                            + "statements, or catch `Exception`.",
+                                            api, minSdk);
+                                }
+                            }
+
                             report(context, message, node, method, name, null,
                                     SearchHints.create(FORWARD).matchJavaSymbol());
                             break;
@@ -900,13 +1044,13 @@
                             // so no need to keep checking up the chain
                             // -- unless it's the support library which is also in
                             // the android/ namespace:
-                            if (owner.startsWith("android/support/")) { //$NON-NLS-1$
+                            if (owner.startsWith("android/support/") && api == -1) { //$NON-NLS-1$
                                 owner = context.getDriver().getSuperClass(owner);
                             } else {
                                 owner = null;
                             }
                         } else if (owner.startsWith("java/")) {    //$NON-NLS-1$
-                            if (owner.equals(LocaleDetector.DATE_FORMAT_OWNER)) {
+                            if (owner.equals("java/text/SimpleDateFormat")) {
                                 checkSimpleDateFormat(context, method, node, minSdk);
                             }
                             // Already inlined; see comment above
@@ -942,7 +1086,7 @@
                             continue;
                         }
 
-                        String fqcn = ClassContext.getFqcn(owner) + '#' + name;
+                        String fqcn = getFqcn(owner) + '#' + name;
                         if (mPendingFields != null) {
                             mPendingFields.remove(fqcn);
                         }
@@ -960,7 +1104,7 @@
 
                         int api = mApiDatabase.getClassVersion(className);
                         if (api > minSdk) {
-                            String fqcn = ClassContext.getFqcn(className);
+                            String fqcn = getFqcn(className);
                             String message = String.format(
                                     "Class requires API level %1$d (current min is %2$d): `%3$s`",
                                     api, minSdk, fqcn);
@@ -978,7 +1122,7 @@
             String signature) {
         int api = mApiDatabase.getClassVersion(signature);
         if (api > classMinSdk) {
-            String fqcn = ClassContext.getFqcn(signature);
+            String fqcn = getFqcn(signature);
             String message = String.format(
                     "Class requires API level %1$d (current min is %2$d): `%3$s`",
                     api, classMinSdk, fqcn);
@@ -1145,7 +1289,7 @@
                             @SuppressWarnings("unchecked") // ASM API
                             List<Integer> keys = lookup.keys;
                             if (keys != null && keys.contains(ordinal)) {
-                                String fqcn = ClassContext.getFqcn(owner) + '#' + name;
+                                String fqcn = getFqcn(owner) + '#' + name;
                                 String message = String.format(
                                         "Enum value requires API level %1$d " +
                                         "(current min is %2$d): `%3$s`",
@@ -1320,13 +1464,14 @@
      * @return the API level to use for this element, or -1
      */
     private static int getLocalMinSdk(@NonNull Element element) {
+        //noinspection ConstantConditions
         while (element != null) {
             String targetApi = element.getAttributeNS(TOOLS_URI, ATTR_TARGET_API);
             if (targetApi != null && !targetApi.isEmpty()) {
                 if (Character.isDigit(targetApi.charAt(0))) {
                     try {
                         return Integer.parseInt(targetApi);
-                    } catch (NumberFormatException nufe) {
+                    } catch (NumberFormatException e) {
                         break;
                     }
                 } else {
@@ -1346,15 +1491,25 @@
     }
 
     /**
-     * Checks if vector drawables are supported by the build system, in which case there is no min API requirement.
+     * Checks if the current project supports features added in {@code minGradleVersion} version of the
+     * Android gradle plugin.
      *
-     * <p>This is the case with Android gradle plugin 1.4+.
+     * @param context Current context.
+     * @param minGradleVersion Version in which support for a given feature was added, or null if it's
+ *                      not supported at build time.
      */
-    private static boolean projectSupportsVectorDrawables(XmlContext context) {
+    private static boolean featureProvidedByGradle(@NonNull XmlContext context,
+            @Nullable String minGradleVersion) {
+        if (minGradleVersion == null) {
+            return false;
+        }
+
         AndroidProject gradleModel = context.getProject().getGradleProjectModel();
         if (gradleModel != null) {
-            FullRevision gradleModelVersion = FullRevision.parseRevision(gradleModel.getModelVersion());
-            if (gradleModelVersion.compareTo(FullRevision.parseRevision("1.4"), FullRevision.PreviewComparison.IGNORE) >= 0) {
+            GradleVersion gradleModelVersion =
+                    GradleVersion.parse(gradleModel.getModelVersion());
+            if (gradleModelVersion.compareIgnoringQualifiers(
+                    GradleVersion.parse(minGradleVersion)) >= 0) {
                 return true;
             }
         }
@@ -1406,7 +1561,7 @@
         super.afterCheckProject(context);
     }
 
-// ---- Implements JavaScanner ----
+    // ---- Implements JavaScanner ----
 
     @Nullable
     @Override
@@ -1422,7 +1577,7 @@
     @Override
     public List<Class<? extends lombok.ast.Node>> getApplicableNodeTypes() {
         List<Class<? extends lombok.ast.Node>> types =
-                new ArrayList<Class<? extends lombok.ast.Node>>(2);
+                new ArrayList<Class<? extends lombok.ast.Node>>(10);
         types.add(ImportDeclaration.class);
         types.add(Select.class);
         types.add(MethodDeclaration.class);
@@ -1430,6 +1585,9 @@
         types.add(VariableDefinitionEntry.class);
         types.add(VariableReference.class);
         types.add(Try.class);
+        types.add(Cast.class);
+        types.add(BinaryExpression.class);
+        types.add(MethodInvocation.class);
         return types;
     }
 
@@ -1598,6 +1756,7 @@
             if (!isField) {
                 // See if there's an R class
                 Select current = node;
+                //noinspection ConstantConditions
                 while (current != null) {
                     Expression operand = current.astOperand();
                     if (operand instanceof Select) {
@@ -1729,6 +1888,7 @@
 
         @Override
         public boolean visitVariableDefinitionEntry(VariableDefinitionEntry node) {
+            //noinspection VariableNotUsedInsideIf
             if (mCurrentMethod != null) {
                 if (mLocalVars == null) {
                     mLocalVars = Sets.newHashSet();
@@ -1740,6 +1900,23 @@
                 }
                 mFields.add(node.astName().astValue());
             }
+
+            Expression initializer = node.astInitializer();
+            if (initializer != null
+                    // Checking cast expressions is handled already; prevent duplicate errors
+                    && !(initializer instanceof Cast)) {
+                TypeDescriptor classType = mContext.getType(initializer);
+                if (classType != null && !classType.isPrimitive()) {
+                    String classOwner = classType.getInternalName();
+                    if (mApiDatabase.isKnownClass(classOwner)) {
+                        TypeDescriptor interfaceType = mContext.getType(node);
+                        if (interfaceType != null) {
+                            checkCast(initializer, classOwner, classType, interfaceType);
+                        }
+                    }
+                }
+            }
+
             return super.visitVariableDefinitionEntry(node);
         }
 
@@ -1758,6 +1935,94 @@
         }
 
         @Override
+        public boolean visitCast(Cast node) {
+            TypeDescriptor classType = mContext.getType(node.astOperand());
+            if (classType != null && !classType.isPrimitive()) {
+                TypeDescriptor interfaceType = mContext.getType(node);
+                checkCast(node, classType.getInternalName(), classType, interfaceType);
+            }
+
+            return super.visitCast(node);
+        }
+
+        @Override
+        public boolean visitBinaryExpression(BinaryExpression node) {
+            if (node.astOperator() == BinaryOperator.ASSIGN &&
+                    // Checking cast expressions is handled already; prevent duplicate errors
+                    !(node.astRight() instanceof Cast)) {
+                TypeDescriptor classType = mContext.getType(node.astRight());
+                if (classType != null && !classType.isPrimitive()) {
+                    String classOwner = classType.getInternalName();
+                    if (mApiDatabase.isKnownClass(classOwner)) {
+                        TypeDescriptor interfaceType = mContext.getType(node.astLeft());
+                        if (interfaceType != null && !interfaceType.isPrimitive()) {
+                            checkCast(node, classOwner, classType, interfaceType);
+                        }
+                    }
+                }
+            }
+            return super.visitBinaryExpression(node);
+        }
+
+        @Override
+        public boolean visitMethodInvocation(MethodInvocation node) {
+            ResolvedMethod method = null;
+            int parameterIndex = 0;
+            for (Expression argument : node.astArguments()) {
+                TypeDescriptor argumentType = mContext.getType(argument);
+                if (argumentType != null && !argumentType.isPrimitive()) {
+                    if (method == null) {
+                        ResolvedNode resolved = mContext.resolve(node);
+                        if (resolved instanceof ResolvedMethod) {
+                            method = (ResolvedMethod) resolved;
+                        } else {
+                            break;
+                        }
+                    }
+                    if (parameterIndex >= method.getArgumentCount()) {
+                        // We can end up with more arguments than parameters when
+                        // there is a varargs call.
+                        break;
+                    }
+                    TypeDescriptor parameterType = method.getArgumentType(parameterIndex);
+                    String argumentOwner = argumentType.getInternalName();
+                    if (mApiDatabase.isKnownClass(argumentOwner)) {
+                        checkCast(argument, argumentOwner, argumentType,
+                                parameterType);
+                    }
+                }
+                parameterIndex++;
+            }
+
+            return super.visitMethodInvocation(node);
+        }
+
+        private void checkCast(
+                @NonNull lombok.ast.Node node,
+                @NonNull String classOwner,
+                @NonNull TypeDescriptor classType,
+                @Nullable TypeDescriptor interfaceType) {
+            if (interfaceType != null && !interfaceType.equals(classType)) {
+                String interfaceInternalName = interfaceType.getInternalName();
+                int api = mApiDatabase.getValidCastVersion(classOwner, interfaceInternalName);
+                if (api != -1 && !"java/lang/Object".equals(interfaceInternalName)) {
+                    int minSdk = getMinSdk(mContext);
+                    if (api > minSdk && api > getLocalMinSdk(node)) {
+                        LintDriver driver = mContext.getDriver();
+                        if (!driver.isSuppressed(mContext, UNSUPPORTED, node)) {
+                            Location location = mContext.getLocation(node);
+                            String message = String.format(
+                                    "Cast from %1$s to %2$s requires API level %3$d (current min is %4$d)",
+                                    classType.getSimpleName(), interfaceType.getSimpleName(),
+                                    api, minSdk);
+                            mContext.report(UNSUPPORTED, node, location, message);
+                        }
+                    }
+                }
+            }
+        }
+
+        @Override
         public boolean visitTry(Try node) {
             Object nativeNode = node.getNativeNode();
             if (nativeNode != null && nativeNode.getClass().getName().equals(
@@ -1787,36 +2052,6 @@
                             mContext.report(UNSUPPORTED, node, location, message);
                         }
                     }
-                } else {
-                    // Special case: check types of catch block variables; these apparently
-                    // need to be available at runtime even if there are no explicit calls
-                    for (Catch c : node.astCatches()) {
-                        VariableDefinition variableDefinition = c.astExceptionDeclaration();
-                        TypeReference typeReference = variableDefinition.astTypeReference();
-                        String fqcn = null;
-                        JavaParser.ResolvedNode resolved = mContext.resolve(typeReference);
-                        if (resolved != null) {
-                            fqcn = resolved.getSignature();
-                        } else if (typeReference.getTypeName().equals(
-                                "ReflectiveOperationException")) {
-                            fqcn = "java.lang.ReflectiveOperationException";
-                        }
-                        if (fqcn != null) {
-                            String owner = getInternalName(fqcn);
-                            int api = mApiDatabase.getClassVersion(owner);
-                            int minSdk = getMinSdk(mContext);
-                            if (api > minSdk && api > getLocalMinSdk(typeReference)) {
-                                Location location = mContext.getLocation(typeReference);
-                                String message = String.format(
-                                    "Class requires API level %1$d (current min is %2$d): `%3$s`",
-                                    api, minSdk, fqcn);
-                                LintDriver driver = mContext.getDriver();
-                                if (!driver.isSuppressed(mContext, UNSUPPORTED, typeReference)) {
-                                    mContext.report(UNSUPPORTED, typeReference, location, message);
-                                }
-                            }
-                        }
-                    }
                 }
             }
 
@@ -1857,6 +2092,7 @@
                         ImportDeclaration d = (ImportDeclaration) node;
                         int startOffset = d.astParts().first().getPosition().getStart();
                         Position start = location.getStart();
+                        assert start != null : location;
                         int startColumn = start.getColumn();
                         int startLine = start.getLine();
                         start = new DefaultPosition(startLine,
@@ -1903,6 +2139,7 @@
                         // so they end up being identical errors.
                         for (Pair<String, Location> pair : list) {
                             Location existingLocation = pair.getSecond();
+                            //noinspection FileEqualsUsage
                             if (location.getFile().equals(existingLocation.getFile())) {
                                 Position start = location.getStart();
                                 Position existingStart = existingLocation.getStart();
@@ -1956,9 +2193,16 @@
                     if (targetApi != -1) {
                         return targetApi;
                     }
-                } else if (type == ClassDeclaration.class) {
-                    // Class
-                    ClassDeclaration declaration = (ClassDeclaration) scope;
+                } else if (TypeDeclaration.class.isAssignableFrom(type)) {
+                    // Class, annotation, enum, interface
+                    TypeDeclaration declaration = (TypeDeclaration) scope;
+                    int targetApi = getTargetApi(declaration.astModifiers());
+                    if (targetApi != -1) {
+                        return targetApi;
+                    }
+                } else if (type == AnnotationMethodDeclaration.class) {
+                    // Look for annotations on the method
+                    AnnotationMethodDeclaration declaration = (AnnotationMethodDeclaration) scope;
                     int targetApi = getTargetApi(declaration.astModifiers());
                     if (targetApi != -1) {
                         return targetApi;
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiLookup.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiLookup.java
index 4097a3f..0044074 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiLookup.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiLookup.java
@@ -18,31 +18,28 @@
 
 import static com.android.SdkConstants.ANDROID_PKG;
 import static com.android.SdkConstants.DOT_XML;
+import static com.android.tools.lint.detector.api.LintUtils.assertionsEnabled;
 
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
 import com.android.annotations.VisibleForTesting;
-import com.android.sdklib.repository.FullRevision;
 import com.android.sdklib.repository.descriptors.PkgType;
 import com.android.sdklib.repository.local.LocalPkgInfo;
 import com.android.sdklib.repository.local.LocalSdk;
 import com.android.tools.lint.client.api.LintClient;
 import com.android.tools.lint.detector.api.LintUtils;
+import com.android.utils.Pair;
 import com.google.common.base.Charsets;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
+import com.google.common.io.ByteSink;
 import com.google.common.io.Files;
 import com.google.common.primitives.UnsignedBytes;
 
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.lang.ref.WeakReference;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.FileChannel.MapMode;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -81,21 +78,27 @@
     /** Relative path to the api-versions.xml database file within the Lint installation */
     private static final String XML_FILE_PATH = "platform-tools/api/api-versions.xml"; //$NON-NLS-1$
     private static final String FILE_HEADER = "API database used by Android lint\000";
-    private static final int BINARY_FORMAT_VERSION = 6;
-    private static final boolean DEBUG_FORCE_REGENERATE_BINARY = false;
+    private static final int BINARY_FORMAT_VERSION = 8;
     private static final boolean DEBUG_SEARCH = false;
     private static final boolean WRITE_STATS = false;
-    /** Default size to reserve for each API entry when creating byte buffer to build up data */
-    private static final int BYTES_PER_ENTRY = 36;
+
+    private static final int CLASS_HEADER_MEMBER_OFFSETS = 1;
+    private static final int CLASS_HEADER_API = 2;
+    private static final int CLASS_HEADER_DEPRECATED = 3;
+    private static final int CLASS_HEADER_INTERFACES = 4;
+    private static final int HAS_DEPRECATION_BYTE_FLAG = 1 << 7;
+    private static final int API_MASK = ~HAS_DEPRECATION_BYTE_FLAG;
+
+    @VisibleForTesting
+    static final boolean DEBUG_FORCE_REGENERATE_BINARY = false;
 
     private final Api mInfo;
     private byte[] mData;
     private int[] mIndices;
-    private int mClassCount;
-    private String[] mJavaPackages;
 
-    private static WeakReference<ApiLookup> sInstance =
-            new WeakReference<ApiLookup>(null);
+    private static WeakReference<ApiLookup> sInstance = new WeakReference<ApiLookup>(null);
+
+    private int mPackageCount;
 
     /**
      * Returns an instance of the API database
@@ -142,13 +145,9 @@
         if (sdk != null) {
             LocalPkgInfo pkgInfo = sdk.getPkgInfo(PkgType.PKG_PLATFORM_TOOLS);
             if (pkgInfo != null) {
-                FullRevision version = pkgInfo.getDesc().getFullRevision();
-                if (version != null) {
-                    return version.toShortString();
-                }
+                return pkgInfo.getDesc().getPreciseRevision().toShortString();
             }
         }
-
         return null;
     }
 
@@ -260,34 +259,63 @@
     /**
      * Database format:
      * <pre>
+     * (Note: all numbers are big endian; the format uses 1, 2, 3 and 4 byte integers.)
+     *
+     *
      * 1. A file header, which is the exact contents of {@link #FILE_HEADER} encoded
      *     as ASCII characters. The purpose of the header is to identify what the file
      *     is for, for anyone attempting to open the file.
      * 2. A file version number. If the binary file does not match the reader's expected
      *     version, it can ignore it (and regenerate the cache from XML).
-     * 3. The number of classes [1 int]
-     * 4. The number of members (across all classes) [1 int].
-     * 5. The number of java/javax packages [1 int]
-     * 6. The java/javax package name table. Each item consists of a byte count for
-     *    the package string (as 1 byte) followed by the UTF-8 encoded bytes for each package.
-     *    These are in sorted order.
-     * 7. Class offset table (one integer per class, pointing to the byte offset in the
-     *      file (relative to the beginning of the file) where each class begins.
-     *      The classes are always sorted alphabetically by fully qualified name.
-     * 8. Member offset table (one integer per member, pointing to the byte offset in the
-     *      file (relative to the beginning of the file) where each member entry begins.
-     *      The members are always sorted alphabetically.
-     * 9. Class entry table. Each class entry consists of the fully qualified class name,
-     *       in JVM format (using / instead of . in package names and $ for inner classes),
-     *       followed by the byte 0 as a terminator, followed by the API version as a byte.
-     * 10. Member entry table. Each member entry consists of the class number (as a short),
-     *      followed by the JVM method/field signature, encoded as UTF-8, followed by a 0 byte
-     *      signature terminator, followed by the API level as a byte.
-     * <p>
-     * TODO: Pack the offsets: They increase by a small amount for each entry, so no need
-     * to spend 4 bytes on each. These will need to be processed when read back in anyway,
-     * so consider storing the offset -deltas- as single bytes and adding them up cumulatively
-     * in readData().
+     *
+     * 3. The index table. When the data file is read, this is used to initialize the
+     *    {@link #mIndices} array. The index table is built up like this:
+     *    a. The number of index entries (e.g. number of elements in the {@link #mIndices} array)
+     *        [1 4-byte int]
+     *    b. The number of java/javax packages [1 4 byte int]
+     *    c. Offsets to the package entries, one for each package, and each offset is 4 bytes.
+     *    d. Offsets to the class entries, one for each class, and each offset is 4 bytes.
+     *    e. Offsets to the member entries, one for each member, and each offset is 4 bytes.
+     *
+     * 4. The member entries -- one for each member. A given class entry will point to the
+     *    first and last members in the index table above, and the offset of a given member
+     *    is pointing to the offset of these entries.
+     *    a. The name and description (except for the return value) of the member, in JVM format
+     *       (e.g. for toLowerCase(char) we'd have "toLowerCase(C)". This is converted into
+     *       UTF_8 representation as bytes [n bytes, the length of the byte representation of
+     *       the description).
+     *    b. A terminating 0 byte [1 byte].
+     *    c. The API level the member was introduced in [1 byte], BUT with the
+     *       top bit ({@link #HAS_DEPRECATION_BYTE_FLAG}) set if the member is deprecated.
+     *    d. IF the member is deprecated, the API level the member was deprecated in [1 byte].
+     *       Note that this byte does not appear if the bit indicated in (c) is not set.
+     *
+     * 5. The class entries -- one for each class.
+     *    a. The index within this class entry where the metadata (other than the name)
+     *       can be found. [1 byte]. This means that if you know a class by its number,
+     *       you can quickly jump to its metadata without scanning through the string to
+     *       find the end of it, by just adding this byte to the current offset and
+     *       then you're at the data described below for (d).
+     *    b. The name of the class (just the base name, not the package), as encoded as a
+     *       UTF-8 string. [n bytes]
+     *    c. A terminating 0 [1 byte].
+     *    d. The index in the index table (3) of the first member in the class [a 3 byte integer.]
+     *    e. The number of members in the class [a 2 byte integer].
+     *    f. The API level the class was introduced in [1 byte], BUT with the
+     *       top bit ({@link #HAS_DEPRECATION_BYTE_FLAG}) set if the class is deprecated.
+     *    g. IF the class is deprecated, the API level the class was deprecated in [1 byte].
+     *       Note that this byte does not appear if the bit indicated in (f) is not set.
+     *    h. The number of new super classes and interfaces [1 byte]. This counts only
+     *       super classes and interfaces added after the original API level of the class.
+     *    i. For each super class or interface counted in h,
+     *       I. The index of the class [a 3 byte integer]
+     *       II. The API level the class/interface was added [1 byte]
+     *
+     * 6. The package entries -- one for each package.
+     *    a. The name of the package as encoded as a UTF-8 string. [n bytes]
+     *    b. A terminating 0 [1 byte].
+     *    c. The index in the index table (3) of the first class in the package [a 3 byte integer.]
+     *    d. The number of classes in the package [a 2 byte integer].
      * </pre>
      */
     private void readData(@NonNull LintClient client, @NonNull File xmlFile,
@@ -298,14 +326,13 @@
         }
         long start = System.currentTimeMillis();
         try {
-            MappedByteBuffer buffer = Files.map(binaryFile, MapMode.READ_ONLY);
-            assert buffer.order() == ByteOrder.BIG_ENDIAN;
+            byte[] b = Files.toByteArray(binaryFile);
 
             // First skip the header
+            int offset = 0;
             byte[] expectedHeader = FILE_HEADER.getBytes(Charsets.US_ASCII);
-            buffer.rewind();
-            for (int offset = 0; offset < expectedHeader.length; offset++) {
-                if (expectedHeader[offset] != buffer.get()) {
+            for (byte anExpectedHeader : expectedHeader) {
+                if (anExpectedHeader != b[offset++]) {
                     client.log(null, "Incorrect file header: not an API database cache " +
                             "file, or a corrupt cache file");
                     return;
@@ -313,7 +340,7 @@
             }
 
             // Read in the format number
-            if (buffer.get() != BINARY_FORMAT_VERSION) {
+            if (b[offset++] != BINARY_FORMAT_VERSION) {
                 // Force regeneration of new binary data with up to date format
                 if (createCache(client, xmlFile, binaryFile)) {
                     readData(client, xmlFile, binaryFile); // Recurse
@@ -322,39 +349,21 @@
                 return;
             }
 
-            mClassCount = buffer.getInt();
-            int methodCount = buffer.getInt();
+            int indexCount = get4ByteInt(b, offset);
+            offset += 4;
+            mPackageCount = get4ByteInt(b, offset);
+            offset += 4;
 
-            int javaPackageCount = buffer.getInt();
-            // Read in the Java packages
-            mJavaPackages = new String[javaPackageCount];
-            for (int i = 0; i < javaPackageCount; i++) {
-                int count = UnsignedBytes.toInt(buffer.get());
-                byte[] bytes = new byte[count];
-                buffer.get(bytes, 0, count);
-                mJavaPackages[i] = new String(bytes, Charsets.UTF_8);
+            mIndices = new int[indexCount];
+            for (int i = 0; i < indexCount; i++) {
+                // TODO: Pack the offsets: They increase by a small amount for each entry, so
+                // no need to spend 4 bytes on each. These will need to be processed when read
+                // back in anyway, so consider storing the offset -deltas- as single bytes and
+                // adding them up cumulatively in readData().
+                mIndices[i] = get4ByteInt(b, offset);
+                offset += 4;
             }
-
-            // Read in the class table indices;
-            int count = mClassCount + methodCount;
-            int[] offsets = new int[count];
-
-            // Another idea: I can just store the DELTAS in the file (and add them up
-            // when reading back in) such that it takes just ONE byte instead of four!
-
-            for (int i = 0; i < count; i++) {
-                offsets[i] = buffer.getInt();
-            }
-
-            // No need to read in the rest -- we'll just keep the whole byte array in memory
-            // TODO: Make this code smarter/more efficient.
-            int size = buffer.limit();
-            byte[] b = new byte[size];
-            buffer.rewind();
-            buffer.get(b);
             mData = b;
-            mIndices = offsets;
-
             // TODO: We only need to keep the data portion here since we've initialized
             // the offset array separately.
             // TODO: Investigate (profile) accessing the byte buffer directly instead of
@@ -376,213 +385,295 @@
 
     /** See the {@link #readData(LintClient,File,File)} for documentation on the data format. */
     private static void writeDatabase(File file, Api info) throws IOException {
-        /*
-         * 1. A file header, which is the exact contents of {@link FILE_HEADER} encoded
-         *     as ASCII characters. The purpose of the header is to identify what the file
-         *     is for, for anyone attempting to open the file.
-         * 2. A file version number. If the binary file does not match the reader's expected
-         *     version, it can ignore it (and regenerate the cache from XML).
-         */
         Map<String, ApiClass> classMap = info.getClasses();
-        // Write the class table
 
-        List<String> classes = new ArrayList<String>(classMap.size());
-        Map<ApiClass, List<String>> memberMap =
-                Maps.newHashMapWithExpectedSize(classMap.size());
-        int memberCount = 0;
-        Set<String> javaPackageSet = Sets.newHashSetWithExpectedSize(70);
-        for (Map.Entry<String, ApiClass> entry : classMap.entrySet()) {
-            String className = entry.getKey();
-            ApiClass apiClass = entry.getValue();
+        List<ApiPackage> packages = Lists.newArrayList(info.getPackages().values());
+        Collections.sort(packages);
 
-            if (className.startsWith("java/")               //$NON-NLS-1$
-                    || className.startsWith("javax/")) {    //$NON-NLS-1$
-                String pkg = apiClass.getPackage();
-                javaPackageSet.add(pkg);
+        // Compute members of each class that must be included in the database; we can
+        // skip those that have the same since-level as the containing class. And we
+        // also need to keep those entries that are marked deprecated.
+        int estimatedSize = 0;
+        for (ApiPackage pkg : packages) {
+            estimatedSize += 4; // offset entry
+            estimatedSize += pkg.getName().length() + 20; // package entry
+
+            if (assertionsEnabled() && !isRelevantOwner(pkg.getName() + "/") &&
+                    !pkg.getName().startsWith("android/support")) {
+                System.out.println("Warning: isRelevantOwner fails for " + pkg.getName() + "/");
             }
 
-            if (!isRelevantOwner(className)) {
-                System.out.println("Warning: The isRelevantOwner method does not pass "
-                        + className);
-            }
+            for (ApiClass apiClass : pkg.getClasses()) {
+                estimatedSize += 4; // offset entry
+                estimatedSize += apiClass.getName().length() + 20; // class entry
 
-            Set<String> allMethods = apiClass.getAllMethods(info);
-            Set<String> allFields = apiClass.getAllFields(info);
-
-            // Strip out all members that have been supported since version 1.
-            // This makes the database *much* leaner (down from about 4M to about
-            // 1.7M), and this just fills the table with entries that ultimately
-            // don't help the API checker since it just needs to know if something
-            // requires a version *higher* than the minimum. If in the future the
-            // database needs to answer queries about whether a method is public
-            // or not, then we'd need to put this data back in.
-            List<String> members = new ArrayList<String>(allMethods.size() + allFields.size());
-            for (String member : allMethods) {
-
-                Integer since = apiClass.getMethod(member, info);
-                if (since == null) {
-                    assert false : className + ':' + member;
-                    since = 1;
-                }
-                if (since != 1) {
-                    members.add(member);
-                }
-            }
-
-            // Strip out all members that have been supported since version 1.
-            // This makes the database *much* leaner (down from about 4M to about
-            // 1.7M), and this just fills the table with entries that ultimately
-            // don't help the API checker since it just needs to know if something
-            // requires a version *higher* than the minimum. If in the future the
-            // database needs to answer queries about whether a method is public
-            // or not, then we'd need to put this data back in.
-            for (String member : allFields) {
-                Integer since = apiClass.getField(member, info);
-                if (since == null) {
-                    assert false : className + ':' + member;
-                    since = 1;
-                }
-                if (since != 1) {
-                    members.add(member);
-                }
-            }
-
-            // Only include classes that have one or more members requiring version 2 or higher:
-            if (!members.isEmpty()) {
-                classes.add(className);
-                memberMap.put(apiClass, members);
-                memberCount += members.size();
-            }
-        }
-        Collections.sort(classes);
-
-        List<String> javaPackages = Lists.newArrayList(javaPackageSet);
-        Collections.sort(javaPackages);
-        int javaPackageCount = javaPackages.size();
-
-        int entryCount = classMap.size() + memberCount;
-        int capacity = entryCount * BYTES_PER_ENTRY;
-        ByteBuffer buffer = ByteBuffer.allocate(capacity);
-        buffer.order(ByteOrder.BIG_ENDIAN);
-        //  1. A file header, which is the exact contents of {@link FILE_HEADER} encoded
-        //      as ASCII characters. The purpose of the header is to identify what the file
-        //      is for, for anyone attempting to open the file.
-
-        buffer.put(FILE_HEADER.getBytes(Charsets.US_ASCII));
-
-        //  2. A file version number. If the binary file does not match the reader's expected
-        //      version, it can ignore it (and regenerate the cache from XML).
-        buffer.put((byte) BINARY_FORMAT_VERSION);
-
-        //  3. The number of classes [1 int]
-        buffer.putInt(classes.size());
-
-        //  4. The number of members (across all classes) [1 int].
-        buffer.putInt(memberCount);
-
-        //  5. The number of Java packages [1 int].
-        buffer.putInt(javaPackageCount);
-
-        //  6. The Java package table. There are javaPackage.size() entries, where each entry
-        //     consists of a string length, as a byte, followed by the bytes in the package.
-        //     There is no terminating 0.
-        for (String pkg : javaPackages) {
-            byte[] bytes = pkg.getBytes(Charsets.UTF_8);
-            assert bytes.length < 255 : pkg;
-            buffer.put((byte) bytes.length);
-            buffer.put(bytes);
-        }
-
-        //  7. Class offset table (one integer per class, pointing to the byte offset in the
-        //       file (relative to the beginning of the file) where each class begins.
-        //       The classes are always sorted alphabetically by fully qualified name.
-        int classOffsetTable = buffer.position();
-
-        // Reserve enough room for the offset table here: we will backfill it with pointers
-        // as we're writing out the data structures below
-        for (int i = 0, n = classes.size(); i < n; i++) {
-            buffer.putInt(0);
-        }
-
-        //  8. Member offset table (one integer per member, pointing to the byte offset in the
-        //       file (relative to the beginning of the file) where each member entry begins.
-        //       The members are always sorted alphabetically.
-        int methodOffsetTable = buffer.position();
-        for (int i = 0, n = memberCount; i < n; i++) {
-            buffer.putInt(0);
-        }
-
-        int nextEntry = buffer.position();
-        int nextOffset = classOffsetTable;
-
-        // 9. Class entry table. Each class entry consists of the fully qualified class name,
-        //      in JVM format (using / instead of . in package names and $ for inner classes),
-        //      followed by the byte 0 as a terminator, followed by the API version as a byte.
-        for (String clz : classes) {
-            buffer.position(nextOffset);
-            buffer.putInt(nextEntry);
-            nextOffset = buffer.position();
-            buffer.position(nextEntry);
-            buffer.put(clz.getBytes(Charsets.UTF_8));
-            buffer.put((byte) 0);
-
-            ApiClass apiClass = classMap.get(clz);
-            assert apiClass != null : clz;
-            int since = apiClass.getSince();
-            assert since == UnsignedBytes.toInt((byte) since) : since; // make sure it fits
-            buffer.put((byte) since);
-
-            nextEntry = buffer.position();
-        }
-
-        //  10. Member entry table. Each member entry consists of the class number (as a short),
-        //       followed by the JVM method/field signature, encoded as UTF-8, followed by a 0 byte
-        //       signature terminator, followed by the API level as a byte.
-        assert nextOffset == methodOffsetTable;
-
-        for (int classNumber = 0, n = classes.size(); classNumber < n; classNumber++) {
-            String clz = classes.get(classNumber);
-            ApiClass apiClass = classMap.get(clz);
-            assert apiClass != null : clz;
-            List<String> members = memberMap.get(apiClass);
-            Collections.sort(members);
-
-            for (String member : members) {
-                buffer.position(nextOffset);
-                buffer.putInt(nextEntry);
-                nextOffset = buffer.position();
-                buffer.position(nextEntry);
-
-                Integer since;
-                if (member.indexOf('(') != -1) {
-                    since = apiClass.getMethod(member, info);
-                } else {
-                    since = apiClass.getField(member, info);
-                }
-                if (since == null) {
-                    assert false : clz + ':' + member;
-                    since = 1;
-                }
-
-                assert classNumber == (short) classNumber;
-                buffer.putShort((short) classNumber);
-                byte[] signature = member.getBytes(Charsets.UTF_8);
-                for (int i = 0; i < signature.length; i++) {
-                    // Make sure all signatures are really just simple ASCII
-                    byte b = signature[i];
-                    assert b == (b & 0x7f) : member;
-                    buffer.put(b);
-                    // Skip types on methods
-                    if (b == (byte) ')') {
-                        break;
+                Set<String> allMethods = apiClass.getAllMethods(info);
+                Set<String> allFields = apiClass.getAllFields(info);
+                // Strip out all members that have been supported since version 1.
+                // This makes the database *much* leaner (down from about 4M to about
+                // 1.7M), and this just fills the table with entries that ultimately
+                // don't help the API checker since it just needs to know if something
+                // requires a version *higher* than the minimum. If in the future the
+                // database needs to answer queries about whether a method is public
+                // or not, then we'd need to put this data back in.
+                int clsSince = apiClass.getSince();
+                List<String> members = new ArrayList<String>(allMethods.size() + allFields.size());
+                for (String member : allMethods) {
+                    if (apiClass.getMethod(member, info) != clsSince
+                            || apiClass.getMemberDeprecatedIn(member, info) > 0) {
+                        members.add(member);
                     }
                 }
+                for (String member : allFields) {
+                    if (apiClass.getField(member, info) != clsSince
+                            || apiClass.getMemberDeprecatedIn(member, info) > 0) {
+                        members.add(member);
+                    }
+                }
+
+                estimatedSize += 2 + 4 * (apiClass.getInterfaces().size());
+                if (apiClass.getSuperClasses().size() > 1) {
+                    estimatedSize += 2 + 4 * (apiClass.getSuperClasses().size());
+                }
+
+                // Only include classes that have one or more members requiring version 2 or higher:
+                Collections.sort(members);
+                apiClass.members = members;
+                for (String member : members) {
+                    estimatedSize += member.length();
+                    estimatedSize += 16;
+                }
+            }
+
+            // Ensure the classes are sorted
+            Collections.sort(pkg.getClasses());
+        }
+
+        // Write header
+        ByteBuffer buffer = ByteBuffer.allocate(estimatedSize);
+        buffer.order(ByteOrder.BIG_ENDIAN);
+        buffer.put(FILE_HEADER.getBytes(Charsets.US_ASCII));
+        buffer.put((byte) BINARY_FORMAT_VERSION);
+
+        int indexCountOffset = buffer.position();
+        int indexCount = 0;
+
+        buffer.putInt(0); // placeholder
+
+        // Write the number of packages in the package index
+        buffer.putInt(packages.size());
+
+        // Write package index
+        int newIndex = buffer.position();
+        for (ApiPackage pkg : packages) {
+            pkg.indexOffset = newIndex;
+            newIndex += 4;
+            indexCount++;
+        }
+
+        // Write class index
+        for (ApiPackage pkg : packages) {
+            for (ApiClass cls : pkg.getClasses()) {
+                cls.indexOffset = newIndex;
+                cls.index = indexCount;
+                newIndex += 4;
+                indexCount++;
+            }
+        }
+
+        // Write member indices
+        for (ApiPackage pkg : packages) {
+            for (ApiClass cls : pkg.getClasses()) {
+                if (cls.members != null && !cls.members.isEmpty()) {
+                    cls.memberOffsetBegin = newIndex;
+                    cls.memberIndexStart = indexCount;
+                    for (String ignored : cls.members) {
+                        newIndex += 4;
+                        indexCount++;
+                    }
+                    cls.memberOffsetEnd = newIndex;
+                    cls.memberIndexLength = indexCount - cls.memberIndexStart;
+                } else {
+                    cls.memberOffsetBegin = -1;
+                    cls.memberOffsetEnd = -1;
+                    cls.memberIndexStart = -1;
+                    cls.memberIndexLength = 0;
+                }
+            }
+        }
+
+        // Fill in the earlier index count
+        buffer.position(indexCountOffset);
+        buffer.putInt(indexCount);
+        buffer.position(newIndex);
+
+        // Write member entries
+        for (ApiPackage pkg : packages) {
+            for (ApiClass apiClass : pkg.getClasses()) {
+                String clz = apiClass.getName();
+                int index = apiClass.memberOffsetBegin;
+                for (String member : apiClass.members) {
+                    // Update member offset to point to this entry
+                    int start = buffer.position();
+                    buffer.position(index);
+                    buffer.putInt(start);
+                    index = buffer.position();
+                    buffer.position(start);
+
+                    int since;
+                    if (member.indexOf('(') != -1) {
+                        since = apiClass.getMethod(member, info);
+                    } else {
+                        since = apiClass.getField(member, info);
+                    }
+                    if (since == Integer.MAX_VALUE) {
+                        assert false : clz + ':' + member;
+                        since = 1;
+                    }
+
+                    int deprecatedIn = apiClass.getMemberDeprecatedIn(member, info);
+                    if (deprecatedIn != 0) {
+                        assert deprecatedIn != -1 : deprecatedIn + " for " + member;
+                    }
+
+                    byte[] signature = member.getBytes(Charsets.UTF_8);
+                    for (byte b : signature) {
+                        // Make sure all signatures are really just simple ASCII
+                        assert b == (b & 0x7f) : member;
+                        buffer.put(b);
+                        // Skip types on methods
+                        if (b == (byte) ')') {
+                            break;
+                        }
+                    }
+                    buffer.put((byte) 0);
+                    int api = since;
+                    assert api == UnsignedBytes.toInt((byte) api);
+                    assert api >= 1 && api < 0xFF; // max that fits in a byte
+
+                    boolean isDeprecated = deprecatedIn > 0;
+                    if (isDeprecated) {
+                        api |= HAS_DEPRECATION_BYTE_FLAG;
+                    }
+
+                    buffer.put((byte) api);
+
+                    if (isDeprecated) {
+                        assert deprecatedIn == UnsignedBytes.toInt((byte) deprecatedIn);
+                        buffer.put((byte) deprecatedIn);
+                    }
+                }
+                assert index == apiClass.memberOffsetEnd : apiClass.memberOffsetEnd;
+            }
+        }
+
+        // Write class entries. These are written together, rather than
+        // being spread out among the member entries, in order to have
+        // reference locality (search that a binary search through the classes
+        // are likely to look at entries near each other.)
+        for (ApiPackage pkg : packages) {
+            List<ApiClass> classes = pkg.getClasses();
+            for (ApiClass cls : classes) {
+                int index = buffer.position();
+                buffer.position(cls.indexOffset);
+                buffer.putInt(index);
+                buffer.position(index);
+                String name = cls.getSimpleName();
+
+                byte[] nameBytes = name.getBytes(Charsets.UTF_8);
+                assert nameBytes.length < 254 : name;
+                buffer.put((byte)(nameBytes.length + 2)); // 2: terminating 0, and this byte itself
+                buffer.put(nameBytes);
                 buffer.put((byte) 0);
-                int api = since;
-                assert api == UnsignedBytes.toInt((byte) api);
-                //assert api >= 1 && api < 0xFF; // max that fits in a byte
-                buffer.put((byte) api);
-                nextEntry = buffer.position();
+
+                // 3 bytes for beginning, 2 bytes for *length*
+                put3ByteInt(buffer, cls.memberIndexStart);
+                put2ByteInt(buffer, cls.memberIndexLength);
+
+                ApiClass apiClass = classMap.get(cls.getName());
+                assert apiClass != null : cls.getName();
+                int since = apiClass.getSince();
+                assert since == UnsignedBytes.toInt((byte) since) : since; // make sure it fits
+                int deprecatedIn = apiClass.getDeprecatedIn();
+                boolean isDeprecated = deprecatedIn > 0;
+                // The first byte is deprecated in
+                if (isDeprecated) {
+                    since |= HAS_DEPRECATION_BYTE_FLAG;
+                    assert since == UnsignedBytes.toInt((byte) since) : since; // make sure it fits
+                }
+                buffer.put((byte) since);
+                if (isDeprecated) {
+                    assert deprecatedIn == UnsignedBytes.toInt((byte) deprecatedIn) : deprecatedIn;
+                    buffer.put((byte) deprecatedIn);
+                }
+
+                List<Pair<String, Integer>> interfaces = apiClass.getInterfaces();
+                int count = 0;
+                if (interfaces != null && !interfaces.isEmpty()) {
+                    for (Pair<String, Integer> pair : interfaces) {
+                        int api = pair.getSecond();
+                        if (api > apiClass.getSince()) {
+                            count++;
+                        }
+                    }
+                }
+                List<Pair<String, Integer>> supers = apiClass.getSuperClasses();
+                if (supers != null && !supers.isEmpty()) {
+                    for (Pair<String, Integer> pair : supers) {
+                        int api = pair.getSecond();
+                        if (api > apiClass.getSince()) {
+                            count++;
+                        }
+                    }
+                }
+                buffer.put((byte)count);
+                if (count > 0) {
+                    if (supers != null) {
+                        for (Pair<String, Integer> pair : supers) {
+                            int api = pair.getSecond();
+                            if (api > apiClass.getSince()) {
+                                ApiClass superClass = classMap.get(pair.getFirst());
+                                assert superClass != null : cls;
+                                put3ByteInt(buffer, superClass.index);
+                                buffer.put((byte) api);
+                            }
+                        }
+                    }
+                    if (interfaces != null) {
+                        for (Pair<String, Integer> pair : interfaces) {
+                            int api = pair.getSecond();
+                            if (api > apiClass.getSince()) {
+                                ApiClass interfaceClass = classMap.get(pair.getFirst());
+                                assert interfaceClass != null : cls;
+                                put3ByteInt(buffer, interfaceClass.index);
+                                buffer.put((byte) api);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        for (ApiPackage pkg : packages) {
+            int index = buffer.position();
+            buffer.position(pkg.indexOffset);
+            buffer.putInt(index);
+            buffer.position(index);
+
+            byte[] bytes = pkg.getName().getBytes(Charsets.UTF_8);
+            buffer.put(bytes);
+            buffer.put((byte)0);
+
+            List<ApiClass> classes = pkg.getClasses();
+            if (classes.isEmpty()) {
+                put3ByteInt(buffer, 0);
+                put2ByteInt(buffer, 0);
+            } else {
+                // 3 bytes for beginning, 2 bytes for *length*
+                int firstClassIndex = classes.get(0).index;
+                int classCount = classes.get(classes.size() - 1).index - firstClassIndex + 1;
+                put3ByteInt(buffer, firstClassIndex);
+                put2ByteInt(buffer, classCount);
             }
         }
 
@@ -591,13 +682,8 @@
         buffer.mark();
 
         if (WRITE_STATS) {
-            System.out.println("Wrote " + classes.size() + " classes and "
-                    + memberCount + " member entries");
             System.out.print("Actual binary size: " + size + " bytes");
             System.out.println(String.format(" (%.1fM)", size/(1024*1024.f)));
-
-            System.out.println("Allocated size: " + (entryCount * BYTES_PER_ENTRY) + " bytes");
-            System.out.println("Required bytes per entry: " + (size/ entryCount) + " bytes");
         }
 
         // Now dump this out as a file
@@ -606,11 +692,11 @@
         buffer.rewind();
         buffer.get(b);
         if (file.exists()) {
-            file.delete();
+            boolean deleted = file.delete();
+            assert deleted : file;
         }
-        FileOutputStream output = Files.newOutputStreamSupplier(file).getOutput();
-        output.write(b);
-        output.close();
+        ByteSink sink = Files.asByteSink(file);
+        sink.write(b);
     }
 
     // For debugging only
@@ -631,9 +717,10 @@
         }
     }
 
-    private static int compare(byte[] data, int offset, byte terminator, String s, int max) {
+    private static int compare(byte[] data, int offset, byte terminator, String s, int sOffset,
+            int max) {
         int i = offset;
-        int j = 0;
+        int j = sOffset;
         for (; j < max; i++, j++) {
             byte b = data[i];
             char c = s.charAt(j);
@@ -650,22 +737,6 @@
     }
 
     /**
-     * Quick determination whether a given class name is possibly interesting; this
-     * is a quick package prefix check to determine whether we need to consider
-     * the class at all. This let's us do less actual searching for the vast majority
-     * of APIs (in libraries, application code etc) that have nothing to do with the
-     * APIs in our packages.
-     * @param name the class name in VM format (e.g. using / instead of .)
-     * @return true if the owner is <b>possibly</b> relevant
-     */
-    public static boolean isRelevantClass(String name) {
-        // TODO: Add quick switching here. This is tied to the database file so if
-        // we end up with unexpected prefixes there, this could break. For that reason,
-        // for now we consider everything relevant.
-        return true;
-    }
-
-    /**
      * Returns the API version required by the given class reference,
      * or -1 if this is not a known API class. Note that it may return -1
      * for classes introduced in version 1; internally the database only
@@ -678,22 +749,12 @@
      *         it's unknown <b>or version 1</b>.
      */
     public int getClassVersion(@NonNull String className) {
-        if (!isRelevantClass(className)) {
-            return -1;
-        }
-
+        //noinspection VariableNotUsedInsideIf
         if (mData != null) {
-            int classNumber = findClass(className);
-            if (classNumber != -1) {
-                int offset = mIndices[classNumber];
-                while (mData[offset] != 0) {
-                    offset++;
-                }
-                offset++;
-                return UnsignedBytes.toInt(mData[offset]);
-            }
+            return getClassVersion(findClass(className));
         }  else {
-           ApiClass clz = mInfo.getClass(className);
+            assert mInfo != null;
+            ApiClass clz = mInfo.getClass(className);
             if (clz != null) {
                 int since = clz.getSince();
                 if (since == Integer.MAX_VALUE) {
@@ -707,6 +768,113 @@
     }
 
     /**
+     * Returns true if the given owner class is known in the API database.
+     *
+     * @param className the internal name of the class, e.g. its fully qualified name (as returned
+     *                  by Class.getName(), but with '.' replaced by '/' (and '$' for inner
+     *                  classes)
+     * @return true if this is a class found in the API database
+     */
+    public boolean isKnownClass(@NonNull String className) {
+        return findClass(className) != -1;
+    }
+
+    private int getClassVersion(int classNumber) {
+        if (classNumber != -1) {
+            int offset = seekClassData(classNumber, CLASS_HEADER_API);
+            int api = UnsignedBytes.toInt(mData[offset]) & API_MASK;
+            return api > 1 ? api : -1;
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the API version required to perform the given cast, or -1 if this is valid for all
+     * versions of the class (or, if these are not known classes or if the cast is not valid at
+     * all.) <p> Note also that this method should only be called for interfaces that are actually
+     * implemented by this class or extending the given super class (check elsewhere); it doesn't
+     * distinguish between interfaces implemented in the initial version of the class and interfaces
+     * not implemented at all.
+     *
+     * @param sourceClass      the internal name of the class, e.g. its fully qualified name (as
+     *                         returned by Class.getName(), but with '.' replaced by '/'.
+     * @param destinationClass the class to cast the sourceClass to
+     * @return the minimum API version the method is supported for, or 1 or -1 if it's unknown.
+     */
+    public int getValidCastVersion(@NonNull String sourceClass,
+            @NonNull String destinationClass) {
+        if (mData != null) {
+            int classNumber = findClass(sourceClass);
+            if (classNumber != -1) {
+                int interfaceNumber = findClass(destinationClass);
+                if (interfaceNumber != -1) {
+                    int offset = seekClassData(classNumber, CLASS_HEADER_INTERFACES);
+                    int interfaceCount = mData[offset++];
+                    for (int i = 0; i < interfaceCount; i++) {
+                        int clsNumber = get3ByteInt(mData, offset);
+                        offset += 3;
+                        int api = mData[offset++];
+                        if (clsNumber == interfaceNumber) {
+                           return api;
+                        }
+                    }
+                    return getClassVersion(classNumber);
+                }
+            }
+        }  else {
+            assert mInfo != null;
+            ApiClass clz = mInfo.getClass(sourceClass);
+            if (clz != null) {
+                List<Pair<String, Integer>> interfaces = clz.getInterfaces();
+                for (Pair<String,Integer> pair : interfaces) {
+                    String interfaceName = pair.getFirst();
+                    if (interfaceName.equals(destinationClass)) {
+                        return pair.getSecond();
+                    }
+                }
+            }
+        }
+
+        return -1;
+    }
+    /**
+     * Returns the API version the given class was deprecated in, or -1 if the class
+     * is not deprecated.
+     *
+     * @param className the internal name of the method's owner class, e.g. its
+     *            fully qualified name (as returned by Class.getName(), but with
+     *            '.' replaced by '/'.
+     * @return the API version the API was deprecated in, or -1 if
+     *         it's unknown <b>or version 0</b>.
+     */
+    public int getClassDeprecatedIn(@NonNull String className) {
+        if (mData != null) {
+            int classNumber = findClass(className);
+            if (classNumber != -1) {
+                int offset = seekClassData(classNumber, CLASS_HEADER_DEPRECATED);
+                if (offset == -1) {
+                    // Not deprecated
+                    return -1;
+                }
+                int deprecatedIn = UnsignedBytes.toInt(mData[offset]);
+                return deprecatedIn != 0 ? deprecatedIn : -1;
+            }
+        }  else {
+            assert mInfo != null;
+            ApiClass clz = mInfo.getClass(className);
+            if (clz != null) {
+                int deprecatedIn = clz.getDeprecatedIn();
+                if (deprecatedIn == Integer.MAX_VALUE) {
+                    deprecatedIn = -1;
+                }
+                return deprecatedIn;
+            }
+        }
+
+        return -1;
+    }
+
+    /**
      * Returns the API version required by the given method call. The method is
      * referred to by its {@code owner}, {@code name} and {@code desc} fields.
      * If the method is unknown it returns -1. Note that it may return -1 for
@@ -718,24 +886,26 @@
      *            '.' replaced by '/'.
      * @param name the method's name
      * @param desc the method's descriptor - see {@link org.objectweb.asm.Type}
-     * @return the minimum API version the method is supported for, or -1 if
-     *         it's unknown <b>or version 1</b>.
+     * @return the minimum API version the method is supported for, or 1 or -1 if
+     *         it's unknown.
      */
     public int getCallVersion(
             @NonNull String owner,
             @NonNull String name,
             @NonNull String desc) {
-        if (!isRelevantClass(owner)) {
-            return -1;
-        }
-
+        //noinspection VariableNotUsedInsideIf
         if (mData != null) {
             int classNumber = findClass(owner);
             if (classNumber != -1) {
-                return findMember(classNumber, name, desc);
+                int api = findMember(classNumber, name, desc);
+                if (api == -1) {
+                    return getClassVersion(classNumber);
+                }
+                return api;
             }
         }  else {
-           ApiClass clz = mInfo.getClass(owner);
+            assert mInfo != null;
+            ApiClass clz = mInfo.getClass(owner);
             if (clz != null) {
                 String signature = name + desc;
                 int since = clz.getMethod(signature, mInfo);
@@ -750,6 +920,45 @@
     }
 
     /**
+     * Returns the API version the given call was deprecated in, or -1 if the method
+     * is not deprecated.
+     *
+     * @param owner the internal name of the method's owner class, e.g. its
+     *            fully qualified name (as returned by Class.getName(), but with
+     *            '.' replaced by '/'.
+     * @param name the method's name
+     * @param desc the method's descriptor - see {@link org.objectweb.asm.Type}
+     * @return the API version the API was deprecated in, or 1 or -1 if
+     *         it's unknown.
+     */
+    public int getCallDeprecatedIn(
+            @NonNull String owner,
+            @NonNull String name,
+            @NonNull String desc) {
+        //noinspection VariableNotUsedInsideIf
+        if (mData != null) {
+            int classNumber = findClass(owner);
+            if (classNumber != -1) {
+                int deprecatedIn = findMemberDeprecatedIn(classNumber, name, desc);
+                return deprecatedIn != 0 ? deprecatedIn : -1;
+            }
+        }  else {
+            assert mInfo != null;
+            ApiClass clz = mInfo.getClass(owner);
+            if (clz != null) {
+                String signature = name + desc;
+                int deprecatedIn = clz.getMemberDeprecatedIn(signature, mInfo);
+                if (deprecatedIn == Integer.MAX_VALUE) {
+                    deprecatedIn = -1;
+                }
+                return deprecatedIn;
+            }
+        }
+
+        return -1;
+    }
+
+    /**
      * Returns the API version required to access the given field, or -1 if this
      * is not a known API method. Note that it may return -1 for classes
      * introduced in version 1; internally the database only stores version data
@@ -759,22 +968,24 @@
      *            fully qualified name (as returned by Class.getName(), but with
      *            '.' replaced by '/'.
      * @param name the method's name
-     * @return the minimum API version the method is supported for, or -1 if
-     *         it's unknown <b>or version 1</b>
+     * @return the minimum API version the method is supported for, or 1 or -1 if
+     *         it's unknown.
      */
     public int getFieldVersion(
             @NonNull String owner,
             @NonNull String name) {
-        if (!isRelevantClass(owner)) {
-            return -1;
-        }
-
+        //noinspection VariableNotUsedInsideIf
         if (mData != null) {
             int classNumber = findClass(owner);
             if (classNumber != -1) {
-                return findMember(classNumber, name, null);
+                int api = findMember(classNumber, name, null);
+                if (api == -1) {
+                    return getClassVersion(classNumber);
+                }
+                return api;
             }
         }  else {
+            assert mInfo != null;
             ApiClass clz = mInfo.getClass(owner);
             if (clz != null) {
                 int since = clz.getField(name, mInfo);
@@ -789,6 +1000,42 @@
     }
 
     /**
+     * Returns the API version the given field was deprecated in, or -1 if the field
+     * is not deprecated.
+     *
+     * @param owner the internal name of the method's owner class, e.g. its
+     *            fully qualified name (as returned by Class.getName(), but with
+     *            '.' replaced by '/'.
+     * @param name the method's name
+     * @return the API version the API was deprecated in, or 1 or -1 if
+     *         it's unknown.
+     */
+    public int getFieldDeprecatedIn(
+            @NonNull String owner,
+            @NonNull String name) {
+        //noinspection VariableNotUsedInsideIf
+        if (mData != null) {
+            int classNumber = findClass(owner);
+            if (classNumber != -1) {
+                int deprecatedIn = findMemberDeprecatedIn(classNumber, name, null);
+                return deprecatedIn != 0 ? deprecatedIn : -1;
+            }
+        }  else {
+            assert mInfo != null;
+            ApiClass clz = mInfo.getClass(owner);
+            if (clz != null) {
+                int deprecatedIn = clz.getMemberDeprecatedIn(name, mInfo);
+                if (deprecatedIn == Integer.MAX_VALUE) {
+                    deprecatedIn = -1;
+                }
+                return deprecatedIn;
+            }
+        }
+
+        return -1;
+    }
+
+    /**
      * Returns true if the given owner (in VM format) is relevant to the database.
      * This allows quick filtering out of owners that won't return any data
      * for the various {@code #getFieldVersion} etc methods.
@@ -822,7 +1069,6 @@
         return false;
     }
 
-
     /**
      * Returns true if the given owner (in VM format) is a valid Java package supported
      * in any version of Android.
@@ -831,28 +1077,34 @@
      * @return true if the package is included in one or more versions of Android
      */
     public boolean isValidJavaPackage(@NonNull String owner) {
-        int packageLength = owner.lastIndexOf('/');
-        if (packageLength == -1) {
-            return false;
-        }
+        return findPackage(owner) != -1;
+    }
+
+    /** Returns the package index of the given class, or -1 if it is unknown */
+    private int findPackage(@NonNull String owner) {
+        assert owner.indexOf('.') == -1 : "Should use / instead of . in owner: " + owner;
 
         // The index array contains class indexes from 0 to classCount and
         //   member indices from classCount to mIndices.length.
         int low = 0;
-        int high = mJavaPackages.length - 1;
+        int high = mPackageCount - 1;
+        // Compare the api info at the given index.
+        int classNameLength = owner.lastIndexOf('/');
         while (low <= high) {
             int middle = (low + high) >>> 1;
-            int offset = middle;
+            int offset = mIndices[middle];
 
             if (DEBUG_SEARCH) {
-                System.out.println("Comparing string " + owner + " with entry at " + offset
-                        + ": " + mJavaPackages[offset]);
+                System.out.println("Comparing string " + owner.substring(0, classNameLength)
+                        + " with entry at " + offset + ": " + dumpEntry(offset));
             }
 
-            // Compare the api info at the given index.
-            int compare = comparePackage(mJavaPackages[offset], owner, packageLength);
+            int compare = compare(mData, offset, (byte) 0, owner, 0, classNameLength);
             if (compare == 0) {
-                return true;
+                if (DEBUG_SEARCH) {
+                    System.out.println("Found " + dumpEntry(offset));
+                }
+                return middle;
             }
 
             if (compare < 0) {
@@ -861,53 +1113,98 @@
                 high = middle - 1;
             } else {
                 assert false; // compare == 0 already handled above
-                return false;
-            }
-        }
-
-        return false;
-    }
-
-    private static int comparePackage(String s1, String s2, int max) {
-        for (int i = 0; i < max; i++) {
-            if (i == s1.length()) {
                 return -1;
             }
-            char c1 = s1.charAt(i);
-            char c2 = s2.charAt(i);
-            if (c1 != c2) {
-                return c1 - c2;
-            }
         }
 
-        if (s1.length() > max) {
-            return 1;
-        }
+        return -1;
+    }
 
-        return 0;
+    private static int get4ByteInt(@NonNull byte[] data, int offset) {
+        byte b1 = data[offset++];
+        byte b2 = data[offset++];
+        byte b3 = data[offset++];
+        byte b4 = data[offset];
+        // The byte data is always big endian.
+        return (b1 & 0xFF) << 24 | (b2 & 0xFF) << 16 | (b3 & 0xFF) << 8 | (b4 & 0xFF);
+    }
+
+    private static void put3ByteInt(@NonNull ByteBuffer buffer, int value) {
+        // Big endian
+        byte b3 = (byte) (value & 0xFF);
+        value >>>= 8;
+        byte b2 = (byte) (value & 0xFF);
+        value >>>= 8;
+        byte b1 = (byte) (value & 0xFF);
+        buffer.put(b1);
+        buffer.put(b2);
+        buffer.put(b3);
+    }
+
+    private static void put2ByteInt(@NonNull ByteBuffer buffer, int value) {
+        // Big endian
+        byte b2 = (byte) (value & 0xFF);
+        value >>>= 8;
+        byte b1 = (byte) (value & 0xFF);
+        buffer.put(b1);
+        buffer.put(b2);
+    }
+
+    private static int get3ByteInt(@NonNull byte[] mData, int offset) {
+        byte b1 = mData[offset++];
+        byte b2 = mData[offset++];
+        byte b3 = mData[offset];
+        // The byte data is always big endian.
+        return (b1 & 0xFF) << 16 | (b2 & 0xFF) << 8 | (b3 & 0xFF);
+    }
+
+    private static int get2ByteInt(@NonNull byte[] data, int offset) {
+        byte b1 = data[offset++];
+        byte b2 = data[offset];
+        // The byte data is always big endian.
+        return (b1 & 0xFF) << 8 | (b2 & 0xFF);
     }
 
     /** Returns the class number of the given class, or -1 if it is unknown */
     private int findClass(@NonNull String owner) {
         assert owner.indexOf('.') == -1 : "Should use / instead of . in owner: " + owner;
 
-        // The index array contains class indexes from 0 to classCount and
-        //   member indices from classCount to mIndices.length.
-        int low = 0;
-        int high = mClassCount - 1;
-        // Compare the api info at the given index.
+        int packageNumber = findPackage(owner);
+        if (packageNumber == -1) {
+            return -1;
+        }
+        int curr = mIndices[packageNumber];
+        while (mData[curr] != 0) {
+            curr++;
+        }
+        curr++;
+
+        // 3 bytes for first offset
+        int low = get3ByteInt(mData, curr);
+        curr += 3;
+
+        int length = get2ByteInt(mData, curr);
+        if (length == 0) {
+            return -1;
+        }
+        int high = low + length - 1;
+        int index = owner.lastIndexOf('/');
         int classNameLength = owner.length();
         while (low <= high) {
             int middle = (low + high) >>> 1;
             int offset = mIndices[middle];
+            offset++; // skip the byte which points to the metadata after the name
 
             if (DEBUG_SEARCH) {
-                System.out.println("Comparing string " + owner + " with entry at " + offset
-                        + ": " + dumpEntry(offset));
+                System.out.println("Comparing string " + owner.substring(0, classNameLength)
+                        + " with entry at " + offset + ": " + dumpEntry(offset));
             }
 
-            int compare = compare(mData, offset, (byte) 0, owner, classNameLength);
+            int compare = compare(mData, offset, (byte) 0, owner, index + 1, classNameLength);
             if (compare == 0) {
+                if (DEBUG_SEARCH) {
+                    System.out.println("Found " + dumpEntry(offset));
+                }
                 return middle;
             }
 
@@ -925,10 +1222,49 @@
     }
 
     private int findMember(int classNumber, @NonNull String name, @Nullable String desc) {
-        // The index array contains class indexes from 0 to classCount and
-        // member indices from classCount to mIndices.length.
-        int low = mClassCount;
-        int high = mIndices.length - 1;
+        return findMember(classNumber, name, desc, false);
+    }
+
+    private int findMemberDeprecatedIn(int classNumber, @NonNull String name,
+            @Nullable String desc) {
+        return findMember(classNumber, name, desc, true);
+    }
+
+    private int seekClassData(int classNumber, int field) {
+        int offset = mIndices[classNumber];
+        offset += mData[offset] & 0xFF;
+        if (field == CLASS_HEADER_MEMBER_OFFSETS) {
+            return offset;
+        }
+        offset += 5; // 3 bytes for start, 2 bytes for length
+        if (field == CLASS_HEADER_API) {
+            return offset;
+        }
+        boolean hasDeprecation = (mData[offset] & HAS_DEPRECATION_BYTE_FLAG) != 0;
+        offset++;
+        if (field == CLASS_HEADER_DEPRECATED) {
+            return hasDeprecation ? offset : -1;
+        } else if (hasDeprecation) {
+            offset++;
+        }
+        assert field == CLASS_HEADER_INTERFACES;
+        return offset;
+    }
+
+    private int findMember(int classNumber, @NonNull String name, @Nullable String desc,
+            boolean deprecation) {
+        int curr = seekClassData(classNumber, CLASS_HEADER_MEMBER_OFFSETS);
+
+        // 3 bytes for first offset
+        int low = get3ByteInt(mData, curr);
+        curr += 3;
+
+        int length = get2ByteInt(mData, curr);
+        if (length == 0) {
+            return -1;
+        }
+        int high = low + length - 1;
+
         while (low <= high) {
             int middle = (low + high) >>> 1;
             int offset = mIndices[middle];
@@ -938,38 +1274,56 @@
                         " with entry at " + offset + ": " + dumpEntry(offset));
             }
 
-            // Check class number: read short. The byte data is always big endian.
-            int entryClass = (mData[offset++] & 0xFF) << 8 | (mData[offset++] & 0xFF);
-            int compare = entryClass - classNumber;
-            if (compare == 0) {
-                if (desc != null) {
-                    // Method
-                    int nameLength = name.length();
-                    compare = compare(mData, offset, (byte) '(', name, nameLength);
+            int compare;
+            if (desc != null) {
+                // Method
+                int nameLength = name.length();
+                compare = compare(mData, offset, (byte) '(', name, 0, nameLength);
+                if (compare == 0) {
+                    offset += nameLength;
+                    int argsEnd = desc.indexOf(')');
+                    // Only compare up to the ) -- after that we have a return value in the
+                    // input description, which isn't there in the database
+                    compare = compare(mData, offset, (byte) ')', desc, 0, argsEnd);
                     if (compare == 0) {
-                        offset += nameLength;
-                        int argsEnd = desc.indexOf(')');
-                        // Only compare up to the ) -- after that we have a return value in the
-                        // input description, which isn't there in the database
-                        compare = compare(mData, offset, (byte) ')', desc, argsEnd);
-                        if (compare == 0) {
-                            offset += argsEnd + 1;
+                        if (DEBUG_SEARCH) {
+                            System.out.println("Found " + dumpEntry(offset));
+                        }
 
-                            if (mData[offset++] == 0) {
-                                // Yes, terminated argument list: get the API level
-                                return UnsignedBytes.toInt(mData[offset]);
+                        offset += argsEnd + 1;
+
+                        if (mData[offset++] == 0) {
+                            // Yes, terminated argument list: get the API level
+                            int api = UnsignedBytes.toInt(mData[offset]);
+                            if (deprecation) {
+                                if ((api & HAS_DEPRECATION_BYTE_FLAG) != 0) {
+                                    return UnsignedBytes.toInt(mData[offset + 1]);
+                                } else {
+                                    return -1;
+                                }
+                            } else {
+                                return api & API_MASK;
                             }
                         }
                     }
-                } else {
-                    // Field
-                    int nameLength = name.length();
-                    compare = compare(mData, offset, (byte) 0, name, nameLength);
-                    if (compare == 0) {
-                        offset += nameLength;
-                        if (mData[offset++] == 0) {
-                            // Yes, terminated argument list: get the API level
-                            return UnsignedBytes.toInt(mData[offset]);
+                }
+            } else {
+                // Field
+                int nameLength = name.length();
+                compare = compare(mData, offset, (byte) 0, name, 0, nameLength);
+                if (compare == 0) {
+                    offset += nameLength;
+                    if (mData[offset++] == 0) {
+                        // Yes, terminated argument list: get the API level
+                        int api = UnsignedBytes.toInt(mData[offset]);
+                        if (deprecation) {
+                            if ((api & HAS_DEPRECATION_BYTE_FLAG) != 0) {
+                                return UnsignedBytes.toInt(mData[offset + 1]);
+                            } else {
+                                return -1;
+                            }
+                        } else {
+                            return api & API_MASK;
                         }
                     }
                 }
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiPackage.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiPackage.java
new file mode 100644
index 0000000..8c0e058
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiPackage.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2015 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.annotations.NonNull;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+/**
+ * Represents a package and its classes
+ */
+public class ApiPackage implements Comparable<ApiPackage> {
+    private final String mName;
+    private final List<ApiClass> mClasses = Lists.newArrayListWithExpectedSize(100);
+
+    // Persistence data: Used when writing out binary data in ApiLookup
+    int indexOffset;         // offset of the package entry
+
+    ApiPackage(@NonNull String name) {
+        mName = name;
+    }
+
+    /**
+     * Returns the name of the class (fully qualified name)
+     * @return the name of the class
+     */
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Returns the classes in this package
+     * @return the classes in this package
+     */
+    @NonNull
+    public List<ApiClass> getClasses() {
+        return mClasses;
+    }
+
+    void addClass(@NonNull ApiClass clz) {
+        mClasses.add(clz);
+    }
+
+    @Override
+    public int compareTo(@NonNull ApiPackage other) {
+        return mName.compareTo(other.mName);
+    }
+
+    @Override
+    public String toString() {
+        return mName;
+    }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiParser.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiParser.java
index b3c2f2a..1698961 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiParser.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiParser.java
@@ -38,8 +38,10 @@
 
     private static final String ATTR_NAME = "name";
     private static final String ATTR_SINCE = "since";
+    private static final String ATTR_DEPRECATED = "deprecated";
 
-    private final Map<String, ApiClass> mClasses = new HashMap<String, ApiClass>();
+    private final Map<String, ApiClass> mClasses = new HashMap<String, ApiClass>(1000);
+    private final Map<String, ApiPackage> mPackages = new HashMap<String, ApiPackage>();
 
     private ApiClass mCurrentClass;
 
@@ -49,6 +51,7 @@
     Map<String, ApiClass> getClasses() {
         return mClasses;
     }
+    Map<String, ApiPackage> getPackages() { return mPackages; }
 
     @Override
     public void startElement(String uri, String localName, String qName, Attributes attributes)
@@ -59,14 +62,21 @@
         }
 
         try {
+            //noinspection StatementWithEmptyBody
             if (NODE_API.equals(localName)) {
                 // do nothing.
-
             } else if (NODE_CLASS.equals(localName)) {
                 String name = attributes.getValue(ATTR_NAME);
                 int since = Integer.parseInt(attributes.getValue(ATTR_SINCE));
 
-                mCurrentClass = addClass(name, since);
+                String deprecatedAttr = attributes.getValue(ATTR_DEPRECATED);
+                int deprecatedIn;
+                if (deprecatedAttr != null) {
+                    deprecatedIn = Integer.parseInt(deprecatedAttr);
+                } else {
+                    deprecatedIn = 0;
+                }
+                mCurrentClass = addClass(name, since, deprecatedIn);
 
             } else if (NODE_EXTENDS.equals(localName)) {
                 String name = attributes.getValue(ATTR_NAME);
@@ -83,14 +93,15 @@
             } else if (NODE_METHOD.equals(localName)) {
                 String name = attributes.getValue(ATTR_NAME);
                 int since = getSince(attributes);
-
-                mCurrentClass.addMethod(name, since);
+                int deprecatedIn = getDeprecatedIn(attributes);
+                mCurrentClass.addMethod(name, since, deprecatedIn);
 
             } else if (NODE_FIELD.equals(localName)) {
                 String name = attributes.getValue(ATTR_NAME);
                 int since = getSince(attributes);
+                int deprecatedIn = getDeprecatedIn(attributes);
 
-                mCurrentClass.addField(name, since);
+                mCurrentClass.addField(name, since, deprecatedIn);
 
             }
 
@@ -99,11 +110,21 @@
         }
     }
 
-    private ApiClass addClass(String name, int apiLevel) {
+    private ApiClass addClass(String name, int apiLevel, int deprecatedIn) {
+        // There should not be any duplicates
         ApiClass theClass = mClasses.get(name);
-        if (theClass == null) {
-            theClass = new ApiClass(name, apiLevel);
-            mClasses.put(name, theClass);
+        assert theClass == null;
+        theClass = new ApiClass(name, apiLevel, deprecatedIn);
+        mClasses.put(name, theClass);
+
+        String pkg = theClass.getPackage();
+        if (pkg != null) {
+            ApiPackage apiPackage = mPackages.get(pkg);
+            if (apiPackage == null) {
+                apiPackage = new ApiPackage(pkg);
+                mPackages.put(pkg, apiPackage);
+            }
+            apiPackage.addClass(theClass);
         }
 
         return theClass;
@@ -119,4 +140,15 @@
 
         return since;
     }
+
+    private int getDeprecatedIn(Attributes attributes) {
+        int deprecatedIn = mCurrentClass.getDeprecatedIn();
+        String deprecatedAttr = attributes.getValue(ATTR_DEPRECATED);
+
+        if (deprecatedAttr != null) {
+            deprecatedIn = Integer.parseInt(deprecatedAttr);
+        }
+
+        return deprecatedIn;
+    }
 }
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppCompatResourceDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppCompatResourceDetector.java
index 11222ac..91ed21f 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppCompatResourceDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppCompatResourceDetector.java
@@ -55,7 +55,7 @@
             "Similarly, when *not* using the appcompat library, you should be using " +
             "the `android:showAsAction` attribute.",
 
-            Category.USABILITY,
+            Category.CORRECTNESS,
             5,
             Severity.ERROR,
             new Implementation(
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppIndexingApiDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppIndexingApiDetector.java
index 1a8dd67..bb9781c 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppIndexingApiDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppIndexingApiDetector.java
@@ -16,19 +16,22 @@
 package com.android.tools.lint.checks;
 
 import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_EXPORTED;
 import static com.android.SdkConstants.ATTR_HOST;
 import static com.android.SdkConstants.ATTR_PATH;
-import static com.android.SdkConstants.ATTR_PATH_PATTERN;
 import static com.android.SdkConstants.ATTR_PATH_PREFIX;
 import static com.android.SdkConstants.ATTR_SCHEME;
-
+import static com.android.SdkConstants.CLASS_ACTIVITY;
 import static com.android.xml.AndroidManifest.ATTRIBUTE_MIME_TYPE;
 import static com.android.xml.AndroidManifest.ATTRIBUTE_NAME;
 import static com.android.xml.AndroidManifest.ATTRIBUTE_PORT;
 import static com.android.xml.AndroidManifest.NODE_ACTION;
+import static com.android.xml.AndroidManifest.NODE_ACTIVITY;
+import static com.android.xml.AndroidManifest.NODE_APPLICATION;
 import static com.android.xml.AndroidManifest.NODE_CATEGORY;
 import static com.android.xml.AndroidManifest.NODE_DATA;
 import static com.android.xml.AndroidManifest.NODE_INTENT;
+import static com.android.xml.AndroidManifest.NODE_MANIFEST;
 
 import com.android.SdkConstants;
 import com.android.annotations.NonNull;
@@ -38,60 +41,430 @@
 import com.android.ide.common.res2.ResourceItem;
 import com.android.ide.common.resources.ResourceUrl;
 import com.android.resources.ResourceType;
+import com.android.tools.lint.client.api.JavaParser;
 import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.client.api.XmlParser;
 import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
 import com.android.tools.lint.detector.api.Detector;
 import com.android.tools.lint.detector.api.Implementation;
 import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.LintUtils;
 import com.android.tools.lint.detector.api.Project;
 import com.android.tools.lint.detector.api.Scope;
 import com.android.tools.lint.detector.api.Severity;
 import com.android.tools.lint.detector.api.XmlContext;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
 
 import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.NamedNodeMap;
 import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
 
+import java.io.File;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.EnumSet;
 import java.util.List;
+import java.util.Set;
+
+import lombok.ast.ClassDeclaration;
+import lombok.ast.Expression;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.MethodInvocation;
 
 
 /**
  * Check if the usage of App Indexing is correct.
  */
-public class AppIndexingApiDetector extends Detector implements Detector.XmlScanner {
+public class AppIndexingApiDetector extends Detector
+        implements Detector.XmlScanner, Detector.JavaScanner {
 
-    private static final Implementation IMPLEMENTATION = new Implementation(
+    private static final Implementation URL_IMPLEMENTATION = new Implementation(
             AppIndexingApiDetector.class, Scope.MANIFEST_SCOPE);
 
-    public static final Issue ISSUE_ERROR = Issue.create("AppIndexingError", //$NON-NLS-1$
-            "Wrong Usage of App Indexing",
-            "Ensures the app can correctly handle deep links and integrate with " +
-                    "App Indexing for Google search.",
-            Category.USABILITY, 5, Severity.ERROR, IMPLEMENTATION)
-            .addMoreInfo("https://g.co/AppIndexing");
+    private static final Implementation APP_INDEXING_API_IMPLEMENTATION =
+            new Implementation(
+                    AppIndexingApiDetector.class,
+                    EnumSet.of(Scope.JAVA_FILE, Scope.MANIFEST),
+                    Scope.JAVA_FILE_SCOPE, Scope.MANIFEST_SCOPE);
 
-    public static final Issue ISSUE_WARNING = Issue.create("AppIndexingWarning", //$NON-NLS-1$
-            "Missing App Indexing Support",
-            "Ensures the app can correctly handle deep links and integrate with " +
-                    "App Indexing for Google search.",
-            Category.USABILITY, 5, Severity.WARNING, IMPLEMENTATION)
-            .addMoreInfo("https://g.co/AppIndexing");
+    public static final Issue ISSUE_URL_ERROR = Issue.create(
+            "GoogleAppIndexingUrlError", //$NON-NLS-1$
+            "URL not supported by app for Google App Indexing",
+            "Ensure the URL is supported by your app, to get installs and traffic to your"
+                    + " app from Google Search.",
+            Category.USABILITY, 5, Severity.ERROR, URL_IMPLEMENTATION)
+            .addMoreInfo("https://g.co/AppIndexing/AndroidStudio");
 
-    private static final String[] PATH_ATTR_LIST = new String[]{ATTR_PATH_PREFIX, ATTR_PATH,
-            ATTR_PATH_PATTERN};
+    public static final Issue ISSUE_APP_INDEXING =
+      Issue.create(
+        "GoogleAppIndexingWarning", //$NON-NLS-1$
+        "Missing support for Google App Indexing",
+        "Adds URLs to get your app into the Google index, to get installs"
+          + " and traffic to your app from Google Search.",
+        Category.USABILITY, 5, Severity.WARNING, URL_IMPLEMENTATION)
+        .addMoreInfo("https://g.co/AppIndexing/AndroidStudio");
 
+    public static final Issue ISSUE_APP_INDEXING_API =
+            Issue.create(
+                    "GoogleAppIndexingApiWarning", //$NON-NLS-1$
+                    "Missing support for Google App Indexing Api",
+                    "Adds URLs to get your app into the Google index, to get installs"
+                            + " and traffic to your app from Google Search.",
+                    Category.USABILITY, 5, Severity.WARNING, APP_INDEXING_API_IMPLEMENTATION)
+                    .addMoreInfo("https://g.co/AppIndexing/AndroidStudio")
+                    .setEnabledByDefault(false);
+
+    private static final String[] PATH_ATTR_LIST = new String[]{ATTR_PATH_PREFIX, ATTR_PATH};
+    private static final String SCHEME_MISSING = "android:scheme is missing";
+    private static final String HOST_MISSING = "android:host is missing";
+    private static final String DATA_MISSING = "Missing data element";
+    private static final String URL_MISSING = "Missing URL for the intent filter";
+    private static final String NOT_BROWSABLE
+            = "Activity supporting ACTION_VIEW is not set as BROWSABLE";
+    private static final String ILLEGAL_NUMBER = "android:port is not a legal number";
+
+    private static final String APP_INDEX_START = "start"; //$NON-NLS-1$
+    private static final String APP_INDEX_END = "end"; //$NON-NLS-1$
+    private static final String APP_INDEX_VIEW = "view"; //$NON-NLS-1$
+    private static final String APP_INDEX_VIEW_END = "viewEnd"; //$NON-NLS-1$
+    private static final String CLIENT_CONNECT = "connect"; //$NON-NLS-1$
+    private static final String CLIENT_DISCONNECT = "disconnect"; //$NON-NLS-1$
+    private static final String ADD_API = "addApi"; //$NON-NLS-1$
+
+    private static final String APP_INDEXING_API_CLASS
+            = "com.google.android.gms.appindexing.AppIndexApi";
+    private static final String GOOGLE_API_CLIENT_CLASS
+            = "com.google.android.gms.common.api.GoogleApiClient";
+    private static final String GOOGLE_API_CLIENT_BUILDER_CLASS
+            = "com.google.android.gms.common.api.GoogleApiClient.Builder";
+    private static final String API_CLASS = "com.google.android.gms.appindexing.AppIndex";
+
+    public enum IssueType {
+        SCHEME_MISSING(AppIndexingApiDetector.SCHEME_MISSING),
+        HOST_MISSING(AppIndexingApiDetector.HOST_MISSING),
+        DATA_MISSING(AppIndexingApiDetector.DATA_MISSING),
+        URL_MISSING(AppIndexingApiDetector.URL_MISSING),
+        NOT_BROWSABLE(AppIndexingApiDetector.NOT_BROWSABLE),
+        ILLEGAL_NUMBER(AppIndexingApiDetector.ILLEGAL_NUMBER),
+        EMPTY_FIELD("cannot be empty"),
+        MISSING_SLASH("attribute should start with '/'"),
+        UNKNOWN("unknown error type");
+
+        private String message;
+
+        IssueType(String str) {
+            this.message = str;
+        }
+
+        public static IssueType parse(String str) {
+            for (IssueType type : IssueType.values()) {
+                if (str.contains(type.message)) {
+                    return type;
+                }
+            }
+            return UNKNOWN;
+        }
+    }
+
+    // ---- Implements XmlScanner ----
     @Override
     @Nullable
     public Collection<String> getApplicableElements() {
-        return Collections.singletonList(NODE_INTENT);
+        return Collections.singletonList(NODE_APPLICATION);
     }
 
     @Override
-    public void visitElement(@NonNull XmlContext context, @NonNull Element intent) {
+    public void visitElement(@NonNull XmlContext context, @NonNull Element application) {
+        List<Element> activities = extractChildrenByName(application, NODE_ACTIVITY);
+        boolean applicationHasActionView = false;
+        for (Element activity : activities) {
+            List<Element> intents = extractChildrenByName(activity, NODE_INTENT);
+            boolean activityHasActionView = false;
+            for (Element intent : intents) {
+                boolean actionView = hasActionView(intent);
+                if (actionView) {
+                    activityHasActionView = true;
+                }
+                visitIntent(context, intent);
+            }
+            if (activityHasActionView) {
+                applicationHasActionView = true;
+                if (activity.hasAttributeNS(ANDROID_URI, ATTR_EXPORTED)) {
+                    Attr exported = activity.getAttributeNodeNS(ANDROID_URI, ATTR_EXPORTED);
+                    if (!exported.getValue().equals("true")) {
+                        // Report error if the activity supporting action view is not exported.
+                        context.report(ISSUE_URL_ERROR, activity,
+                                       context.getLocation(activity),
+                                       "Activity supporting ACTION_VIEW is not exported");
+                    }
+                }
+            }
+        }
+        if (!applicationHasActionView && !context.getProject().isLibrary()) {
+            // Report warning if there is no activity that supports action view.
+            context.report(ISSUE_APP_INDEXING, application, context.getLocation(application),
+                           // This error message is more verbose than the other app indexing lint warnings, because it
+                           // shows up on a blank project, and we want to make it obvious by just looking at the error
+                           // message what this is
+                           "App is not indexable by Google Search; consider adding at least one Activity with an ACTION-VIEW " +
+                           "intent-filler. See issue explanation for more details.");
+        }
+    }
+
+    @Nullable
+    @Override
+    public List<String> applicableSuperClasses() {
+        return Collections.singletonList(CLASS_ACTIVITY);
+    }
+
+    @Override
+    public void checkClass(@NonNull JavaContext javaContext,
+                           @Nullable ClassDeclaration node,
+                           @NonNull lombok.ast.Node declarationOrAnonymous,
+                           @NonNull JavaParser.ResolvedClass cls) {
+        if (node == null) {
+            return;
+        }
+
+        // In case linting the base class itself.
+        if (!cls.isInheritingFrom(CLASS_ACTIVITY, true)) {
+            return;
+        }
+
+        node.accept(new MethodVisitor(javaContext));
+    }
+
+    static class MethodVisitor extends ForwardingAstVisitor {
+        private final JavaContext mJavaContext;
+        private List<MethodInvocation> mStartMethods;
+        private List<MethodInvocation> mEndMethods;
+        private List<MethodInvocation> mConnectMethods;
+        private List<MethodInvocation> mDisconnectMethods;
+        private boolean mHasAddAppIndexApi;
+
+        MethodVisitor(JavaContext javaContext) {
+            mJavaContext = javaContext;
+            mStartMethods = Lists.newArrayListWithExpectedSize(2);
+            mEndMethods = Lists.newArrayListWithExpectedSize(2);
+            mConnectMethods = Lists.newArrayListWithExpectedSize(2);
+            mDisconnectMethods = Lists.newArrayListWithExpectedSize(2);
+        }
+
+        @Override
+        public boolean visitMethodInvocation(MethodInvocation node) {
+            JavaParser.ResolvedNode resolved = mJavaContext.resolve(node);
+            if (!(resolved instanceof JavaParser.ResolvedMethod)) {
+                return super.visitMethodInvocation(node);
+            }
+            JavaParser.ResolvedMethod method = (JavaParser.ResolvedMethod) resolved;
+            String methodName = node.astName().astValue();
+
+            if (methodName.equals(APP_INDEX_START)) {
+                if (method.getContainingClass().getName().equals(APP_INDEXING_API_CLASS)) {
+                    mStartMethods.add(node);
+                }
+            } else if (methodName.equals(APP_INDEX_END)) {
+                if (method.getContainingClass().getName().equals(APP_INDEXING_API_CLASS)) {
+                    mEndMethods.add(node);
+                }
+            } else if (methodName.equals(APP_INDEX_VIEW)) {
+                if (method.getContainingClass().getName().equals(APP_INDEXING_API_CLASS)) {
+                    mStartMethods.add(node);
+                }
+            } else if (methodName.equals(APP_INDEX_VIEW_END)) {
+                if (method.getContainingClass().getName().equals(APP_INDEXING_API_CLASS)) {
+                    mEndMethods.add(node);
+                }
+            } else if (methodName.equals(CLIENT_CONNECT)) {
+                if (method.getContainingClass().getName().equals(GOOGLE_API_CLIENT_CLASS)) {
+                    mConnectMethods.add(node);
+                }
+            } else if (methodName.equals(CLIENT_DISCONNECT)) {
+                if (method.getContainingClass().getName().equals(GOOGLE_API_CLIENT_CLASS)) {
+                    mDisconnectMethods.add(node);
+                }
+            } else if (methodName.equals(ADD_API)) {
+                if (method.getContainingClass().getName().equals(GOOGLE_API_CLIENT_BUILDER_CLASS)) {
+                    JavaParser.ResolvedNode arg0 = mJavaContext
+                            .resolve(node.astArguments().first());
+                    if (arg0 instanceof JavaParser.ResolvedField) {
+                        JavaParser.ResolvedField resolvedArg0 = (JavaParser.ResolvedField) arg0;
+                        JavaParser.ResolvedClass cls = resolvedArg0.getContainingClass();
+                        if (cls != null && cls.getName().equals(API_CLASS)) {
+                            mHasAddAppIndexApi = true;
+                        }
+                    }
+                }
+            }
+            return super.visitMethodInvocation(node);
+        }
+
+        @Override
+        public void endVisit(lombok.ast.Node root){
+            if (!(root instanceof ClassDeclaration)) {
+                return;
+            }
+            ClassDeclaration node = (ClassDeclaration)root;
+            JavaParser.ResolvedNode resolvedNode = mJavaContext.resolve(node);
+            if (resolvedNode == null || !(resolvedNode instanceof JavaParser.ResolvedClass)) {
+                return;
+            }
+
+            if (!((JavaParser.ResolvedClass)resolvedNode).isInheritingFrom(CLASS_ACTIVITY, true)) {
+                return;
+            }
+
+            // finds the activity classes that need app activity annotation
+            Set<String> activitiesToCheck = getActivitiesToCheck(mJavaContext);
+
+            // app indexing API used but no support in manifest
+            boolean hasIntent = activitiesToCheck.contains(resolvedNode.getName());
+            if (!hasIntent) {
+                for (MethodInvocation method : mStartMethods) {
+                    mJavaContext.report(ISSUE_APP_INDEXING_API, method,
+                            mJavaContext.getLocation(method.astName()),
+                            "Missing support for Google App Indexing in the manifest");
+                }
+                for (MethodInvocation method : mEndMethods) {
+                    mJavaContext.report(ISSUE_APP_INDEXING_API, method,
+                            mJavaContext.getLocation(method.astName()),
+                            "Missing support for Google App Indexing in the manifest");
+                }
+                return;
+            }
+
+            // `AppIndex.AppIndexApi.start / end / view / viewEnd` should exist
+            if (mStartMethods.isEmpty() && mEndMethods.isEmpty()) {
+                mJavaContext.report(ISSUE_APP_INDEXING_API, node,
+                        mJavaContext.getLocation(node.astName()),
+                        "Missing support for Google App Indexing API");
+                return;
+            }
+
+            for (MethodInvocation startNode : mStartMethods) {
+                Expression startClient = startNode.astArguments().first();
+
+                // GoogleApiClient should `addApi(AppIndex.APP_INDEX_API)`
+                if (!mHasAddAppIndexApi) {
+                    String message = String.format(
+                            "GoogleApiClient `%1$s` has not added support for App Indexing API",
+                            startClient.toString());
+                    mJavaContext.report(ISSUE_APP_INDEXING_API, startClient,
+                            mJavaContext.getLocation(startClient), message);
+                }
+
+                // GoogleApiClient `connect` should exist
+                if (!hasOperand(startClient, mConnectMethods)) {
+                    String message = String
+                            .format("GoogleApiClient `%1$s` is not connected", startClient.toString());
+                    mJavaContext.report(ISSUE_APP_INDEXING_API, startClient,
+                            mJavaContext.getLocation(startClient), message);
+                }
+
+                // `AppIndex.AppIndexApi.end` should pair with `AppIndex.AppIndexApi.start`
+                if (!hasFirstArgument(startClient, mEndMethods)) {
+                    mJavaContext.report(ISSUE_APP_INDEXING_API, startNode,
+                            mJavaContext.getLocation(startNode.astName()),
+                            "Missing corresponding `AppIndex.AppIndexApi.end` method");
+                }
+            }
+
+            for (MethodInvocation endNode : mEndMethods) {
+                Expression endClient = endNode.astArguments().first();
+
+                // GoogleApiClient should `addApi(AppIndex.APP_INDEX_API)`
+                if (!mHasAddAppIndexApi) {
+                    String message = String.format(
+                            "GoogleApiClient `%1$s` has not added support for App Indexing API",
+                            endClient.toString());
+                    mJavaContext.report(ISSUE_APP_INDEXING_API, endClient,
+                            mJavaContext.getLocation(endClient), message);
+                }
+
+                // GoogleApiClient `disconnect` should exist
+                if (!hasOperand(endClient, mDisconnectMethods)) {
+                    String message = String.format("GoogleApiClient `%1$s`"
+                            + " is not disconnected", endClient.toString());
+                    mJavaContext.report(ISSUE_APP_INDEXING_API, endClient,
+                            mJavaContext.getLocation(endClient), message);
+                }
+
+                // `AppIndex.AppIndexApi.start` should pair with `AppIndex.AppIndexApi.end`
+                if (!hasFirstArgument(endClient, mStartMethods)) {
+                    mJavaContext.report(ISSUE_APP_INDEXING_API, endNode,
+                            mJavaContext.getLocation(endNode.astName()),
+                            "Missing corresponding `AppIndex.AppIndexApi.start` method");
+                }
+            }
+        }
+    }
+
+    /**
+     * Gets names of activities which needs app indexing. i.e. the activities have data tag in their
+     * intent filters.
+     * TODO: Cache the activities to speed up batch lint.
+     *
+     * @param context The context to check in.
+     */
+    private static Set<String> getActivitiesToCheck(Context context) {
+        Set<String> activitiesToCheck = Sets.newHashSet();
+        List<File> manifestFiles = context.getProject().getManifestFiles();
+        XmlParser xmlParser = context.getDriver().getClient().getXmlParser();
+        if (xmlParser != null) {
+            // TODO: Avoid visit all manifest files before enable this check by default.
+            for (File manifest : manifestFiles) {
+                XmlContext xmlContext =
+                        new XmlContext(context.getDriver(), context.getProject(),
+                                null, manifest, null, xmlParser);
+                Document doc = xmlParser.parseXml(xmlContext);
+                if (doc != null) {
+                    List<Element> children = LintUtils.getChildren(doc);
+                    for (Element child : children) {
+                        if (child.getNodeName().equals(NODE_MANIFEST)) {
+                            List<Element> apps = extractChildrenByName(child, NODE_APPLICATION);
+                            for (Element app : apps) {
+                                List<Element> acts = extractChildrenByName(app, NODE_ACTIVITY);
+                                for (Element act : acts) {
+                                    List<Element> intents = extractChildrenByName(act, NODE_INTENT);
+                                    for (Element intent : intents) {
+                                        List<Element> data = extractChildrenByName(intent,
+                                                NODE_DATA);
+                                        if (!data.isEmpty() && act.hasAttributeNS(
+                                                ANDROID_URI, ATTRIBUTE_NAME)) {
+                                            Attr attr = act.getAttributeNodeNS(
+                                                    ANDROID_URI, ATTRIBUTE_NAME);
+                                            String activityName = attr.getValue();
+                                            int dotIndex = activityName.indexOf('.');
+                                            if (dotIndex <= 0) {
+                                                String pkg = context.getMainProject().getPackage();
+                                                if (pkg != null) {
+                                                    if (dotIndex == 0) {
+                                                        activityName = pkg + activityName;
+                                                    }
+                                                    else {
+                                                        activityName = pkg + '.' + activityName;
+                                                    }
+                                                }
+                                            }
+                                            activitiesToCheck.add(activityName);
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return activitiesToCheck;
+    }
+
+    private static void visitIntent(@NonNull XmlContext context, @NonNull Element intent) {
         boolean actionView = hasActionView(intent);
         boolean browsable = isBrowsable(intent);
         boolean isHttp = false;
@@ -101,40 +474,36 @@
         boolean hasPath = false;
         boolean hasMimeType = false;
         Element firstData = null;
-        NodeList children = intent.getChildNodes();
-        for (int i = 0; i < children.getLength(); i++) {
-            Node child = children.item(i);
-            if (child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().equals(NODE_DATA)) {
-                Element data = (Element) child;
-                if (firstData == null) {
-                    firstData = data;
-                }
-                if (isHttpSchema(data)) {
-                    isHttp = true;
-                }
-                checkSingleData(context, data);
+        List<Element> children = extractChildrenByName(intent, NODE_DATA);
+        for (Element data : children) {
+            if (firstData == null) {
+                firstData = data;
+            }
+            if (isHttpSchema(data)) {
+                isHttp = true;
+            }
+            checkSingleData(context, data);
 
-                for (String name : PATH_ATTR_LIST) {
-                    if (data.hasAttributeNS(ANDROID_URI, name)) {
-                        hasPath = true;
-                    }
+            for (String name : PATH_ATTR_LIST) {
+                if (data.hasAttributeNS(ANDROID_URI, name)) {
+                    hasPath = true;
                 }
+            }
 
-                if (data.hasAttributeNS(ANDROID_URI, ATTR_SCHEME)) {
-                    hasScheme = true;
-                }
+            if (data.hasAttributeNS(ANDROID_URI, ATTR_SCHEME)) {
+                hasScheme = true;
+            }
 
-                if (data.hasAttributeNS(ANDROID_URI, ATTR_HOST)) {
-                    hasHost = true;
-                }
+            if (data.hasAttributeNS(ANDROID_URI, ATTR_HOST)) {
+                hasHost = true;
+            }
 
-                if (data.hasAttributeNS(ANDROID_URI, ATTRIBUTE_PORT)) {
-                    hasPort = true;
-                }
+            if (data.hasAttributeNS(ANDROID_URI, ATTRIBUTE_PORT)) {
+                hasPort = true;
+            }
 
-                if (data.hasAttributeNS(ANDROID_URI, ATTRIBUTE_MIME_TYPE)) {
-                    hasMimeType = true;
-                }
+            if (data.hasAttributeNS(ANDROID_URI, ATTRIBUTE_MIME_TYPE)) {
+                hasMimeType = true;
             }
         }
 
@@ -142,74 +511,87 @@
         // <scheme>://<host>:<port>[<path>|<pathPrefix>|<pathPattern>]
         // Each part of the URL should not have illegal character.
         if ((hasPath || hasHost || hasPort) && !hasScheme) {
-            context.report(ISSUE_ERROR, firstData, context.getLocation(firstData),
-                    "android:scheme missing");
+            context.report(ISSUE_URL_ERROR, firstData, context.getLocation(firstData),
+                    SCHEME_MISSING);
         }
 
         if ((hasPath || hasPort) && !hasHost) {
-            context.report(ISSUE_ERROR, firstData, context.getLocation(firstData),
-                    "android:host missing");
+            context.report(ISSUE_URL_ERROR, firstData, context.getLocation(firstData),
+                    HOST_MISSING);
         }
 
         if (actionView && browsable) {
             if (firstData == null) {
                 // If this activity is an ACTION_VIEW action with category BROWSABLE, but doesn't
                 // have data node, it may be a mistake and we will report error.
-                context.report(ISSUE_ERROR, intent, context.getLocation(intent),
-                        "Missing data node?");
+                context.report(ISSUE_URL_ERROR, intent, context.getLocation(intent),
+                        DATA_MISSING);
             } else if (!hasScheme && !hasMimeType) {
                 // If this activity is an action view, is browsable, but has neither a
                 // URL nor mimeType, it may be a mistake and we will report error.
-                context.report(ISSUE_ERROR, firstData, context.getLocation(firstData),
-                        "Missing URL for the intent filter?");
+                context.report(ISSUE_URL_ERROR, firstData, context.getLocation(firstData),
+                        URL_MISSING);
             }
         }
 
         // If this activity is an ACTION_VIEW action, has a http URL but doesn't have
         // BROWSABLE, it may be a mistake and and we will report warning.
         if (actionView && isHttp && !browsable) {
-            context.report(ISSUE_WARNING, intent, context.getLocation(intent),
-                    "Activity supporting ACTION_VIEW is not set as BROWSABLE");
+            context.report(ISSUE_APP_INDEXING, intent, context.getLocation(intent),
+                    NOT_BROWSABLE);
+        }
+
+        if (actionView && !hasScheme) {
+            context.report(ISSUE_APP_INDEXING, intent, context.getLocation(intent),
+                    "Missing URL");
         }
     }
 
-    private static boolean hasActionView(Element intent) {
-        NodeList children = intent.getChildNodes();
-        for (int i = 0; i < children.getLength(); i++) {
-            Node child = children.item(i);
-            if (child.getNodeType() == Node.ELEMENT_NODE &&
-                    child.getNodeName().equals(NODE_ACTION)) {
-                Element action = (Element) child;
-                if (action.hasAttributeNS(ANDROID_URI, ATTRIBUTE_NAME)) {
-                    Attr attr = action.getAttributeNodeNS(ANDROID_URI, ATTRIBUTE_NAME);
-                    if (attr.getValue().equals("android.intent.action.VIEW")) {
-                        return true;
-                    }
+    /**
+     * Check if the intent filter supports action view.
+     *
+     * @param intent the intent filter
+     * @return true if it does
+     */
+    private static boolean hasActionView(@NonNull Element intent) {
+        List<Element> children = extractChildrenByName(intent, NODE_ACTION);
+        for (Element action : children) {
+            if (action.hasAttributeNS(ANDROID_URI, ATTRIBUTE_NAME)) {
+                Attr attr = action.getAttributeNodeNS(ANDROID_URI, ATTRIBUTE_NAME);
+                if (attr.getValue().equals("android.intent.action.VIEW")) {
+                    return true;
                 }
             }
         }
         return false;
     }
 
-    private static boolean isBrowsable(Element intent) {
-        NodeList children = intent.getChildNodes();
-        for (int i = 0; i < children.getLength(); i++) {
-            Node child = children.item(i);
-            if (child.getNodeType() == Node.ELEMENT_NODE &&
-                    child.getNodeName().equals(NODE_CATEGORY)) {
-                Element e = (Element) child;
-                if (e.hasAttributeNS(ANDROID_URI, ATTRIBUTE_NAME)) {
-                    Attr attr = e.getAttributeNodeNS(ANDROID_URI, ATTRIBUTE_NAME);
-                    if (attr.getNodeValue().equals("android.intent.category.BROWSABLE")) {
-                        return true;
-                    }
+    /**
+     * Check if the intent filter is browsable.
+     *
+     * @param intent the intent filter
+     * @return true if it does
+     */
+    private static boolean isBrowsable(@NonNull Element intent) {
+        List<Element> children = extractChildrenByName(intent, NODE_CATEGORY);
+        for (Element e : children) {
+            if (e.hasAttributeNS(ANDROID_URI, ATTRIBUTE_NAME)) {
+                Attr attr = e.getAttributeNodeNS(ANDROID_URI, ATTRIBUTE_NAME);
+                if (attr.getNodeValue().equals("android.intent.category.BROWSABLE")) {
+                    return true;
                 }
             }
         }
         return false;
     }
 
-    private static boolean isHttpSchema(Element data) {
+    /**
+     * Check if the data node contains http schema
+     *
+     * @param data the data node
+     * @return true if it does
+     */
+    private static boolean isHttpSchema(@NonNull Element data) {
         if (data.hasAttributeNS(ANDROID_URI, ATTR_SCHEME)) {
             String value = data.getAttributeNodeNS(ANDROID_URI, ATTR_SCHEME).getValue();
             if (value.equalsIgnoreCase("http") || value.equalsIgnoreCase("https")) {
@@ -219,14 +601,14 @@
         return false;
     }
 
-    private static void checkSingleData(XmlContext context, Element data) {
+    private static void checkSingleData(@NonNull XmlContext context, @NonNull Element data) {
         // path, pathPrefix and pathPattern should starts with /.
         for (String name : PATH_ATTR_LIST) {
             if (data.hasAttributeNS(ANDROID_URI, name)) {
                 Attr attr = data.getAttributeNodeNS(ANDROID_URI, name);
                 String path = replaceUrlWithValue(context, attr.getValue());
                 if (!path.startsWith("/") && !path.startsWith(SdkConstants.PREFIX_RESOURCE_REF)) {
-                    context.report(ISSUE_ERROR, attr, context.getLocation(attr),
+                    context.report(ISSUE_URL_ERROR, attr, context.getLocation(attr),
                             "android:" + name + " attribute should start with '/', but it is : "
                                     + path);
                 }
@@ -240,8 +622,8 @@
                 String port = replaceUrlWithValue(context, attr.getValue());
                 Integer.parseInt(port);
             } catch (NumberFormatException e) {
-                context.report(ISSUE_ERROR, attr, context.getLocation(attr),
-                        "android:port is not a legal number");
+                context.report(ISSUE_URL_ERROR, attr, context.getLocation(attr),
+                        ILLEGAL_NUMBER);
             }
         }
 
@@ -252,7 +634,7 @@
             if (item.getNodeType() == Node.ATTRIBUTE_NODE) {
                 Attr attr = (Attr) attrs.item(i);
                 if (attr.getValue().isEmpty()) {
-                    context.report(ISSUE_ERROR, attr, context.getLocation(attr),
+                    context.report(ISSUE_URL_ERROR, attr, context.getLocation(attr),
                             attr.getName() + " cannot be empty");
                 }
             }
@@ -284,4 +666,50 @@
         }
         return resourceValue.getValue() == null ? str : resourceValue.getValue();
     }
+
+    /**
+     * If a method with a certain argument exists in the list of methods.
+     *
+     * @param argument The first argument of the method.
+     * @param list     The methods list.
+     * @return If such a method exists in the list.
+     */
+    private static boolean hasFirstArgument(Expression argument, List<MethodInvocation> list) {
+        for (MethodInvocation method : list) {
+            Expression argument1 = method.astArguments().first();
+            if (argument.toString().equals(argument1.toString())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * If a method with a certain operand exists in the list of methods.
+     *
+     * @param operand The operand of the method.
+     * @param list    The methods list.
+     * @return If such a method exists in the list.
+     */
+    private static boolean hasOperand(Expression operand, List<MethodInvocation> list) {
+        for (MethodInvocation method : list) {
+            Expression operand1 = method.astOperand();
+            if (operand.toString().equals(operand1.toString())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static List<Element> extractChildrenByName(@NonNull Element node,
+            @NonNull String name) {
+        List<Element> result = Lists.newArrayList();
+        List<Element> children = LintUtils.getChildren(node);
+        for (Element child : children) {
+            if (child.getNodeName().equals(name)) {
+                result.add(child);
+            }
+        }
+        return result;
+    }
 }
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppLinksAutoVerifyDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppLinksAutoVerifyDetector.java
new file mode 100644
index 0000000..7e29756
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppLinksAutoVerifyDetector.java
@@ -0,0 +1,421 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_HOST;
+import static com.android.SdkConstants.ATTR_SCHEME;
+import static com.android.SdkConstants.UTF_8;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_NAME;
+import static com.android.xml.AndroidManifest.NODE_ACTION;
+import static com.android.xml.AndroidManifest.NODE_CATEGORY;
+import static com.android.xml.AndroidManifest.NODE_DATA;
+import static com.android.xml.AndroidManifest.NODE_INTENT;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+
+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.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/**
+ * Check if the App Link which needs auto verification is correctly set.
+ */
+public class AppLinksAutoVerifyDetector extends Detector implements Detector.XmlScanner {
+
+    private static final Implementation IMPLEMENTATION = new Implementation(
+            AppLinksAutoVerifyDetector.class, Scope.MANIFEST_SCOPE);
+
+    public static final Issue ISSUE_ERROR = Issue.create(
+            "AppLinksAutoVerifyError", //$NON-NLS-1$
+            "App Links Auto Verification Failure",
+            "Ensures that app links are correctly set and associated with website.",
+            Category.CORRECTNESS, 5, Severity.ERROR, IMPLEMENTATION)
+            .addMoreInfo("https://g.co/appindexing/applinks").setEnabledByDefault(false);
+
+    public static final Issue ISSUE_WARNING = Issue.create(
+            "AppLinksAutoVerifyWarning", //$NON-NLS-1$
+            "Potential App Links Auto Verification Failure",
+            "Ensures that app links are correctly set and associated with website.",
+            Category.CORRECTNESS, 5, Severity.WARNING, IMPLEMENTATION)
+            .addMoreInfo("https://g.co/appindexing/applinks").setEnabledByDefault(false);
+
+    private static final String ATTRIBUTE_AUTO_VERIFY = "autoVerify";
+    private static final String JSON_RELATIVE_PATH = "/.well-known/assetlinks.json";
+
+    @VisibleForTesting
+    static final int STATUS_HTTP_CONNECT_FAIL = -1;
+    @VisibleForTesting
+    static final int STATUS_MALFORMED_URL = -2;
+    @VisibleForTesting
+    static final int STATUS_UNKNOWN_HOST = -3;
+    @VisibleForTesting
+    static final int STATUS_NOT_FOUND = -4;
+    @VisibleForTesting
+    static final int STATUS_WRONG_JSON_SYNTAX = -5;
+    @VisibleForTesting
+    static final int STATUS_JSON_PARSE_FAIL = -6;
+    @VisibleForTesting
+    static final int STATUS_HTTP_OK = 200;
+
+    /* Maps website host url to a future task which will send HTTP request to fetch the JSON file
+     * and also return the status code during the fetching process. */
+    private Map<String, Future<HttpResult>> mFutures = Maps.newHashMap();
+
+    /* Maps website host url to host attribute in AndroidManifest.xml. */
+    private Map<String, Attr> mJsonHost = Maps.newHashMap();
+
+    @Override
+    public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
+
+        // This check sends http request. Only done in batch mode.
+        if (!context.getScope().contains(Scope.ALL_JAVA_FILES)) {
+            return;
+        }
+
+        if (document.getDocumentElement() != null) {
+            List<Element> intents = getTags(document.getDocumentElement(), NODE_INTENT);
+            if (!needAutoVerification(intents)) {
+                return;
+            }
+
+            for (Element intent : intents) {
+                boolean actionView = hasNamedSubTag(intent, NODE_ACTION,
+                        "android.intent.action.VIEW");
+                boolean browsableCategory = hasNamedSubTag(intent, NODE_CATEGORY,
+                        "android.intent.category.BROWSABLE");
+                if (!actionView || !browsableCategory) {
+                    continue;
+                }
+                mJsonHost.putAll(getJsonUrl(intent));
+            }
+        }
+
+        Map<String, HttpResult> results = getJsonFileAsync();
+
+        String packageName = context.getProject().getPackage();
+        for (Map.Entry<String, HttpResult> result : results.entrySet()) {
+            if (result.getValue() == null) {
+                continue;
+            }
+            Attr host = mJsonHost.get(result.getKey());
+            String jsonPath = result.getKey() + JSON_RELATIVE_PATH;
+            switch (result.getValue().mStatus) {
+                case STATUS_HTTP_OK:
+                    List<String> packageNames = getPackageNameFromJson(
+                            result.getValue().mJsonFile);
+                    if (!packageNames.contains(packageName)) {
+                        context.report(ISSUE_ERROR, host, context.getLocation(host), String.format(
+                                "This host does not support app links to your app. Checks the Digital Asset Links JSON file: %s",
+                                jsonPath));
+                    }
+                    break;
+                case STATUS_HTTP_CONNECT_FAIL:
+                    context.report(ISSUE_WARNING, host, context.getLocation(host),
+                            String.format("Connection to Digital Asset Links JSON file %s fails",
+                                    jsonPath));
+                    break;
+                case STATUS_MALFORMED_URL:
+                    context.report(ISSUE_ERROR, host, context.getLocation(host), String.format(
+                            "Malformed URL of Digital Asset Links JSON file: %s. An unknown protocol is specified",
+                            jsonPath));
+                    break;
+                case STATUS_UNKNOWN_HOST:
+                    context.report(ISSUE_WARNING, host, context.getLocation(host), String.format(
+                            "Unknown host: %s. Check if the host exists, and check your network connection",
+                            result.getKey()));
+                    break;
+                case STATUS_NOT_FOUND:
+                    context.report(ISSUE_ERROR, host, context.getLocation(host), String.format(
+                            "Digital Asset Links JSON file %s is not found on the host", jsonPath));
+                    break;
+                case STATUS_WRONG_JSON_SYNTAX:
+                    context.report(ISSUE_ERROR, host, context.getLocation(host),
+                            String.format("%s has incorrect JSON syntax", jsonPath));
+                    break;
+                case STATUS_JSON_PARSE_FAIL:
+                    context.report(ISSUE_ERROR, host, context.getLocation(host),
+                            String.format("Parsing JSON file %s fails", jsonPath));
+                    break;
+                default:
+                    context.report(ISSUE_WARNING, host, context.getLocation(host), String.format(
+                            "HTTP request for Digital Asset Links JSON file %1$s fails. HTTP response code: %2$s",
+                            jsonPath, result.getValue().mStatus));
+            }
+        }
+    }
+
+    /**
+     * Gets all the tag elements with a specific tag name, within a parent tag element.
+     *
+     * @param element The parent tag element.
+     * @return List of tag elements found.
+     */
+    @NonNull
+    private static List<Element> getTags(@NonNull Element element, @NonNull String tagName) {
+        List<Element> tagList = Lists.newArrayList();
+        if (element.getTagName().equalsIgnoreCase(tagName)) {
+            tagList.add(element);
+        } else {
+            NodeList children = element.getChildNodes();
+            for (int i = 0; i < children.getLength(); i++) {
+                Node child = children.item(i);
+                if (child instanceof Element) {
+                    tagList.addAll(getTags((Element) child, tagName));
+                }
+            }
+        }
+        return tagList;
+    }
+
+    /**
+     * Checks if auto verification is needed. i.e. any intent tag element's autoVerify attribute is
+     * set to true.
+     *
+     * @param intents The intent tag elements.
+     * @return true if auto verification is needed.
+     */
+    private static boolean needAutoVerification(@NonNull List<Element> intents) {
+        for (Element intent : intents) {
+            if (intent.getAttributeNS(ANDROID_URI, ATTRIBUTE_AUTO_VERIFY).equals(SdkConstants.VALUE_TRUE)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Checks if the element has a sub tag with specific name and specific name attribute.
+     *
+     * @param element       The tag element.
+     * @param tagName       The name of the sub tag.
+     * @param nameAttrValue The value of the name attribute.
+     * @return If the element has such a sub tag.
+     */
+    private static boolean hasNamedSubTag(@NonNull Element element, @NonNull String tagName,
+            @NonNull String nameAttrValue) {
+        NodeList children = element.getElementsByTagName(tagName);
+        for (int i = 0; i < children.getLength(); i++) {
+            Element e = (Element) children.item(i);
+            if (e.getAttributeNS(ANDROID_URI, ATTRIBUTE_NAME).equals(nameAttrValue)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Gets the urls of all the host from which Digital Asset Links JSON files will be fetched.
+     *
+     * @param intent The intent tag element.
+     * @return List of JSON file urls.
+     */
+    @NonNull
+    private static Map<String, Attr> getJsonUrl(@NonNull Element intent) {
+        List<String> schemes = Lists.newArrayList();
+        List<Attr> hosts = Lists.newArrayList();
+        NodeList dataTags = intent.getElementsByTagName(NODE_DATA);
+        for (int k = 0; k < dataTags.getLength(); k++) {
+            Element dataTag = (Element) dataTags.item(k);
+            String scheme = dataTag.getAttributeNS(ANDROID_URI, ATTR_SCHEME);
+            if (scheme.equals("http") || scheme.equals("https")) {
+                schemes.add(scheme);
+            }
+            if (dataTag.hasAttributeNS(ANDROID_URI, ATTR_HOST)) {
+                hosts.add(dataTag.getAttributeNodeNS(ANDROID_URI, ATTR_HOST));
+            }
+        }
+        Map<String, Attr> urls = Maps.newHashMap();
+        for (String scheme : schemes) {
+            for (Attr host : hosts) {
+                urls.put(scheme + "://" + host.getValue(), host);
+            }
+        }
+        return urls;
+    }
+
+    /* Normally null. Used for testing. */
+    @Nullable
+    @VisibleForTesting
+    static Map<String, HttpResult> sMockData;
+
+    /**
+     * Gets all the Digital Asset Links JSON file asynchronously.
+     *
+     * @return The map between the host url and the HTTP result.
+     */
+    private Map<String, HttpResult> getJsonFileAsync() {
+        if (sMockData != null) {
+            return sMockData;
+        }
+
+        ExecutorService executorService = Executors.newCachedThreadPool();
+        for (final Map.Entry<String, Attr> url : mJsonHost.entrySet()) {
+            Future<HttpResult> future = executorService.submit(new Callable<HttpResult>() {
+                @Override
+                public HttpResult call() {
+                    return getJson(url.getKey() + JSON_RELATIVE_PATH);
+                }
+            });
+            mFutures.put(url.getKey(), future);
+        }
+        executorService.shutdown();
+
+        Map<String, HttpResult> jsons = Maps.newHashMap();
+        for (Map.Entry<String, Future<HttpResult>> future : mFutures.entrySet()) {
+            try {
+                jsons.put(future.getKey(), future.getValue().get());
+            } catch (Exception e) {
+                jsons.put(future.getKey(), null);
+            }
+        }
+        return jsons;
+    }
+
+    /**
+     * Gets the Digital Asset Links JSON file on the website host.
+     *
+     * @param url The URL of the host on which JSON file will be fetched.
+     */
+    @NonNull
+    private static HttpResult getJson(@NonNull String url) {
+        try {
+            URL urlObj = new URL(url);
+            HttpURLConnection connection = (HttpURLConnection) urlObj.openConnection();
+            if (connection == null) {
+                return new HttpResult(STATUS_HTTP_CONNECT_FAIL, null);
+            }
+            try {
+                InputStream inputStream = connection.getInputStream();
+                if (inputStream == null) {
+                    return new HttpResult(connection.getResponseCode(), null);
+                }
+                BufferedReader reader = new BufferedReader(
+                        new InputStreamReader(inputStream, UTF_8));
+                try {
+                    String line;
+                    StringBuilder response = new StringBuilder();
+                    while ((line = reader.readLine()) != null) {
+                        response.append(line);
+                        response.append('\n');
+                    }
+
+                    try {
+                        JsonElement jsonFile = new JsonParser().parse(response.toString());
+                        return new HttpResult(connection.getResponseCode(), jsonFile);
+                    } catch (JsonSyntaxException e) {
+                        return new HttpResult(STATUS_WRONG_JSON_SYNTAX, null);
+                    } catch (RuntimeException e) {
+                        return new HttpResult(STATUS_JSON_PARSE_FAIL, null);
+                    }
+                } finally {
+                    reader.close();
+                }
+            } finally {
+                connection.disconnect();
+            }
+        } catch (MalformedURLException e) {
+            return new HttpResult(STATUS_MALFORMED_URL, null);
+        } catch (UnknownHostException e) {
+            return new HttpResult(STATUS_UNKNOWN_HOST, null);
+        } catch (FileNotFoundException e) {
+            return new HttpResult(STATUS_NOT_FOUND, null);
+        } catch (IOException e) {
+            return new HttpResult(STATUS_HTTP_CONNECT_FAIL, null);
+        }
+    }
+
+    /**
+     * Gets the package names of all the apps from the Digital Asset Links JSON file.
+     *
+     * @param element The JsonElement of the json file.
+     * @return All the package names.
+     */
+    private static List<String> getPackageNameFromJson(JsonElement element) {
+        List<String> packageNames = Lists.newArrayList();
+        if (element instanceof JsonArray) {
+            JsonArray jsonArray = (JsonArray) element;
+            for (int i = 0; i < jsonArray.size(); i++) {
+                JsonElement app = jsonArray.get(i);
+                if (app instanceof JsonObject) {
+                    JsonObject target = ((JsonObject) app).getAsJsonObject("target");
+                    if (target != null) {
+                        // Checks namespace to ensure it is an app statement.
+                        JsonElement namespace = target.get("namespace");
+                        JsonElement packageName = target.get("package_name");
+                        if (namespace != null && namespace.getAsString().equals("android_app")
+                                && packageName != null) {
+                            packageNames.add(packageName.getAsString());
+                        }
+                    }
+                }
+            }
+        }
+        return packageNames;
+    }
+
+    /* For storing the result of getting Digital Asset Links Json File */
+    @VisibleForTesting
+    static final class HttpResult {
+
+        /* HTTP response code or others errors related to HTTP connection, JSON file parsing. */
+        private int mStatus;
+        private JsonElement mJsonFile;
+
+        @VisibleForTesting
+        HttpResult(int status, JsonElement jsonFile) {
+            mStatus = status;
+            mJsonFile = jsonFile;
+        }
+    }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/BadHostnameVerifierDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/BadHostnameVerifierDetector.java
new file mode 100644
index 0000000..2128896
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/BadHostnameVerifierDetector.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.tools.lint.client.api.JavaParser.TYPE_STRING;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ConstantEvaluator;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Detector.ClassScanner;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+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 java.util.Collections;
+import java.util.List;
+
+import lombok.ast.BooleanLiteral;
+import lombok.ast.ClassDeclaration;
+import lombok.ast.Expression;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+import lombok.ast.NormalTypeBody;
+import lombok.ast.Return;
+import lombok.ast.Throw;
+
+public class BadHostnameVerifierDetector extends Detector implements JavaScanner {
+
+    @SuppressWarnings("unchecked")
+    private static final Implementation IMPLEMENTATION =
+            new Implementation(BadHostnameVerifierDetector.class,
+                    Scope.JAVA_FILE_SCOPE);
+
+    public static final Issue ISSUE = Issue.create("BadHostnameVerifier",
+            "Insecure HostnameVerifier",
+            "This check looks for implementations of `HostnameVerifier` " +
+            "whose `verify` method always returns true (thus trusting any hostname) " +
+            "which could result in insecure network traffic caused by trusting arbitrary " +
+            "hostnames in TLS/SSL certificates presented by peers.",
+            Category.SECURITY,
+            6,
+            Severity.WARNING,
+            IMPLEMENTATION);
+
+    // ---- Implements JavaScanner ----
+
+    @Nullable
+    @Override
+    public List<String> applicableSuperClasses() {
+        return Collections.singletonList("javax.net.ssl.HostnameVerifier");
+    }
+
+    @Override
+    public void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration node,
+            @NonNull Node declarationOrAnonymous, @NonNull ResolvedClass cls) {
+        NormalTypeBody body;
+        if (declarationOrAnonymous instanceof NormalTypeBody) {
+            body = (NormalTypeBody) declarationOrAnonymous;
+        } else if (node != null) {
+            body = node.astBody();
+        } else {
+            return;
+        }
+
+        for (Node member : body.astMembers()) {
+            if (member instanceof MethodDeclaration) {
+                MethodDeclaration declaration = (MethodDeclaration)member;
+                if ("verify".equals(declaration.astMethodName().astValue())
+                        && declaration.astParameters().size() == 2) {
+                    ResolvedNode resolved = context.resolve(declaration);
+                    if (resolved instanceof ResolvedMethod) {
+                        ResolvedMethod method = (ResolvedMethod) resolved;
+                        if (method.getArgumentCount() == 2
+                                && method.getArgumentType(0).matchesName(TYPE_STRING)
+                                && method.getArgumentType(1).matchesName("javax.net.ssl.SSLSession")) {
+
+                            ComplexVisitor visitor = new ComplexVisitor(context);
+                            declaration.accept(visitor);
+                            if (visitor.isComplex()) {
+                                return;
+                            }
+
+                            Location location = context.getNameLocation(declaration);
+                            String message = String.format("`%1$s` always returns `true`, which " +
+                                    "could cause insecure network traffic due to trusting "
+                                    + "TLS/SSL server certificates for wrong hostnames",
+                                    method.getName());
+                            context.report(ISSUE, location, message);
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private static class ComplexVisitor extends ForwardingAstVisitor {
+        @SuppressWarnings("unused")
+        private final JavaContext mContext;
+        private boolean mComplex;
+
+        public ComplexVisitor(JavaContext context) {
+            mContext = context;
+        }
+
+        @Override
+        public boolean visitThrow(Throw node) {
+            mComplex = true;
+            return super.visitThrow(node);
+        }
+
+        @Override
+        public boolean visitMethodInvocation(MethodInvocation node) {
+            // TODO: Ignore certain known safe methods, e.g. Logging etc
+            mComplex = true;
+            return super.visitMethodInvocation(node);
+        }
+
+        @Override
+        public boolean visitReturn(Return node) {
+            Expression argument = node.astValue();
+            if (argument != null) {
+                // TODO: Only do this if certain that there isn't some intermediate
+                // assignment, as exposed by the unit test
+                //Object value = ConstantEvaluator.evaluate(mContext, argument);
+                //if (Boolean.TRUE.equals(value)) {
+                if (argument instanceof BooleanLiteral &&
+                        Boolean.TRUE.equals(((BooleanLiteral)argument).astValue())) {
+                    mComplex = false;
+                } else {
+                    mComplex = true; // "return false" or some complicated logic
+                }
+            }
+            return super.visitReturn(node);
+        }
+
+        public boolean isComplex() {
+            return mComplex;
+        }
+    }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/BuiltinIssueRegistry.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/BuiltinIssueRegistry.java
index 4e50397..039b70a 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/BuiltinIssueRegistry.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/BuiltinIssueRegistry.java
@@ -30,7 +30,8 @@
 /** Registry which provides a list of checks to be performed on an Android project */
 public class BuiltinIssueRegistry extends IssueRegistry {
     private static final List<Issue> sIssues;
-    static final int INITIAL_CAPACITY = 222;
+
+    static final int INITIAL_CAPACITY = 253;
 
     static {
         List<Issue> issues = new ArrayList<Issue>(INITIAL_CAPACITY);
@@ -38,9 +39,15 @@
         issues.add(AccessibilityDetector.ISSUE);
         issues.add(AddJavascriptInterfaceDetector.ISSUE);
         issues.add(AlarmDetector.ISSUE);
+        issues.add(AllowAllHostnameVerifierDetector.ISSUE);
         issues.add(AlwaysShowActionDetector.ISSUE);
+        issues.add(AndroidAutoDetector.INVALID_USES_TAG_ISSUE);
+        issues.add(AndroidAutoDetector.MISSING_INTENT_FILTER_FOR_MEDIA_SEARCH);
+        issues.add(AndroidAutoDetector.MISSING_MEDIA_BROWSER_SERVICE_ACTION_ISSUE);
+        issues.add(AndroidAutoDetector.MISSING_ON_PLAY_FROM_SEARCH);
         issues.add(AnnotationDetector.FLAG_STYLE);
         issues.add(AnnotationDetector.INSIDE_METHOD);
+        issues.add(AnnotationDetector.SWITCH_TYPE_DEF);
         issues.add(AnnotationDetector.UNIQUE);
         issues.add(ApiDetector.INLINED);
         issues.add(ApiDetector.OVERRIDE);
@@ -48,10 +55,14 @@
         issues.add(ApiDetector.UNUSED);
         issues.add(AppCompatCallDetector.ISSUE);
         issues.add(AppCompatResourceDetector.ISSUE);
-        issues.add(AppIndexingApiDetector.ISSUE_ERROR);
-        issues.add(AppIndexingApiDetector.ISSUE_WARNING);
+        issues.add(AppIndexingApiDetector.ISSUE_APP_INDEXING_API);
+        issues.add(AppIndexingApiDetector.ISSUE_URL_ERROR);
+        issues.add(AppIndexingApiDetector.ISSUE_APP_INDEXING);
+        issues.add(AppLinksAutoVerifyDetector.ISSUE_ERROR);
+        issues.add(AppLinksAutoVerifyDetector.ISSUE_WARNING);
         issues.add(ArraySizeDetector.INCONSISTENT);
         issues.add(AssertDetector.ISSUE);
+        issues.add(BadHostnameVerifierDetector.ISSUE);
         issues.add(ButtonDetector.BACK_BUTTON);
         issues.add(ButtonDetector.CASE);
         issues.add(ButtonDetector.ORDER);
@@ -69,6 +80,7 @@
         issues.add(CustomViewDetector.ISSUE);
         issues.add(CutPasteDetector.ISSUE);
         issues.add(DateFormatDetector.DATE_FORMAT);
+        issues.add(SetTextDetector.SET_TEXT_I18N);
         issues.add(DeprecationDetector.ISSUE);
         issues.add(DetectMissingPrefix.MISSING_NAMESPACE);
         issues.add(DosLineEndingDetector.ISSUE);
@@ -76,6 +88,8 @@
         issues.add(DuplicateIdDetector.WITHIN_LAYOUT);
         issues.add(DuplicateResourceDetector.ISSUE);
         issues.add(DuplicateResourceDetector.TYPE_MISMATCH);
+        issues.add(UnsafeNativeCodeDetector.LOAD);
+        issues.add(UnsafeNativeCodeDetector.UNSAFE_NATIVE_CODE_LOCATION);
         issues.add(ExtraTextDetector.ISSUE);
         issues.add(FieldGetterDetector.ISSUE);
         issues.add(FullBackupContentDetector.ISSUE);
@@ -87,6 +101,7 @@
         issues.add(GradleDetector.DEPRECATED);
         issues.add(GradleDetector.GRADLE_GETTER);
         issues.add(GradleDetector.IDE_SUPPORT);
+        issues.add(GradleDetector.NOT_INTERPOLATED);
         issues.add(GradleDetector.PATH);
         issues.add(GradleDetector.PLUS);
         issues.add(GradleDetector.STRING_INTEGER);
@@ -116,6 +131,7 @@
         issues.add(InefficientWeightDetector.NESTED_WEIGHTS);
         issues.add(InefficientWeightDetector.ORIENTATION);
         issues.add(InefficientWeightDetector.WRONG_0DP);
+        issues.add(TrustAllX509TrustManagerDetector.ISSUE);
         issues.add(InvalidPackageDetector.ISSUE);
         issues.add(JavaPerformanceDetector.PAINT_ALLOC);
         issues.add(JavaPerformanceDetector.USE_SPARSE_ARRAY);
@@ -148,6 +164,7 @@
         issues.add(ManifestDetector.UNIQUE_PERMISSION);
         issues.add(ManifestDetector.USES_SDK);
         issues.add(ManifestDetector.WRONG_PARENT);
+        issues.add(ManifestResourceDetector.ISSUE);
         issues.add(ManifestTypoDetector.ISSUE);
         issues.add(MathDetector.ISSUE);
         issues.add(MergeRootFrameLayoutDetector.ISSUE);
@@ -183,12 +200,15 @@
         issues.add(PxUsageDetector.IN_MM_ISSUE);
         issues.add(PxUsageDetector.PX_ISSUE);
         issues.add(PxUsageDetector.SMALL_SP_ISSUE);
+        issues.add(ReadParcelableDetector.ISSUE);
+        issues.add(RecyclerViewDetector.ISSUE);
         issues.add(RegistrationDetector.ISSUE);
         issues.add(RelativeOverlapDetector.ISSUE);
         issues.add(RequiredAttributeDetector.ISSUE);
         issues.add(ResourceCycleDetector.CRASH);
         issues.add(ResourceCycleDetector.CYCLE);
         issues.add(ResourcePrefixDetector.ISSUE);
+        issues.add(RestrictionsDetector.ISSUE);
         issues.add(RtlDetector.COMPAT);
         issues.add(RtlDetector.ENABLED);
         issues.add(RtlDetector.SYMMETRY);
@@ -200,6 +220,8 @@
         issues.add(SecurityDetector.EXPORTED_PROVIDER);
         issues.add(SecurityDetector.EXPORTED_RECEIVER);
         issues.add(SecurityDetector.EXPORTED_SERVICE);
+        issues.add(SecurityDetector.SET_READABLE);
+        issues.add(SecurityDetector.SET_WRITABLE);
         issues.add(SecurityDetector.OPEN_PROVIDER);
         issues.add(SecurityDetector.WORLD_READABLE);
         issues.add(SecurityDetector.WORLD_WRITEABLE);
@@ -208,6 +230,8 @@
         issues.add(SharedPrefsDetector.ISSUE);
         issues.add(SignatureOrSystemDetector.ISSUE);
         issues.add(SQLiteDetector.ISSUE);
+        issues.add(SslCertificateSocketFactoryDetector.CREATE_SOCKET);
+        issues.add(SslCertificateSocketFactoryDetector.GET_INSECURE);
         issues.add(StateListDetector.ISSUE);
         issues.add(StringFormatDetector.ARG_COUNT);
         issues.add(StringFormatDetector.ARG_TYPES);
@@ -231,18 +255,26 @@
         issues.add(TooManyViewsDetector.TOO_MANY);
         issues.add(TranslationDetector.EXTRA);
         issues.add(TranslationDetector.MISSING);
+        issues.add(AndroidTvDetector.MISSING_LEANBACK_LAUNCHER);
+        issues.add(AndroidTvDetector.MISSING_LEANBACK_SUPPORT);
+        issues.add(AndroidTvDetector.PERMISSION_IMPLIES_UNSUPPORTED_HARDWARE);
+        issues.add(AndroidTvDetector.UNSUPPORTED_TV_HARDWARE);
+        issues.add(AndroidTvDetector.MISSING_BANNER);
         issues.add(TypoDetector.ISSUE);
         issues.add(TypographyDetector.DASHES);
         issues.add(TypographyDetector.ELLIPSIS);
         issues.add(TypographyDetector.FRACTIONS);
         issues.add(TypographyDetector.OTHER);
         issues.add(TypographyDetector.QUOTES);
+        issues.add(UnsafeBroadcastReceiverDetector.ACTION_STRING);
+        issues.add(UnsafeBroadcastReceiverDetector.BROADCAST_SMS);
         issues.add(UnusedResourceDetector.ISSUE);
         issues.add(UnusedResourceDetector.ISSUE_IDS);
         issues.add(UseCompoundDrawableDetector.ISSUE);
         issues.add(UselessViewDetector.USELESS_LEAF);
         issues.add(UselessViewDetector.USELESS_PARENT);
         issues.add(Utf8Detector.ISSUE);
+        issues.add(VectorDetector.ISSUE);
         issues.add(ViewConstructorDetector.ISSUE);
         issues.add(ViewHolderDetector.ISSUE);
         issues.add(ViewTagDetector.ISSUE);
@@ -286,11 +318,11 @@
             }
 
             if (scope.contains(Scope.JAVA_FILE)) {
-                initialSize += 55;
+                initialSize += 72;
             } else if (scope.contains(Scope.CLASS_FILE)) {
                 initialSize += 15;
             } else if (scope.contains(Scope.MANIFEST)) {
-                initialSize += 30;
+                initialSize += 37;
             } else if (scope.contains(Scope.GRADLE_FILE)) {
                 initialSize += 5;
             }
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ButtonDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ButtonDetector.java
index 568293d..0b7c1e4 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ButtonDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ButtonDetector.java
@@ -37,6 +37,7 @@
 import static com.android.SdkConstants.VALUE_SELECTABLE_ITEM_BACKGROUND;
 import static com.android.SdkConstants.VALUE_TRUE;
 import static com.android.SdkConstants.VALUE_VERTICAL;
+import static com.android.tools.lint.checks.RequiredAttributeDetector.PERCENT_RELATIVE_LAYOUT;
 
 import com.android.SdkConstants;
 import com.android.annotations.NonNull;
@@ -723,7 +724,7 @@
             }
 
             return false;
-        } else if (layout.equals(RELATIVE_LAYOUT)) {
+        } else if (layout.equals(RELATIVE_LAYOUT) || layout.equals(PERCENT_RELATIVE_LAYOUT)) {
             // In RelativeLayouts, look for attachments which look like a clear sign
             // that the OK or Cancel buttons are out of order:
             //   -- a left attachment on a Cancel button (where the left attachment
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CallSuperDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CallSuperDetector.java
index 6dc6e67..42df6d9 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CallSuperDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CallSuperDetector.java
@@ -120,7 +120,7 @@
                 String methodName = method.getName();
                 String message = "Overriding method should call `super."
                         + methodName + "`";
-                Location location = context.getLocation(declaration.astMethodName());
+                Location location = context.getNameLocation(declaration);
                 context.report(ISSUE, declaration, location, message);
             }
         }
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CipherGetInstanceDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CipherGetInstanceDetector.java
index 3c53650..5eb91cd 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CipherGetInstanceDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CipherGetInstanceDetector.java
@@ -18,7 +18,9 @@
 
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.JavaParser.ResolvedField;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
 import com.android.tools.lint.detector.api.Category;
 import com.android.tools.lint.detector.api.Detector;
 import com.android.tools.lint.detector.api.Implementation;
@@ -81,11 +83,11 @@
     public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
             @NonNull MethodInvocation node) {
         // Ignore if the method doesn't fit our description.
-        JavaParser.ResolvedNode resolved = context.resolve(node);
-        if (!(resolved instanceof JavaParser.ResolvedMethod)) {
+        ResolvedNode resolved = context.resolve(node);
+        if (!(resolved instanceof ResolvedMethod)) {
             return;
         }
-        JavaParser.ResolvedMethod method = (JavaParser.ResolvedMethod) resolved;
+        ResolvedMethod method = (ResolvedMethod) resolved;
         if (!method.getContainingClass().isSubclassOf(CIPHER, false)) {
             return;
         }
@@ -97,9 +99,9 @@
                 String parameter = argument.astValue();
                 checkParameter(context, node, argument, parameter, false);
             } else {
-                JavaParser.ResolvedNode resolve = context.resolve(expression);
-                if (resolve instanceof JavaParser.ResolvedField) {
-                    JavaParser.ResolvedField field = (JavaParser.ResolvedField) resolve;
+                ResolvedNode resolve = context.resolve(expression);
+                if (resolve instanceof ResolvedField) {
+                    ResolvedField field = (ResolvedField) resolve;
                     Object value = field.getValue();
                     if (value instanceof String) {
                         checkParameter(context, node, expression, (String)value, true);
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CleanupDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CleanupDetector.java
index b28df29..ba32081 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CleanupDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CleanupDetector.java
@@ -18,6 +18,7 @@
 
 import static com.android.SdkConstants.CLASS_CONTEXT;
 
+import com.android.SdkConstants;
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
 import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
@@ -109,7 +110,6 @@
     private static final String CLOSE = "close";                                      //$NON-NLS-1$
 
     private static final String MOTION_EVENT_CLS = "android.view.MotionEvent";        //$NON-NLS-1$
-    private static final String RESOURCES_CLS = "android.content.res.Resources";      //$NON-NLS-1$
     private static final String PARCEL_CLS = "android.os.Parcel";                     //$NON-NLS-1$
     private static final String TYPED_ARRAY_CLS = "android.content.res.TypedArray";   //$NON-NLS-1$
     private static final String VELOCITY_TRACKER_CLS = "android.view.VelocityTracker";//$NON-NLS-1$
@@ -208,7 +208,7 @@
                 || OBTAIN_ATTRIBUTES.equals(name)
                 || OBTAIN_TYPED_ARRAY.equals(name)) &&
                 (containingClass.isSubclassOf(CLASS_CONTEXT, false) ||
-                        containingClass.isSubclassOf(RESOURCES_CLS, false))) {
+                        containingClass.isSubclassOf(SdkConstants.CLASS_RESOURCES, false))) {
             TypeDescriptor returnType = method.getReturnType();
             if (returnType != null && returnType.matchesSignature(TYPED_ARRAY_CLS)) {
                 checkRecycled(context, node, TYPED_ARRAY_CLS, RECYCLE);
@@ -244,7 +244,11 @@
 
     private static void checkRecycled(@NonNull final JavaContext context, @NonNull Node node,
             @NonNull final String recycleType, @NonNull final String recycleName) {
-        ResolvedVariable boundVariable = getVariable(context, node);
+        Node variableNode = getVariableNode(node);
+        if (variableNode == null) {
+            return;
+        }
+        ResolvedVariable boundVariable = getResolvedVariable(context, variableNode);
         if (boundVariable == null) {
             return;
         }
@@ -253,8 +257,7 @@
         if (method == null) {
             return;
         }
-
-        FinishVisitor visitor = new FinishVisitor(context, boundVariable) {
+        FinishVisitor visitor = new FinishVisitor(context, variableNode, boundVariable) {
             @Override
             protected boolean isCleanupCall(@NonNull MethodInvocation call) {
                 String methodName = call.astName().astValue();
@@ -305,7 +308,9 @@
     private static boolean checkTransactionCommits(@NonNull JavaContext context,
             @NonNull MethodInvocation node) {
         if (isBeginTransaction(context, node)) {
-            ResolvedVariable boundVariable = getVariable(context, node);
+            Node variableNode = getVariableNode(node);
+            ResolvedVariable boundVariable = variableNode != null
+                    ? getResolvedVariable(context, variableNode) : null;
             if (boundVariable == null && isCommittedInChainedCalls(context, node)) {
                 return true;
             }
@@ -316,7 +321,8 @@
                     return true;
                 }
 
-                FinishVisitor commitVisitor = new FinishVisitor(context, boundVariable) {
+                FinishVisitor commitVisitor = new FinishVisitor(context, variableNode,
+                        boundVariable) {
                     @Override
                     protected boolean isCleanupCall(@NonNull MethodInvocation call) {
                         if (isTransactionCommitMethodCall(mContext, call)) {
@@ -400,7 +406,8 @@
         return (COMMIT.equals(methodName) || COMMIT_ALLOWING_LOSS.equals(methodName)) &&
                 isMethodOnFragmentClass(context, call,
                         FRAGMENT_TRANSACTION_CLS,
-                        FRAGMENT_TRANSACTION_V4_CLS);
+                        FRAGMENT_TRANSACTION_V4_CLS,
+                        true);
     }
 
     private static boolean isShowFragmentMethodCall(@NonNull JavaContext context,
@@ -408,42 +415,50 @@
         String methodName = call.astName().astValue();
         return SHOW.equals(methodName)
                 && isMethodOnFragmentClass(context, call,
-                DIALOG_FRAGMENT, DIALOG_V4_FRAGMENT);
+                DIALOG_FRAGMENT, DIALOG_V4_FRAGMENT, true);
     }
 
     private static boolean isMethodOnFragmentClass(
             @NonNull JavaContext context,
             @NonNull MethodInvocation call,
             @NonNull String fragmentClass,
-            @NonNull String v4FragmentClass) {
+            @NonNull String v4FragmentClass,
+            boolean returnForUnresolved) {
         ResolvedNode resolved = context.resolve(call);
         if (resolved instanceof ResolvedMethod) {
             ResolvedClass containingClass = ((ResolvedMethod) resolved).getContainingClass();
             return containingClass.isSubclassOf(fragmentClass, false) ||
                     containingClass.isSubclassOf(v4FragmentClass, false);
+        } else if (resolved == null) {
+            // If we *can't* resolve the method call, caller can decide
+            // whether to consider the method called or not
+            return returnForUnresolved;
         }
 
         return false;
     }
 
     @Nullable
-    public static ResolvedVariable getVariable(@NonNull JavaContext context,
-            @NonNull Node expression) {
+    public static Node getVariableNode(@NonNull Node expression) {
         Node parent = expression.getParent();
         if (parent instanceof BinaryExpression) {
             BinaryExpression binaryExpression = (BinaryExpression) parent;
             if (binaryExpression.astOperator() == BinaryOperator.ASSIGN) {
-                Expression lhs = binaryExpression.astLeft();
-                ResolvedNode resolved = context.resolve(lhs);
-                if (resolved instanceof ResolvedVariable) {
-                    return (ResolvedVariable) resolved;
-                }
+                return binaryExpression.astLeft();
             }
         } else if (parent instanceof VariableDefinitionEntry) {
-            ResolvedNode resolved = context.resolve(parent);
-            if (resolved instanceof ResolvedVariable) {
-                return (ResolvedVariable) resolved;
-            }
+            return parent;
+        }
+
+        return null;
+    }
+
+    @Nullable
+    public static ResolvedVariable getResolvedVariable(@NonNull JavaContext context,
+            @NonNull Node variable) {
+        ResolvedNode resolved = context.resolve(variable);
+        if (resolved instanceof ResolvedVariable) {
+            return (ResolvedVariable) resolved;
         }
 
         return null;
@@ -478,11 +493,15 @@
     private abstract static class FinishVisitor extends ForwardingAstVisitor {
         protected final JavaContext mContext;
         protected final List<ResolvedVariable> mVariables;
+        private final Node mOriginalVariableNode;
+
         private boolean mContainsCleanup;
         private boolean mEscapes;
 
-        public FinishVisitor(JavaContext context, @NonNull ResolvedVariable variable) {
+        public FinishVisitor(JavaContext context, @NonNull Node variableNode,
+                @NonNull ResolvedVariable variable) {
             mContext = context;
+            mOriginalVariableNode = variableNode;
             mVariables = Lists.newArrayList(variable);
         }
 
@@ -567,10 +586,15 @@
         public boolean visitBinaryExpression(BinaryExpression node) {
             if (node.astOperator() == BinaryOperator.ASSIGN) {
                 Expression rhs = node.astRight();
+                // TEMPORARILY DISABLED; see testDatabaseCursorReassignment
+                // This can result in some false positives right now. Play it
+                // safe instead.
+                boolean clearLhs = false;
                 if (rhs instanceof VariableReference) {
                     ResolvedNode resolved = mContext.resolve(rhs);
                     //noinspection SuspiciousMethodCalls
                     if (resolved != null && mVariables.contains(resolved)) {
+                        clearLhs = false;
                         ResolvedNode resolvedLhs = mContext.resolve(node.astLeft());
                         if (resolvedLhs instanceof ResolvedVariable) {
                             ResolvedVariable variable = (ResolvedVariable) resolvedLhs;
@@ -580,6 +604,18 @@
                         }
                     }
                 }
+
+                if (clearLhs) {
+                    // If we reassign one of the variables, clear it out
+                    Expression lhs = node.astLeft();
+                    ResolvedNode resolved = mContext.resolve(lhs);
+                    //noinspection SuspiciousMethodCalls
+                    if (resolved != null && !lhs.equals(mOriginalVariableNode)
+                            && mVariables.contains(resolved)) {
+                        //noinspection SuspiciousMethodCalls
+                        mVariables.remove(resolved);
+                    }
+                }
             }
             return super.visitBinaryExpression(node);
         }
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ControlFlowGraph.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ControlFlowGraph.java
index 6c11ec1..3a907de 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ControlFlowGraph.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ControlFlowGraph.java
@@ -284,7 +284,10 @@
         AbstractInsnNode curr = start;
         Node handlerNode = getNode(tcb.handler);
         while (curr != end && curr != null) {
-            if (curr.getType() == AbstractInsnNode.METHOD_INSN) {
+            // A method can throw can exception, or a throw instruction directly
+            if (curr.getType() == AbstractInsnNode.METHOD_INSN
+                    || (curr.getType() == AbstractInsnNode.INSN
+                    && curr.getOpcode() == Opcodes.ATHROW)) {
                 // Method call; add exception edge to handler
                 if (tcb.type == null) {
                     // finally block: not an exception path
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DeprecationDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DeprecationDetector.java
index fec87c8..299cc68 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DeprecationDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DeprecationDetector.java
@@ -28,6 +28,8 @@
 import static com.android.SdkConstants.ATTR_PHONE_NUMBER;
 import static com.android.SdkConstants.ATTR_SINGLE_LINE;
 import static com.android.SdkConstants.EDIT_TEXT;
+import static com.android.SdkConstants.TAG_USES_PERMISSION_SDK_23;
+import static com.android.SdkConstants.TAG_USES_PERMISSION_SDK_M;
 import static com.android.SdkConstants.VALUE_TRUE;
 
 import com.android.annotations.NonNull;
@@ -52,6 +54,7 @@
  */
 public class DeprecationDetector extends LayoutDetector {
     /** Usage of deprecated views or attributes */
+    @SuppressWarnings("unchecked")
     public static final Issue ISSUE = Issue.create(
             "Deprecated", //$NON-NLS-1$
             "Using deprecated resources",
@@ -62,6 +65,8 @@
             Severity.WARNING,
             new Implementation(
                     DeprecationDetector.class,
+                    Scope.MANIFEST_AND_RESOURCE_SCOPE,
+                    Scope.MANIFEST_SCOPE,
                     Scope.RESOURCE_FILE_SCOPE));
 
     /** Constructs a new {@link DeprecationDetector} */
@@ -76,8 +81,9 @@
 
     @Override
     public Collection<String> getApplicableElements() {
-        return Collections.singletonList(
-                ABSOLUTE_LAYOUT
+        return Arrays.asList(
+                ABSOLUTE_LAYOUT,
+                TAG_USES_PERMISSION_SDK_M
         );
     }
 
@@ -126,8 +132,13 @@
 
     @Override
     public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+        String tagName = element.getTagName();
+        String message = String.format("`%1$s` is deprecated", tagName);
+        if (TAG_USES_PERMISSION_SDK_M.equals(tagName)) {
+            message += ": Use `" + TAG_USES_PERMISSION_SDK_23 + " instead";
+        }
         context.report(ISSUE, element, context.getLocation(element),
-                String.format("`%1$s` is deprecated", element.getTagName()));
+                message);
     }
 
     @Override
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DetectMissingPrefix.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DetectMissingPrefix.java
index c7271fc..358ff36 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DetectMissingPrefix.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DetectMissingPrefix.java
@@ -26,6 +26,7 @@
 import static com.android.SdkConstants.ATTR_PACKAGE;
 import static com.android.SdkConstants.ATTR_STYLE;
 import static com.android.SdkConstants.AUTO_URI;
+import static com.android.SdkConstants.IMAGE_VIEW;
 import static com.android.SdkConstants.TAG_LAYOUT;
 import static com.android.SdkConstants.TOOLS_URI;
 import static com.android.SdkConstants.VIEW_TAG;
@@ -173,6 +174,12 @@
                 if (TAG_LAYOUT.equals(root.getTagName())) {
                     return;
                 }
+
+                // Appcompat now encourages decorating standard views (like ImageView and
+                // ImageButton) with srcCompat in the app namespace
+                if (attribute.getLocalName().equals("srcCompat")) {
+                    return;
+                }
             }
 
             context.report(MISSING_NAMESPACE, attribute,
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DosLineEndingDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DosLineEndingDetector.java
index 7fbc3f3..b0c83fc 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DosLineEndingDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DosLineEndingDetector.java
@@ -52,6 +52,7 @@
             new Implementation(
                     DosLineEndingDetector.class,
                     Scope.RESOURCE_FILE_SCOPE))
+            .setEnabledByDefault(false) // This check is probably not relevant for most users anymore
             .addMoreInfo("https://bugs.eclipse.org/bugs/show_bug.cgi?id=375421"); //$NON-NLS-1$
 
      /** Constructs a new {@link DosLineEndingDetector} */
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DuplicateResourceDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DuplicateResourceDetector.java
index ee3ca8b..fcbf9e3 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DuplicateResourceDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DuplicateResourceDetector.java
@@ -157,6 +157,17 @@
         ResourceType type = ResourceType.getEnum(typeString);
         if (type == null) {
             return;
+        } else if (type == ResourceType.PUBLIC) {
+            // We can't easily check public declarations since it's not as simple as
+            // just looking up the type attribute and switching the ResourceType to it;
+            // that would treat <dimen name="foo"> and <public name="foo" type="dimen">
+            // as an actual duplicate name. A simple way to do it would be to change the
+            // name, e.g. by prefixing "public-" to it, but that would require restructuring
+            // the code a bit, and we'd need to remove it when displaying the conflicts --
+            // and most importantly, there really isn't a good reason to do it; a public
+            // declaration has no value, so there's no chance of creating conflicting
+            // definitions.
+            return;
         }
 
         if (type == ResourceType.ATTR
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FragmentDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FragmentDetector.java
index 40e0a73..bd01fbb 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FragmentDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FragmentDetector.java
@@ -39,6 +39,7 @@
 
 import lombok.ast.ClassDeclaration;
 import lombok.ast.ConstructorDeclaration;
+import lombok.ast.ConstructorInvocation;
 import lombok.ast.Node;
 import lombok.ast.NormalTypeBody;
 import lombok.ast.TypeMember;
@@ -98,6 +99,16 @@
     public void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration node,
             @NonNull Node declarationOrAnonymous, @NonNull ResolvedClass cls) {
         if (node == null) {
+            String message = "Fragments should be static such that they can be re-instantiated by " +
+                             "the system, and anonymous classes are not static";
+            Node locationNode = declarationOrAnonymous;
+            if (locationNode.getParent() instanceof ConstructorInvocation) {
+                ConstructorInvocation constructor = (ConstructorInvocation)locationNode.getParent();
+                if (constructor.astTypeReference() != null) {
+                    locationNode = constructor.astTypeReference();
+                }
+            }
+            context.report(ISSUE, declarationOrAnonymous, context.getLocation(locationNode), message);
             return;
         }
 
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FullBackupContentDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FullBackupContentDetector.java
index 3b05589..6780de7 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FullBackupContentDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FullBackupContentDetector.java
@@ -61,7 +61,8 @@
             Severity.FATAL,
             new Implementation(
                     FullBackupContentDetector.class,
-                    Scope.RESOURCE_FILE_SCOPE));
+                    Scope.RESOURCE_FILE_SCOPE))
+            .addMoreInfo("http://android-developers.blogspot.com/2015/07/auto-backup-for-apps-made-simple.html");
 
     @SuppressWarnings("SpellCheckingInspection")
     private static final String DOMAIN_SHARED_PREF = "sharedpref";
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GradleDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GradleDetector.java
index b99b81c..243b780 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GradleDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GradleDetector.java
@@ -16,12 +16,12 @@
 package com.android.tools.lint.checks;
 
 import static com.android.SdkConstants.FD_BUILD_TOOLS;
-import static com.android.SdkConstants.FN_BUILD_GRADLE;
 import static com.android.SdkConstants.GRADLE_PLUGIN_MINIMUM_VERSION;
 import static com.android.SdkConstants.GRADLE_PLUGIN_RECOMMENDED_VERSION;
 import static com.android.ide.common.repository.GradleCoordinate.COMPARE_PLUS_HIGHER;
 import static com.android.tools.lint.checks.ManifestDetector.TARGET_NEWER;
 import static com.android.tools.lint.detector.api.LintUtils.findSubstring;
+import static com.android.tools.lint.detector.api.LintUtils.guessGradleLocation;
 import static com.google.common.base.Charsets.UTF_8;
 
 import com.android.SdkConstants;
@@ -107,7 +107,7 @@
 
     /** Incompatible Android Gradle plugin */
     public static final Issue GRADLE_PLUGIN_COMPATIBILITY = Issue.create(
-            "AndroidGradlePluginVersion", //$NON-NLS-1$
+            "GradlePluginVersion", //$NON-NLS-1$
             "Incompatible Android Gradle Plugin",
             "Not all versions of the Android Gradle plugin are compatible with all versions " +
             "of the SDK. If you update your tools, or if you are trying to open a project that " +
@@ -210,6 +210,20 @@
             Severity.ERROR,
             IMPLEMENTATION);
 
+    /** Attempting to use substitution with single quotes */
+    public static final Issue NOT_INTERPOLATED = Issue.create(
+          "NotInterpolated", //$NON-NLS-1$
+          "Incorrect Interpolation",
+
+          "To insert the value of a variable, you can use `${variable}` inside " +
+          "a string literal, but *only* if you are using double quotes!",
+
+          Category.CORRECTNESS,
+          8,
+          Severity.ERROR,
+          IMPLEMENTATION)
+          .addMoreInfo("http://www.groovy-lang.org/syntax.html#_string_interpolation");
+
     /** A newer version is available on a remote server */
     public static final Issue REMOTE_VERSION = Issue.create(
             "NewerVersionAvailable", //$NON-NLS-1$
@@ -432,6 +446,14 @@
                 if (dependency != null) {
                     GradleCoordinate gc = GradleCoordinate.parseCoordinateString(dependency);
                     if (gc != null && dependency.contains("$")) {
+                        if (value.startsWith("'") && value.endsWith("'") &&
+                            context.isEnabled(NOT_INTERPOLATED)) {
+                            String message = "It looks like you are trying to substitute a "
+                                             + "version variable, but using single quotes ('). For Groovy "
+                                             + "string interpolation you must use double quotes (\").";
+                            report(context, statementCookie, NOT_INTERPOLATED, message);
+                        }
+
                         gc = resolveCoordinate(context, gc);
                     }
                     if (gc != null) {
@@ -586,12 +608,12 @@
         } else if (issue == STRING_INTEGER) {
             return findSubstring(errorMessage, "replace ", " with ");
         } else if (issue == DEPRECATED) {
-            if (errorMessage.contains(GradleDetector.APP_PLUGIN_ID) &&
-                    errorMessage.contains(GradleDetector.OLD_APP_PLUGIN_ID)) {
-                return GradleDetector.OLD_APP_PLUGIN_ID;
-            } else if (errorMessage.contains(GradleDetector.LIB_PLUGIN_ID) &&
-                    errorMessage.contains(GradleDetector.OLD_LIB_PLUGIN_ID)) {
-                return GradleDetector.OLD_LIB_PLUGIN_ID;
+            if (errorMessage.contains(APP_PLUGIN_ID) &&
+                errorMessage.contains(OLD_APP_PLUGIN_ID)) {
+                return OLD_APP_PLUGIN_ID;
+            } else if (errorMessage.contains(LIB_PLUGIN_ID) &&
+                       errorMessage.contains(OLD_LIB_PLUGIN_ID)) {
+                return OLD_LIB_PLUGIN_ID;
             }
             // "Deprecated: Replace 'packageNameSuffix' with 'applicationIdSuffix'"
             return findSubstring(errorMessage, "Replace '", "'");
@@ -635,12 +657,12 @@
         } else if (issue == STRING_INTEGER) {
             return findSubstring(errorMessage, " just ", ")");
         } else if (issue == DEPRECATED) {
-            if (errorMessage.contains(GradleDetector.APP_PLUGIN_ID) &&
-                    errorMessage.contains(GradleDetector.OLD_APP_PLUGIN_ID)) {
-                return GradleDetector.APP_PLUGIN_ID;
-            } else if (errorMessage.contains(GradleDetector.LIB_PLUGIN_ID) &&
-                    errorMessage.contains(GradleDetector.OLD_LIB_PLUGIN_ID)) {
-                return GradleDetector.LIB_PLUGIN_ID;
+            if (errorMessage.contains(APP_PLUGIN_ID) &&
+                errorMessage.contains(OLD_APP_PLUGIN_ID)) {
+                return APP_PLUGIN_ID;
+            } else if (errorMessage.contains(LIB_PLUGIN_ID) &&
+                       errorMessage.contains(OLD_LIB_PLUGIN_ID)) {
+                return LIB_PLUGIN_ID;
             }
             // "Deprecated: Replace 'packageNameSuffix' with 'applicationIdSuffix'"
             return findSubstring(errorMessage, " with '", "'");
@@ -696,7 +718,7 @@
     }
 
     private static boolean isModelOlderThan011(@NonNull Context context) {
-        return LintUtils.isModelOlderThan(context.getProject().getGradleProjectModel(), 0, 11, 0);
+        return LintUtils.isModelOlderThan(context.getProject(), 0, 11, 0);
     }
 
     private static int sMajorBuildTools;
@@ -717,7 +739,11 @@
             sMajorBuildTools = major;
 
             List<PreciseRevision> revisions = Lists.newArrayList();
-            if (major == 21) {
+            if (major == 23) {
+                revisions.add(new PreciseRevision(23, 0, 1));
+            } else if (major == 22) {
+                revisions.add(new PreciseRevision(22, 0, 1));
+            } else if (major == 21) {
                 revisions.add(new PreciseRevision(21, 1, 2));
             } else if (major == 20) {
                 revisions.add(new PreciseRevision(20));
@@ -854,7 +880,7 @@
             version = getNewerRevision(dependency, new PreciseRevision(18, 0));
         } else if ("com.google.code.gson".equals(dependency.getGroupId()) &&
                 "gson".equals(dependency.getArtifactId())) {
-            version = getNewerRevision(dependency, new PreciseRevision(2, 3));
+            version = getNewerRevision(dependency, new PreciseRevision(2, 4));
         } else if ("org.apache.httpcomponents".equals(dependency.getGroupId()) &&
                 "httpclient".equals(dependency.getArtifactId())) {
             version = getNewerRevision(dependency, new PreciseRevision(4, 3, 5));
@@ -1061,8 +1087,16 @@
         String artifactId = dependency.getArtifactId();
         assert groupId != null && artifactId != null;
 
-        // See if the support library version is lower than the targetSdkVersion
-        if (mTargetSdkVersion > 0 && dependency.getMajorVersion() < mTargetSdkVersion &&
+        if (mCompileSdkVersion >= 18 && dependency.getMajorVersion() != mCompileSdkVersion &&
+                dependency.getMajorVersion() != GradleCoordinate.PLUS_REV_VALUE &&
+                // The multidex library doesn't follow normal supportlib numbering scheme
+                !dependency.getArtifactId().startsWith("multidex") &&
+                context.isEnabled(COMPATIBILITY)) {
+            String message = "This support library should not use a different version ("
+                    + dependency.getMajorVersion() + ") than the `compileSdkVersion` ("
+                    + mCompileSdkVersion + ")";
+            report(context, cookie, COMPATIBILITY, message);
+        } else if (mTargetSdkVersion > 0 && dependency.getMajorVersion() < mTargetSdkVersion &&
                 dependency.getMajorVersion() != GradleCoordinate.PLUS_REV_VALUE &&
                 // The multidex library doesn't follow normal supportlib numbering scheme
                 !dependency.getArtifactId().startsWith("multidex") &&
@@ -1159,19 +1193,7 @@
             if (cookie != null) {
                 report(context, cookie, COMPATIBILITY, message);
             } else {
-                // Associate the error with the top level build.gradle file, if found
-                // (if not, fall back to the project directory). This is necessary because
-                // we're doing this analysis based on the Gradle interpreted model, not from
-                // parsing Gradle files - and the model doesn't provide source positions.
-                File dir = context.getProject().getDir();
-                Location location;
-                File topLevel = new File(dir, FN_BUILD_GRADLE);
-                if (topLevel.exists()) {
-                    location = Location.create(topLevel);
-                } else {
-                    location = Location.create(dir);
-                }
-                context.report(COMPATIBILITY, location, message);
+                context.report(COMPATIBILITY, guessGradleLocation(context.getProject()), message);
             }
         }
     }
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HandlerDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HandlerDetector.java
index f436283..3b8a41c 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HandlerDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HandlerDetector.java
@@ -36,7 +36,11 @@
 import java.util.List;
 
 import lombok.ast.ClassDeclaration;
+import lombok.ast.ConstructorInvocation;
+import lombok.ast.Expression;
+import lombok.ast.MethodDeclaration;
 import lombok.ast.Node;
+import lombok.ast.NormalTypeBody;
 
 /**
  * Checks that Handler implementations are top level classes or static.
@@ -98,16 +102,51 @@
         }
 
         // Only flag handlers using the default looper
-        if (hasLooperConstructorParameter(cls)) {
+        ConstructorInvocation invocation = null;
+        Node current = node;
+        while (current != null) {
+            if (current instanceof ConstructorInvocation) {
+                invocation = (ConstructorInvocation) current;
+                break;
+            } else if (current instanceof MethodDeclaration ||
+                    current instanceof ClassDeclaration) {
+                break;
+            }
+            current = current.getParent();
+        }
+
+        if (invocation != null) {
+            for (Expression expression : invocation.astArguments()) {
+                TypeDescriptor type = context.getType(expression);
+                if (type != null && type.matchesName(LOOPER_CLS)) {
+                    return;
+                }
+            }
+        } else if (hasLooperConstructorParameter(cls)) {
+            // This is an inner class which takes a Looper parameter:
+            // possibly used correctly from elsewhere
             return;
         }
 
-        Node locationNode = node instanceof ClassDeclaration
-                ? ((ClassDeclaration) node).astName() : node;
-        Location location = context.getLocation(locationNode);
+        Location location;
+        Node locationNode;
+        if (node instanceof ClassDeclaration) {
+            locationNode = node;
+            location = context.getLocation(((ClassDeclaration) node).astName());
+        } else if (node instanceof NormalTypeBody
+                && node.getParent() instanceof ConstructorInvocation) {
+            ConstructorInvocation parent = (ConstructorInvocation)node.getParent();
+            locationNode = parent;
+            location = context.getRangeLocation(parent, 0, parent.astTypeReference(), 0);
+        } else {
+            locationNode = node;
+            location = context.getLocation(node);;
+        }
+
+        //noinspection VariableNotUsedInsideIf
         context.report(ISSUE, locationNode, location, String.format(
                 "This Handler class should be static or leaks might occur (%1$s)",
-                cls.getName()));
+                declaration == null ? "anonymous " + cls.getName() : cls.getName()));
     }
 
     private static boolean isInnerClass(@Nullable ClassDeclaration node) {
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HardcodedValuesDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HardcodedValuesDetector.java
index 288b3b1..6c2e3b4 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HardcodedValuesDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HardcodedValuesDetector.java
@@ -23,6 +23,8 @@
 import static com.android.SdkConstants.ATTR_PROMPT;
 import static com.android.SdkConstants.ATTR_TEXT;
 import static com.android.SdkConstants.ATTR_TITLE;
+import static com.android.tools.lint.checks.RestrictionsDetector.ATTR_DESCRIPTION;
+import static com.android.tools.lint.checks.RestrictionsDetector.TAG_RESTRICTIONS;
 
 import com.android.annotations.NonNull;
 import com.android.resources.ResourceFolderType;
@@ -91,13 +93,18 @@
                 ATTR_PROMPT,
 
                 // Menus
-                ATTR_TITLE
+                ATTR_TITLE,
+
+                // App restrictions
+                ATTR_DESCRIPTION
         );
     }
 
     @Override
     public boolean appliesTo(@NonNull ResourceFolderType folderType) {
-        return folderType == ResourceFolderType.LAYOUT || folderType == ResourceFolderType.MENU;
+        return folderType == ResourceFolderType.LAYOUT
+                || folderType == ResourceFolderType.MENU
+                || folderType == ResourceFolderType.XML;
     }
 
     @Override
@@ -109,6 +116,34 @@
                 return;
             }
 
+            // Filter out a few special cases:
+
+            if (value.equals("Hello World!")) {
+                // This is the default text in new templates. Users are unlikely to
+                // leave this in, so let's not add warnings in the editor as their
+                // welcome to Android development greeting.
+                return;
+            }
+            if (value.equals("Large Text") || value.equals("Medium Text") ||
+                    value.equals("Small Text") || value.startsWith("New ") &&
+                    (value.equals("New Text")
+                            || value.equals("New " + attribute.getOwnerElement().getTagName()))) {
+                // The layout editor initially places the label "New Button", "New TextView",
+                // etc on widgets dropped on the layout editor. Again, users are unlikely
+                // to leave it that way, so let's not flag it until they change it.
+                return;
+            }
+
+            // In XML folders, currently only checking application restriction files
+            // (since in general the res/xml folder can contain arbitrary XML content
+            // interpreted by the app)
+            if (context.getResourceFolderType() == ResourceFolderType.XML) {
+                String tagName = attribute.getOwnerDocument().getDocumentElement().getTagName();
+                if (!tagName.equals(TAG_RESTRICTIONS)) {
+                    return;
+                }
+            }
+
             context.report(ISSUE, attribute, context.getLocation(attribute),
                 String.format("[I18N] Hardcoded string \"%1$s\", should use `@string` resource",
                               value));
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LocaleDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LocaleDetector.java
index 59998ea..1b16852 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LocaleDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LocaleDetector.java
@@ -17,43 +17,41 @@
 package com.android.tools.lint.checks;
 
 import static com.android.SdkConstants.FORMAT_METHOD;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_STRING;
 
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.client.api.LintClient;
 import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
+import com.android.tools.lint.detector.api.ConstantEvaluator;
 import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Detector.ClassScanner;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
 import com.android.tools.lint.detector.api.Implementation;
 import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
 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.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.InsnList;
-import org.objectweb.asm.tree.LdcInsnNode;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.objectweb.asm.tree.analysis.Analyzer;
-import org.objectweb.asm.tree.analysis.AnalyzerException;
-import org.objectweb.asm.tree.analysis.Frame;
-import org.objectweb.asm.tree.analysis.SourceInterpreter;
-import org.objectweb.asm.tree.analysis.SourceValue;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
+import lombok.ast.AstVisitor;
+import lombok.ast.Expression;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+
 /**
  * Checks for errors related to locale handling
  */
-public class LocaleDetector extends Detector implements ClassScanner {
+public class LocaleDetector extends Detector implements JavaScanner {
     private static final Implementation IMPLEMENTATION = new Implementation(
             LocaleDetector.class,
-            Scope.CLASS_FILE_SCOPE);
+            Scope.JAVA_FILE_SCOPE);
 
     /** Calling risky convenience methods */
     public static final Issue STRING_LOCALE = Issue.create(
@@ -78,107 +76,100 @@
             .addMoreInfo(
             "http://developer.android.com/reference/java/util/Locale.html#default_locale"); //$NON-NLS-1$
 
-    static final String DATE_FORMAT_OWNER = "java/text/SimpleDateFormat"; //$NON-NLS-1$
-    private static final String STRING_OWNER = "java/lang/String";                //$NON-NLS-1$
-
     /** Constructs a new {@link LocaleDetector} */
     public LocaleDetector() {
     }
 
-    @NonNull
-    @Override
-    public Speed getSpeed() {
-        return Speed.FAST;
-    }
-
-    // ---- Implements ClassScanner ----
+    // ---- Implements JavaScanner ----
 
     @Override
-    @Nullable
-    public List<String> getApplicableCallNames() {
-        return Arrays.asList(
-                "toLowerCase", //$NON-NLS-1$
-                "toUpperCase", //$NON-NLS-1$
-                FORMAT_METHOD
-        );
+    public List<String> getApplicableMethodNames() {
+        if (LintClient.isStudio()) {
+            // In the IDE, don't flag toUpperCase/toLowerCase; these
+            // are already flagged by built-in IDE inspections, so we don't
+            // want duplicate warnings.
+            return Collections.singletonList(FORMAT_METHOD);
+        } else {
+            return Arrays.asList(
+                    // Only when not running in the IDE
+                    "toLowerCase", //$NON-NLS-1$
+                    "toUpperCase", //$NON-NLS-1$
+                    FORMAT_METHOD
+            );
+        }
     }
 
     @Override
-    public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
-            @NonNull MethodNode method, @NonNull MethodInsnNode call) {
-        String owner = call.owner;
-        if (!owner.equals(STRING_OWNER)) {
+    public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+            @NonNull MethodInvocation call) {
+        ResolvedNode resolved = context.resolve(call);
+        if (resolved instanceof ResolvedMethod) {
+            ResolvedMethod method = (ResolvedMethod) resolved;
+            if (method.getContainingClass().matches(TYPE_STRING)) {
+                String name = method.getName();
+                if (name.equals(FORMAT_METHOD)) {
+                    checkFormat(context, method, call);
+                } else if (method.getArgumentCount() == 0) {
+                    Location location = context.getNameLocation(call);
+                    String message = String.format(
+                            "Implicitly using the default locale is a common source of bugs: " +
+                                    "Use `%1$s(Locale)` instead", name);
+                    context.report(STRING_LOCALE, call, location, message);
+                }
+            }
+        }
+    }
+
+    /** Returns true if the given node is a parameter to a Logging call */
+    private static boolean isLoggingParameter(
+            @NonNull JavaContext context,
+            @NonNull MethodInvocation node) {
+        Node parent = node.getParent();
+        if (parent instanceof MethodInvocation) {
+            MethodInvocation call = (MethodInvocation)parent;
+            String name = call.astName().astValue();
+            if (name.length() == 1) { // "d", "i", "e" etc in Log
+                ResolvedNode resolved = context.resolve(call);
+                if (resolved instanceof ResolvedMethod) {
+                    ResolvedMethod method = (ResolvedMethod) resolved;
+                    if (method.getContainingClass().matches(LogDetector.LOG_CLS)) {
+                        return true;
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+
+    private static void checkFormat(
+            @NonNull JavaContext context,
+            @NonNull ResolvedMethod method,
+            @NonNull MethodInvocation call) {
+        // Only check the non-locale version of String.format
+        if (method.getArgumentCount() == 0
+                || !method.getArgumentType(0).matchesName(TYPE_STRING)
+                || call.astArguments().isEmpty()) {
             return;
         }
 
-        String desc = call.desc;
-        String name = call.name;
+        // Find the formatting string
+        Expression first = call.astArguments().first();
+        Object value = ConstantEvaluator.evaluate(context, first);
+        if (!(value instanceof String)) {
+            return;
+        }
 
-        if (name.equals(FORMAT_METHOD)) {
-            // Only check the non-locale version of String.format
-            if (!desc.equals("(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;")) { //$NON-NLS-1$
+        String format = (String) value;
+        if (StringFormatDetector.isLocaleSpecific(format)) {
+            if (isLoggingParameter(context, call)) {
                 return;
             }
-            // Find the formatting string
-            Analyzer analyzer = new Analyzer(new SourceInterpreter() {
-                @Override
-                public SourceValue newOperation(AbstractInsnNode insn) {
-                    if (insn.getOpcode() == Opcodes.LDC) {
-                        Object cst = ((LdcInsnNode) insn).cst;
-                        if (cst instanceof String) {
-                            return new StringValue(1, (String) cst);
-                        }
-                    }
-                    return super.newOperation(insn);
-                }
-            });
-            try {
-                Frame[] frames = analyzer.analyze(classNode.name, method);
-                InsnList instructions = method.instructions;
-                Frame frame = frames[instructions.indexOf(call)];
-                if (frame.getStackSize() == 0) {
-                    return;
-                }
-                SourceValue stackValue = (SourceValue) frame.getStack(0);
-                if (stackValue instanceof StringValue) {
-                    String format = ((StringValue) stackValue).getString();
-                    if (format != null && StringFormatDetector.isLocaleSpecific(format)) {
-                        Location location = context.getLocation(call);
-                        String message =
-                            "Implicitly using the default locale is a common source of bugs: " +
-                            "Use `String.format(Locale, ...)` instead";
-                        context.report(STRING_LOCALE, method, call, location, message);
-                    }
-                }
-            } catch (AnalyzerException e) {
-                context.log(e, null);
-            }
-        } else {
-            if (desc.equals("()Ljava/lang/String;")) {   //$NON-NLS-1$
-                Location location = context.getLocation(call);
-                String message = String.format(
+            Location location = context.getLocation(call);
+            String message =
                     "Implicitly using the default locale is a common source of bugs: " +
-                    "Use `%1$s(Locale)` instead", name);
-                context.report(STRING_LOCALE, method, call, location, message);
-            }
-        }
-    }
-
-    private static class StringValue extends SourceValue {
-        private final String mString;
-
-        StringValue(int size, String string) {
-            super(size);
-            mString = string;
-        }
-
-        String getString() {
-            return mString;
-        }
-
-        @Override
-        public int getSize() {
-            return 1;
+                            "Use `String.format(Locale, ...)` instead";
+            context.report(STRING_LOCALE, call, location, message);
         }
     }
 }
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LogDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LogDetector.java
index e8fd0e9..0530b4d 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LogDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LogDetector.java
@@ -103,7 +103,7 @@
 
     @SuppressWarnings("SpellCheckingInspection")
     private static final String IS_LOGGABLE = "isLoggable";       //$NON-NLS-1$
-    private static final String LOG_CLS = "android.util.Log";     //$NON-NLS-1$
+    public static final String LOG_CLS = "android.util.Log";     //$NON-NLS-1$
     private static final String PRINTLN = "println";              //$NON-NLS-1$
 
     // ---- Implements Detector.JavaScanner ----
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestDetector.java
index decacff..8cd9f7a 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestDetector.java
@@ -19,6 +19,7 @@
 import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
 import static com.android.SdkConstants.ANDROID_URI;
 import static com.android.SdkConstants.ATTR_ALLOW_BACKUP;
+import static com.android.SdkConstants.ATTR_FULL_BACKUP_CONTENT;
 import static com.android.SdkConstants.ATTR_ICON;
 import static com.android.SdkConstants.ATTR_MIN_SDK_VERSION;
 import static com.android.SdkConstants.ATTR_NAME;
@@ -39,6 +40,7 @@
 import static com.android.SdkConstants.TAG_USES_LIBRARY;
 import static com.android.SdkConstants.TAG_USES_PERMISSION;
 import static com.android.SdkConstants.TAG_USES_SDK;
+import static com.android.SdkConstants.TOOLS_URI;
 import static com.android.SdkConstants.VALUE_FALSE;
 import static com.android.xml.AndroidManifest.NODE_ACTION;
 import static com.android.xml.AndroidManifest.NODE_DATA;
@@ -169,7 +171,7 @@
 
             "The `<uses-library>` element should be defined as a direct child of the " +
             "`<application>` tag, not the `<manifest>` tag or an `<activity>` tag. Similarly, " +
-            "a `<uses-sdk>` tag much be declared at the root level, and so on. This check " +
+            "a `<uses-sdk>` tag must be declared at the root level, and so on. This check " +
             "looks for incorrect declaration locations in the manifest, and complains " +
             "if an element is found in the wrong place.",
 
@@ -206,9 +208,9 @@
     /** Not explicitly defining allowBackup */
     public static final Issue ALLOW_BACKUP = Issue.create(
             "AllowBackup", //$NON-NLS-1$
-            "Missing `allowBackup` attribute",
+            "AllowBackup/FullBackupContent Problems",
 
-            "The allowBackup attribute determines if an application's data can be backed up " +
+            "The `allowBackup` attribute determines if an application's data can be backed up " +
             "and restored. It is documented at " +
             "http://developer.android.com/reference/android/R.attr.html#allowBackup\n" +
             "\n" +
@@ -228,7 +230,10 @@
             "restore.\n" +
             "\n" +
             "To fix this warning, decide whether your application should support backup, " +
-            "and explicitly set `android:allowBackup=(true|false)\"`",
+            "and explicitly set `android:allowBackup=(true|false)\"`.\n" +
+            "\n" +
+            "If not set to false, and if targeting API 23 or later, lint will also warn " +
+            "that you should set `android:fullBackupContent` to configure auto backup.",
 
             Category.SECURITY,
             3,
@@ -543,23 +548,19 @@
                 ProductFlavor flavor = variant.getMergedFlavor();
                 String gradleValue = null;
                 if (ATTR_MIN_SDK_VERSION.equals(attributeName)) {
-                    try {
-                        ApiVersion minSdkVersion = flavor.getMinSdkVersion();
-                        gradleValue = minSdkVersion != null ? minSdkVersion.getApiString() : null;
-                    } catch (Throwable e) {
-                        // TODO: REMOVE ME
-                        // This method was added in the 0.11 model. We'll need to drop support
-                        // for 0.10 shortly but until 0.11 is available this is a stopgap measure
+                    if (element.hasAttributeNS(TOOLS_URI, "overrideLibrary")) {
+                        // The manifest may be setting a minSdkVersion here to deliberately
+                        // let the manifest merger know that a library dependency's manifest
+                        // with a higher value is okay: this value wins. The manifest merger
+                        // should really be taking the Gradle file into account instead,
+                        // but for now we filter these out; http://b.android.com/186762
+                        return;
                     }
+                    ApiVersion minSdkVersion = flavor.getMinSdkVersion();
+                    gradleValue = minSdkVersion != null ? minSdkVersion.getApiString() : null;
                 } else if (ATTR_TARGET_SDK_VERSION.equals(attributeName)) {
-                    try {
-                        ApiVersion targetSdkVersion = flavor.getTargetSdkVersion();
-                        gradleValue = targetSdkVersion != null ? targetSdkVersion.getApiString() : null;
-                    } catch (Throwable e) {
-                        // TODO: REMOVE ME
-                        // This method was added in the 0.11 model. We'll need to drop support
-                        // for 0.10 shortly but until 0.11 is available this is a stopgap measure
-                    }
+                    ApiVersion targetSdkVersion = flavor.getTargetSdkVersion();
+                    gradleValue = targetSdkVersion != null ? targetSdkVersion.getApiString() : null;
                 } else if (ATTR_VERSION_CODE.equals(attributeName)) {
                     Integer versionCode = flavor.getVersionCode();
                     if (versionCode != null) {
@@ -828,7 +829,7 @@
                     (mApplicationTagHandle == null || isMainManifest(context, context.file))) {
                 mApplicationTagHandle = context.createLocationHandle(element);
             }
-            Attr fullBackupNode = element.getAttributeNodeNS(ANDROID_URI, "fullBackupContent");
+            Attr fullBackupNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_FULL_BACKUP_CONTENT);
             if (fullBackupNode != null &&
                     fullBackupNode.getValue().startsWith(PREFIX_RESOURCE_REF) &&
                     context.getClient().supportsProjectResources()) {
@@ -843,6 +844,7 @@
                             "Missing `<full-backup-content>` resource");
                 }
             } else if (fullBackupNode == null && !VALUE_FALSE.equals(allowBackup)
+                    && !context.getProject().isLibrary()
                     && context.getMainProject().getTargetSdk() >= 23) {
                 if (hasGcmReceiver(element)) {
                     Location location = context.getLocation(element);
@@ -1003,6 +1005,13 @@
             }
 
             // Test source set?
+            for (SourceProviderContainer extra : model.getDefaultConfig().getExtraSourceProviders()) {
+                String artifactName = extra.getArtifactName();
+                if (AndroidProject.ARTIFACT_ANDROID_TEST.equals(artifactName)
+                        && manifestFile.equals(extra.getSourceProvider().getManifestFile())) {
+                    return true;
+                }
+            }
             for (ProductFlavorContainer container : model.getProductFlavors()) {
                 for (SourceProviderContainer extra : container.getExtraSourceProviders()) {
                     String artifactName = extra.getArtifactName();
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestResourceDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestResourceDetector.java
new file mode 100644
index 0000000..63adda0
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestResourceDetector.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_ICON;
+import static com.android.SdkConstants.ATTR_LABEL;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_THEME;
+import static com.android.SdkConstants.ATTR_TYPE;
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.TAG_ITEM;
+import static com.android.utils.SdkUtils.endsWithIgnoreCase;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.res2.AbstractResourceRepository;
+import com.android.ide.common.res2.ResourceItem;
+import com.android.ide.common.resources.ResourceUrl;
+import com.android.ide.common.resources.configuration.VersionQualifier;
+import com.android.resources.FolderTypeRelationship;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Project;
+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.XmlContext;
+import com.android.xml.AndroidManifest;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Detects references to resources in the manifest that vary by configuration
+ */
+public class ManifestResourceDetector extends ResourceXmlDetector {
+    /** Using resources in the manifest that vary by configuration */
+    @SuppressWarnings("unchecked")
+    public static final Issue ISSUE = Issue.create(
+            "ManifestResource", //$NON-NLS-1$
+            "Manifest Resource References",
+            "Elements in the manifest can reference resources, but those resources cannot " +
+            "vary across configurations (except as a special case, by version, and except " +
+            "for a few specific package attributes such as the application title and icon.)",
+
+            Category.CORRECTNESS,
+            6,
+            Severity.FATAL,
+            new Implementation(
+                    ManifestResourceDetector.class,
+                    Scope.MANIFEST_AND_RESOURCE_SCOPE,
+                    Scope.MANIFEST_SCOPE));
+
+    /**
+     * Map from resource name to resource type to manifest location; used
+     * in batch mode to report errors when resource overrides are found
+     */
+    private Map<String, Multimap<ResourceType, Location>> mManifestLocations;
+
+    /** Constructs a new {@link ManifestResourceDetector} */
+    public ManifestResourceDetector() {
+    }
+
+    @Override
+    public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
+        if (endsWithIgnoreCase(context.file.getPath(), ANDROID_MANIFEST_XML)) {
+            checkManifest(context, document);
+        } else {
+            //noinspection VariableNotUsedInsideIf
+            if (mManifestLocations != null) {
+                checkResourceFile(context, document);
+            }
+        }
+    }
+
+    private void checkManifest(@NonNull XmlContext context, @NonNull Document document) {
+        LintClient client = context.getClient();
+        Project project = context.getProject();
+        AbstractResourceRepository repository = null;
+        if (client.supportsProjectResources()) {
+            repository = client.getProjectResources(project, true);
+        }
+        if (repository == null && !context.getScope().contains(Scope.RESOURCE_FILE)) {
+            // Can't perform incremental analysis without a resource repository
+            return;
+        }
+
+        Element root = document.getDocumentElement();
+        if (root != null) {
+            visit(context, root, repository);
+        }
+    }
+
+    private void visit(@NonNull XmlContext context, @NonNull Element element,
+            @Nullable AbstractResourceRepository repository) {
+        NamedNodeMap attributes = element.getAttributes();
+        for (int i = 0, n = attributes.getLength(); i < n; i++) {
+            Node node = attributes.item(i);
+            String value = node.getNodeValue();
+            if (value.startsWith(PREFIX_RESOURCE_REF)) {
+                Attr attribute = (Attr) node;
+                if (!isAllowedToVary(attribute)) {
+                    checkReference(context, attribute, value, repository);
+                }
+            }
+        }
+
+        NodeList children = element.getChildNodes();
+        for (int i = 0, n = children.getLength(); i < n; i++) {
+            Node child = children.item(i);
+            if (child.getNodeType() == Node.ELEMENT_NODE) {
+                visit(context, ((Element)child), repository);
+            }
+        }
+    }
+
+    /**
+     * Is the given attribute allowed to reference a resource that has different
+     * values across configurations (other than with version qualifiers) ?
+     * <p>
+     * When the manifest is read, it has a fixed configuration with only the API level set.
+     * When strings are read, we can either read the actual string, or a resource reference.
+     * For labels and icons, we only read the resource reference -- that is the package manager
+     * doesn't need the actual string (like it would need for, say, the name of an activity),
+     * but just gets the resource ID, and then clients if they need the actual resource value can
+     * load it at that point using their current configuration.
+     * <p>
+     * To see which specific attributes in the manifest are processed this way, look at
+     * android.content.pm.PackageItemInfo to see what pieces of data are kept as raw resource
+     * IDs instead of loading their value. (For label resources we also keep the non localized
+     * label resource to allow people to specify hardcoded strings instead of a resource reference.)
+     *
+     * @param attribute the attribute node to look up
+     * @return true if this resource is allowed to have delayed configuration values
+     */
+    private static boolean isAllowedToVary(@NonNull Attr attribute) {
+        // This corresponds to the getResourceId() calls in PackageParser
+        // where we store the actual resource id such that they can be
+        // resolved later
+        String name = attribute.getLocalName();
+        if (ATTR_LABEL.equals(name)
+                || ATTR_ICON.equals(name)
+                || ATTR_THEME.equals(name)
+                || "description".equals(name)
+                || "logo".equals(name)
+                || "banner".equals(name)
+                || "sharedUserLabel".equals(name)) {
+            return ANDROID_URI.equals(attribute.getNamespaceURI());
+        }
+
+        return false;
+    }
+
+    private void checkReference(
+            @NonNull XmlContext context,
+            @NonNull Attr attribute,
+            @NonNull String value,
+            @Nullable AbstractResourceRepository repository) {
+        ResourceUrl url = ResourceUrl.parse(value);
+        if (url != null && !url.framework) {
+            if (repository != null) {
+                List<ResourceItem> items = repository.getResourceItem(url.type, url.name);
+                if (items != null && items.size() > 1) {
+                    List<String> list = Lists.newArrayListWithExpectedSize(5);
+                    for (ResourceItem item : items) {
+                        String qualifiers = item.getQualifiers();
+                        // Default folder is okay
+                        if (qualifiers.isEmpty()) {
+                            continue;
+                        }
+
+                        // Version qualifier is okay
+                        if (VersionQualifier.getQualifier(qualifiers) != null) {
+                            continue;
+                        }
+
+                        list.add(qualifiers);
+                    }
+                    if (!list.isEmpty()) {
+                        Collections.sort(list);
+                        String message = getErrorMessage(Joiner.on(", ").join(list));
+                        context.report(ISSUE, attribute, context.getValueLocation(attribute),
+                                message);
+                    }
+                }
+            } else if (!context.getDriver().isSuppressed(context, ISSUE, attribute)) {
+                // Don't have a resource repository; need to check resource files during batch
+                // run
+                if (mManifestLocations == null) {
+                    mManifestLocations = Maps.newHashMap();
+                }
+                Multimap<ResourceType, Location> typeMap = mManifestLocations.get(url.name);
+                if (typeMap == null) {
+                    typeMap = ArrayListMultimap.create();
+                    mManifestLocations.put(url.name, typeMap);
+                }
+                typeMap.put(url.type, context.getValueLocation(attribute));
+            }
+        }
+    }
+
+    private void checkResourceFile(
+            @NonNull XmlContext context,
+            @NonNull Document document) {
+        File parentFile = context.file.getParentFile();
+        if (parentFile == null) {
+            return;
+        }
+        String parentName = parentFile.getName();
+        // Base folders are okay
+        int index = parentName.indexOf('-');
+        if (index == -1) {
+            return;
+        }
+
+        // Version qualifier is okay
+        String qualifiers = parentName.substring(index + 1);
+        if (VersionQualifier.getQualifier(qualifiers) != null) {
+            return;
+        }
+
+        ResourceFolderType folderType = context.getResourceFolderType();
+        if (folderType == ResourceFolderType.VALUES) {
+            Element root = document.getDocumentElement();
+            if (root != null) {
+                NodeList children = root.getChildNodes();
+                for (int i = 0, n = children.getLength(); i < n; i++) {
+                    Node child = children.item(i);
+                    if (child.getNodeType() == Node.ELEMENT_NODE) {
+                        Element item = (Element)child;
+                        String name = item.getAttribute(ATTR_NAME);
+                        if (name != null && mManifestLocations.containsKey(name)) {
+                            String tag = item.getTagName();
+                            String typeString = tag;
+                            if (tag.equals(TAG_ITEM)) {
+                                typeString = item.getAttribute(ATTR_TYPE);
+                            }
+                            ResourceType type = ResourceType.getEnum(typeString);
+                            if (type != null) {
+                                reportIfFound(context, qualifiers, name, type, item);
+                            }
+                        }
+                    }
+                }
+
+            }
+        } else if (folderType != null) {
+            String name = LintUtils.getBaseName(context.file.getName());
+            if (mManifestLocations.containsKey(name)) {
+                List<ResourceType> types =
+                        FolderTypeRelationship.getRelatedResourceTypes(folderType);
+                for (ResourceType type : types) {
+                    reportIfFound(context, qualifiers, name, type, document.getDocumentElement());
+                }
+            }
+        }
+    }
+
+    private void reportIfFound(@NonNull XmlContext context, @NonNull  String qualifiers,
+            @NonNull  String name, @NonNull  ResourceType type, @Nullable Node secondary) {
+        Multimap<ResourceType, Location> typeMap = mManifestLocations.get(name);
+        if (typeMap != null) {
+            Collection<Location> locations = typeMap.get(type);
+            if (locations != null) {
+                for (Location location : locations) {
+                    String message = getErrorMessage(qualifiers);
+                    if (secondary != null) {
+                        Location secondaryLocation = context.getLocation(secondary);
+                        secondaryLocation.setSecondary(location.getSecondary());
+                        secondaryLocation.setMessage("This value will not be used");
+                        location.setSecondary(secondaryLocation);
+                    }
+                    context.report(ISSUE, location, message);
+                }
+            }
+        }
+    }
+
+    @NonNull
+    private static String getErrorMessage(@NonNull  String qualifiers) {
+        return "Resources referenced from the manifest cannot vary by configuration "
+                + "(except for version qualifiers, e.g. `-v21`.) Found variation in " + qualifiers;
+    }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MathDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MathDetector.java
index e9bb3a5..6f627a0 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MathDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MathDetector.java
@@ -18,27 +18,29 @@
 
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
 import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
 import com.android.tools.lint.detector.api.Detector;
 import com.android.tools.lint.detector.api.Implementation;
 import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+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.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
 
 import java.util.Arrays;
 import java.util.List;
 
+import lombok.ast.AstVisitor;
+import lombok.ast.Expression;
+import lombok.ast.MethodInvocation;
+
 /**
  * Looks for usages of {@link java.lang.Math} methods which can be replaced with
  * {@code android.util.FloatMath} methods to avoid casting.
  */
-public class MathDetector extends Detector implements Detector.ClassScanner {
+public class MathDetector extends Detector implements Detector.JavaScanner {
     /** The main issue discovered by this detector */
     public static final Issue ISSUE = Issue.create(
             "FloatMath", //$NON-NLS-1$
@@ -56,7 +58,7 @@
             Severity.WARNING,
             new Implementation(
                     MathDetector.class,
-                    Scope.CLASS_FILE_SCOPE))
+                    Scope.JAVA_FILE_SCOPE))
             .addMoreInfo(
             "http://developer.android.com/guide/practices/design/performance.html#avoidfloat"); //$NON-NLS-1$
 
@@ -64,17 +66,11 @@
     public MathDetector() {
     }
 
-    @NonNull
-    @Override
-    public Speed getSpeed() {
-        return Speed.FAST;
-    }
+    // ---- Implements JavaScanner ----
 
-    // ---- Implements ClassScanner ----
-
-    @Override
     @Nullable
-    public List<String> getApplicableCallNames() {
+    @Override
+    public List<String> getApplicableMethodNames() {
         return Arrays.asList(
                 "sin",   //$NON-NLS-1$
                 "cos",   //$NON-NLS-1$
@@ -85,16 +81,22 @@
     }
 
     @Override
-    public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
-            @NonNull MethodNode method, @NonNull MethodInsnNode call) {
-        String owner = call.owner;
-
-        if (owner.equals("android/util/FloatMath")  //$NON-NLS-1$
-                && context.getProject().getMinSdk() >= 8) {
-            String message = String.format(
-                    "Use `java.lang.Math#%1$s` instead of `android.util.FloatMath#%1$s()` " +
-                    "since it is faster as of API 8", call.name);
-            context.report(ISSUE, method, call, context.getLocation(call), message  /*data*/);
+    public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+            @NonNull MethodInvocation call) {
+        Expression operand = call.astOperand();
+        if (operand == null || !operand.toString().equals("Math")) {
+            ResolvedNode resolved = context.resolve(call);
+            if (resolved instanceof ResolvedMethod &&
+                    ((ResolvedMethod)resolved).getContainingClass().matches("android.util.FloatMath")
+                    && context.getProject().getMinSdk() >= 8) {
+                String message = String.format(
+                        "Use `java.lang.Math#%1$s` instead of `android.util.FloatMath#%1$s()` " +
+                                "since it is faster as of API 8", call.astName().astValue());
+                Location location = operand != null
+                        ? context.getRangeLocation(operand, 0, call.astName(), 0)
+                        : context.getLocation(call);
+                context.report(ISSUE, call, location, message);
+            }
         }
     }
 }
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MissingClassDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MissingClassDetector.java
index 0ad7a7e..e5887b3 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MissingClassDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MissingClassDetector.java
@@ -83,10 +83,10 @@
             "MissingRegistered", //$NON-NLS-1$
             "Missing registered class",
 
-            "If a class is referenced in the manifest, it must also exist in the project (or in one " +
-            "of the libraries included by the project. This check helps uncover typos in " +
-            "registration names, or attempts to rename or move classes without updating the " +
-            "manifest file properly.",
+            "If a class is referenced in the manifest or in a layout file, it must also exist " +
+            "in the project (or in one of the libraries included by the project. This check " +
+            "helps uncover typos in registration names, or attempts to rename or move classes " +
+            "without updating the manifest file properly.",
 
             Category.CORRECTNESS,
             8,
@@ -103,6 +103,7 @@
             "Registered class is not instantiatable",
 
             "Activities, services, broadcast receivers etc. registered in the manifest file " +
+            "(or for custom views, in a layout file) " +
             "must be \"instantiatable\" by the system, which means that the class must be " +
             "public, it must have an empty public constructor, and if it's an inner class, " +
             "it must be a static inner class.",
@@ -119,8 +120,8 @@
             "InnerclassSeparator", //$NON-NLS-1$
             "Inner classes should use `$` rather than `.`",
 
-            "When you reference an inner class in a manifest file, you must use '$' instead of '.' " +
-            "as the separator character, i.e. Outer$Inner instead of Outer.Inner.\n" +
+            "When you reference an inner class in a manifest file, you must use '$' instead of " +
+            "'.' as the separator character, i.e. Outer$Inner instead of Outer.Inner.\n" +
             "\n" +
             "(If you get this warning for a class which is not actually an inner class, it's " +
             "because you are using uppercase characters in your package name, which is not " +
@@ -319,8 +320,7 @@
             }
             if (!haveUpperCase) {
                 String fixed = className.charAt(0) + className.substring(1).replace('.','$');
-                String message = "Use '$' instead of '.' for inner classes " +
-                        "(or use only lowercase letters in package names); replace \"" +
+                String message = "Use '$' instead of '.' for inner classes (or use only lowercase letters in package names); replace \"" +
                         className + "\" with \"" + fixed + "\"";
                 Location location = context.getLocation(classNameNode);
                 context.report(INNERCLASS, element, location, message);
@@ -330,7 +330,8 @@
 
     @Override
     public void afterCheckProject(@NonNull Context context) {
-        if (!context.getProject().isLibrary() && mHaveClasses
+        if (context.getProject() == context.getMainProject() && mHaveClasses
+                && !context.getMainProject().isLibrary()
                 && mReferencedClasses != null && !mReferencedClasses.isEmpty()
                 && context.getDriver().getScope().contains(Scope.CLASS_FILE)) {
             List<String> classes = new ArrayList<String>(mReferencedClasses.keySet());
@@ -358,8 +359,8 @@
                 }
 
                 String message = String.format(
-                        "Class referenced in the manifest, `%1$s`, was not found in the " +
-                                "project or the libraries", fqcn);
+                        "Class referenced in the manifest, `%1$s`, was not found in the project or the libraries",
+                                fqcn);
                 Location location = handle.resolve();
                 File parentFile = location.getFile().getParentFile();
                 if (parentFile != null) {
@@ -367,17 +368,17 @@
                     ResourceFolderType type = ResourceFolderType.getFolderType(parent);
                     if (type == LAYOUT) {
                         message = String.format(
-                            "Class referenced in the layout file, `%1$s`, was not found in "
-                                + "the project or the libraries", fqcn);
+                            "Class referenced in the layout file, `%1$s`, was not found in the project or the libraries",
+                                    fqcn);
                     } else if (type == XML) {
                         message = String.format(
-                                "Class referenced in the preference header file, `%1$s`, was not "
-                                        + "found in the project or the libraries", fqcn);
+                                "Class referenced in the preference header file, `%1$s`, was not found in the project or the libraries",
+                                        fqcn);
 
                     } else if (type == VALUES) {
                         message = String.format(
-                                "Class referenced in the analytics file, `%1$s`, was not "
-                                        + "found in the project or the libraries", fqcn);
+                                "Class referenced in the analytics file, `%1$s`, was not found in the project or the libraries",
+                                        fqcn);
                     }
                 }
 
@@ -440,8 +441,7 @@
             if (!hasDefaultConstructor && !isCustomView && !context.isFromClassLibrary()
                     && context.getProject().getReportIssues()) {
                 context.report(INSTANTIATABLE, context.getLocation(classNode), String.format(
-                        "This class should provide a default constructor (a public " +
-                        "constructor with no arguments) (%1$s)",
+                        "This class should provide a default constructor (a public constructor with no arguments) (%1$s)",
                             ClassContext.createSignature(classNode.name, null, null)));
             }
         }
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ParcelDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ParcelDetector.java
index 1fe3afb..f6fbb13 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ParcelDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ParcelDetector.java
@@ -20,7 +20,6 @@
 
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.JavaParser;
 import com.android.tools.lint.detector.api.Category;
 import com.android.tools.lint.detector.api.Detector;
 import com.android.tools.lint.detector.api.Implementation;
@@ -29,18 +28,14 @@
 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.objectweb.asm.Opcodes;
 
 import java.util.Collections;
 import java.util.List;
 
-import lombok.ast.AstVisitor;
 import lombok.ast.ClassDeclaration;
-import lombok.ast.ForwardingAstVisitor;
 import lombok.ast.Node;
-import lombok.ast.TypeReference;
 
 /**
  * Looks for Parcelable classes that are missing a CREATOR field
@@ -55,9 +50,9 @@
             "According to the `Parcelable` interface documentation, " +
             "\"Classes implementing the Parcelable interface must also have a " +
             "static field called `CREATOR`, which is an object implementing the " +
-            "`Parcelable.Creator` interface.",
+            "`Parcelable.Creator` interface.\"",
 
-            Category.USABILITY,
+            Category.CORRECTNESS,
             3,
             Severity.ERROR,
             new Implementation(
@@ -69,70 +64,36 @@
     public ParcelDetector() {
     }
 
-    @NonNull
-    @Override
-    public Speed getSpeed() {
-        return Speed.FAST;
-    }
-
     // ---- Implements JavaScanner ----
 
     @Nullable
     @Override
-    public List<Class<? extends Node>> getApplicableNodeTypes() {
-        return Collections.<Class<? extends Node>>singletonList(ClassDeclaration.class);
+    public List<String> applicableSuperClasses() {
+        return Collections.singletonList("android.os.Parcelable");
     }
 
-    @Nullable
     @Override
-    public AstVisitor createJavaVisitor(@NonNull final JavaContext context) {
-        return new ParcelVisitor(context);
-    }
-
-    private static class ParcelVisitor extends ForwardingAstVisitor {
-        private final JavaContext mContext;
-
-        public ParcelVisitor(JavaContext context) {
-            mContext = context;
+    public void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration node,
+            @NonNull Node declarationOrAnonymous, @NonNull ResolvedClass cls) {
+        if (node == null) {
+            // Anonymous classes aren't parcelable
+            return;
+        }
+        // Only applies to concrete classes
+        int flags = node.astModifiers().getExplicitModifierFlags();
+        if ((flags & (Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT)) != 0) {
+            return;
         }
 
-        @Override
-        public boolean visitClassDeclaration(ClassDeclaration node) {
-            // Only applies to concrete classes
-            int flags = node.astModifiers().getExplicitModifierFlags();
-            if ((flags & (Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT)) != 0) {
-                return true;
+        // Parceling spans is handled in TextUtils#CHAR_SEQUENCE_CREATOR
+        if (!cls.isImplementing("android.text.ParcelableSpan", false)) {
+            ResolvedField field = cls.getField("CREATOR", false);
+            if (field == null) {
+                Location location = context.getLocation(node.astName());
+                context.report(ISSUE, node, location,
+                        "This class implements `Parcelable` but does not "
+                                + "provide a `CREATOR` field");
             }
-
-            if (node.astImplementing() != null)
-                for (TypeReference reference : node.astImplementing()) {
-                    String name = reference.astParts().last().astIdentifier().astValue();
-                    if (name.equals("Parcelable")) {
-                        JavaParser.ResolvedNode resolved = mContext.resolve(node);
-                        if (resolved instanceof ResolvedClass) {
-                            ResolvedClass cls = (ResolvedClass) resolved;
-                            ResolvedField field = cls.getField("CREATOR", false);
-                            if (field == null) {
-                                // Make doubly sure that we're really implementing
-                                // android.os.Parcelable
-                                JavaParser.ResolvedNode r = mContext.resolve(reference);
-                                if (r instanceof ResolvedClass) {
-                                    ResolvedClass parcelable = (ResolvedClass) r;
-                                    if (!parcelable.isSubclassOf("android.os.Parcelable", false)) {
-                                        return true;
-                                    }
-                                }
-                                Location location = mContext.getLocation(node.astName());
-                                mContext.report(ISSUE, node, location,
-                                        "This class implements `Parcelable` but does not "
-                                                + "provide a `CREATOR` field");
-                            }
-                        }
-                        break;
-                    }
-                }
-
-            return true;
         }
     }
 }
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PermissionFinder.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PermissionFinder.java
index 6494e78..439f0e5 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PermissionFinder.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PermissionFinder.java
@@ -23,7 +23,9 @@
 
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser;
 import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
 import com.android.tools.lint.client.api.JavaParser.ResolvedField;
 import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
 import com.android.tools.lint.detector.api.JavaContext;
@@ -243,7 +245,10 @@
             @NonNull ResolvedField field,
             @NonNull ResolvedAnnotation annotation) {
         PermissionRequirement requirement = PermissionRequirement.create(mContext, annotation);
-        String name = field.getContainingClass().getSimpleName() + "." + field.getName();
+        ResolvedClass containingClass = field.getContainingClass();
+        String name = containingClass != null
+                ? containingClass.getSimpleName() + "." + field.getName()
+                : field.getName();
         return new Result(mOperation, requirement, name);
     }
 }
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PermissionRequirement.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PermissionRequirement.java
index 8923fcb..3ba9e18 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PermissionRequirement.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PermissionRequirement.java
@@ -27,6 +27,7 @@
 import com.android.sdklib.AndroidVersion;
 import com.android.tools.lint.client.api.JavaParser;
 import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
+import com.android.tools.lint.client.api.JavaParser.ResolvedField;
 import com.android.tools.lint.detector.api.Context;
 import com.android.tools.lint.detector.api.JavaContext;
 import com.google.common.collect.Lists;
@@ -130,34 +131,54 @@
         }
 
         Object v = annotation.getValue(ATTR_ANY_OF);
-        if (v != null) {
-            if (v instanceof String[]) {
-                String[] anyOf = (String[])v;
-                if (anyOf.length > 0) {
-                    return new Many(annotation, BinaryOperator.LOGICAL_OR, anyOf);
-                }
-            } else if (v instanceof String) {
-                String[] anyOf = new String[] { (String)v };
+        String[] anyOf = getAnnotationStrings(v);
+        if (anyOf != null) {
+            if (anyOf.length > 1) {
                 return new Many(annotation, BinaryOperator.LOGICAL_OR, anyOf);
+            } else if (anyOf.length == 1) {
+                return new Single(annotation, anyOf[0]);
             }
         }
 
         v = annotation.getValue(ATTR_ALL_OF);
-        if (v != null) {
-            if (v instanceof String[]) {
-                String[] allOf = (String[])v;
-                if (allOf.length > 0) {
-                    return new Many(annotation, BinaryOperator.LOGICAL_AND, allOf);
-                }
-            } else if (v instanceof String) {
-                String[] allOf = new String[] { (String)v };
+        String[] allOf = getAnnotationStrings(v);
+        if (allOf != null) {
+            if (allOf.length > 1) {
                 return new Many(annotation, BinaryOperator.LOGICAL_AND, allOf);
+            } else if (allOf.length == 1) {
+                return new Single(annotation, allOf[0]);
             }
         }
 
         return NONE;
     }
 
+    @Nullable
+    private static String[] getAnnotationStrings(@Nullable Object v) {
+        if (v != null) {
+            if (v instanceof String[]) {
+                return (String[])v;
+            } else if (v instanceof String) {
+                return new String[] { (String)v };
+            } else if (v instanceof Object[]) {
+                List<String> strings = Lists.newArrayList();
+                for (Object o : (Object[])v) {
+                    if (o instanceof ResolvedField) {
+                        Object vs = ((ResolvedField)o).getValue();
+                        if (vs instanceof String) {
+                            strings.add((String)vs);
+                        }
+                    } else if (o instanceof String) {
+                        strings.add((String)o);
+                    }
+                }
+                return strings.toArray(new String[strings.size()]);
+            }
+        }
+
+        return null;
+    }
+
     /**
      * Returns false if this permission does not apply given the specified minimum and
      * target sdk versions
@@ -237,6 +258,11 @@
         Object o = annotation.getValue(ATTR_CONDITIONAL);
         if (o instanceof Boolean) {
             return (Boolean)o;
+        } else if (o instanceof ResolvedField) {
+            o = ((ResolvedField)o).getValue();
+            if (o instanceof Boolean) {
+                return (Boolean)o;
+            }
         }
         return false;
     }
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PluralsDatabase.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PluralsDatabase.java
index f9d1fc4..cb40ad7 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PluralsDatabase.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PluralsDatabase.java
@@ -176,44 +176,44 @@
 
     /** Set of language codes relevant to plurals data */
     private static final String[] LANGUAGE_CODES = new String[] {
-            "af", "ak", "am", "ar", "az", "be", "bg", "bh", "bm", "bn",
-            "bo", "br", "bs", "ca", "cs", "cy", "da", "de", "dv", "dz",
-            "ee", "el", "en", "eo", "es", "et", "eu", "fa", "ff", "fi",
-            "fo", "fr", "fy", "ga", "gd", "gl", "gu", "gv", "ha", "he",
-            "hi", "hr", "hu", "hy", "id", "ig", "ii", "in", "is", "it",
-            "iu", "iw", "ja", "ji", "jv", "ka", "kk", "kl", "km", "kn",
-            "ko", "ks", "ku", "kw", "ky", "lb", "lg", "ln", "lo", "lt",
-            "lv", "mg", "mk", "ml", "mn", "mr", "ms", "mt", "my", "nb",
-            "nd", "ne", "nl", "nn", "no", "nr", "ny", "om", "or", "os",
-            "pa", "pl", "ps", "pt", "rm", "ro", "ru", "se", "sg", "si",
-            "sk", "sl", "sn", "so", "sq", "sr", "ss", "st", "sv", "sw",
-            "ta", "te", "th", "ti", "tk", "tl", "tn", "to", "tr", "ts",
-            "ug", "uk", "ur", "uz", "ve", "vi", "vo", "wa", "wo", "xh",
-            "yi", "yo", "zh", "zu"
+            "af", "ak", "am", "ar", "as", "az", "be", "bg", "bh", "bm",
+            "bn", "bo", "br", "bs", "ca", "ce", "cs", "cy", "da", "de",
+            "dv", "dz", "ee", "el", "en", "eo", "es", "et", "eu", "fa",
+            "ff", "fi", "fo", "fr", "fy", "ga", "gd", "gl", "gu", "gv",
+            "ha", "he", "hi", "hr", "hu", "hy", "id", "ig", "ii", "in",
+            "is", "it", "iu", "iw", "ja", "ji", "jv", "ka", "kk", "kl",
+            "km", "kn", "ko", "ks", "ku", "kw", "ky", "lb", "lg", "ln",
+            "lo", "lt", "lv", "mg", "mk", "ml", "mn", "mr", "ms", "mt",
+            "my", "nb", "nd", "ne", "nl", "nn", "no", "nr", "ny", "om",
+            "or", "os", "pa", "pl", "ps", "pt", "rm", "ro", "ru", "se",
+            "sg", "si", "sk", "sl", "sn", "so", "sq", "sr", "ss", "st",
+            "sv", "sw", "ta", "te", "th", "ti", "tk", "tl", "tn", "to",
+            "tr", "ts", "ug", "uk", "ur", "uz", "ve", "vi", "vo", "wa",
+            "wo", "xh", "yi", "yo", "zh", "zu"
     };
 
     /**
      * Relevant flags for each language (corresponding to each language listed
-     * in the same position in {@link #LANGUAGE_CODES}).
+     * in the same position in {@link #LANGUAGE_CODES})
      */
     private static final int[] FLAGS = new int[] {
-            0x0002, 0x0042, 0x0042, 0x001f, 0x0002, 0x005a, 0x0002, 0x0042,
-            0x0000, 0x0042, 0x0000, 0x00de, 0x004a, 0x0002, 0x000a, 0x001f,
-            0x0002, 0x0002, 0x0002, 0x0000, 0x0002, 0x0002, 0x0002, 0x0002,
-            0x0002, 0x0002, 0x0002, 0x0042, 0x0042, 0x0002, 0x0002, 0x0042,
-            0x0002, 0x001e, 0x00ce, 0x0002, 0x0042, 0x00ce, 0x0002, 0x0016,
-            0x0042, 0x004a, 0x0002, 0x0042, 0x0000, 0x0000, 0x0000, 0x0000,
-            0x0042, 0x0002, 0x0006, 0x0016, 0x0000, 0x0002, 0x0000, 0x0002,
-            0x0002, 0x0002, 0x0000, 0x0042, 0x0000, 0x0002, 0x0002, 0x0006,
-            0x0002, 0x0002, 0x0002, 0x0042, 0x0000, 0x004a, 0x0063, 0x0042,
-            0x0042, 0x0002, 0x0002, 0x0042, 0x0000, 0x001a, 0x0000, 0x0002,
-            0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002,
-            0x0002, 0x0002, 0x0042, 0x001a, 0x0002, 0x0002, 0x0002, 0x000a,
-            0x005a, 0x0006, 0x0000, 0x0042, 0x000a, 0x00ce, 0x0002, 0x0002,
-            0x0002, 0x004a, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002,
-            0x0000, 0x0042, 0x0002, 0x0042, 0x0002, 0x0000, 0x0002, 0x0002,
-            0x0002, 0x005a, 0x0002, 0x0002, 0x0002, 0x0000, 0x0002, 0x0042,
-            0x0000, 0x0002, 0x0002, 0x0000, 0x0000, 0x0042
+            0x0002, 0x0042, 0x0042, 0x001f, 0x0042, 0x0002, 0x005a, 0x0002,
+            0x0042, 0x0000, 0x0042, 0x0000, 0x00de, 0x004a, 0x0002, 0x0002,
+            0x000a, 0x001f, 0x0002, 0x0002, 0x0002, 0x0000, 0x0002, 0x0002,
+            0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0042, 0x0042, 0x0002,
+            0x0002, 0x0042, 0x0002, 0x001e, 0x00ce, 0x0002, 0x0042, 0x00ce,
+            0x0002, 0x0016, 0x0042, 0x004a, 0x0002, 0x0042, 0x0000, 0x0000,
+            0x0000, 0x0000, 0x0042, 0x0002, 0x0006, 0x0016, 0x0000, 0x0002,
+            0x0000, 0x0002, 0x0002, 0x0002, 0x0000, 0x0042, 0x0000, 0x0002,
+            0x0002, 0x0006, 0x0002, 0x0002, 0x0002, 0x0042, 0x0000, 0x004a,
+            0x0063, 0x0042, 0x0042, 0x0002, 0x0002, 0x0042, 0x0000, 0x001a,
+            0x0000, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002,
+            0x0002, 0x0002, 0x0002, 0x0002, 0x0042, 0x001a, 0x0002, 0x0042,
+            0x0002, 0x000a, 0x005a, 0x0006, 0x0000, 0x0042, 0x000a, 0x00ce,
+            0x0002, 0x0002, 0x0002, 0x004a, 0x0002, 0x0002, 0x0002, 0x0002,
+            0x0002, 0x0002, 0x0000, 0x0042, 0x0002, 0x0042, 0x0002, 0x0000,
+            0x0002, 0x0002, 0x0002, 0x005a, 0x0002, 0x0002, 0x0002, 0x0000,
+            0x0002, 0x0042, 0x0000, 0x0002, 0x0002, 0x0000, 0x0000, 0x0042
     };
 
     @Nullable
@@ -221,7 +221,7 @@
         int index = getLanguageIndex(language);
         switch (index) {
             // set14
-            case 70: // lv
+            case 72: // lv
                 return "0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, \u2026";
             case -1:
             default:
@@ -235,69 +235,73 @@
         switch (index) {
             // set1
             case 2: // am
-            case 9: // bn
-            case 27: // fa
-            case 36: // gu
-            case 40: // hi
-            case 59: // kn
-            case 75: // mr
-            case 133: // zu
+            case 4: // as
+            case 10: // bn
+            case 29: // fa
+            case 38: // gu
+            case 42: // hi
+            case 61: // kn
+            case 77: // mr
+            case 135: // zu
                 return "0, 1";
             // set11
-            case 48: // is
+            case 50: // is
                 return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
             // set12
-            case 72: // mk
+            case 74: // mk
                 return "1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, \u2026";
             // set13
-            case 115: // tl
+            case 117: // tl
                 return "0~3, 5, 7, 8, 10~13, 15, 17, 18, 20, 21, 100, 1000, 10000, 100000, 1000000, \u2026";
             // set14
-            case 70: // lv
+            case 72: // lv
                 return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
             // set2
-            case 28: // ff
-            case 31: // fr
-            case 43: // hy
+            case 30: // ff
+            case 33: // fr
+            case 45: // hy
                 return "0, 1";
             // set20
-            case 12: // bs
-            case 41: // hr
-            case 105: // sr
+            case 13: // bs
+            case 43: // hr
+            case 107: // sr
                 return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
             // set21
-            case 34: // gd
+            case 36: // gd
                 return "1, 11";
             // set22
-            case 101: // sl
+            case 103: // sl
                 return "1, 101, 201, 301, 401, 501, 601, 701, 1001, \u2026";
-            // set26
-            case 5: // be
-                return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
             // set27
-            case 69: // lt
+            case 6: // be
                 return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
-            // set29
-            case 96: // ru
-            case 121: // uk
+            // set28
+            case 71: // lt
                 return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
             // set30
-            case 11: // br
+            case 98: // ru
+            case 123: // uk
+                return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
+            // set31
+            case 12: // br
                 return "1, 21, 31, 41, 51, 61, 81, 101, 1001, \u2026";
-            // set32
-            case 37: // gv
+            // set33
+            case 39: // gv
                 return "1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, \u2026";
-            // set5
-            case 99: // si
+            // set4
+            case 101: // si
                 return "0, 1";
-            // set6
+            // set5
             case 1: // ak
-            case 7: // bh
-            case 67: // ln
-            case 71: // mg
-            case 90: // pa
-            case 113: // ti
-            case 127: // wa
+            case 8: // bh
+            case 69: // ln
+            case 73: // mg
+            case 92: // pa
+            case 115: // ti
+            case 129: // wa
+                return "0, 1";
+            // set7
+            case 95: // pt
                 return "0, 1";
             case -1:
             default:
@@ -310,16 +314,16 @@
         int index = getLanguageIndex(language);
         switch (index) {
             // set21
-            case 34: // gd
+            case 36: // gd
                 return "2, 12";
             // set22
-            case 101: // sl
+            case 103: // sl
                 return "2, 102, 202, 302, 402, 502, 602, 702, 1002, \u2026";
-            // set30
-            case 11: // br
+            // set31
+            case 12: // br
                 return "2, 22, 32, 42, 52, 62, 82, 102, 1002, \u2026";
-            // set32
-            case 37: // gv
+            // set33
+            case 39: // gv
                 return "2, 12, 22, 32, 42, 52, 62, 72, 102, 1002, \u2026";
             case -1:
             default:
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PrivateResourceDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PrivateResourceDetector.java
index a3e0400..a4a8bab 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PrivateResourceDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PrivateResourceDetector.java
@@ -211,6 +211,15 @@
     }
 
     private static boolean isPrivate(Context context, ResourceType type, String name) {
+        if (type == ResourceType.ID) {
+            // No need to complain about "overriding" id's. There's no harm
+            // in doing so. (This avoids warning about cases like for example
+            // appcompat's (private) @id/title resource, which would otherwise
+            // flag any attempt to create a resource named title in the user's
+            // project.
+            return false;
+        }
+
         if (context.getProject().isGradleProject()) {
             ResourceVisibilityLookup lookup = context.getProject().getResourceVisibility();
             return lookup.isPrivate(type, name);
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PropertyFileDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PropertyFileDetector.java
index 744e5a3..9c8408d 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PropertyFileDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PropertyFileDetector.java
@@ -30,6 +30,7 @@
 import com.android.tools.lint.detector.api.Scope;
 import com.android.tools.lint.detector.api.Severity;
 import com.android.tools.lint.detector.api.TextFormat;
+import com.android.utils.SdkUtils;
 import com.google.common.base.Splitter;
 
 import java.io.File;
@@ -175,7 +176,7 @@
     @NonNull
     static String suggestEscapes(@NonNull String value) {
         value = value.replace("\\:", ":").replace("\\\\", "\\");
-        return LintUtils.escapePropertyValue(value);
+        return SdkUtils.escapePropertyValue(value);
     }
 
     /**
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ReadParcelableDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ReadParcelableDetector.java
new file mode 100644
index 0000000..c6884af
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ReadParcelableDetector.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2015 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.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+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 java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.Expression;
+import lombok.ast.Identifier;
+import lombok.ast.MethodInvocation;
+import lombok.ast.NullLiteral;
+
+/**
+ * Looks for Parcelable classes that are missing a CREATOR field
+ */
+public class ReadParcelableDetector extends Detector implements Detector.JavaScanner {
+
+    /** The main issue discovered by this detector */
+    public static final Issue ISSUE = Issue.create(
+            "ParcelClassLoader", //$NON-NLS-1$
+            "Default Parcel Class Loader",
+
+            "The documentation for `Parcel#readParcelable(ClassLoader)` (and its variations) " +
+            "says that you can pass in `null` to pick up the default class loader. However, " +
+            "that ClassLoader is a system class loader and is not able to find classes in " +
+            "your own application.\n" +
+            "\n" +
+            "If you are writing your own classes into the `Parcel` (not just SDK classes like " +
+            "`String` and so on), then you should supply a `ClassLoader` for your application " +
+            "instead; a simple way to obtain one is to just call `getClass().getClassLoader()` " +
+            "from your own class.",
+
+            Category.CORRECTNESS,
+            3,
+            Severity.WARNING,
+            new Implementation(
+                    ReadParcelableDetector.class,
+                    Scope.JAVA_FILE_SCOPE))
+            .addMoreInfo("http://developer.android.com/reference/android/os/Parcel.html");
+
+    /** Constructs a new {@link ReadParcelableDetector} check */
+    public ReadParcelableDetector() {
+    }
+
+    // ---- Implements JavaScanner ----
+
+
+    @Override
+    public List<String> getApplicableMethodNames() {
+        return Arrays.asList(
+                "readParcelable",
+                "readParcelableArray",
+                "readBundle",
+                "readArray",
+                "readSparseArray",
+                "readValue",
+                "readPersistableBundle"
+        );
+    }
+
+    @Override
+    public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+            @NonNull MethodInvocation node) {
+        ResolvedNode resolved = context.resolve(node);
+        if (resolved instanceof ResolvedMethod) {
+            ResolvedMethod method = (ResolvedMethod) resolved;
+            if (method.getContainingClass().matches("android.os.Parcel")) {
+                int argumentCount = method.getArgumentCount();
+                if (argumentCount == 0) {
+                    Identifier name = node.astName();
+                    String message = String.format("Using the default class loader "
+                            + "will not work if you are restoring your own classes. Consider "
+                            + "using for example `%1$s(getClass().getClassLoader())` instead.",
+                            name.astValue());
+                    Location location = context.getRangeLocation(name, 0, name, 2);
+                    context.report(ISSUE, node, location, message);
+                } else if (argumentCount == 1) {
+                    Expression parameter = node.astArguments().first();
+                    if (parameter instanceof NullLiteral) {
+                        String message = "Passing null here (to use the default class loader) "
+                                + "will not work if you are restoring your own classes. Consider "
+                                + "using for example `getClass().getClassLoader()` instead.";
+                        Location location = context.getRangeLocation(node.astName(), 0, parameter, 1);
+                        context.report(ISSUE, node, location, message);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RecyclerViewDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RecyclerViewDetector.java
new file mode 100644
index 0000000..a4c5461
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RecyclerViewDetector.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+
+import static com.android.tools.lint.detector.api.JavaContext.findSurroundingClass;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedField;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.client.api.JavaParser.ResolvedVariable;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.google.common.collect.Lists;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import lombok.ast.BinaryExpression;
+import lombok.ast.BinaryOperator;
+import lombok.ast.ClassDeclaration;
+import lombok.ast.ClassLiteral;
+import lombok.ast.ConstructorInvocation;
+import lombok.ast.Expression;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.Node;
+import lombok.ast.NormalTypeBody;
+import lombok.ast.VariableDefinition;
+import lombok.ast.VariableDefinitionEntry;
+import lombok.ast.VariableReference;
+
+/**
+ * Checks related to RecyclerView usage.
+ * // https://code.google.com/p/android/issues/detail?id=172335
+ */
+public class RecyclerViewDetector extends Detector implements Detector.JavaScanner {
+    public static final Issue ISSUE = Issue.create(
+            "RecyclerView", //$NON-NLS-1$
+            "RecyclerView Problems",
+            "`RecyclerView` will *not* call `onBindViewHolder` again when the position of " +
+            "the item changes in the data set unless the item itself is " +
+            "invalidated or the new position cannot be determined.\n" +
+            "\n" +
+            "For this reason, you should *only* use the position parameter " +
+            "while acquiring the related data item inside this method, and " +
+            "should *not* keep a copy of it.\n" +
+            "\n" +
+            "If you need the position of an item later on (e.g. in a click " +
+            "listener), use `getAdapterPosition()` which will have the updated " +
+            "adapter position.",
+            Category.CORRECTNESS,
+            8,
+            Severity.WARNING,
+            new Implementation(
+                    RecyclerViewDetector.class,
+                    Scope.JAVA_FILE_SCOPE));
+
+    private static final String VIEW_ADAPTER = "android.support.v7.widget.RecyclerView.Adapter"; //$NON-NLS-1$
+    private static final String ON_BIND_VIEW_HOLDER = "onBindViewHolder"; //$NON-NLS-1$
+
+    // ---- Implements JavaScanner ----
+
+    @Nullable
+    @Override
+    public List<String> applicableSuperClasses() {
+        return Collections.singletonList(VIEW_ADAPTER);
+    }
+
+    @Override
+    public void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration declaration,
+            @NonNull Node node, @NonNull ResolvedClass resolvedClass) {
+        NormalTypeBody body;
+        if (declaration != null) {
+            body = declaration.astBody();
+        } else if (node instanceof NormalTypeBody) {
+            // anonymous inner class
+            body = (NormalTypeBody) node;
+        } else {
+            return;
+        }
+
+        for (Node child : body.astMembers()) {
+            if (child instanceof MethodDeclaration) {
+                MethodDeclaration method = (MethodDeclaration) child;
+                if (method.astMethodName().astValue().equals(ON_BIND_VIEW_HOLDER)) {
+                    int size = method.astParameters().size();
+                    if (size == 2 || size == 3) {
+                        checkMethod(context, method);
+                    }
+                }
+            }
+        }
+    }
+
+    private static void checkMethod(@NonNull JavaContext context,
+            @NonNull MethodDeclaration declaration) {
+        Iterator<VariableDefinition> iterator = declaration.astParameters().iterator();
+        if (!iterator.hasNext()) {
+            return;
+        }
+        VariableDefinition viewHolder = iterator.next();
+        if (!iterator.hasNext()) {
+            return;
+        }
+        VariableDefinition parameter = iterator.next();
+        ResolvedNode reference = context.resolve(parameter);
+
+        if (reference instanceof ResolvedVariable) {
+            ParameterEscapesVisitor visitor = new ParameterEscapesVisitor(context, declaration,
+                    (ResolvedVariable) reference);
+            declaration.accept(visitor);
+            if (visitor.variableEscapes()) {
+                reportError(context, viewHolder, parameter);
+            }
+        } else if (parameter.astModifiers().isFinal()) {
+            reportError(context, viewHolder, parameter);
+        }
+    }
+
+    private static void reportError(@NonNull JavaContext context, VariableDefinition viewHolder,
+            VariableDefinition parameter) {
+        String variablePrefix;
+        VariableDefinitionEntry first = viewHolder.astVariables().first();
+        if (first != null) {
+            variablePrefix = first.astName().astValue();
+        } else {
+            variablePrefix = "ViewHolder";
+        }
+        String message = String.format("Do not treat position as fixed; only use immediately "
+                + "and call `%1$s.getAdapterPosition()` to look it up later",
+                variablePrefix);
+        context.report(ISSUE, parameter, context.getLocation(parameter),
+                message);
+    }
+
+    /**
+     * Determines whether a given variable "escapes" either to a field or to a nested
+     * runnable. (We deliberately ignore variables that escape via method calls.)
+     */
+    private static class ParameterEscapesVisitor extends ForwardingAstVisitor {
+        protected final JavaContext mContext;
+        protected final List<ResolvedVariable> mVariables;
+        private final ClassDeclaration mBindClass;
+        private boolean mEscapes;
+        private boolean mFoundInnerClass;
+
+        public ParameterEscapesVisitor(JavaContext context,
+                @NonNull MethodDeclaration onBindMethod,
+                @NonNull ResolvedVariable variable) {
+            mContext = context;
+            mVariables = Lists.newArrayList(variable);
+            mBindClass = findSurroundingClass(onBindMethod);
+        }
+
+        public boolean variableEscapes() {
+            return mEscapes;
+        }
+
+        @Override
+        public boolean visitNode(Node node) {
+            return mEscapes || super.visitNode(node);
+        }
+
+        @Override
+        public boolean visitVariableDefinitionEntry(VariableDefinitionEntry node) {
+            Expression initializer = node.astInitializer();
+            if (initializer instanceof VariableReference) {
+                ResolvedNode resolved = mContext.resolve(initializer);
+                //noinspection SuspiciousMethodCalls
+                if (resolved != null && mVariables.contains(resolved)) {
+                    ResolvedNode resolvedVariable = mContext.resolve(node);
+                    if (resolvedVariable instanceof ResolvedVariable) {
+                        ResolvedVariable variable = (ResolvedVariable) resolvedVariable;
+                        mVariables.add(variable);
+                    } else if (resolvedVariable instanceof ResolvedField) {
+                        mEscapes = true;
+                    }
+                }
+            }
+            return super.visitVariableDefinitionEntry(node);
+        }
+
+        @Override
+        public boolean visitBinaryExpression(BinaryExpression node) {
+            if (node.astOperator() == BinaryOperator.ASSIGN) {
+                Expression rhs = node.astRight();
+                boolean clearLhs = true;
+                if (rhs instanceof VariableReference) {
+                    ResolvedNode resolved = mContext.resolve(rhs);
+                    //noinspection SuspiciousMethodCalls
+                    if (resolved != null && mVariables.contains(resolved)) {
+                        clearLhs = false;
+                        ResolvedNode resolvedLhs = mContext.resolve(node.astLeft());
+                        if (resolvedLhs instanceof ResolvedVariable) {
+                            ResolvedVariable variable = (ResolvedVariable) resolvedLhs;
+                            mVariables.add(variable);
+                        } else if (resolvedLhs instanceof ResolvedField) {
+                            mEscapes = true;
+                        }
+                    }
+                }
+                if (clearLhs) {
+                    // If we reassign one of the variables, clear it out
+                    ResolvedNode resolved = mContext.resolve(node.astLeft());
+                    //noinspection SuspiciousMethodCalls
+                    if (resolved != null && mVariables.contains(resolved)) {
+                        //noinspection SuspiciousMethodCalls
+                        mVariables.remove(resolved);
+                    }
+                }
+            }
+            return super.visitBinaryExpression(node);
+        }
+
+        @Override
+        public boolean visitVariableReference(VariableReference node) {
+            if (mFoundInnerClass) {
+                // Check to see if this reference is inside the same class as the original
+                // onBind (e.g. is this a reference from an inner class, or a reference
+                // to a variable assigned from there)
+                ResolvedNode resolved = mContext.resolve(node);
+                //noinspection SuspiciousMethodCalls
+                if (resolved != null && mVariables.contains(resolved)) {
+                    Node scope = node.getParent();
+                    while (scope != null) {
+                        if (scope instanceof NormalTypeBody) {
+                            if (scope != mBindClass.astBody()) {
+                                mEscapes = true;
+                            }
+                            break;
+                        }
+                        scope = scope.getParent();
+                    }
+                }
+            }
+            return super.visitVariableReference(node);
+        }
+
+        @Override
+        public boolean visitClassLiteral(ClassLiteral node) {
+            mFoundInnerClass = true;
+
+            return super.visitClassLiteral(node);
+        }
+
+        @Override
+        public boolean visitConstructorInvocation(ConstructorInvocation node) {
+            NormalTypeBody anonymous = node.astAnonymousClassBody();
+            if (anonymous != null) {
+                mFoundInnerClass = true;
+            }
+
+            return super.visitConstructorInvocation(node);
+        }
+    }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RegistrationDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RegistrationDetector.java
index de262a0..315b2ff 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RegistrationDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RegistrationDetector.java
@@ -16,48 +16,58 @@
 
 package com.android.tools.lint.checks;
 
-import static com.android.SdkConstants.ANDROID_APP_ACTIVITY;
-import static com.android.SdkConstants.ANDROID_APP_SERVICE;
-import static com.android.SdkConstants.ANDROID_CONTENT_BROADCAST_RECEIVER;
-import static com.android.SdkConstants.ANDROID_CONTENT_CONTENT_PROVIDER;
 import static com.android.SdkConstants.ANDROID_URI;
 import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.CLASS_ACTIVITY;
+import static com.android.SdkConstants.CLASS_APPLICATION;
+import static com.android.SdkConstants.CLASS_BROADCASTRECEIVER;
+import static com.android.SdkConstants.CLASS_CONTENTPROVIDER;
+import static com.android.SdkConstants.CLASS_SERVICE;
 import static com.android.SdkConstants.TAG_ACTIVITY;
+import static com.android.SdkConstants.TAG_APPLICATION;
 import static com.android.SdkConstants.TAG_PROVIDER;
 import static com.android.SdkConstants.TAG_RECEIVER;
 import static com.android.SdkConstants.TAG_SERVICE;
 
 import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.BuildTypeContainer;
+import com.android.builder.model.ProductFlavorContainer;
+import com.android.builder.model.SourceProviderContainer;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
 import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Detector.ClassScanner;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
 import com.android.tools.lint.detector.api.Implementation;
 import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
 import com.android.tools.lint.detector.api.LayoutDetector;
 import com.android.tools.lint.detector.api.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 com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Multimap;
+import com.android.utils.SdkUtils;
+import com.google.common.collect.Maps;
 
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.ClassNode;
 import org.w3c.dom.Element;
 
+import java.io.File;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.EnumSet;
-import java.util.Map.Entry;
+import java.util.List;
+import java.util.Map;
+
+import lombok.ast.ClassDeclaration;
+import lombok.ast.Modifiers;
+import lombok.ast.Node;
 
 /**
  * Checks for missing manifest registrations for activities, services etc
  * and also makes sure that they are registered with the correct tag
- * <p>
- * TODO: Rewrite as Java visitor!
  */
-public class RegistrationDetector extends LayoutDetector implements ClassScanner {
+public class RegistrationDetector extends LayoutDetector implements JavaScanner {
     /** Unregistered activities and services */
     public static final Issue ISSUE = Issue.create(
             "Registered", //$NON-NLS-1$
@@ -74,11 +84,11 @@
             Severity.WARNING,
             new Implementation(
                     RegistrationDetector.class,
-                    EnumSet.of(Scope.MANIFEST, Scope.CLASS_FILE)))
+                    EnumSet.of(Scope.MANIFEST, Scope.JAVA_FILE)))
             .addMoreInfo(
             "http://developer.android.com/guide/topics/manifest/manifest-intro.html"); //$NON-NLS-1$
 
-    protected Multimap<String, String> mManifestRegistrations;
+    protected Map<String, String> mManifestRegistrations;
 
     /** Constructs a new {@link RegistrationDetector} */
     public RegistrationDetector() {
@@ -99,25 +109,22 @@
 
     @Override
     public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+        if (!element.hasAttributeNS(ANDROID_URI, ATTR_NAME)) {
+            // For example, application appears in manifest and doesn't always have a name
+            return;
+        }
         String fqcn = getFqcn(context, element);
         String tag = element.getTagName();
         String frameworkClass = tagToClass(tag);
         if (frameworkClass != null) {
-            String signature = ClassContext.getInternalName(fqcn);
+            String signature = fqcn;
             if (mManifestRegistrations == null) {
-                mManifestRegistrations = ArrayListMultimap.create(4, 8);
+                mManifestRegistrations = Maps.newHashMap();
             }
-            mManifestRegistrations.put(frameworkClass, signature);
+            mManifestRegistrations.put(signature, frameworkClass);
             if (signature.indexOf('$') != -1) {
-                // The internal name contains a $ which means it's an inner class.
-                // The conversion from fqcn to internal name is a bit ambiguous:
-                // "a.b.C.D" usually means "inner class D in class C in package a.b".
-                // However, it can (see issue 31592) also mean class D in package "a.b.C".
-                // Place *both* of these possibilities in the registered map, since this
-                // is only used to check that an activity is registered, not the other way
-                // (so it's okay to have entries there that do not correspond to real classes).
-                signature = signature.replace('$', '/');
-                mManifestRegistrations.put(frameworkClass, signature);
+                signature = signature.replace('$', '.');
+                mManifestRegistrations.put(signature, frameworkClass);
             }
         }
     }
@@ -134,91 +141,148 @@
     private static String getFqcn(@NonNull XmlContext context, @NonNull Element element) {
         String className = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
         if (className.startsWith(".")) { //$NON-NLS-1$
-            return context.getMainProject().getPackage() + className;
+            return context.getProject().getPackage() + className;
         } else if (className.indexOf('.') == -1) {
             // According to the <activity> manifest element documentation, this is not
             // valid ( http://developer.android.com/guide/topics/manifest/activity-element.html )
             // but it appears in manifest files and appears to be supported by the runtime
             // so handle this in code as well:
-            return context.getMainProject().getPackage() + '.' + className;
+            return context.getProject().getPackage() + '.' + className;
         } // else: the class name is already a fully qualified class name
 
         return className;
     }
 
-    // ---- Implements ClassScanner ----
+    // ---- Implements JavaScanner ----
+
+    @Nullable
+    @Override
+    public List<String> applicableSuperClasses() {
+        return Arrays.asList(
+                // Common super class for Activity, ContentProvider, Service, Application
+                // (as well as some other classes not registered in the manifest, such as
+                // Fragment and VoiceInteractionSession)
+                "android.content.ComponentCallbacks2",
+                CLASS_BROADCASTRECEIVER);
+    }
 
     @Override
-    public void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode) {
-        // Abstract classes do not need to be registered
-        if ((classNode.access & Opcodes.ACC_ABSTRACT) != 0) {
+    public void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration node,
+            @NonNull Node declarationOrAnonymous, @NonNull ResolvedClass cls) {
+        if (node == null) {
+            // anonymous class; can't be registered
             return;
         }
-        String curr = classNode.name;
 
-        int lastIndex = curr.lastIndexOf('$');
-        if (lastIndex != -1 && lastIndex < curr.length() - 1) {
-            if (Character.isDigit(curr.charAt(lastIndex+1))) {
-                // Anonymous inner class, doesn't need to be registered
-                return;
-            }
+        Modifiers modifiers = node.astModifiers();
+        if (modifiers.isAbstract()) {
+            // Abstract classes do not need to be registered
+            return;
         }
 
-        while (curr != null) {
-            for (String s : sClasses) {
-                if (curr.equals(s)) {
-                    Collection<String> registered = mManifestRegistrations != null ?
-                            mManifestRegistrations.get(curr) : null;
-                    if (registered == null || !registered.contains(classNode.name)) {
-                        report(context, classNode, curr);
-                    }
+        if (modifiers.isPrivate()) {
+            // Private classes are clearly not intended to be registered
+            return;
+        }
 
-                }
+        String rightTag = getTag(cls);
+        if (rightTag == null) {
+            // some non-registered Context, such as a BackupAgent
+            return;
+        }
+        String className = cls.getName();
+        if (mManifestRegistrations != null) {
+            String framework = mManifestRegistrations.get(className);
+            if (framework == null) {
+                reportMissing(context, node, className, rightTag);
+            } else if (!cls.isSubclassOf(framework, false)) {
+                reportWrongTag(context, node, rightTag, className, framework);
             }
-
-            curr = context.getDriver().getSuperClass(curr);
+        } else {
+            reportMissing(context, node, className, rightTag);
         }
     }
 
-    private void report(ClassContext context, ClassNode classNode, String curr) {
-        String tag = classToTag(curr);
-        String className = ClassContext.createSignature(classNode.name, null, null);
+    private static void reportWrongTag(
+            @NonNull JavaContext context,
+            @NonNull ClassDeclaration node,
+            @NonNull String rightTag,
+            @NonNull String className,
+            @NonNull String framework) {
+        String wrongTag = classToTag(framework);
+        if (wrongTag == null) {
+            return;
+        }
+        Location location = context.getNameLocation(node);
+        String message = String.format("`%1$s` is %2$s but is registered "
+                        + "in the manifest as %3$s", className, describeTag(rightTag),
+                describeTag(wrongTag));
+        context.report(ISSUE, node, location, message);
+    }
 
-        String wrongClass = null; // The framework class this class actually extends
-        if (mManifestRegistrations != null) {
-            Collection<Entry<String,String>> entries =
-                    mManifestRegistrations.entries();
-            for (Entry<String,String> entry : entries) {
-                if (entry.getValue().equals(classNode.name)) {
-                    wrongClass = entry.getKey();
-                    break;
+    private static String describeTag(@NonNull String tag) {
+        String article = tag.startsWith("a") ? "an" : "a"; // an for activity and application
+        return String.format("%1$s `<%2$s>`", article, tag);
+    }
+
+    private static void reportMissing(
+            @NonNull JavaContext context,
+            @NonNull ClassDeclaration node,
+            @NonNull String className,
+            @NonNull String tag) {
+        if (tag.equals(TAG_RECEIVER)) {
+            // Receivers can be registered in code; don't flag these.
+            return;
+        }
+
+        // Don't flag activities registered in test source sets
+        if (context.getProject().isGradleProject()) {
+            AndroidProject model = context.getProject().getGradleProjectModel();
+            if (model != null) {
+                String javaSource = context.file.getPath();
+                // Test source set?
+
+                for (SourceProviderContainer extra : model.getDefaultConfig().getExtraSourceProviders()) {
+                    String artifactName = extra.getArtifactName();
+                    if (AndroidProject.ARTIFACT_ANDROID_TEST.equals(artifactName)) {
+                        for (File file : extra.getSourceProvider().getJavaDirectories()) {
+                            if (SdkUtils.startsWithIgnoreCase(javaSource, file.getPath())) {
+                                return;
+                            }
+                        }
+                    }
+                }
+
+                for (ProductFlavorContainer container : model.getProductFlavors()) {
+                    for (SourceProviderContainer extra : container.getExtraSourceProviders()) {
+                        String artifactName = extra.getArtifactName();
+                        if (AndroidProject.ARTIFACT_ANDROID_TEST.equals(artifactName)) {
+                            for (File file : extra.getSourceProvider().getJavaDirectories()) {
+                                if (SdkUtils.startsWithIgnoreCase(javaSource, file.getPath())) {
+                                    return;
+                                }
+                            }
+                        }
+                    }
                 }
             }
         }
-        if (wrongClass != null) {
-            Location location = context.getLocation(classNode);
-            context.report(
-                    ISSUE,
-                    location,
-                    String.format(
-                            "`%1$s` is a `<%2$s>` but is registered in the manifest as a `<%3$s>`",
-                            className, tag, classToTag(wrongClass)));
-        } else if (!TAG_RECEIVER.equals(tag)) { // don't need to be registered
-            if (context.getMainProject().isGradleProject()) {
-                // Disabled for now; we need to formalize the difference between
-                // the *manifest* package and the variant package, since in some contexts
-                // (such as manifest registrations) we should be using the manifest package,
-                // not the gradle package
-                return;
+
+        Location location = context.getNameLocation(node);
+        String message = String.format("The `<%1$s> %2$s` is not registered in the manifest",
+                tag, className);
+        context.report(ISSUE, node, location, message);
+    }
+
+    private static String getTag(@NonNull ResolvedClass cls) {
+        String tag = null;
+        for (String s : sClasses) {
+            if (cls.isSubclassOf(s, false)) {
+                tag = classToTag(s);
+                break;
             }
-            Location location = context.getLocation(classNode);
-            context.report(
-                    ISSUE,
-                    location,
-                    String.format(
-                            "The `<%1$s> %2$s` is not registered in the manifest",
-                            tag, className));
         }
+        return tag;
     }
 
     /** The manifest tags we care about */
@@ -227,15 +291,17 @@
         TAG_SERVICE,
         TAG_RECEIVER,
         TAG_PROVIDER,
+        TAG_APPLICATION
         // Keep synchronized with {@link #sClasses}
     };
 
     /** The corresponding framework classes that the tags in {@link #sTags} should extend */
     private static final String[] sClasses = new String[] {
-            ANDROID_APP_ACTIVITY,
-            ANDROID_APP_SERVICE,
-            ANDROID_CONTENT_BROADCAST_RECEIVER,
-            ANDROID_CONTENT_CONTENT_PROVIDER,
+            CLASS_ACTIVITY,
+            CLASS_SERVICE,
+            CLASS_BROADCASTRECEIVER,
+            CLASS_CONTENTPROVIDER,
+            CLASS_APPLICATION
             // Keep synchronized with {@link #sTags}
     };
 
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RelativeOverlapDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RelativeOverlapDetector.java
index 1fae9e6..caab887 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RelativeOverlapDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RelativeOverlapDetector.java
@@ -47,6 +47,7 @@
 import static com.android.SdkConstants.VALUE_WRAP_CONTENT;
 import static com.android.SdkConstants.VIEW;
 import static com.android.SdkConstants.VIEW_INCLUDE;
+import static com.android.tools.lint.checks.RequiredAttributeDetector.PERCENT_RELATIVE_LAYOUT;
 
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
@@ -64,8 +65,8 @@
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 
+import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Set;
@@ -357,7 +358,7 @@
 
     @Override
     public Collection<String> getApplicableElements() {
-        return Collections.singletonList(RELATIVE_LAYOUT);
+        return Arrays.asList(RELATIVE_LAYOUT, PERCENT_RELATIVE_LAYOUT);
     }
 
     @Override
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RequiredAttributeDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RequiredAttributeDetector.java
index a78ca03..71f1e24 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RequiredAttributeDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RequiredAttributeDetector.java
@@ -25,6 +25,7 @@
 import static com.android.SdkConstants.ATTR_NAME;
 import static com.android.SdkConstants.ATTR_PARENT;
 import static com.android.SdkConstants.ATTR_STYLE;
+import static com.android.SdkConstants.AUTO_URI;
 import static com.android.SdkConstants.FD_RES_LAYOUT;
 import static com.android.SdkConstants.FN_RESOURCE_BASE;
 import static com.android.SdkConstants.FQCN_GRID_LAYOUT_V7;
@@ -102,6 +103,11 @@
                     RequiredAttributeDetector.class,
                     EnumSet.of(Scope.JAVA_FILE, Scope.ALL_RESOURCE_FILES)));
 
+    public static final String PERCENT_RELATIVE_LAYOUT
+            = "android.support.percent.PercentRelativeLayout";
+    public static final String ATTR_LAYOUT_WIDTH_PERCENT = "layout_widthPercent";
+    public static final String ATTR_LAYOUT_HEIGHT_PERCENT = "layout_heightPercent";
+
     /** Map from each style name to parent style */
     @Nullable private Map<String, String> mStyleParents;
 
@@ -432,6 +438,16 @@
                     return;
                 }
 
+                // PercentRelativeLayout or PercentFrameLayout?
+                boolean isPercent = parentTag.startsWith("android.support.percent.Percent");
+                if (isPercent) {
+                    hasWidth |= element.hasAttributeNS(AUTO_URI, ATTR_LAYOUT_WIDTH_PERCENT);
+                    hasHeight |= element.hasAttributeNS(AUTO_URI, ATTR_LAYOUT_HEIGHT_PERCENT);
+                    if (hasWidth && hasHeight) {
+                        return;
+                    }
+                }
+
                 if (!context.getProject().getReportIssues()) {
                     // If this is a library project not being analyzed, ignore it
                     return;
@@ -502,6 +518,15 @@
                                 attribute);
                     }
                 }
+                if (isPercent) {
+                    String escapedLayoutWidth = '`' + ATTR_LAYOUT_WIDTH + '`';
+                    String escapedLayoutHeight = '`' + ATTR_LAYOUT_HEIGHT + '`';
+                    String escapedLayoutWidthPercent = '`' + ATTR_LAYOUT_WIDTH_PERCENT + '`';
+                    String escapedLayoutHeightPercent = '`' + ATTR_LAYOUT_HEIGHT_PERCENT + '`';
+                    message = message.replace(escapedLayoutWidth, escapedLayoutWidth + " or "
+                            + escapedLayoutWidthPercent).replace(escapedLayoutHeight,
+                            escapedLayoutHeight + " or " + escapedLayoutHeightPercent);
+                }
                 context.report(ISSUE, element, context.getLocation(element),
                         message);
             }
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourcePrefixDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourcePrefixDetector.java
index 7250d78..bac39af 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourcePrefixDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourcePrefixDetector.java
@@ -188,6 +188,14 @@
             if (folderType != null && folderType != ResourceFolderType.VALUES) {
                 String name = LintUtils.getBaseName(context.file.getName());
                 if (!name.startsWith(mPrefix)) {
+                    // Turns out the Gradle plugin will generate raw resources
+                    // for renderscript. We don't want to flag these.
+                    // We don't have a good way to recognize them today.
+                    String path = context.file.getPath();
+                    if (path.endsWith(".bc") && folderType == ResourceFolderType.RAW) {
+                        return;
+                    }
+
                     Location location = Location.create(context.file);
                     context.report(ISSUE, location, getErrorMessage(name));
                 }
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourceUsageModel.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourceUsageModel.java
new file mode 100644
index 0000000..9c5416f
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourceUsageModel.java
@@ -0,0 +1,1655 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_DISCARD;
+import static com.android.SdkConstants.ATTR_ID;
+import static com.android.SdkConstants.ATTR_KEEP;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_PARENT;
+import static com.android.SdkConstants.ATTR_SHRINK_MODE;
+import static com.android.SdkConstants.ATTR_TYPE;
+import static com.android.SdkConstants.PREFIX_ANDROID;
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.PREFIX_THEME_REF;
+import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX;
+import static com.android.SdkConstants.TAG_ITEM;
+import static com.android.SdkConstants.TAG_STYLE;
+import static com.android.SdkConstants.TOOLS_URI;
+import static com.android.SdkConstants.VALUE_SAFE;
+import static com.android.SdkConstants.VALUE_STRICT;
+import static com.android.utils.SdkUtils.endsWithIgnoreCase;
+import static com.google.common.base.Charsets.UTF_8;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.resources.ResourceUrl;
+import com.android.resources.FolderTypeRelationship;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.tools.lint.client.api.DefaultConfiguration;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.io.Files;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * A model for Android resource declarations and usages
+ */
+public class ResourceUsageModel {
+    private static final int TYPICAL_RESOURCE_COUNT = 200;
+
+    /** List of all known resources (parsed from R.java) */
+    private List<Resource> mResources = Lists.newArrayListWithExpectedSize(TYPICAL_RESOURCE_COUNT);
+    /** Map from resource type to map from resource name to resource object */
+    private Map<ResourceType, Map<String, Resource>> mTypeToName =
+            Maps.newEnumMap(ResourceType.class);
+    /** Map from R field value to corresponding resource */
+    private Map<Integer, Resource> mValueToResource =
+            Maps.newHashMapWithExpectedSize(TYPICAL_RESOURCE_COUNT);
+
+    public static String getFieldName(Element element) {
+        return LintUtils.getFieldName(element.getAttribute(ATTR_NAME));
+    }
+
+    public static ResourceType getResourceType(Element element) {
+        String tagName = element.getTagName();
+        if (tagName.equals(TAG_ITEM)) {
+            String typeName = element.getAttribute(ATTR_TYPE);
+            if (!typeName.isEmpty()) {
+                return ResourceType.getEnum(typeName);
+            }
+        } else if ("string-array".equals(tagName) || "integer-array".equals(tagName)) {
+            return ResourceType.ARRAY;
+        } else {
+            return ResourceType.getEnum(tagName);
+        }
+        return null;
+    }
+
+    @Nullable
+    public Resource getResource(Element element) {
+        return getResource(element, false);
+    }
+
+    public Resource getResource(Element element, boolean declare) {
+        ResourceType type = getResourceType(element);
+        if (type != null) {
+            String name = getFieldName(element);
+            Resource resource = getResource(type, name);
+            if (resource == null && declare) {
+                resource = addResource(type, name, null);
+                resource.setDeclared(true);
+            }
+            return resource;
+        }
+
+        return null;
+    }
+
+    @SuppressWarnings("unused") // Used by (temporary) copy in Gradle resource shrinker
+    @Nullable
+    public Resource getResource(@NonNull Integer value) {
+        return mValueToResource.get(value);
+    }
+
+    @Nullable
+    public Resource getResource(@NonNull ResourceType type, @NonNull String name) {
+        Map<String, Resource> nameMap = mTypeToName.get(type);
+        if (nameMap != null) {
+            return nameMap.get(LintUtils.getFieldName(name));
+        }
+        return null;
+    }
+
+    @Nullable
+    Resource getResourceFromUrl(@NonNull String possibleUrlReference) {
+        ResourceUrl url = ResourceUrl.parse(possibleUrlReference);
+        if (url != null && !url.framework) {
+            return addResource(url.type, LintUtils.getFieldName(url.name), null);
+        }
+
+        return null;
+    }
+
+    private static final String ANDROID_RES = "android_res/";
+
+    @Nullable
+    public Resource getResourceFromFilePath(@NonNull String url) {
+        int nameSlash = url.lastIndexOf('/');
+        if (nameSlash == -1) {
+            return null;
+        }
+
+        // Look for
+        //   (1) a full resource URL: /android_res/type/name.ext
+        //   (2) a partial URL that uniquely identifies a given resource: drawable/name.ext
+        // e.g. file:///android_res/drawable/bar.png
+        int androidRes = url.indexOf(ANDROID_RES);
+        if (androidRes != -1) {
+            androidRes += ANDROID_RES.length();
+            int slash = url.indexOf('/', androidRes);
+            if (slash != -1) {
+                String folderName = url.substring(androidRes, slash);
+                ResourceFolderType folderType = ResourceFolderType.getFolderType(folderName);
+                if (folderType != null) {
+                    List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(
+                            folderType);
+                    if (!types.isEmpty()) {
+                        ResourceType type = types.get(0);
+                        int nameBegin = slash + 1;
+                        int dot = url.indexOf('.', nameBegin);
+                        String name = url.substring(nameBegin, dot != -1 ? dot : url.length());
+                        return getResource(type, name);
+                    }
+                }
+            }
+        }
+
+        // Some other relative path. Just look from the end:
+        int typeSlash = url.lastIndexOf('/', nameSlash - 1);
+        ResourceType type = ResourceType.getEnum(url.substring(typeSlash + 1, nameSlash));
+        if (type != null) {
+            int nameBegin = nameSlash + 1;
+            int dot = url.indexOf('.', nameBegin);
+            String name = url.substring(nameBegin, dot != -1 ? dot : url.length());
+            return getResource(type, name);
+        }
+
+        return null;
+    }
+
+    /**
+     * Marks the given resource (if non-null) as reachable, and returns true if
+     * this is the first time the resource is marked reachable
+     */
+    public static boolean markReachable(@Nullable Resource resource) {
+        if (resource != null) {
+            boolean wasReachable = resource.isReachable();
+            resource.setReachable(true);
+            return !wasReachable;
+        }
+
+        return false;
+    }
+
+    private static void markUnreachable(@Nullable Resource resource) {
+        if (resource != null) {
+            resource.setReachable(false);
+        }
+    }
+
+
+    public void recordManifestUsages(Node node) {
+        short nodeType = node.getNodeType();
+        if (nodeType == Node.ELEMENT_NODE) {
+            Element element = (Element) node;
+            NamedNodeMap attributes = element.getAttributes();
+            for (int i = 0, n = attributes.getLength(); i < n; i++) {
+                Attr attr = (Attr) attributes.item(i);
+                markReachable(getResourceFromUrl(attr.getValue()));
+            }
+        } else if (nodeType == Node.TEXT_NODE) {
+            // Does this apply to any manifests??
+            String text = node.getNodeValue().trim();
+            markReachable(getResourceFromUrl(text));
+        }
+
+        NodeList children = node.getChildNodes();
+        for (int i = 0, n = children.getLength(); i < n; i++) {
+            Node child = children.item(i);
+            recordManifestUsages(child);
+        }
+    }
+
+    private static final int RESOURCE_DECLARED =    1 << 1;
+    private static final int RESOURCE_PUBLIC =      1 << 2;
+    private static final int RESOURCE_KEEP =        1 << 3;
+    private static final int RESOURCE_DISCARD =     1 << 4;
+    private static final int RESOURCE_REACHABLE =   1 << 5;
+
+    public static class Resource implements Comparable<Resource> {
+        private int mFlags;
+
+        /** Type of resource */
+        public final ResourceType type;
+        /** Name of resource */
+        public final String name;
+        /** Integer id location */
+        public int value;
+
+        /** Resources this resource references. For example, a layout can reference another via
+         * an include; a style reference in a layout references that layout style, and so on. */
+        public List<Resource> references;
+
+        /** Chained list of declaration locations */
+        public Location locations;
+        public List<File> declarations;
+
+        /** Whether we found a declaration for this resource (otherwise we might have seen
+         * a reference to this before we came across its potential declaration, so we added it
+         * to the map, but we don't want to report unused resources for invalid resource
+         * references */
+        public boolean isDeclared() {
+            return (mFlags & RESOURCE_DECLARED) != 0;
+        }
+
+        /** Whether we found a declaration for this resource (otherwise we might have seen
+         * a reference to this before we came across its potential declaration, so we added it
+         * to the map, but we don't want to report unused resources for invalid resource
+         * references */
+        public void setDeclared(boolean on) {
+            mFlags = on ? (mFlags | RESOURCE_DECLARED) : (mFlags & ~RESOURCE_DECLARED);
+        }
+
+        /** This resource is marked as public */
+        public boolean isPublic() {
+            return (mFlags & RESOURCE_PUBLIC) != 0;
+        }
+
+        /** This resource is marked as public */
+        public void setPublic(boolean on) {
+            mFlags = on ? (mFlags | RESOURCE_PUBLIC) : (mFlags & ~RESOURCE_PUBLIC);
+        }
+
+        /** This resource is marked as to be ignored for usage analysis, regardless of
+         * references */
+        public boolean isKeep() {
+            return (mFlags & RESOURCE_KEEP) != 0;
+        }
+
+        /** This resource is marked as to be ignored for usage analysis, regardless of
+         * references */
+        public void setKeep(boolean on) {
+            mFlags = on ? (mFlags | RESOURCE_KEEP) : (mFlags & ~RESOURCE_KEEP);
+        }
+
+        /** This resource is marked as to be ignored for usage analysis, regardless of lack of
+         * references */
+        public boolean isDiscard() {
+            return (mFlags & RESOURCE_DISCARD) != 0;
+        }
+
+        /** This resource is marked as to be ignored for usage analysis, regardless of lack of
+         * references */
+        public void setDiscard(boolean on) {
+            mFlags = on ? (mFlags | RESOURCE_DISCARD) : (mFlags & ~RESOURCE_DISCARD);
+        }
+
+        public boolean isReachable() {
+            return (mFlags & RESOURCE_REACHABLE) != 0;
+        }
+
+        public void setReachable(boolean on) {
+            mFlags = on ? (mFlags | RESOURCE_REACHABLE) : (mFlags & ~RESOURCE_REACHABLE);
+        }
+
+        public Resource(ResourceType type, String name, int value) {
+            this.type = type;
+            this.name = name;
+            this.value = value;
+        }
+
+        @Override
+        public String toString() {
+            return type + ":" + name + ":" + value;
+        }
+
+        @SuppressWarnings("RedundantIfStatement") // Generated by IDE
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+
+            Resource resource = (Resource) o;
+
+            if (name != null ? !name.equals(resource.name) : resource.name != null) {
+                return false;
+            }
+            if (type != resource.type) {
+                return false;
+            }
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = type != null ? type.hashCode() : 0;
+            result = 31 * result + (name != null ? name.hashCode() : 0);
+            return result;
+        }
+
+        public void addLocation(@NonNull File file) {
+            if (declarations == null) {
+                declarations = Lists.newArrayList();
+            }
+            declarations.add(file);
+        }
+
+        public void recordLocation(@NonNull Location location) {
+            Location oldLocation = this.locations;
+            if (oldLocation != null) {
+                location.setSecondary(oldLocation);
+            }
+            this.locations = location;
+        }
+
+        public void addReference(@Nullable Resource resource) {
+            if (resource != null) {
+                if (references == null) {
+                    references = Lists.newArrayList();
+                } else if (references.contains(resource)) {
+                    return;
+                }
+                references.add(resource);
+            }
+        }
+
+        public String getUrl() {
+            return '@' + type.getName() + '/' + name;
+        }
+
+        public String getField() {
+            return "R." + type.getName() + '.' + name;
+        }
+
+        @Override
+        public int compareTo(@NonNull Resource other) {
+            if (type != other.type) {
+                return type.compareTo(other.type);
+            }
+
+            return name.compareTo(other.name);
+        }
+    }
+
+    public List<Resource> findUnused() {
+        return findUnused(mResources);
+    }
+
+    public String dumpReferences() {
+        StringBuilder sb = new StringBuilder(1000);
+        sb.append("Resource Reference Graph:\n");
+        for (Resource resource : mResources) {
+            if (resource.references != null) {
+                sb.append(resource).append(" => ").append(resource.references).append('\n');
+            }
+        }
+        return sb.toString();
+    }
+
+
+    public String dumpResourceModel() {
+        StringBuilder sb = new StringBuilder(1000);
+        Collections.sort(mResources, new Comparator<Resource>() {
+            @Override
+            public int compare(Resource resource1,
+                    Resource resource2) {
+                int delta = resource1.type.compareTo(resource2.type);
+                if (delta != 0) {
+                    return delta;
+                }
+                return resource1.name.compareTo(resource2.name);
+            }
+        });
+
+        for (Resource resource : mResources) {
+            sb.append(resource.getUrl()).append(" : reachable=").append(resource.isReachable());
+            sb.append("\n");
+            if (resource.references != null) {
+                for (Resource referenced : resource.references) {
+                    sb.append("    ");
+                    sb.append(referenced.getUrl());
+                    sb.append("\n");
+                }
+            }
+        }
+
+        return sb.toString();
+    }
+
+    public List<Resource> findUnused(List<Resource> resources) {
+        List<Resource> roots = findRoots(resources);
+
+        Map<Resource,Boolean> seen = new IdentityHashMap<Resource,Boolean>(resources.size());
+        for (Resource root : roots) {
+            visit(root, seen);
+        }
+
+        List<Resource> unused = Lists.newArrayListWithExpectedSize(resources.size());
+        for (Resource resource : resources) {
+            if (!resource.isReachable()
+                    // Styles not yet handled correctly: don't mark as unused
+                    && resource.type != ResourceType.ATTR
+                    && resource.type != ResourceType.DECLARE_STYLEABLE
+                    && resource.type != ResourceType.STYLEABLE
+                    // Don't flag known service keys read by library
+                    && !TranslationDetector.isServiceKey(resource.name)) {
+                unused.add(resource);
+            }
+        }
+
+        return unused;
+    }
+
+    @SuppressWarnings("MethodMayBeStatic")
+    @NonNull
+    protected List<Resource> findRoots(@NonNull List<Resource> resources) {
+        List<Resource> roots = Lists.newArrayList();
+
+        for (Resource resource : resources) {
+            if (resource.isReachable() || resource.isKeep()) {
+                roots.add(resource);
+            }
+        }
+        return roots;
+    }
+
+    private static void visit(Resource root, Map<Resource, Boolean> seen) {
+        if (seen.containsKey(root)) {
+            return;
+        }
+        seen.put(root, Boolean.TRUE);
+        root.setReachable(true);
+        if (root.references != null) {
+            for (Resource referenced : root.references) {
+                visit(referenced, seen);
+            }
+        }
+    }
+
+    @NonNull
+    public Resource addDeclaredResource(@NonNull ResourceType type, @NonNull String name,
+            @Nullable String value, boolean declared) {
+        Resource resource = addResource(type, name, value);
+        if (declared) {
+            resource.setDeclared(true);
+        }
+        return resource;
+    }
+
+    @NonNull
+    public Resource addResource(@NonNull ResourceType type, @NonNull String name,
+            @Nullable String value) {
+        int realValue = value != null ? Integer.decode(value) : -1;
+        Resource resource = getResource(type, name);
+        if (resource != null) {
+            //noinspection VariableNotUsedInsideIf
+            if (value != null) {
+                if (resource.value == -1) {
+                    resource.value = realValue;
+                } else {
+                    assert realValue == resource.value;
+                }
+            }
+            return resource;
+        }
+
+        resource = new Resource(type, name, realValue);
+        mResources.add(resource);
+        if (realValue != -1) {
+            mValueToResource.put(realValue, resource);
+        }
+        Map<String, Resource> nameMap = mTypeToName.get(type);
+        if (nameMap == null) {
+            nameMap = Maps.newHashMapWithExpectedSize(30);
+            mTypeToName.put(type, nameMap);
+        }
+        nameMap.put(name, resource);
+
+        // TODO: Assert that we don't set the same resource multiple times to different values.
+        // Could happen if you pass in stale data!
+
+        return resource;
+    }
+
+    /**
+     * Called for a tools:keep attribute containing a resource URL where that resource name
+     * is not referencing a known resource
+     *
+     * @param value The keep value
+     */
+    private void processKeepAttributes(@NonNull String value) {
+        // TODO: When nothing matches one of these attributes, mark it as unused too!
+        // Handle comma separated lists of URLs and globs
+        if (value.indexOf(',') != -1) {
+            for (String portion : Splitter.on(',').omitEmptyStrings().trimResults().split(value)) {
+                processKeepAttributes(portion);
+            }
+            return;
+        }
+
+        ResourceUrl url = ResourceUrl.parse(value);
+        if (url == null || url.framework) {
+            return;
+        }
+
+        Resource resource = getResource(url.type, url.name);
+        if (resource != null) {
+            markReachable(resource);
+        } else if (url.name.contains("*") || url.name.contains("?")) {
+            // Look for globbing patterns
+            String regexp = DefaultConfiguration.globToRegexp(LintUtils.getFieldName(url.name));
+            try {
+                Pattern pattern = Pattern.compile(regexp);
+                Map<String, Resource> nameMap = mTypeToName.get(url.type);
+                if (nameMap != null) {
+                    for (Resource r : nameMap.values()) {
+                        if (pattern.matcher(r.name).matches()) {
+                            markReachable(r);
+                        }
+                    }
+                }
+            } catch (PatternSyntaxException ignored) {
+            }
+        }
+    }
+
+    private void processDiscardAttributes(@NonNull String value) {
+        // Handle comma separated lists of URLs and globs
+        if (value.indexOf(',') != -1) {
+            for (String portion : Splitter.on(',').omitEmptyStrings().trimResults().split(value)) {
+                processDiscardAttributes(portion);
+            }
+            return;
+        }
+
+        ResourceUrl url = ResourceUrl.parse(value);
+        if (url == null || url.framework) {
+            return;
+        }
+
+        Resource resource = getResource(url.type, url.name);
+        if (resource != null) {
+            markUnreachable(resource);
+        } else if (url.name.contains("*") || url.name.contains("?")) {
+            // Look for globbing patterns
+            String regexp = DefaultConfiguration.globToRegexp(LintUtils.getFieldName(url.name));
+            try {
+                Pattern pattern = Pattern.compile(regexp);
+                Map<String, Resource> nameMap = mTypeToName.get(url.type);
+                if (nameMap != null) {
+                    for (Resource r : nameMap.values()) {
+                        if (pattern.matcher(r.name).matches()) {
+                            markUnreachable(r);
+                        }
+                    }
+                }
+            } catch (PatternSyntaxException ignored) {
+            }
+        }
+    }
+
+    /**
+     * Recorded list of keep attributes: these can contain wildcards,
+     * so they can't be applied immediately; we have to apply them after
+     * scanning through all resources (done by {@link #processToolsAttributes()}
+     */
+    private List<String> mKeepAttributes;
+
+    /**
+     * Recorded list of discard attributes: these can contain wildcards,
+     * so they can't be applied immediately; we have to apply them after
+     * scanning through all resources (done by {@link #processToolsAttributes()}
+     */
+    private List<String> mDiscardAttributes;
+
+    private boolean mSafeMode = true;
+
+    /**
+     * Whether we should attempt to guess resources that should be kept based on looking
+     * at the string pool and assuming some of the strings can be used to dynamically construct
+     * the resource names. Can be turned off via {@code tools:shrinkMode="strict"}.
+     */
+    public boolean isSafeMode() {
+        return mSafeMode;
+    }
+
+    public void processToolsAttributes() {
+        if (mKeepAttributes != null) {
+            for (String keep : mKeepAttributes) {
+                processKeepAttributes(keep);
+            }
+        }
+        if (mDiscardAttributes != null) {
+            for (String discard : mDiscardAttributes) {
+                processDiscardAttributes(discard);
+            }
+        }
+    }
+
+    public void recordToolsAttributes(@Nullable Attr attr) {
+        if (attr == null) {
+            return;
+        }
+        String localName = attr.getLocalName();
+        String value = attr.getValue();
+        if (ATTR_KEEP.equals(localName)) {
+            if (mKeepAttributes == null) {
+                mKeepAttributes = Lists.newArrayList();
+            }
+            mKeepAttributes.add(value);
+        } else if (ATTR_DISCARD.equals(localName)) {
+            if (mDiscardAttributes == null) {
+                mDiscardAttributes = Lists.newArrayList();
+            }
+            mDiscardAttributes.add(value);
+        } else if (ATTR_SHRINK_MODE.equals(localName)) {
+            if (VALUE_STRICT.equals(value)) {
+                mSafeMode = false;
+            } else if (VALUE_SAFE.equals(value)) {
+                mSafeMode = true;
+            }
+        }
+    }
+
+    protected Resource declareResource(ResourceType type, String name, Node node) {
+        return addDeclaredResource(type, name, null, true);
+    }
+
+    @NonNull
+    protected String readText(@NonNull File file) {
+        try {
+            return Files.toString(file, UTF_8);
+        } catch (IOException ignore) {
+            return "";
+        }
+    }
+
+    public void visitBinaryResource(
+            @Nullable ResourceFolderType folderType,
+            @NonNull File file) {
+        Resource from = null;
+        if (folderType != ResourceFolderType.VALUES) {
+            // Record resource for the whole file
+            List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(
+                    folderType);
+            ResourceType type = types.get(0);
+            assert type != ResourceType.ID : folderType;
+            String name = LintUtils.getBaseName(file.getName());
+            from = declareResource(type, name, null);
+        }
+
+        if (folderType == ResourceFolderType.RAW) {
+            // Is this an HTML, CSS or JavaScript document bundled with the app?
+            // If so tokenize and look for resource references.
+            String path = file.getPath();
+            if (endsWithIgnoreCase(path, ".html") || endsWithIgnoreCase(path, ".htm")) {
+                tokenizeHtml(from, readText(file));
+            } else if (endsWithIgnoreCase(path, ".css")) {
+                tokenizeCss(from, readText(file));
+            } else if (endsWithIgnoreCase(path, ".js")) {
+                tokenizeJs(from, readText(file));
+            } else if (file.isFile() && !LintUtils.isBitmapFile(file)) {
+                tokenizeUnknownBinary(from, file);
+            }
+        }
+    }
+
+    public void visitXmlDocument(
+            @NonNull File file,
+            @Nullable ResourceFolderType folderType,
+            @NonNull Document document) {
+        if (folderType == null) {
+            // Manifest file
+            recordManifestUsages(document.getDocumentElement());
+            return;
+        }
+        Resource from = null;
+        if (folderType != ResourceFolderType.VALUES) {
+            // Record resource for the whole file
+            List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(
+                    folderType);
+            ResourceType type = types.get(0);
+            assert type != ResourceType.ID : folderType;
+            String name = LintUtils.getBaseName(file.getName());
+
+            from = declareResource(type, name, document.getDocumentElement());
+        } else if (isAnalyticsFile(file)) {
+            return;
+        }
+
+        // For value files, and drawables and colors etc also pull in resource
+        // references inside the context.file
+        recordResourceReferences(folderType, document.getDocumentElement(), from);
+
+        if (folderType == ResourceFolderType.XML) {
+            tokenizeUnknownText(readText(file));
+        }
+    }
+
+    private static final String ANALYTICS_FILE = "analytics.xml"; //$NON-NLS-1$
+
+    /**
+     * Returns true if this XML file corresponds to an Analytics configuration file;
+     * these contain some attributes read by the library which won't be flagged as
+     * used by the application
+     *
+     * @param file the file in question
+     * @return true if the file represents an analytics file
+     */
+    public static boolean isAnalyticsFile(File file) {
+        return file.getPath().endsWith(ANALYTICS_FILE) && file.getName().equals(ANALYTICS_FILE);
+    }
+
+    /**
+     * Records resource declarations and usages within an XML resource file
+     * @param folderType the type of resource file
+     * @param node the root node to start the recursive search from
+     * @param from a referencing context, if any.
+     */
+    public void recordResourceReferences(
+            @NonNull ResourceFolderType folderType,
+            @NonNull Node node,
+            @Nullable Resource from) {
+        short nodeType = node.getNodeType();
+        if (nodeType == Node.ELEMENT_NODE) {
+            Element element = (Element) node;
+            if (from != null) {
+                NamedNodeMap attributes = element.getAttributes();
+                for (int i = 0, n = attributes.getLength(); i < n; i++) {
+                    Attr attr = (Attr) attributes.item(i);
+
+                    // Ignore tools: namespace attributes, unless it's
+                    // a keep attribute
+                    if (TOOLS_URI.equals(attr.getNamespaceURI())) {
+                        recordToolsAttributes(attr);
+                        // Skip all other tools: attributes
+                        continue;
+                    }
+
+                    String value = attr.getValue();
+                    if (!(value.startsWith(PREFIX_RESOURCE_REF) || value.startsWith(PREFIX_THEME_REF))) {
+                        continue;
+                    }
+                    ResourceUrl url = ResourceUrl.parse(value);
+                    if (url != null && !url.framework) {
+                        Resource resource;
+                        if (url.create) {
+                            resource = declareResource(url.type, url.name, attr);
+                            if (!ATTR_ID.equals(attr.getLocalName()) || !ANDROID_URI.equals(attr.getNamespaceURI())) {
+                                // Declaring an id is not a reference to that id
+                                from.addReference(resource);
+                            }
+                        } else {
+                            resource = addResource(url.type, url.name, null);
+                            from.addReference(resource);
+                        }
+                    } else if (value.startsWith("@{")) {
+                        // Data binding expression: there could be multiple references here
+                        int length = value.length();
+                        int index = 2; // skip @{
+                        while (true) {
+                            index = value.indexOf('@', index);
+                            if (index == -1) {
+                                break;
+                            }
+                            // Find end of (potential) resource URL: first non resource URL character
+                            int end = index + 1;
+                            while (end < length) {
+                                char c = value.charAt(end);
+                                if (!(Character.isJavaIdentifierPart(c) ||
+                                        c == '_' ||
+                                        c == '.' ||
+                                        c == '/' ||
+                                        c == '+')) {
+                                    break;
+                                }
+                                end++;
+                            }
+                            url = ResourceUrl.parse(value.substring(index, end));
+                            if (url != null && !url.framework) {
+                                Resource resource;
+                                if (url.create) {
+                                    resource = declareResource(url.type, url.name, attr);
+                                } else {
+                                    resource = addResource(url.type, url.name, null);
+                                }
+                                from.addReference(resource);
+                            }
+
+                            index = end;
+                        }
+                    }
+                }
+
+                // Android Wear. We *could* limit ourselves to only doing this in files
+                // referenced from a manifest meta-data element, e.g.
+                // <meta-data android:name="com.google.android.wearable.beta.app"
+                //    android:resource="@xml/wearable_app_desc"/>
+                // but given that that property has "beta" in the name, it seems likely
+                // to change and therefore hardcoding it for that key risks breakage
+                // in the future.
+                if ("rawPathResId".equals(element.getTagName())) {
+                    StringBuilder sb = new StringBuilder();
+                    NodeList children = node.getChildNodes();
+                    for (int i = 0, n = children.getLength(); i < n; i++) {
+                        Node child = children.item(i);
+                        if (child.getNodeType() == Element.TEXT_NODE
+                                || child.getNodeType() == Element.CDATA_SECTION_NODE) {
+                            sb.append(child.getNodeValue());
+                        }
+                    }
+                    if (sb.length() > 0) {
+                        Resource resource = getResource(ResourceType.RAW, sb.toString().trim());
+                        from.addReference(resource);
+                    }
+                }
+            } else {
+                // Look for keep attributes everywhere else since they don't require a source
+                recordToolsAttributes(element.getAttributeNodeNS(TOOLS_URI, ATTR_KEEP));
+                recordToolsAttributes(element.getAttributeNodeNS(TOOLS_URI, ATTR_DISCARD));
+                recordToolsAttributes(element.getAttributeNodeNS(TOOLS_URI, ATTR_SHRINK_MODE));
+            }
+
+            if (folderType == ResourceFolderType.VALUES) {
+
+                Resource definition = null;
+                ResourceType type = getResourceType(element);
+                if (type != null) {
+                    String name = getFieldName(element);
+                    if (type == ResourceType.PUBLIC) {
+                        String typeName = element.getAttribute(ATTR_TYPE);
+                        if (!typeName.isEmpty()) {
+                            type = ResourceType.getEnum(typeName);
+                            if (type != null) {
+                                definition = declareResource(type, name, element);
+                                definition.setPublic(true);
+                            }
+                        }
+                    } else {
+                        definition = declareResource(type, name, element);
+                    }
+                }
+                if (definition != null) {
+                    from = definition;
+                }
+
+                String tagName = element.getTagName();
+                if (TAG_STYLE.equals(tagName)) {
+                    if (element.hasAttribute(ATTR_PARENT)) {
+                        String parent = element.getAttribute(ATTR_PARENT);
+                        if (!parent.isEmpty() && !parent.startsWith(ANDROID_STYLE_RESOURCE_PREFIX)
+                                && !parent.startsWith(PREFIX_ANDROID)) {
+                            String parentStyle = parent;
+                            if (!parentStyle.startsWith(STYLE_RESOURCE_PREFIX)) {
+                                parentStyle = STYLE_RESOURCE_PREFIX + parentStyle;
+                            }
+                            Resource ps = getResourceFromUrl(
+                                    LintUtils.getFieldName(parentStyle));
+                            if (ps != null && definition != null) {
+                                ps.addReference(definition);
+                                definition.addReference(ps);
+                            }
+                        } else if (definition != null) {
+                            // Extending a builtin theme: treat these as used
+                            markReachable(definition);
+                        }
+                    } else {
+                        // Implicit parent styles by name
+                        String name = getFieldName(element);
+                        while (true) {
+                            int index = name.lastIndexOf('_');
+                            if (index != -1) {
+                                name = name.substring(0, index);
+                                Resource ps = getResourceFromUrl(
+                                        STYLE_RESOURCE_PREFIX + LintUtils.getFieldName(name));
+                                if (ps != null && definition != null) {
+                                    ps.addReference(definition);
+                                    definition.addReference(ps);
+                                }
+                            } else {
+                                break;
+                            }
+                        }
+                    }
+                }
+
+                if (TAG_ITEM.equals(tagName)) {
+                    // In style? If so the name: attribute can be a reference
+                    if (element.getParentNode() != null
+                            && element.getParentNode().getNodeName().equals(TAG_STYLE)) {
+                        String name = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
+                        if (!name.isEmpty() && !name.startsWith("android:")) {
+                            Resource resource = getResource(ResourceType.ATTR, name);
+                            if (definition == null) {
+                                Element style = (Element) element.getParentNode();
+                                definition = getResource(style);
+                                if (definition != null) {
+                                    from = definition;
+                                    definition.addReference(resource);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        } else if (nodeType == Node.TEXT_NODE || nodeType == Node.CDATA_SECTION_NODE) {
+            String text = node.getNodeValue().trim();
+            // Why are we calling getFieldName here? That doesn't make sense! for styles I guess
+            Resource textResource = getResourceFromUrl(
+                    LintUtils.getFieldName(text));
+            if (textResource != null && from != null) {
+                from.addReference(textResource);
+            }
+        }
+
+        NodeList children = node.getChildNodes();
+        for (int i = 0, n = children.getLength(); i < n; i++) {
+            Node child = children.item(i);
+            recordResourceReferences(folderType, child, from);
+        }
+    }
+
+    public void tokenizeHtml(@Nullable Resource from, @NonNull String html) {
+        // Look for
+        //    (1) URLs of the form /android_res/drawable/foo.ext
+        //        which we will use to keep R.drawable.foo
+        // and
+        //    (2) Filenames. If the web content is loaded with something like
+        //        WebView.loadDataWithBaseURL("file:///android_res/drawable/", ...)
+        //        this is similar to Resources#getIdentifier handling where all
+        //        *potentially* aliased filenames are kept to play it safe.
+
+        // Simple HTML tokenizer
+        int length = html.length();
+        final int STATE_TEXT = 1;
+        final int STATE_SLASH = 2;
+        final int STATE_ATTRIBUTE_NAME = 3;
+        final int STATE_BEFORE_TAG = 4;
+        final int STATE_IN_TAG = 5;
+        final int STATE_BEFORE_ATTRIBUTE = 6;
+        final int STATE_ATTRIBUTE_BEFORE_EQUALS = 7;
+        final int STATE_ATTRIBUTE_AFTER_EQUALS = 8;
+        final int STATE_ATTRIBUTE_VALUE_NONE = 9;
+        final int STATE_ATTRIBUTE_VALUE_SINGLE = 10;
+        final int STATE_ATTRIBUTE_VALUE_DOUBLE = 11;
+        final int STATE_CLOSE_TAG = 12;
+
+        int state = STATE_TEXT;
+        int offset = 0;
+        int valueStart = 0;
+        int tagStart = 0;
+        String tag = null;
+        String attribute = null;
+        int attributeStart = 0;
+        int prev = -1;
+        while (offset < length) {
+            if (offset == prev) {
+                // Purely here to prevent potential bugs in the state machine from looping
+                // infinitely
+                offset++;
+            }
+            prev = offset;
+
+
+            char c = html.charAt(offset);
+
+            // MAke sure I handle doctypes properly.
+            // Make sure I handle cdata properly.
+            // Oh and what about <style> tags? tokenize everything inside as CSS!
+            // ANd <script> tag content as js!
+            switch (state) {
+                case STATE_TEXT: {
+                    if (c == '<') {
+                        state = STATE_SLASH;
+                        offset++;
+                        continue;
+                    }
+
+                    // Other text is just ignored
+                    offset++;
+                    break;
+                }
+
+                case STATE_SLASH: {
+                    if (c == '!') {
+                        if (html.startsWith("!--", offset)) {
+                            // Comment
+                            int end = html.indexOf("-->", offset + 3);
+                            if (end == -1) {
+                                offset = length;
+                                break;
+                            }
+                            offset = end + 3;
+                            continue;
+                        } else if (html.startsWith("![CDATA[", offset)) {
+                            // Skip CDATA text content; HTML text is irrelevant to this tokenizer
+                            // anyway
+                            int end = html.indexOf("]]>", offset + 8);
+                            if (end == -1) {
+                                offset = length;
+                                break;
+                            }
+                            offset = end + 3;
+                            continue;
+                        }
+                    } else if (c == '/') {
+                        state = STATE_CLOSE_TAG;
+                        offset++;
+                        continue;
+                    } else if (c == '?') {
+                        // XML Prologue
+                        int end = html.indexOf('>', offset + 2);
+                        if (end == -1) {
+                            offset = length;
+                            break;
+                        }
+                        offset = end + 1;
+                        continue;
+                    }
+                    state = STATE_IN_TAG;
+                    tagStart = offset;
+                    break;
+                }
+
+                case STATE_CLOSE_TAG: {
+                    if (c == '>') {
+                        state = STATE_TEXT;
+                    }
+                    offset++;
+                    break;
+                }
+
+                case STATE_BEFORE_TAG: {
+                    if (!Character.isWhitespace(c)) {
+                        state = STATE_IN_TAG;
+                        tagStart = offset;
+                    }
+                    // (For an end tag we'll include / in the tag name here)
+                    offset++;
+                    break;
+                }
+                case STATE_IN_TAG: {
+                    if (Character.isWhitespace(c)) {
+                        state = STATE_BEFORE_ATTRIBUTE;
+                        tag = html.substring(tagStart, offset).trim();
+                    } else if (c == '>') {
+                        tag = html.substring(tagStart, offset).trim();
+                        endHtmlTag(from, html, offset, tag);
+                        state = STATE_TEXT;
+                    }
+                    offset++;
+                    break;
+                }
+                case STATE_BEFORE_ATTRIBUTE: {
+                    if (c == '>') {
+                        endHtmlTag(from, html, offset, tag);
+                        state = STATE_TEXT;
+                    } else //noinspection StatementWithEmptyBody
+                        if (c == '/') {
+                            // we expect an '>' next to close the tag
+                        } else if (!Character.isWhitespace(c)) {
+                            state = STATE_ATTRIBUTE_NAME;
+                            attributeStart = offset;
+                        }
+                    offset++;
+                    break;
+                }
+                case STATE_ATTRIBUTE_NAME: {
+                    if (c == '>') {
+                        endHtmlTag(from, html, offset, tag);
+                        state = STATE_TEXT;
+                    } else if (c == '=') {
+                        attribute = html.substring(attributeStart, offset);
+                        state = STATE_ATTRIBUTE_AFTER_EQUALS;
+                    } else if (Character.isWhitespace(c)) {
+                        attribute = html.substring(attributeStart, offset);
+                        state = STATE_ATTRIBUTE_BEFORE_EQUALS;
+                    }
+                    offset++;
+                    break;
+                }
+                case STATE_ATTRIBUTE_BEFORE_EQUALS: {
+                    if (c == '=') {
+                        state = STATE_ATTRIBUTE_AFTER_EQUALS;
+                    } else if (c == '>') {
+                        endHtmlTag(from, html, offset, tag);
+                        state = STATE_TEXT;
+                    } else if (!Character.isWhitespace(c)) {
+                        // Attribute value not specified (used for some boolean attributes)
+                        state = STATE_ATTRIBUTE_NAME;
+                        attributeStart = offset;
+                    }
+                    offset++;
+                    break;
+                }
+
+                case STATE_ATTRIBUTE_AFTER_EQUALS: {
+                    if (c == '\'') {
+                        // a='b'
+                        state = STATE_ATTRIBUTE_VALUE_SINGLE;
+                        valueStart = offset + 1;
+                    } else if (c == '"') {
+                        // a="b"
+                        state = STATE_ATTRIBUTE_VALUE_DOUBLE;
+                        valueStart = offset + 1;
+                    } else if (!Character.isWhitespace(c)) {
+                        // a=b
+                        state = STATE_ATTRIBUTE_VALUE_NONE;
+                        valueStart = offset + 1;
+                    }
+                    offset++;
+                    break;
+                }
+
+                case STATE_ATTRIBUTE_VALUE_SINGLE: {
+                    if (c == '\'') {
+                        state = STATE_BEFORE_ATTRIBUTE;
+                        recordHtmlAttributeValue(from, tag, attribute,
+                                html.substring(valueStart, offset));
+                    }
+                    offset++;
+                    break;
+                }
+                case STATE_ATTRIBUTE_VALUE_DOUBLE: {
+                    if (c == '"') {
+                        state = STATE_BEFORE_ATTRIBUTE;
+                        recordHtmlAttributeValue(from, tag, attribute,
+                                html.substring(valueStart, offset));
+                    }
+                    offset++;
+                    break;
+                }
+                case STATE_ATTRIBUTE_VALUE_NONE: {
+                    if (c == '>') {
+                        recordHtmlAttributeValue(from, tag, attribute,
+                                html.substring(valueStart, offset));
+                        endHtmlTag(from, html, offset, tag);
+                        state = STATE_TEXT;
+                    } else if (Character.isWhitespace(c)) {
+                        state = STATE_BEFORE_ATTRIBUTE;
+                        recordHtmlAttributeValue(from, tag, attribute,
+                                html.substring(valueStart, offset));
+                    }
+                    offset++;
+                    break;
+                }
+                default:
+                    assert false : state;
+            }
+        }
+    }
+
+    private void endHtmlTag(@Nullable Resource from, @NonNull String html, int offset,
+            @Nullable String tag) {
+        if ("script".equals(tag)) {
+            int end = html.indexOf("</script>", offset + 1);
+            if (end != -1) {
+                // Attempt to tokenize the text as JavaScript
+                String js = html.substring(offset + 1, end);
+                tokenizeJs(from, js);
+            }
+        } else if ("style".equals(tag)) {
+            int end = html.indexOf("</style>", offset + 1);
+            if (end != -1) {
+                // Attempt to tokenize the text as CSS
+                String css = html.substring(offset + 1, end);
+                tokenizeCss(from, css);
+            }
+        }
+    }
+
+    public void tokenizeJs(@Nullable Resource from, @NonNull String js) {
+        // Simple JavaScript tokenizer: only looks for literal strings,
+        // and records those as string references
+        int length = js.length();
+        final int STATE_INIT = 1;
+        final int STATE_SLASH = 2;
+        final int STATE_STRING_DOUBLE = 3;
+        final int STATE_STRING_DOUBLE_QUOTED = 4;
+        final int STATE_STRING_SINGLE = 5;
+        final int STATE_STRING_SINGLE_QUOTED = 6;
+
+        int state = STATE_INIT;
+        int offset = 0;
+        int stringStart = 0;
+        int prev = -1;
+        while (offset < length) {
+            if (offset == prev) {
+                // Purely here to prevent potential bugs in the state machine from looping
+                // infinitely
+                offset++;
+            }
+            prev = offset;
+
+            char c = js.charAt(offset);
+            switch (state) {
+                case STATE_INIT: {
+                    if (c == '/') {
+                        state = STATE_SLASH;
+                    } else if (c == '"') {
+                        stringStart = offset + 1;
+                        state = STATE_STRING_DOUBLE;
+                    } else if (c == '\'') {
+                        stringStart = offset + 1;
+                        state = STATE_STRING_SINGLE;
+                    }
+                    offset++;
+                    break;
+                }
+                case STATE_SLASH: {
+                    if (c == '*') {
+                        // Comment block
+                        state = STATE_INIT;
+                        int end = js.indexOf("*/", offset + 1);
+                        if (end == -1) {
+                            offset = length; // unterminated
+                            break;
+                        }
+                        offset = end + 2;
+                        continue;
+                    } else if (c == '/') {
+                        // Line comment
+                        state = STATE_INIT;
+                        int end = js.indexOf('\n', offset + 1);
+                        if (end == -1) {
+                            offset = length;
+                            break;
+                        }
+                        offset = end + 1;
+                        continue;
+                    } else {
+                        // division - just continue
+                        state = STATE_INIT;
+                        offset++;
+                        break;
+                    }
+                }
+                case STATE_STRING_DOUBLE: {
+                    if (c == '"') {
+                        recordJsString(js.substring(stringStart, offset));
+                        state = STATE_INIT;
+                    } else if (c == '\\') {
+                        state = STATE_STRING_DOUBLE_QUOTED;
+                    }
+                    offset++;
+                    break;
+                }
+                case STATE_STRING_DOUBLE_QUOTED: {
+                    state = STATE_STRING_DOUBLE;
+                    offset++;
+                    break;
+                }
+                case STATE_STRING_SINGLE: {
+                    if (c == '\'') {
+                        recordJsString(js.substring(stringStart, offset));
+                        state = STATE_INIT;
+                    } else if (c == '\\') {
+                        state = STATE_STRING_SINGLE_QUOTED;
+                    }
+                    offset++;
+                    break;
+                }
+                case STATE_STRING_SINGLE_QUOTED: {
+                    state = STATE_STRING_SINGLE;
+                    offset++;
+                    break;
+                }
+                default:
+                    assert false : state;
+            }
+        }
+    }
+
+    public void tokenizeCss(@Nullable Resource from, @NonNull String css) {
+        // Simple CSS tokenizer: Only looks for URL references, and records those
+        // filenames. Skips everything else (unrelated to images).
+        int length = css.length();
+        final int STATE_INIT = 1;
+        final int STATE_SLASH = 2;
+        int state = STATE_INIT;
+        int offset = 0;
+        int prev = -1;
+        while (offset < length) {
+            if (offset == prev) {
+                // Purely here to prevent potential bugs in the state machine from looping
+                // infinitely
+                offset++;
+            }
+            prev = offset;
+
+            char c = css.charAt(offset);
+            switch (state) {
+                case STATE_INIT: {
+                    if (c == '/') {
+                        state = STATE_SLASH;
+                    } else if (c == 'u' && css.startsWith("url(", offset) && offset > 0) {
+                        char prevChar = css.charAt(offset-1);
+                        if (Character.isWhitespace(prevChar) || prevChar == ':') {
+                            int end = css.indexOf(')', offset);
+                            offset += 4; // skip url(
+                            while (offset < length && Character.isWhitespace(css.charAt(offset))) {
+                                offset++;
+                            }
+                            if (end != -1 && end > offset + 1) {
+                                while (end > offset
+                                        && Character.isWhitespace(css.charAt(end - 1))) {
+                                    end--;
+                                }
+                                if ((css.charAt(offset) == '"'
+                                        && css.charAt(end - 1) == '"')
+                                        || (css.charAt(offset) == '\''
+                                        && css.charAt(end - 1) == '\'')) {
+                                    // Strip " or '
+                                    offset++;
+                                    end--;
+                                }
+                                recordCssUrl(from, css.substring(offset, end).trim());
+                            }
+                            offset = end + 1;
+                            continue;
+                        }
+
+                    }
+                    offset++;
+                    break;
+                }
+                case STATE_SLASH: {
+                    if (c == '*') {
+                        // CSS comment? Skip the whole block rather than staying within the
+                        // character tokenizer.
+                        int end = css.indexOf("*/", offset + 1);
+                        if (end == -1) {
+                            offset = length;
+                            break;
+                        }
+                        offset = end + 2;
+                        continue;
+                    }
+                    state = STATE_INIT;
+                    offset++;
+                    break;
+                }
+                default:
+                    assert false : state;
+            }
+        }
+    }
+
+    private static byte[] sAndroidResBytes;
+
+    /** Look through binary/unknown files looking for resource URLs */
+    public void tokenizeUnknownBinary(@Nullable Resource from, @NonNull File file) {
+        try {
+            if (sAndroidResBytes == null) {
+                sAndroidResBytes = ANDROID_RES.getBytes(SdkConstants.UTF_8);
+            }
+            byte[] bytes = Files.toByteArray(file);
+            int index = 0;
+            while (index != -1) {
+                index = indexOf(bytes, sAndroidResBytes, index);
+                if (index != -1) {
+                    index += sAndroidResBytes.length;
+
+                    // Find the end of the URL
+                    int begin = index;
+                    int end = begin;
+                    for (; end < bytes.length; end++) {
+                        byte c = bytes[end];
+                        if (c != '/' && !Character.isJavaIdentifierPart((char)c)) {
+                            // android_res/raw/my_drawable.png => @raw/my_drawable
+                            String url = "@" + new String(bytes, begin, end - begin, UTF_8);
+                            Resource resource = getResourceFromUrl(url);
+                            if (resource != null) {
+                                if (from != null) {
+                                    from.addReference(resource);
+                                } else {
+                                    markReachable(resource);
+                                }
+                            }
+                            break;
+                        }
+                    }
+                }
+            }
+        } catch (IOException e) {
+            // Ignore
+        }
+    }
+
+    /**
+     * Returns the index of the given target array in the first array, looking from the given
+     * index
+     */
+    private static int indexOf(byte[] array, byte[] target, int fromIndex) {
+        outer:
+        for (int i = fromIndex; i < array.length - target.length + 1; i++) {
+            for (int j = 0; j < target.length; j++) {
+                if (array[i + j] != target[j]) {
+                    continue outer;
+                }
+            }
+            return i;
+        }
+        return -1;
+    }
+
+    /** Look through text files of unknown structure looking for resource URLs */
+    private void tokenizeUnknownText(@NonNull String text) {
+        int index = 0;
+        while (index != -1) {
+            index = text.indexOf(ANDROID_RES, index);
+            if (index != -1) {
+                index += ANDROID_RES.length();
+
+                // Find the end of the URL
+                int begin = index;
+                int end = begin;
+                int length = text.length();
+                for (; end < length; end++) {
+                    char c = text.charAt(end);
+                    if (c != '/' && !Character.isJavaIdentifierPart(c)) {
+                        // android_res/raw/my_drawable.png => @raw/my_drawable
+                        markReachable(getResourceFromUrl("@" + text.substring(begin, end)));
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    /** Adds the resource identifiers found in the given Java source code into the reference map */
+    public void tokenizeJavaCode(@NonNull String s) {
+        if (s.length() <= 2) {
+            return;
+        }
+
+        // Scan looking for R.{type}.name identifiers
+        // Extremely simple state machine which just avoids comments, line comments
+        // and strings, and outside of that records any R. identifiers it finds
+        int index = 0;
+        int length = s.length();
+
+        char c;
+        char next;
+        for (; index < length; index++) {
+            c = s.charAt(index);
+            if (index == length - 1) {
+                break;
+            }
+            next = s.charAt(index + 1);
+            if (Character.isWhitespace(c)) {
+                continue;
+            }
+            if (c == '/') {
+                if (next == '*') {
+                    // Block comment
+                    while (index < length - 2) {
+                        if (s.charAt(index) == '*' && s.charAt(index + 1) == '/') {
+                            break;
+                        }
+                        index++;
+                    }
+                    index++;
+                } else if (next == '/') {
+                    // Line comment
+                    while (index < length && s.charAt(index) != '\n') {
+                        index++;
+                    }
+                }
+            } else if (c == '\'') {
+                // Character
+                if (next == '\\') {
+                    // Skip '\c'
+                    index += 2;
+                } else {
+                    // Skip 'c'
+                    index++;
+                }
+            } else if (c == '\"') {
+                // String: Skip to end
+                index++;
+                while (index < length - 1) {
+                    char t = s.charAt(index);
+                    if (t == '\\') {
+                        index++;
+                    } else if (t == '"') {
+                        break;
+                    }
+                    index++;
+                }
+            } else if (c == 'R' && next == '.') {
+                // This might be a pattern
+                int begin = index;
+                index += 2;
+                while (index < length) {
+                    char t = s.charAt(index);
+                    if (t == '.') {
+                        String typeName = s.substring(begin + 2, index);
+                        ResourceType type = ResourceType.getEnum(typeName);
+                        if (type != null) {
+                            index++;
+                            begin = index;
+                            while (index < length &&
+                                    Character.isJavaIdentifierPart(s.charAt(index))) {
+                                index++;
+                            }
+                            if (index > begin) {
+                                String name = s.substring(begin, index);
+                                Resource resource = addResource(type, name, null);
+                                ResourceUsageModel.markReachable(resource);
+                            }
+                        }
+                        index--;
+                        break;
+                    } else if (!Character.isJavaIdentifierStart(t)) {
+                        break;
+                    }
+                    index++;
+                }
+            } else if (Character.isJavaIdentifierPart(c)) {
+                // Skip to the end of the identifier
+                while (index < length && Character.isJavaIdentifierPart(s.charAt(index))) {
+                    index++;
+                }
+                // Back up so the next character can be checked to see if it's a " etc
+                index--;
+            } // else just punctuation/operators ( ) ;  etc
+        }
+    }
+
+    protected void referencedString(@NonNull String string) {
+    }
+
+    private void recordCssUrl(@Nullable Resource from, @NonNull String value) {
+        if (!referencedUrl(from, value)) {
+            referencedString(value);
+        }
+    }
+
+    /**
+     * See if the given URL is a URL that we can resolve to a specific resource; if so,
+     * record it and return true, otherwise returns false.
+     */
+    private boolean referencedUrl(@Nullable Resource from, @NonNull String url) {
+        Resource resource = getResourceFromFilePath(url);
+        if (resource == null && url.indexOf('/') == -1) {
+            // URLs are often within the raw folder
+            resource = getResource(ResourceType.RAW,
+                    LintUtils.getFieldName(LintUtils.getBaseName(url)));
+        }
+        if (resource != null) {
+            if (from != null) {
+                from.addReference(resource);
+            } else {
+                // We don't have an inclusion context, so just assume this resource is reachable
+                markReachable(resource);
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+    private void recordHtmlAttributeValue(@Nullable Resource from, @Nullable String tagName,
+            @Nullable String attribute, @NonNull String value) {
+        if ("href".equals(attribute) || "src".equals(attribute)) {
+            // In general we'd need to unescape the HTML here (e.g. remove entities) but
+            // those wouldn't be valid characters in the resource name anyway
+            if (!referencedUrl(from, value)) {
+                referencedString(value);
+            }
+
+            // If this document includes another, record the reachability of that script/resource
+            if (from != null) {
+                from.addReference(getResourceFromFilePath(attribute));
+            }
+        }
+    }
+
+    private void recordJsString(@NonNull String string) {
+        referencedString(string);
+    }
+
+    public List<Resource> getResources() {
+        return mResources;
+    }
+
+    @NonNull
+    public Collection<Map<String, Resource>> getResourceMaps() {
+        return mTypeToName.values();
+    }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RestrictionsDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RestrictionsDetector.java
new file mode 100644
index 0000000..381e7c4
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RestrictionsDetector.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_TITLE;
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.STRING_PREFIX;
+import static com.android.SdkConstants.XMLNS_PREFIX;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.resources.ResourceFolderType;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.google.common.collect.Maps;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Check which makes sure that an application restrictions file is correct.
+ * The rules are specified in
+ * https://developer.android.com/reference/android/content/RestrictionsManager.html
+ */
+public class RestrictionsDetector extends ResourceXmlDetector implements JavaScanner {
+
+    // Copied from Google Play store's AppRestrictionBuilder
+    @VisibleForTesting static final int MAX_NESTING_DEPTH = 20;
+    // Copied from Google Play store's AppRestrictionBuilder
+    @VisibleForTesting static final int MAX_NUMBER_OF_NESTED_RESTRICTIONS = 1000;
+    /**
+     * Validation of {@code <restrictions>} XML elements
+     */
+    public static final Issue ISSUE = Issue.create(
+            "ValidRestrictions", //$NON-NLS-1$
+            "Invalid Restrictions Descriptor",
+
+            "Ensures that an applications restrictions XML file is properly formed",
+
+            Category.CORRECTNESS,
+            5,
+            Severity.FATAL,
+            new Implementation(
+                    RestrictionsDetector.class,
+                    Scope.RESOURCE_FILE_SCOPE))
+            .addMoreInfo("https://developer.android.com/reference/android/content/RestrictionsManager.html");
+
+    static final String TAG_RESTRICTIONS = "restrictions";
+    static final String TAG_RESTRICTION = "restriction";
+    static final String ATTR_RESTRICTION_TYPE = "restrictionType";
+    static final String ATTR_KEY = "key";
+    static final String ATTR_DESCRIPTION = "description";
+    static final String VALUE_BUNDLE = "bundle";
+    static final String VALUE_BUNDLE_ARRAY = "bundle_array";
+    static final String VALUE_CHOICE = "choice";
+    static final String VALUE_MULTI_SELECT = "multi-select";
+    static final String VALUE_ENTRIES = "entries";
+    static final String VALUE_ENTRY_VALUES = "entryValues";
+    static final String VALUE_HIDDEN = "hidden";
+    static final String VALUE_DEFAULT_VALUE = "defaultValue";
+    static final String VALUE_INTEGER = "integer";
+
+    /**
+     * Constructs a new {@link RestrictionsDetector}
+     */
+    public RestrictionsDetector() {
+    }
+
+    @Override
+    public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+        return folderType == ResourceFolderType.XML;
+    }
+
+    @Override
+    public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
+        Element root = document.getDocumentElement();
+        if (root == null) {
+            return;
+        }
+        if (!TAG_RESTRICTIONS.equals(root.getTagName())) {
+            return;
+        }
+
+        Map<String, Element> keys = Maps.newHashMap();
+        validateNestedRestrictions(context, root, null, keys, 0);
+    }
+
+    /** Validates the {@code <restriction>} <b>children</b> of the given element */
+    private static void validateNestedRestrictions(
+            @NonNull XmlContext context,
+            @NonNull  Element element,
+            @Nullable String restrictionType,
+            @NonNull  Map<String, Element> keys,
+            int depth) {
+        assert depth == 0 || restrictionType != null;
+
+        List<Element> children = LintUtils.getChildren(element);
+
+        // Only restrictions of type bundle and bundle_array can have one or multiple nested
+        // restriction elements.
+        if (depth == 0
+                || restrictionType.equals(VALUE_BUNDLE)
+                || restrictionType.equals(VALUE_BUNDLE_ARRAY)) {
+            // Bundle and bundle array should not have a default value
+            Attr defaultValue = element.getAttributeNodeNS(ANDROID_URI, VALUE_DEFAULT_VALUE);
+            if (defaultValue != null) {
+                context.report(ISSUE, element, context.getLocation(defaultValue),
+                        String.format("Restriction type `%1$s` should not have a default value",
+                                restrictionType));
+            }
+            for (Element child : children) {
+                if (verifyRestrictionTagName(context, child)) {
+                    validateRestriction(context, child, depth + 1, keys);
+                }
+            }
+
+            //noinspection StatementWithEmptyBody
+            if (depth == 0) {
+                // It's okay to have <restrictions />
+            } else if (restrictionType.equals(VALUE_BUNDLE_ARRAY)) {
+                if (children.size() != 1) {
+                    context.report(ISSUE, element, context.getLocation(element),
+                            "Expected exactly one child for restriction of type `bundle_array`");
+                }
+            } else {
+                assert restrictionType.equals(VALUE_BUNDLE);
+                if (children.isEmpty()) {
+                    context.report(ISSUE, element, context.getLocation(element),
+                            "Restriction type `bundle` should have at least one nested "
+                                    + "restriction");
+
+                }
+            }
+
+            if (children.size() > MAX_NUMBER_OF_NESTED_RESTRICTIONS) {
+                context.report(ISSUE, element, context.getLocation(element), String.format(
+                        // TODO: Reference Google Play store restriction here in error message,
+                        // e.g. that violating this will cause APK to be rejected?
+                        "Invalid nested restriction: too many nested restrictions "
+                                + "(was %1$d, max %2$d)",
+                        children.size(), MAX_NUMBER_OF_NESTED_RESTRICTIONS));
+            } else if (depth > MAX_NESTING_DEPTH) {
+                // Same comment as for MAX_NUMBER_OF_NESTED_RESTRICTIONS: include source?
+                context.report(ISSUE, element, context.getLocation(element), String.format(
+                        "Invalid nested restriction: nesting depth %1$d too large (max %2$d",
+                        depth, MAX_NESTING_DEPTH));
+            }
+        } else if (!children.isEmpty()) {
+            context.report(ISSUE, element, context.getNameLocation(element),
+                    "Only restrictions of type `bundle` and `bundle_array` can have "
+                            + "one or multiple nested restriction elements");
+        }
+    }
+
+    /** Validates a {@code <restriction>} element (and recurses to validate the children) */
+    private static void validateRestriction(@NonNull XmlContext context, Node node, int depth,
+            Map<String, Element> keys) {
+
+        if (node.getNodeType() != Node.ELEMENT_NODE) {
+            return;
+        }
+        Element element = (Element)node;
+
+        // key, title and restrictionType are mandatory.
+        String restrictionType = checkRequiredAttribute(context, element, ATTR_RESTRICTION_TYPE);
+        String key = checkRequiredAttribute(context, element, ATTR_KEY);
+        String title = checkRequiredAttribute(context, element, ATTR_TITLE);
+        if (restrictionType == null || key == null || title == null) {
+            return;
+        }
+
+        // You use each restriction's android:key attribute to read its value from a
+        // restrictions bundle. For this reason, each restriction must have a unique key string,
+        // and the string cannot be localized. It must be specified with a string literal.
+        if (key.startsWith(STRING_PREFIX)) {
+            Attr attribute = element.getAttributeNodeNS(ANDROID_URI, ATTR_KEY);
+            Location valueLocation = context.getValueLocation(attribute);
+            context.report(ISSUE, element, valueLocation,
+                    "Keys cannot be localized, they should be specified with a string literal");
+        } else if (keys.containsKey(key)) {
+            Attr thisAttribute = element.getAttributeNodeNS(ANDROID_URI, ATTR_KEY);
+            Location location = context.getValueLocation(
+                    thisAttribute);
+            Element prev = keys.get(key);
+            Attr prevAttribute = prev.getAttributeNodeNS(ANDROID_URI, ATTR_KEY);
+            Location previousLocation = context.getValueLocation(prevAttribute);
+            previousLocation.setMessage("Previous use of key here");
+            location.setSecondary(previousLocation);
+            context.report(ISSUE, element, location, String.format("Duplicate key `%1$s`", key));
+        } else {
+            keys.put(key, element);
+        }
+
+        if (restrictionType.equals(VALUE_CHOICE) || restrictionType.equals(VALUE_MULTI_SELECT)) {
+            // entries and entryValues are required if restrictionType is choice or multi-select.
+            //noinspection unused
+            boolean ok = // deliberate short circuit evaluation
+                    checkRequiredAttribute(context, element, VALUE_ENTRIES) != null
+                        || checkRequiredAttribute(context, element, VALUE_ENTRY_VALUES) != null;
+        } else if (restrictionType.equals(VALUE_HIDDEN)) {
+            // hidden type must have a defaultValue
+            checkRequiredAttribute(context, element, VALUE_DEFAULT_VALUE);
+        } else if (restrictionType.equals(VALUE_INTEGER)) {
+            Attr defaultValue = element.getAttributeNodeNS(ANDROID_URI, VALUE_DEFAULT_VALUE);
+            if (defaultValue != null && !defaultValue.getValue().startsWith(PREFIX_RESOURCE_REF)) {
+                try {
+                    //noinspection ResultOfMethodCallIgnored
+                    Integer.decode(defaultValue.getValue());
+                } catch (NumberFormatException e) {
+                    context.report(ISSUE, element, context.getValueLocation(defaultValue),
+                            "Invalid number");
+                }
+            }
+        }
+
+        validateNestedRestrictions(context, element, restrictionType, keys, depth);
+    }
+
+    /**
+     * Makes sure that the given element corresponds to a restriction tag, and if not, reports
+     * it and return false */
+    private static boolean verifyRestrictionTagName(@NonNull XmlContext context, Element element) {
+        String tagName = element.getTagName();
+        if (!tagName.equals(TAG_RESTRICTION)) {
+            context.report(ISSUE, element, context.getNameLocation(element),
+                    String.format("Unexpected tag `<%1$s>`, expected `<%2$s>`",
+                            tagName, TAG_RESTRICTION));
+            return false;
+        }
+        return true;
+    }
+
+    private static String checkRequiredAttribute(@NonNull XmlContext context, Element element,
+            String attribute) {
+        if (!element.hasAttributeNS(ANDROID_URI, attribute)) {
+            String prefix = element.getOwnerDocument().lookupNamespaceURI(ANDROID_URI);
+            if (prefix == null) {
+                Element root = element.getOwnerDocument().getDocumentElement();
+                NamedNodeMap attributes = root.getAttributes();
+                for (int i = 0, n = attributes.getLength(); i < n; i++) {
+                    Attr a = (Attr)attributes.item(i);
+                    if (a.getName().startsWith(XMLNS_PREFIX) &&
+                            ANDROID_URI.equals(a.getValue())) {
+                        prefix = a.getName().substring(XMLNS_PREFIX.length());
+                        break;
+                    }
+                }
+            }
+            if (prefix != null) {
+                attribute = prefix + ':' + attribute;
+            }
+            context.report(ISSUE, element, context.getLocation(element),
+                    // TODO: Include namespace prefix?
+                    String.format("Missing required attribute `%1$s`", attribute));
+            return null;
+        }
+        return element.getAttributeNS(ANDROID_URI, attribute);
+    }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RtlDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RtlDetector.java
index bc98129..a004cd0 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RtlDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RtlDetector.java
@@ -58,7 +58,9 @@
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
 import com.android.annotations.VisibleForTesting;
-import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedField;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
 import com.android.tools.lint.detector.api.Category;
 import com.android.tools.lint.detector.api.Context;
 import com.android.tools.lint.detector.api.Detector;
@@ -195,8 +197,8 @@
     private static final String GRAVITY_CLASS = "Gravity";                      //$NON-NLS-1$
     private static final String FQCN_GRAVITY = "android.view.Gravity";          //$NON-NLS-1$
     private static final String FQCN_GRAVITY_PREFIX = "android.view.Gravity.";  //$NON-NLS-1$
-    private static final String ATTR_SUPPORTS_RTL = "supportsRtl";              //$NON-NLS-1$
     private static final String ATTR_TEXT_ALIGNMENT = "textAlignment";          //$NON-NLS-1$
+    static final String ATTR_SUPPORTS_RTL = "supportsRtl";                      //$NON-NLS-1$
 
     /** API version in which RTL support was added */
     private static final int RTL_API = 17;
@@ -605,13 +607,14 @@
                 return false;
             }
 
-            JavaParser.ResolvedNode resolved = mContext.resolve(node);
+            ResolvedNode resolved = mContext.resolve(node);
             if (resolved != null) {
-                if (!(resolved instanceof JavaParser.ResolvedField)) {
+                if (!(resolved instanceof ResolvedField)) {
                     return false;
                 } else {
-                    JavaParser.ResolvedField field = (JavaParser.ResolvedField) resolved;
-                    if (!field.getContainingClass().matches(FQCN_GRAVITY)) {
+                    ResolvedField field = (ResolvedField) resolved;
+                    ResolvedClass containingClass = field.getContainingClass();
+                    if (containingClass == null || !containingClass.matches(FQCN_GRAVITY)) {
                         return false;
                     }
                 }
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomDetector.java
index 48b1a35..7a0b729 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomDetector.java
@@ -18,36 +18,33 @@
 
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
 import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
+import com.android.tools.lint.detector.api.ConstantEvaluator;
 import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Detector.ClassScanner;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
 import com.android.tools.lint.detector.api.Implementation;
 import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.JavaContext;
 import com.android.tools.lint.detector.api.Scope;
 import com.android.tools.lint.detector.api.Severity;
-
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.InsnList;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.objectweb.asm.tree.analysis.Analyzer;
-import org.objectweb.asm.tree.analysis.AnalyzerException;
-import org.objectweb.asm.tree.analysis.BasicInterpreter;
-import org.objectweb.asm.tree.analysis.BasicValue;
-import org.objectweb.asm.tree.analysis.Frame;
+import com.android.tools.lint.detector.api.TypeEvaluator;
 
 import java.util.Collections;
 import java.util.List;
 
+import lombok.ast.AstVisitor;
+import lombok.ast.Expression;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+
 /**
  * Checks for hardcoded seeds with random numbers.
  */
-public class SecureRandomDetector extends Detector implements ClassScanner {
+public class SecureRandomDetector extends Detector implements JavaScanner {
     /** Unregistered activities and services */
     public static final Issue ISSUE = Issue.create(
             "SecureRandom", //$NON-NLS-1$
@@ -61,101 +58,81 @@
             Severity.WARNING,
             new Implementation(
                     SecureRandomDetector.class,
-                    Scope.CLASS_FILE_SCOPE))
+                    Scope.JAVA_FILE_SCOPE))
             .addMoreInfo("http://developer.android.com/reference/java/security/SecureRandom.html");
 
     private static final String SET_SEED = "setSeed"; //$NON-NLS-1$
-    static final String OWNER_SECURE_RANDOM = "java/security/SecureRandom"; //$NON-NLS-1$
-    private static final String OWNER_RANDOM = "java/util/Random"; //$NON-NLS-1$
-    private static final String VM_SECURE_RANDOM = 'L' + OWNER_SECURE_RANDOM + ';';
-    /** Method description for a method that takes a long argument (no return type specified */
-    private static final String LONG_ARG = "(J)"; //$NON-NLS-1$
+    public static final String JAVA_SECURITY_SECURE_RANDOM = "java.security.SecureRandom";
+    public static final String JAVA_UTIL_RANDOM = "java.util.Random";
 
     /** Constructs a new {@link SecureRandomDetector} */
     public SecureRandomDetector() {
     }
 
-    // ---- Implements ClassScanner ----
+    // ---- Implements JavaScanner ----
 
-    @Override
     @Nullable
-    public List<String> getApplicableCallNames() {
+    @Override
+    public List<String> getApplicableMethodNames() {
         return Collections.singletonList(SET_SEED);
     }
 
     @Override
-    public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
-            @NonNull MethodNode method, @NonNull MethodInsnNode call) {
-        String owner = call.owner;
-        String desc = call.desc;
-        if (owner.equals(OWNER_SECURE_RANDOM)) {
-            if (desc.startsWith(LONG_ARG)) {
-                checkValidSetSeed(context, call);
-            } else if (desc.startsWith("([B)")) { //$NON-NLS-1$
-                // setSeed(byte[]) ...
-                // We could do some flow analysis here to see whether the byte array getting
-                // passed in appears to be fixed.
-                // However, people calling this constructor rather than the simpler one
-                // with a fixed integer are probably less likely to make that mistake... right?
-            }
-        } else if (owner.equals(OWNER_RANDOM) && desc.startsWith(LONG_ARG)) {
-            // Called setSeed(long) on an instanceof a Random object. Flag this if the instance
-            // is likely a SecureRandom.
-
-            // Track allocations such that we know whether the type of the call
-            // is on a SecureRandom rather than a Random
-            Analyzer analyzer = new Analyzer(new BasicInterpreter() {
-                @Override
-                public BasicValue newValue(Type type) {
-                    if (type != null && type.getDescriptor().equals(VM_SECURE_RANDOM)) {
-                        return new BasicValue(type);
+    public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+            @NonNull MethodInvocation call) {
+        ResolvedNode resolved = context.resolve(call);
+        if (!(resolved instanceof ResolvedMethod)) {
+            return;
+        }
+        ResolvedMethod method = (ResolvedMethod) resolved;
+        Expression seedArgument = call.astArguments().first();
+        if (seedArgument == null) {
+            return;
+        }
+        ResolvedClass containingClass = method.getContainingClass();
+        if (containingClass.matches(JAVA_SECURITY_SECURE_RANDOM) ||
+                containingClass.isSubclassOf(JAVA_UTIL_RANDOM, false)
+                        && isSecureRandomReceiver(context, call)) {
+            // Called with a fixed seed?
+            Object seed = ConstantEvaluator.evaluate(context, seedArgument);
+            //noinspection VariableNotUsedInsideIf
+            if (seed != null) {
+                context.report(ISSUE, call, context.getLocation(call),
+                        "Do not call `setSeed()` on a `SecureRandom` with a fixed seed: " +
+                                "it is not secure. Use `getSeed()`.");
+            } else {
+                // Called with a simple System.currentTimeMillis() seed or something like that?
+                ResolvedNode resolvedArgument = context.resolve(seedArgument);
+                if (resolvedArgument instanceof ResolvedMethod) {
+                    ResolvedMethod seedMethod = (ResolvedMethod) resolvedArgument;
+                    String methodName = seedMethod.getName();
+                    if (methodName.equals("currentTimeMillis") || methodName
+                            .equals("nanoTime")) {
+                        context.report(ISSUE, call, context.getLocation(call),
+                                "It is dangerous to seed `SecureRandom` with the current "
+                                        + "time because that value is more predictable to "
+                                        + "an attacker than the default seed.");
                     }
-                    return super.newValue(type);
                 }
-            });
-            try {
-                Frame[] frames = analyzer.analyze(classNode.name, method);
-                InsnList instructions = method.instructions;
-                Frame frame = frames[instructions.indexOf(call)];
-                int stackSlot = frame.getStackSize();
-                for (Type type : Type.getArgumentTypes(desc)) {
-                    stackSlot -= type.getSize();
-                }
-                BasicValue stackValue = (BasicValue) frame.getStack(stackSlot);
-                Type type = stackValue.getType();
-                if (type != null && type.getDescriptor().equals(VM_SECURE_RANDOM)) {
-                    checkValidSetSeed(context, call);
-                }
-            } catch (AnalyzerException e) {
-                context.log(e, null);
             }
-        } else if (owner.equals(OWNER_RANDOM) && desc.startsWith(LONG_ARG)) {
-            // Called setSeed(long) on an instanceof a Random object. Flag this if the instance
-            // is likely a SecureRandom.
-            // TODO
         }
     }
 
-    private static void checkValidSetSeed(ClassContext context, MethodInsnNode call) {
-        assert call.name.equals(SET_SEED);
+    /**
+     * Returns true if the given invocation is assigned a SecureRandom type
+     */
+    private static boolean isSecureRandomReceiver(@NonNull JavaContext context,
+            @NonNull MethodInvocation call) {
+        Expression operand = call.astOperand();
+        return operand != null && isSecureRandomType(context, operand);
+    }
 
-        // Make sure the argument passed is not a literal
-        AbstractInsnNode prev = LintUtils.getPrevInstruction(call);
-        if (prev == null) {
-            return;
-        }
-        int opcode = prev.getOpcode();
-        if (opcode == Opcodes.LCONST_0 || opcode == Opcodes.LCONST_1 || opcode == Opcodes.LDC) {
-            context.report(ISSUE, context.getLocation(call),
-                    "Do not call `setSeed()` on a `SecureRandom` with a fixed seed: " +
-                    "it is not secure. Use `getSeed()`.");
-        } else if (opcode == Opcodes.INVOKESTATIC) {
-            String methodName = ((MethodInsnNode) prev).name;
-            if (methodName.equals("currentTimeMillis") || methodName.equals("nanoTime")) {
-                context.report(ISSUE, context.getLocation(call),
-                        "It is dangerous to seed `SecureRandom` with the current time because " +
-                        "that value is more predictable to an attacker than the default seed.");
-            }
-        }
+    /**
+     * Returns true if the node evaluates to an instance of type SecureRandom
+     */
+    private static boolean isSecureRandomType(@NonNull JavaContext context, @NonNull Node node) {
+        TypeDescriptor type = TypeEvaluator.evaluate(context, node);
+        return type != null && type.matchesName(JAVA_SECURITY_SECURE_RANDOM);
+
     }
 }
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomGeneratorDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomGeneratorDetector.java
index fc3bd2f..cb6504a 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomGeneratorDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomGeneratorDetector.java
@@ -17,7 +17,6 @@
 package com.android.tools.lint.checks;
 
 import static com.android.SdkConstants.CONSTRUCTOR_NAME;
-import static com.android.tools.lint.checks.SecureRandomDetector.OWNER_SECURE_RANDOM;
 
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
@@ -49,6 +48,7 @@
  * Checks for pseudo random number generator initialization issues
  */
 public class SecureRandomGeneratorDetector extends Detector implements ClassScanner {
+    private static final String OWNER_SECURE_RANDOM = "java/security/SecureRandom"; //$NON-NLS-1$
 
     @SuppressWarnings("SpellCheckingInspection")
     private static final String BLOG_URL
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecurityDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecurityDetector.java
index 9071724..35a5334 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecurityDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecurityDetector.java
@@ -38,7 +38,11 @@
 
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
 import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ConstantEvaluator;
 import com.android.tools.lint.detector.api.Context;
 import com.android.tools.lint.detector.api.Detector;
 import com.android.tools.lint.detector.api.Implementation;
@@ -134,10 +138,36 @@
             Severity.WARNING,
             IMPLEMENTATION_MANIFEST);
 
+    /** Using java.io.File.setReadable(true, false) to set file world-readable */
+    public static final Issue SET_READABLE = Issue.create(
+            "SetWorldReadable",
+            "`File.setReadable()` used to make file world-readable",
+            "Setting files world-readable is very dangerous, and likely to cause security " +
+            "holes in applications. It is strongly discouraged; instead, applications should " +
+            "use more formal mechanisms for interactions such as `ContentProvider`, " +
+            "`BroadcastReceiver`, and `Service`.",
+            Category.SECURITY,
+            6,
+            Severity.WARNING,
+            IMPLEMENTATION_JAVA);
+
+    /** Using java.io.File.setWritable(true, false) to set file world-writable */
+    public static final Issue SET_WRITABLE = Issue.create(
+            "SetWorldWritable",
+            "`File.setWritable()` used to make file world-writable",
+            "Setting files world-writable is very dangerous, and likely to cause security " +
+            "holes in applications. It is strongly discouraged; instead, applications should " +
+            "use more formal mechanisms for interactions such as `ContentProvider`, " +
+            "`BroadcastReceiver`, and `Service`.",
+            Category.SECURITY,
+            6,
+            Severity.WARNING,
+            IMPLEMENTATION_JAVA);
+
     /** Using the world-writable flag */
     public static final Issue WORLD_WRITEABLE = Issue.create(
             "WorldWriteableFiles", //$NON-NLS-1$
-            "`openFileOutput()` call passing `MODE_WORLD_WRITEABLE`",
+            "`openFileOutput()` or similar call passing `MODE_WORLD_WRITEABLE`",
             "There are cases where it is appropriate for an application to write " +
             "world writeable files, but these should be reviewed carefully to " +
             "ensure that they contain no private data, and that if the file is " +
@@ -152,7 +182,7 @@
     /** Using the world-readable flag */
     public static final Issue WORLD_READABLE = Issue.create(
             "WorldReadableFiles", //$NON-NLS-1$
-            "`openFileOutput()` call passing `MODE_WORLD_READABLE`",
+            "`openFileOutput()` or similar call passing `MODE_WORLD_READABLE`",
             "There are cases where it is appropriate for an application to write " +
             "world readable files, but these should be reviewed carefully to " +
             "ensure that they contain no private data that is leaked to other " +
@@ -162,6 +192,8 @@
             Severity.WARNING,
             IMPLEMENTATION_JAVA);
 
+    private static final String FILE_CLASS = "java.io.File"; //$NON-NLS-1$
+
     /** Constructs a new {@link SecurityDetector} check */
     public SecurityDetector() {
     }
@@ -361,17 +393,53 @@
 
     @Override
     public List<String> getApplicableMethodNames() {
-        // These are the API calls that can accept a MODE_WORLD_READABLE/MODE_WORLD_WRITABLE
+        // These are the API calls that can accept a MODE_WORLD_READABLE/MODE_WORLD_WRITEABLE
         // argument.
-        List<String> values = new ArrayList<String>(2);
+        List<String> values = new ArrayList<String>(3);
         values.add("openFileOutput"); //$NON-NLS-1$
         values.add("getSharedPreferences"); //$NON-NLS-1$
+        values.add("getDir"); //$NON-NLS-1$
+        // These API calls can be used to set files world-readable or world-writable
+        values.add("setReadable"); //$NON-NLS-1$
+        values.add("setWritable"); //$NON-NLS-1$
         return values;
     }
 
     @Override
     public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
             @NonNull MethodInvocation node) {
+        ResolvedNode resolved = context.resolve(node);
+        if (resolved instanceof ResolvedMethod) {
+            ResolvedClass resolvedClass = ((ResolvedMethod) resolved).getContainingClass();
+            String methodName = node.astName().astValue();
+            if (resolvedClass.isSubclassOf(FILE_CLASS, false)) {
+                // Report calls to java.io.File.setReadable(true, false) or
+                // java.io.File.setWritable(true, false)
+                if ("setReadable".equals(methodName)) {
+                    if (node.astArguments().size() == 2 &&
+                            Boolean.TRUE.equals(ConstantEvaluator.evaluate(context,
+                                    node.astArguments().first())) &&
+                            Boolean.FALSE.equals(ConstantEvaluator.evaluate(context,
+                                    node.astArguments().last()))) {
+                        context.report(SET_READABLE, node, context.getLocation(node),
+                                "Setting file permissions to world-readable can be " +
+                                        "risky, review carefully");
+                    }
+                    return;
+                } else if ("setWritable".equals(methodName)) {
+                    if (node.astArguments().size() == 2 &&
+                            Boolean.TRUE.equals(ConstantEvaluator.evaluate(context,
+                                    node.astArguments().first())) &&
+                            Boolean.FALSE.equals(ConstantEvaluator.evaluate(context,
+                                    node.astArguments().last()))) {
+                        context.report(SET_WRITABLE, node, context.getLocation(node),
+                                "Setting file permissions to world-writable can be " +
+                                        "risky, review carefully");
+                    }
+                    return;
+                }
+            }
+        }
         StrictListAccessor<Expression,MethodInvocation> args = node.astArguments();
         for (Expression arg : args) {
             arg.accept(visitor);
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ServiceCastDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ServiceCastDetector.java
index f11eaf4..71541e8 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ServiceCastDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ServiceCastDetector.java
@@ -129,7 +129,7 @@
     @NonNull
     private static Map<String, String> getServiceMap() {
         if (sServiceMap == null) {
-            final int EXPECTED_SIZE = 49;
+            final int EXPECTED_SIZE = 55;
             sServiceMap = Maps.newHashMapWithExpectedSize(EXPECTED_SIZE);
 
             sServiceMap.put("ACCESSIBILITY_SERVICE", "android.view.accessibility.AccessibilityManager");
@@ -143,13 +143,15 @@
             sServiceMap.put("BLUETOOTH_SERVICE", "android.bluetooth.BluetoothManager");
             sServiceMap.put("CAMERA_SERVICE", "android.hardware.camera2.CameraManager");
             sServiceMap.put("CAPTIONING_SERVICE", "android.view.accessibility.CaptioningManager");
-            sServiceMap.put("CLIPBOARD_SERVICE", "android.text.ClipboardManager");
+            sServiceMap.put("CARRIER_CONFIG_SERVICE", "android.telephony.CarrierConfigManager");
+            sServiceMap.put("CLIPBOARD_SERVICE", "android.text.ClipboardManager"); // also allow @Deprecated android.content.ClipboardManager
             sServiceMap.put("CONNECTIVITY_SERVICE", "android.net.ConnectivityManager");
             sServiceMap.put("CONSUMER_IR_SERVICE", "android.hardware.ConsumerIrManager");
             sServiceMap.put("DEVICE_POLICY_SERVICE", "android.app.admin.DevicePolicyManager");
             sServiceMap.put("DISPLAY_SERVICE", "android.hardware.display.DisplayManager");
             sServiceMap.put("DOWNLOAD_SERVICE", "android.app.DownloadManager");
             sServiceMap.put("DROPBOX_SERVICE", "android.os.DropBoxManager");
+            sServiceMap.put("FINGERPRINT_SERVICE", "android.hardware.fingerprint.FingerprintManager");
             sServiceMap.put("INPUT_METHOD_SERVICE", "android.view.inputmethod.InputMethodManager");
             sServiceMap.put("INPUT_SERVICE", "android.hardware.input.InputManager");
             sServiceMap.put("JOB_SCHEDULER_SERVICE", "android.app.job.JobScheduler");
@@ -160,6 +162,8 @@
             sServiceMap.put("MEDIA_PROJECTION_SERVICE", "android.media.projection.MediaProjectionManager");
             sServiceMap.put("MEDIA_ROUTER_SERVICE", "android.media.MediaRouter");
             sServiceMap.put("MEDIA_SESSION_SERVICE", "android.media.session.MediaSessionManager");
+            sServiceMap.put("MIDI_SERVICE", "android.media.midi.MidiManager");
+            sServiceMap.put("NETWORK_STATS_SERVICE", "android.app.usage.NetworkStatsManager");
             sServiceMap.put("NFC_SERVICE", "android.nfc.NfcManager");
             sServiceMap.put("NOTIFICATION_SERVICE", "android.app.NotificationManager");
             sServiceMap.put("NSD_SERVICE", "android.net.nsd.NsdManager");
@@ -171,9 +175,11 @@
             sServiceMap.put("STORAGE_SERVICE", "android.os.storage.StorageManager");
             sServiceMap.put("TELECOM_SERVICE", "android.telecom.TelecomManager");
             sServiceMap.put("TELEPHONY_SERVICE", "android.telephony.TelephonyManager");
+            sServiceMap.put("TELEPHONY_SUBSCRIPTION_SERVICE", "android.telephony.SubscriptionManager");
             sServiceMap.put("TEXT_SERVICES_MANAGER_SERVICE", "android.view.textservice.TextServicesManager");
             sServiceMap.put("TV_INPUT_SERVICE", "android.media.tv.TvInputManager");
             sServiceMap.put("UI_MODE_SERVICE", "android.app.UiModeManager");
+            sServiceMap.put("USAGE_STATS_SERVICE", "android.app.usage.UsageStatsManager");
             sServiceMap.put("USB_SERVICE", "android.hardware.usb.UsbManager");
             sServiceMap.put("USER_SERVICE", "android.os.UserManager");
             sServiceMap.put("VIBRATOR_SERVICE", "android.os.Vibrator");
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SetTextDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SetTextDetector.java
new file mode 100644
index 0000000..fc55166
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SetTextDetector.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2014 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.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+
+import java.util.Collections;
+import java.util.List;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.BinaryExpression;
+import lombok.ast.BinaryOperator;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+import lombok.ast.StringLiteral;
+
+/**
+ * Checks for errors related to TextView#setText and internationalization
+ */
+public class SetTextDetector extends Detector implements JavaScanner {
+
+    private static final Implementation IMPLEMENTATION = new Implementation(
+            SetTextDetector.class,
+            Scope.JAVA_FILE_SCOPE);
+
+    /** Constructing SimpleDateFormat without an explicit locale */
+    public static final Issue SET_TEXT_I18N = Issue.create(
+            "SetTextI18n", //$NON-NLS-1$
+            "TextView Internationalization",
+
+            "When calling `TextView#setText`\n"  +
+            "* Never call `Number#toString()` to format numbers; it will not handle fraction " +
+            "separators and locale-specific digits properly. Consider using `String#format` " +
+            "with proper format specifications (`%d` or `%f`) instead.\n" +
+            "* Do not pass a string literal (e.g. \"Hello\") to display text. Hardcoded " +
+            "text can not be properly translated to other languages. Consider using Android " +
+            "resource strings instead.\n" +
+            "* Do not build messages by concatenating text chunks. Such messages can not be " +
+            "properly translated.",
+
+            Category.I18N,
+            6,
+            Severity.WARNING,
+            IMPLEMENTATION)
+            .addMoreInfo("http://developer.android.com/guide/topics/resources/localization.html");
+
+
+    private static final String METHOD_NAME = "setText";
+    private static final String TO_STRING_NAME = "toString";
+    private static final String CHAR_SEQUENCE_CLS = "java.lang.CharSequence";
+    private static final String NUMBER_CLS = "java.lang.Number";
+    private static final String TEXT_VIEW_CLS = "android.widget.TextView";
+
+    // Pattern to match string literal that require translation. These are those having word
+    // characters in it.
+    private static final String WORD_PATTERN = ".*\\w{2,}.*";
+
+    /** Constructs a new {@link SetTextDetector} */
+    public SetTextDetector() {
+    }
+
+    // ---- Implements JavaScanner ----
+
+    @Nullable
+    @Override
+    public List<String> getApplicableMethodNames() {
+        return Collections.singletonList(METHOD_NAME);
+    }
+
+    @Override
+    public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+            @NonNull MethodInvocation call) {
+        ResolvedMethod method = (ResolvedMethod) context.resolve(call);
+        if (method != null && method.getContainingClass().matches(TEXT_VIEW_CLS)
+                && method.matches(METHOD_NAME)
+                && method.getArgumentCount() > 0
+                && method.getArgumentType(0).matchesSignature(CHAR_SEQUENCE_CLS)) {
+            checkNode(context, call.astArguments().first());
+        }
+    }
+
+    private static void checkNode(JavaContext context, Node node) {
+        if (node instanceof StringLiteral) {
+            if (((StringLiteral) node).astValue().matches(WORD_PATTERN)) {
+                context.report(SET_TEXT_I18N, node, context.getLocation(node),
+                        "String literal in `setText` can not be translated. Use Android "
+                                + "resources instead.");
+            }
+        } else if (node instanceof MethodInvocation) {
+            ResolvedMethod rm = (ResolvedMethod) context.resolve(node);
+            if (rm != null && rm.getName().matches(TO_STRING_NAME)) {
+                ResolvedClass superClass = rm.getContainingClass().getSuperClass();
+                if (superClass != null && superClass.matches(NUMBER_CLS)) {
+                    context.report(SET_TEXT_I18N, node, context.getLocation(node),
+                            "Number formatting does not take into account locale settings. " +
+                                    "Consider using `String.format` instead.");
+                }
+            }
+        } else if (node instanceof BinaryExpression) {
+            BinaryExpression expression = (BinaryExpression) node;
+            if (expression.astOperator() == BinaryOperator.PLUS) {
+                context.report(SET_TEXT_I18N, node, context.getLocation(node),
+                    "Do not concatenate text displayed with `setText`. "
+                            + "Use resource string with placeholders.");
+            }
+            checkNode(context, expression.astLeft());
+            checkNode(context, expression.astRight());
+        }
+    }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SharedPrefsDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SharedPrefsDetector.java
index 2310d23..3b0bd09 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SharedPrefsDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SharedPrefsDetector.java
@@ -22,7 +22,7 @@
 
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
 import com.android.tools.lint.detector.api.Category;
 import com.android.tools.lint.detector.api.Context;
 import com.android.tools.lint.detector.api.Detector;
@@ -164,7 +164,7 @@
                 }
                 allowCommitBeforeTarget = true;
             } else {
-                return;
+                allowCommitBeforeTarget = false;
             }
         } else {
             if (!verifiedType) {
@@ -250,6 +250,24 @@
         public boolean visitMethodInvocation(MethodInvocation node) {
             if (node == mTarget) {
                 mSeenTarget = true;
+
+                // Look for chained calls on the target node
+                Node parent = node.getParent();
+                while (parent instanceof MethodInvocation) {
+                    MethodInvocation methodInvocation = (MethodInvocation) parent;
+                    String name = methodInvocation.astName().astValue();
+                    boolean isCommit = "commit".equals(name);
+                    if (isCommit || "apply".equals(name)) {
+                        mFound = true;
+                        if (isCommit) {
+                            suggestApplyIfApplicable(node);
+                        }
+                        break;
+                    }
+
+                    parent = parent.getParent();
+                }
+
             } else if (mAllowCommitBeforeTarget || mSeenTarget || node.astOperand() == mTarget) {
                 String name = node.astName().astValue();
                 boolean isCommit = "commit".equals(name);
@@ -258,50 +276,8 @@
                     // on the right type of object?
                     mFound = true;
 
-                    ResolvedNode resolved = mContext.resolve(node);
-                    if (resolved instanceof JavaParser.ResolvedMethod) {
-                        ResolvedMethod method = (ResolvedMethod) resolved;
-                        JavaParser.ResolvedClass clz = method.getContainingClass();
-                        if (clz.isSubclassOf("android.content.SharedPreferences.Editor", false)
-                                && mContext.getProject().getMinSdkVersion().getApiLevel() >= 9) {
-                            // See if the return value is read: can only replace commit with
-                            // apply if the return value is not considered
-                            Node parent = node.getParent();
-                            boolean returnValueIgnored = false;
-                            if (parent instanceof MethodDeclaration ||
-                                    parent instanceof ConstructorDeclaration ||
-                                    parent instanceof ClassDeclaration ||
-                                    parent instanceof Block ||
-                                    parent instanceof ExpressionStatement) {
-                                returnValueIgnored = true;
-                            } else if (parent instanceof Statement) {
-                                if (parent instanceof If) {
-                                    returnValueIgnored = ((If) parent).astCondition() != node;
-                                } else if (parent instanceof Return) {
-                                    returnValueIgnored = false;
-                                } else if (parent instanceof VariableDeclaration) {
-                                    returnValueIgnored = false;
-                                } else if (parent instanceof For) {
-                                    returnValueIgnored = ((For) parent).astCondition() != node;
-                                } else if (parent instanceof While) {
-                                    returnValueIgnored = ((While) parent).astCondition() != node;
-                                } else if (parent instanceof DoWhile) {
-                                    returnValueIgnored = ((DoWhile) parent).astCondition() != node;
-                                } else if (parent instanceof Case) {
-                                    returnValueIgnored = ((Case) parent).astCondition() != node;
-                                } else if (parent instanceof Assert) {
-                                    returnValueIgnored = ((Assert) parent).astAssertion() != node;
-                                } else {
-                                    returnValueIgnored = true;
-                                }
-                            }
-                            if (returnValueIgnored && isCommit) {
-                                String message = "Consider using `apply()` instead; `commit` writes "
-                                        + "its data to persistent storage immediately, whereas "
-                                        + "`apply` will handle it in the background";
-                                mContext.report(ISSUE, node, mContext.getLocation(node), message);
-                            }
-                        }
+                    if (isCommit) {
+                        suggestApplyIfApplicable(node);
                     }
                 }
             }
@@ -309,6 +285,54 @@
             return super.visitMethodInvocation(node);
         }
 
+        private void suggestApplyIfApplicable(MethodInvocation node) {
+            ResolvedNode resolved = mContext.resolve(node);
+            if (resolved instanceof ResolvedMethod) {
+                ResolvedMethod method = (ResolvedMethod) resolved;
+                ResolvedClass clz = method.getContainingClass();
+                if (clz.isSubclassOf("android.content.SharedPreferences.Editor", false)
+                        && mContext.getProject().getMinSdkVersion().getApiLevel() >= 9) {
+                    // See if the return value is read: can only replace commit with
+                    // apply if the return value is not considered
+                    Node parent = node.getParent();
+                    boolean returnValueIgnored = false;
+                    if (parent instanceof MethodDeclaration ||
+                            parent instanceof ConstructorDeclaration ||
+                            parent instanceof ClassDeclaration ||
+                            parent instanceof Block ||
+                            parent instanceof ExpressionStatement) {
+                        returnValueIgnored = true;
+                    } else if (parent instanceof Statement) {
+                        if (parent instanceof If) {
+                            returnValueIgnored = ((If) parent).astCondition() != node;
+                        } else if (parent instanceof Return) {
+                            returnValueIgnored = false;
+                        } else if (parent instanceof VariableDeclaration) {
+                            returnValueIgnored = false;
+                        } else if (parent instanceof For) {
+                            returnValueIgnored = ((For) parent).astCondition() != node;
+                        } else if (parent instanceof While) {
+                            returnValueIgnored = ((While) parent).astCondition() != node;
+                        } else if (parent instanceof DoWhile) {
+                            returnValueIgnored = ((DoWhile) parent).astCondition() != node;
+                        } else if (parent instanceof Case) {
+                            returnValueIgnored = ((Case) parent).astCondition() != node;
+                        } else if (parent instanceof Assert) {
+                            returnValueIgnored = ((Assert) parent).astAssertion() != node;
+                        } else {
+                            returnValueIgnored = true;
+                        }
+                    }
+                    if (returnValueIgnored) {
+                        String message = "Consider using `apply()` instead; `commit` writes "
+                                + "its data to persistent storage immediately, whereas "
+                                + "`apply` will handle it in the background";
+                        mContext.report(ISSUE, node, mContext.getLocation(node), message);
+                    }
+                }
+            }
+        }
+
         @Override
         public boolean visitReturn(Return node) {
             if (node.astValue() == mTarget) {
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SslCertificateSocketFactoryDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SslCertificateSocketFactoryDetector.java
new file mode 100644
index 0000000..393458a
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SslCertificateSocketFactoryDetector.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2015 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.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+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 java.util.Arrays;
+import java.util.List;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.Expression;
+import lombok.ast.MethodInvocation;
+import lombok.ast.StrictListAccessor;
+
+public class SslCertificateSocketFactoryDetector extends Detector
+        implements Detector.JavaScanner {
+
+    private static final Implementation IMPLEMENTATION_JAVA = new Implementation(
+            SslCertificateSocketFactoryDetector.class,
+            Scope.JAVA_FILE_SCOPE);
+
+    public static final Issue CREATE_SOCKET = Issue.create(
+            "SSLCertificateSocketFactoryCreateSocket", //$NON-NLS-1$
+            "Insecure call to `SSLCertificateSocketFactory.createSocket()`",
+            "When `SSLCertificateSocketFactory.createSocket()` is called with an `InetAddress` " +
+            "as the first parameter, TLS/SSL hostname verification is not performed, which " +
+            "could result in insecure network traffic caused by trusting arbitrary " +
+            "hostnames in TLS/SSL certificates presented by peers. In this case, developers " +
+            "must ensure that the `InetAddress` is explicitly verified against the certificate " +
+            "through other means, such as by calling " +
+            "`SSLCertificateSocketFactory.getDefaultHostnameVerifier() to get a " +
+            "`HostnameVerifier` and calling `HostnameVerifier.verify()`.",
+            Category.SECURITY,
+            6,
+            Severity.WARNING,
+            IMPLEMENTATION_JAVA);
+
+    public static final Issue GET_INSECURE = Issue.create(
+            "SSLCertificateSocketFactoryGetInsecure", //$NON-NLS-1$
+            "Call to `SSLCertificateSocketFactory.getInsecure()`",
+            "The `SSLCertificateSocketFactory.getInsecure()` method returns " +
+            "an SSLSocketFactory with all TLS/SSL security checks disabled, which " +
+            "could result in insecure network traffic caused by trusting arbitrary " +
+            "TLS/SSL certificates presented by peers. This method should be " +
+            "avoided unless needed for a special circumstance such as " +
+            "debugging. Instead, `SSLCertificateSocketFactory.getDefault()` " +
+            "should be used.",
+            Category.SECURITY,
+            6,
+            Severity.WARNING,
+            IMPLEMENTATION_JAVA);
+
+    private static final String INET_ADDRESS_CLASS =
+            "java.net.InetAddress";
+
+    private static final String SSL_CERTIFICATE_SOCKET_FACTORY_CLASS =
+            "android.net.SSLCertificateSocketFactory";
+
+    @NonNull
+    @Override
+    public Speed getSpeed() {
+        return Speed.FAST;
+    }
+
+    // ---- Implements Detector.JavaScanner ----
+
+    @Override
+    public List<String> getApplicableMethodNames() {
+        // Detect calls to potentially insecure SSLCertificateSocketFactory methods
+        return Arrays.asList("createSocket", "getInsecure");
+    }
+
+    @Override
+    public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+            @NonNull MethodInvocation node) {
+        ResolvedNode resolved = context.resolve(node);
+        if (resolved instanceof ResolvedMethod) {
+            String methodName = node.astName().astValue();
+            ResolvedClass resolvedClass = ((ResolvedMethod) resolved).getContainingClass();
+            if (resolvedClass.isSubclassOf(SSL_CERTIFICATE_SOCKET_FACTORY_CLASS, false)) {
+                if ("createSocket".equals(methodName)) {
+                    StrictListAccessor<Expression, MethodInvocation> argumentList =
+                            node.astArguments();
+                    if (argumentList != null) {
+                        TypeDescriptor firstParameterType = context.getType(argumentList.first());
+                        if (firstParameterType != null) {
+                            ResolvedClass firstParameterClass = firstParameterType.getTypeClass();
+                            if (firstParameterClass != null &&
+                                    firstParameterClass.isSubclassOf(INET_ADDRESS_CLASS, false)) {
+                                context.report(CREATE_SOCKET, node, context.getLocation(node),
+                                        "Use of `SSLCertificateSocketFactory.createSocket()` " +
+                                        "with an InetAddress parameter can cause insecure " +
+                                        "network traffic due to trusting arbitrary hostnames in " +
+                                        "TLS/SSL certificates presented by peers");
+                            }
+                        }
+                    }
+                } else if ("getInsecure".equals(methodName)) {
+                    context.report(GET_INSECURE, node, context.getLocation(node),
+                            "Use of `SSLCertificateSocketFactory.getInsecure()` can cause " +
+                            "insecure network traffic due to trusting arbitrary TLS/SSL " +
+                            "certificates presented by peers");
+                }
+            }
+        }
+    }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/StringFormatDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/StringFormatDetector.java
index 6b902a5..6031729 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/StringFormatDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/StringFormatDetector.java
@@ -17,13 +17,15 @@
 package com.android.tools.lint.checks;
 
 import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.CLASS_CONTEXT;
+import static com.android.SdkConstants.CLASS_FRAGMENT;
+import static com.android.SdkConstants.CLASS_RESOURCES;
+import static com.android.SdkConstants.CLASS_V4_FRAGMENT;
 import static com.android.SdkConstants.DOT_JAVA;
 import static com.android.SdkConstants.FORMAT_METHOD;
 import static com.android.SdkConstants.GET_STRING_METHOD;
 import static com.android.SdkConstants.R_CLASS;
-import static com.android.SdkConstants.R_PREFIX;
 import static com.android.SdkConstants.TAG_STRING;
-import static com.android.tools.lint.checks.SharedPrefsDetector.ANDROID_CONTENT_SHARED_PREFERENCES;
 import static com.android.tools.lint.client.api.JavaParser.TYPE_BOOLEAN;
 import static com.android.tools.lint.client.api.JavaParser.TYPE_BYTE;
 import static com.android.tools.lint.client.api.JavaParser.TYPE_CHAR;
@@ -45,7 +47,9 @@
 import com.android.ide.common.res2.ResourceItem;
 import com.android.resources.ResourceFolderType;
 import com.android.resources.ResourceType;
-import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
 import com.android.tools.lint.client.api.LintClient;
 import com.android.tools.lint.detector.api.Category;
 import com.android.tools.lint.detector.api.Context;
@@ -62,6 +66,7 @@
 import com.android.tools.lint.detector.api.Severity;
 import com.android.tools.lint.detector.api.XmlContext;
 import com.android.utils.Pair;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
@@ -85,6 +90,9 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import lombok.ast.ArrayCreation;
+import lombok.ast.ArrayDimension;
+import lombok.ast.ArrayInitializer;
 import lombok.ast.AstVisitor;
 import lombok.ast.BooleanLiteral;
 import lombok.ast.CharLiteral;
@@ -219,8 +227,7 @@
     private Map<String, List<Pair<Handle, String>>> mFormatStrings;
 
     /**
-     * Map of strings that contain percents that aren't formatting strings; these
-     * should not be passed to String.format.
+     * Map of strings that do not contain any formatting.
      */
     private final Map<String, Handle> mNotFormatStrings = new HashMap<String, Handle>();
 
@@ -301,7 +308,7 @@
     }
 
     private void checkTextNode(XmlContext context, Element element, String text) {
-        String name = null;
+        String name = element.getAttribute(ATTR_NAME);
         boolean found = false;
         boolean foundPlural = false;
 
@@ -313,10 +320,6 @@
                 j++;
             }
             if (c == '%') {
-                if (name == null) {
-                    name = element.getAttribute(ATTR_NAME);
-                }
-
                 // Also make sure this String isn't an unformatted String
                 String formatted = element.getAttribute("formatted"); //$NON-NLS-1$
                 if (!formatted.isEmpty() && !Boolean.parseBoolean(formatted)) {
@@ -375,25 +378,29 @@
             }
         }
 
-        if (found && name != null) {
-            if (!context.getProject().getReportIssues()) {
-                // If this is a library project not being analyzed, ignore it
-                return;
-            }
+        if (!context.getProject().getReportIssues()) {
+            // If this is a library project not being analyzed, ignore it
+            return;
+        }
 
-            // Record it for analysis when seen in Java code
-            if (mFormatStrings == null) {
-                mFormatStrings = new HashMap<String, List<Pair<Handle,String>>>();
-            }
-
-            List<Pair<Handle, String>> list = mFormatStrings.get(name);
-            if (list == null) {
-                list = new ArrayList<Pair<Handle, String>>();
-                mFormatStrings.put(name, list);
-            }
+        if (name != null) {
             Handle handle = context.createLocationHandle(element);
             handle.setClientData(element);
-            list.add(Pair.of(handle, text));
+            if (found) {
+                // Record it for analysis when seen in Java code
+                if (mFormatStrings == null) {
+                    mFormatStrings = new HashMap<String, List<Pair<Handle, String>>>();
+                }
+
+                List<Pair<Handle, String>> list = mFormatStrings.get(name);
+                if (list == null) {
+                    list = new ArrayList<Pair<Handle, String>>();
+                    mFormatStrings.put(name, list);
+                }
+                list.add(Pair.of(handle, text));
+            } else {
+                mNotFormatStrings.put(name, handle);
+            }
         }
     }
 
@@ -483,6 +490,11 @@
 
                 // Check argument counts
                 if (checkCount) {
+                    Handle notFormatted = mNotFormatStrings.get(name);
+                    if (notFormatted != null) {
+                        list = ImmutableList.<Pair<Handle, String>>builder()
+                                .add(Pair.of(notFormatted, name)).addAll(list).build();
+                    }
                     checkArity(context, name, list);
                 }
 
@@ -1007,54 +1019,114 @@
             return;
         }
 
+        ResolvedNode resolved = context.resolve(node);
+        if (!(resolved instanceof ResolvedMethod)) {
+            return;
+        }
+        ResolvedMethod method = (ResolvedMethod) resolved;
         String methodName = node.astName().astValue();
         if (methodName.equals(FORMAT_METHOD)) {
-            // String.format(getResources().getString(R.string.foo), arg1, arg2, ...)
-            // Check that the arguments in R.string.foo match arg1, arg2, ...
-            if (node.astOperand() instanceof VariableReference) {
-                VariableReference ref = (VariableReference) node.astOperand();
-                if ("String".equals(ref.astIdentifier().astValue())) { //$NON-NLS-1$
-                    // Found a String.format call
-                    // Look inside to see if we can find an R string
-                    // Find surrounding method
-                    checkFormatCall(context, node);
-                }
+            if (method.getContainingClass().matches(TYPE_STRING)) {
+                // Check formatting parameters for
+                //   java.lang.String#format(String format, Object... formatArgs)
+                //   java.lang.String#format(Locale locale, String format, Object... formatArgs)
+                checkFormatCall(context, node, method.getArgumentCount() == 3);
+
+                // TODO: Consider also enforcing
+                // java.util.Formatter#format(String string, Object... formatArgs)
             }
         } else {
-            // getResources().getString(R.string.foo, arg1, arg2, ...)
-            // Check that the arguments in R.string.foo match arg1, arg2, ...
-            if (node.astArguments().size() > 1 && node.astOperand() != null ) {
-                checkFormatCall(context, node);
+            // Look up any of these string formatting methods:
+            // android.content.res.Resources#getString(@StringRes int resId, Object... formatArgs)
+            // android.content.Context#getString(@StringRes int resId, Object... formatArgs)
+            // android.app.Fragment#getString(@StringRes int resId, Object... formatArgs)
+            // android.support.v4.app.Fragment#getString(@StringRes int resId, Object... formatArgs)
+
+            // Many of these also define a plain getString method:
+            // android.content.res.Resources#getString(@StringRes int resId)
+            // However, while it's possible that these contain formatting strings) it's
+            // also possible that they're looking up strings that are not intended to be used
+            // for formatting so while we may want to warn about this it's not necessarily
+            // an error.
+            if (method.getArgumentCount() < 2) {
+                return;
             }
+
+            ResolvedClass containingClass = method.getContainingClass();
+            if (containingClass.isSubclassOf(CLASS_RESOURCES, false) ||
+                    containingClass.isSubclassOf(CLASS_CONTEXT, false) ||
+                    containingClass.isSubclassOf(CLASS_FRAGMENT, false) ||
+                    containingClass.isSubclassOf(CLASS_V4_FRAGMENT, false)) {
+                checkFormatCall(context, node, false);
+            }
+
+            // TODO: Consider also looking up
+            // android.content.res.Resources#getQuantityString(@PluralsRes int id, int quantity,
+            //              Object... formatArgs)
+            // though this will require being smarter about cross referencing formatting
+            // strings since we'll need to go via the quantity string definitions
         }
     }
 
-    private void checkFormatCall(JavaContext context, MethodInvocation node) {
+    private void checkFormatCall(JavaContext context, MethodInvocation node,
+            boolean specifiesLocale) {
         lombok.ast.Node current = getParentMethod(node);
         if (current != null) {
-            checkStringFormatCall(context, current, node);
+            checkStringFormatCall(context, current, node, specifiesLocale);
         }
     }
 
     /**
-     * Check the given String.format call (with the given arguments) to see if
-     * the string format is being used correctly
-     *
+     * Checks a String.format call that is using a string that doesn't contain format placeholders.
      * @param context the context to report errors to
-     * @param method the method containing the {@link String#format} call
      * @param call the AST node for the {@link String#format}
+     * @param name the string name
+     * @param handle the string location
+     */
+    private static void checkNotFormattedHandle(
+            JavaContext context,
+            MethodInvocation call,
+            String name,
+            Handle handle) {
+        Object clientData = handle.getClientData();
+        if (clientData instanceof Node) {
+            if (context.getDriver().isSuppressed(null, INVALID, (Node) clientData)) {
+                return;
+            }
+        }
+        Location location = context.getLocation(call);
+        Location secondary = handle.resolve();
+        secondary.setMessage("This definition does not require arguments");
+        location.setSecondary(secondary);
+        String message = String.format(
+                "Format string '`%1$s`' is not a valid format string so it should not be " +
+                        "passed to `String.format`",
+                name);
+        context.report(INVALID, call, location, message);
+    }
+
+    /**
+     * Check the given String.format call (with the given arguments) to see if the string format is
+     * being used correctly
+     *
+     * @param context         the context to report errors to
+     * @param method          the method containing the {@link String#format} call
+     * @param call            the AST node for the {@link String#format}
+     * @param specifiesLocale whether the first parameter is a locale string, shifting the
+     *                        formatting string to the second argument
      */
     private void checkStringFormatCall(
             JavaContext context,
             lombok.ast.Node method,
-            MethodInvocation call) {
+            MethodInvocation call,
+            boolean specifiesLocale) {
 
         StrictListAccessor<Expression, MethodInvocation> args = call.astArguments();
         if (args.isEmpty()) {
             return;
         }
 
-        StringTracker tracker = new StringTracker(context, method, call, 0);
+        StringTracker tracker = new StringTracker(context, method, call, specifiesLocale ? 1 : 0);
         method.accept(tracker);
         String name = tracker.getFormatStringName();
         if (name == null) {
@@ -1065,44 +1137,44 @@
             return;
         }
 
-        if (mNotFormatStrings.containsKey(name)) {
-            Handle handle = mNotFormatStrings.get(name);
-            Object clientData = handle.getClientData();
-            if (clientData instanceof Node) {
-                if (context.getDriver().isSuppressed(null, INVALID, (Node) clientData)) {
+        boolean passingVarArgsArray = false;
+        int callCount = args.size() - 1 - (specifiesLocale ? 1 : 0);
+        if (callCount == 1) {
+            // If instead of a varargs call like
+            //    getString(R.string.foo, arg1, arg2, arg3)
+            // the code is calling the varargs method with a packed Object array, as in
+            //    getString(R.string.foo, new Object[] { arg1, arg2, arg3 })
+            // we'll need to handle that such that we don't think this is a single
+            // argument
+            TypeDescriptor type = context.getType(args.last());
+            if (type != null && type.isArray() && !type.isPrimitive()) {
+                boolean knownArity = false;
+                if (args.last() instanceof ArrayCreation) {
+                    ArrayCreation creation = (ArrayCreation) args.last();
+                    ArrayInitializer initializer = creation.astInitializer();
+                    if (initializer != null) {
+                        callCount = initializer.astExpressions().size();
+                        knownArity = true;
+                    } else if (creation.astDimensions() != null
+                            && creation.astDimensions().size() == 1) {
+                        ArrayDimension first = creation.astDimensions().first();
+                        Expression expression = first.astDimension();
+                        if (expression instanceof IntegralLiteral) {
+                            callCount = ((IntegralLiteral) expression).astIntValue();
+                            knownArity = true;
+                        }
+                    }
+                }
+                if (!knownArity) {
                     return;
                 }
+                passingVarArgsArray = true;
             }
-            Location location = handle.resolve();
-            String message = String.format(
-                    "Format string '`%1$s`' is not a valid format string so it should not be " +
-                    "passed to `String.format`",
-                    name);
-            context.report(INVALID, call, location, message);
-            return;
         }
 
-        Iterator<Expression> argIterator = args.iterator();
-        Expression first = argIterator.next();
-        Expression second = argIterator.hasNext() ? argIterator.next() : null;
-
-        boolean specifiesLocale;
-        TypeDescriptor parameterType = context.getType(first);
-        if (parameterType != null) {
-            specifiesLocale = isLocaleReference(parameterType.getName());
-        } else if (!call.astName().astValue().equals(FORMAT_METHOD)) {
-            specifiesLocale = false;
-        } else {
-            // No type information with this AST; use string patterns instead to make
-            // an educated guess
-            String firstName = first.toString();
-            specifiesLocale = firstName.startsWith("Locale.")                     //$NON-NLS-1$
-                    || firstName.contains("locale")                               //$NON-NLS-1$
-                    || firstName.equals("null")                                   //$NON-NLS-1$
-                    || (second != null && second.toString().contains("getString") //$NON-NLS-1$
-                        && !firstName.contains("getString")                       //$NON-NLS-1$
-                        && !firstName.contains(R_PREFIX)
-                        && !(first instanceof StringLiteral));
+        if (callCount > 0 && mNotFormatStrings.containsKey(name)) {
+            checkNotFormattedHandle(context, call, name, mNotFormatStrings.get(name));
+            return;
         }
 
         List<Pair<Handle, String>> list = mFormatStrings != null ? mFormatStrings.get(name) : null;
@@ -1112,8 +1184,13 @@
                     !context.getScope().contains(Scope.RESOURCE_FILE)) {
                 AbstractResourceRepository resources = client
                         .getProjectResources(context.getMainProject(), true);
-                List<ResourceItem> items = resources
-                        .getResourceItem(ResourceType.STRING, name);
+                List<ResourceItem> items;
+                if (resources != null) {
+                    items = resources.getResourceItem(ResourceType.STRING, name);
+                } else {
+                    // Must be a non-Android module
+                    items = null;
+                }
                 if (items != null) {
                     for (final ResourceItem item : items) {
                         ResourceValue v = item.getResourceValue(false);
@@ -1148,6 +1225,7 @@
                                     // If the user marked the string with
                                 }
 
+                                Handle handle = client.createResourceItemHandle(item);
                                 if (isFormattingString) {
                                     if (list == null) {
                                         list = Lists.newArrayList();
@@ -1156,8 +1234,9 @@
                                         }
                                         mFormatStrings.put(name, list);
                                     }
-                                    Handle handle = client.createResourceItemHandle(item);
                                     list.add(Pair.of(handle, value));
+                                } else if (callCount > 0) {
+                                    checkNotFormattedHandle(context, call, name, handle);
                                 }
                             }
                         }
@@ -1177,11 +1256,7 @@
                 }
                 int count = getFormatArgumentCount(s, null);
                 Handle handle = pair.getFirst();
-                if (count != args.size() - 1 - (specifiesLocale ? 1 : 0)) {
-                    if (isSharedPreferenceGetString(context, call)) {
-                        continue;
-                    }
-
+                if (count != callCount) {
                     Location location = context.getLocation(call);
                     Location secondary = handle.resolve();
                     secondary.setMessage(String.format("This definition requires %1$d arguments",
@@ -1190,13 +1265,18 @@
                     String message = String.format(
                             "Wrong argument count, format string `%1$s` requires `%2$d` but format " +
                             "call supplies `%3$d`",
-                            name, count, args.size() - 1 - (specifiesLocale ? 1 : 0));
+                            name, count, callCount);
                     context.report(ARG_TYPES, method, location, message);
                     if (reported == null) {
                         reported = Sets.newHashSet();
                     }
                     reported.add(s);
                 } else {
+                    if (passingVarArgsArray) {
+                        // Can't currently check these: make sure we don't incorrectly
+                        // flag parameters on the Object[] instead of the wrapped parameters
+                        return;
+                    }
                     for (int i = 1; i <= count; i++) {
                         int argumentIndex = i + (specifiesLocale ? 1 : 0);
                         Class<?> type = tracker.getArgumentType(argumentIndex);
@@ -1261,10 +1341,6 @@
                             }
 
                             if (!valid) {
-                                if (isSharedPreferenceGetString(context, call)) {
-                                    continue;
-                                }
-
                                 Expression argument = tracker.getArgument(argumentIndex);
                                 Location location = context.getLocation(argument);
                                 Location secondary = handle.resolve();
@@ -1290,22 +1366,6 @@
         }
     }
 
-    private static boolean isSharedPreferenceGetString(@NonNull JavaContext context,
-            @NonNull MethodInvocation call) {
-        if (!GET_STRING_METHOD.equals(call.astName().astValue())) {
-            return false;
-        }
-
-        JavaParser.ResolvedNode resolved = context.resolve(call);
-        if (resolved instanceof JavaParser.ResolvedMethod) {
-            JavaParser.ResolvedMethod resolvedMethod = (JavaParser.ResolvedMethod) resolved;
-            JavaParser.ResolvedClass containingClass = resolvedMethod.getContainingClass();
-            return containingClass.isSubclassOf(ANDROID_CONTENT_SHARED_PREFERENCES, false);
-        }
-
-        return false; // not certain
-    }
-
     private static boolean isLocaleReference(@Nullable TypeDescriptor reference) {
         return reference != null && isLocaleReference(reference.getName());
     }
@@ -1525,13 +1585,45 @@
                 // See if we're on the right hand side of an assignment
                 lombok.ast.Node current = node.getParent().getParent();
                 String reference = ((Select) current).astIdentifier().astValue();
-
+                lombok.ast.Node prev = current;
                 while (current != mTop && !(current instanceof VariableDefinitionEntry)) {
                     if (current == mTargetNode) {
-                        mName = reference;
-                        mDone = true;
-                        return false;
+                        // Make sure the reference we found was part of the
+                        // target parameter (e.g. for a string format check,
+                        // the actual formatting string, not one of the arguments
+                        // supplied to the formatting string)
+                        boolean isParameterArg = false;
+                        Iterator<Expression> iterator = null;
+                        if (mTargetNode instanceof MethodInvocation) {
+                            MethodInvocation call = (MethodInvocation) mTargetNode;
+                            iterator = call.astArguments().iterator();
+                        } else if (mTargetNode instanceof ConstructorInvocation) {
+                            ConstructorInvocation call = (ConstructorInvocation) mTargetNode;
+                            iterator = call.astArguments().iterator();
+                        }
+                        if (iterator != null) {
+                            Expression arg = null;
+                            for (int i = 0; i <= mArgIndex; i++) {
+                                if (iterator.hasNext()) {
+                                    arg = iterator.next();
+                                } else {
+                                    arg = null;
+                                }
+                            }
+                            if (arg == prev) {
+                                isParameterArg = true;
+                            }
+                        } else {
+                            // Constructor?
+                            isParameterArg = true;
+                        }
+                        if (isParameterArg) {
+                            mName = reference;
+                            mDone = true;
+                            return false;
+                        }
                     }
+                    prev = current;
                     current = current.getParent();
                 }
                 if (current instanceof VariableDefinitionEntry) {
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SupportAnnotationDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SupportAnnotationDetector.java
index d34759a..672c637 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SupportAnnotationDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SupportAnnotationDetector.java
@@ -26,6 +26,8 @@
 import static com.android.SdkConstants.SUPPORT_ANNOTATIONS_PREFIX;
 import static com.android.SdkConstants.TAG_PERMISSION;
 import static com.android.SdkConstants.TAG_USES_PERMISSION;
+import static com.android.SdkConstants.TAG_USES_PERMISSION_SDK_23;
+import static com.android.SdkConstants.TAG_USES_PERMISSION_SDK_M;
 import static com.android.SdkConstants.TYPE_DEF_FLAG_ATTRIBUTE;
 import static com.android.resources.ResourceType.COLOR;
 import static com.android.resources.ResourceType.DRAWABLE;
@@ -35,6 +37,8 @@
 import static com.android.tools.lint.checks.PermissionFinder.Operation.WRITE;
 import static com.android.tools.lint.checks.PermissionRequirement.ATTR_PROTECTION_LEVEL;
 import static com.android.tools.lint.checks.PermissionRequirement.VALUE_DANGEROUS;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_INT;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_LONG;
 import static com.android.tools.lint.detector.api.JavaContext.findSurroundingMethod;
 import static com.android.tools.lint.detector.api.JavaContext.getParentOfType;
 
@@ -45,6 +49,7 @@
 import com.android.tools.lint.checks.PermissionFinder.Operation;
 import com.android.tools.lint.checks.PermissionFinder.Result;
 import com.android.tools.lint.checks.PermissionHolder.SetPermissionLookup;
+import com.android.tools.lint.client.api.JavaParser;
 import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
 import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
 import com.android.tools.lint.client.api.JavaParser.ResolvedField;
@@ -61,8 +66,9 @@
 import com.android.tools.lint.detector.api.Project;
 import com.android.tools.lint.detector.api.Scope;
 import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.TextFormat;
 import com.android.utils.XmlUtils;
-import com.google.common.collect.Lists;
+import com.google.common.base.Joiner;
 import com.google.common.collect.Sets;
 
 import org.w3c.dom.Document;
@@ -70,9 +76,11 @@
 import org.w3c.dom.NodeList;
 
 import java.io.File;
+import java.lang.reflect.Modifier;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.EnumSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.ListIterator;
@@ -301,53 +309,85 @@
         }
     }
 
-    private void checkParameterAnnotation(
+    private void checkParameterAnnotations(
             @NonNull JavaContext context,
             @NonNull Node argument,
             @NonNull Node call,
             @NonNull ResolvedMethod method,
-            @NonNull ResolvedAnnotation annotation,
-            @NonNull Iterable<ResolvedAnnotation> allAnnotations) {
-        String signature = annotation.getSignature();
+            @NonNull Iterable<ResolvedAnnotation> annotations) {
+        boolean handledResourceTypes = false;
+        for (ResolvedAnnotation annotation : annotations) {
+            String signature = annotation.getSignature();
 
-        if (COLOR_INT_ANNOTATION.equals(signature)) {
-            checkColor(context, argument);
-        } else if (signature.equals(INT_RANGE_ANNOTATION)) {
-            checkIntRange(context, annotation, argument, allAnnotations);
-        } else if (signature.equals(FLOAT_RANGE_ANNOTATION)) {
-            checkFloatRange(context, annotation, argument);
-        } else if (signature.equals(SIZE_ANNOTATION)) {
-            checkSize(context, annotation, argument);
-        } else if (signature.startsWith(PERMISSION_ANNOTATION)) {
-            // PERMISSION_ANNOTATION, PERMISSION_ANNOTATION_READ, PERMISSION_ANNOTATION_WRITE
-            // When specified on a parameter, that indicates that we're dealing with
-            // a permission requirement on this *method* which depends on the value
-            // supplied by this parameter
-            checkParameterPermission(context, signature, call, method, argument);
-        } else {
-            // We only run @IntDef, @StringDef and @<Type>Res checks if we're not
-            // running inside Android Studio / IntelliJ where there are already inspections
-            // covering the same warnings (using IntelliJ's own data flow analysis); we
-            // don't want to (a) create redundant warnings or (b) work harder than we
-            // have to
-            if (signature.equals(INT_DEF_ANNOTATION)) {
-                boolean flag = annotation.getValue(TYPE_DEF_FLAG_ATTRIBUTE) == Boolean.TRUE;
-                checkTypeDefConstant(context, annotation, argument, null, flag, allAnnotations);
-            } else if (signature.equals(STRING_DEF_ANNOTATION)) {
-                checkTypeDefConstant(context, annotation, argument, null, false, allAnnotations);
-            } else if (signature.endsWith(RES_SUFFIX)) {
-                String typeString = signature.substring(SUPPORT_ANNOTATIONS_PREFIX.length(),
-                        signature.length() - RES_SUFFIX.length()).toLowerCase(Locale.US);
-                ResourceType type = ResourceType.getEnum(typeString);
-                if (type != null) {
-                    checkResourceType(context, argument, type);
-                } else if (typeString.equals("any")) { // @AnyRes
-                    checkResourceType(context, argument, null);
+            if (COLOR_INT_ANNOTATION.equals(signature)) {
+                checkColor(context, argument);
+            } else if (signature.equals(INT_RANGE_ANNOTATION)) {
+                checkIntRange(context, annotation, argument, annotations);
+            } else if (signature.equals(FLOAT_RANGE_ANNOTATION)) {
+                checkFloatRange(context, annotation, argument);
+            } else if (signature.equals(SIZE_ANNOTATION)) {
+                checkSize(context, annotation, argument);
+            } else if (signature.startsWith(PERMISSION_ANNOTATION)) {
+                // PERMISSION_ANNOTATION, PERMISSION_ANNOTATION_READ, PERMISSION_ANNOTATION_WRITE
+                // When specified on a parameter, that indicates that we're dealing with
+                // a permission requirement on this *method* which depends on the value
+                // supplied by this parameter
+                checkParameterPermission(context, signature, call, method, argument);
+            } else {
+                // We only run @IntDef, @StringDef and @<Type>Res checks if we're not
+                // running inside Android Studio / IntelliJ where there are already inspections
+                // covering the same warnings (using IntelliJ's own data flow analysis); we
+                // don't want to (a) create redundant warnings or (b) work harder than we
+                // have to
+                if (signature.equals(INT_DEF_ANNOTATION)) {
+                    boolean flag = annotation.getValue(TYPE_DEF_FLAG_ATTRIBUTE) == Boolean.TRUE;
+                    checkTypeDefConstant(context, annotation, argument, null, flag,
+                            annotations);
+                } else if (signature.equals(STRING_DEF_ANNOTATION)) {
+                    checkTypeDefConstant(context, annotation, argument, null, false,
+                            annotations);
+                } else if (signature.endsWith(RES_SUFFIX)) {
+                    if (handledResourceTypes) {
+                        continue;
+                    }
+                    handledResourceTypes = true;
+                    EnumSet<ResourceType> types = null;
+                    // Handle all resource type annotations in one go: there could be multiple
+                    // resource type annotations specified on the same element; we need to
+                    // know about them all up front.
+                    for (ResolvedAnnotation a : annotations) {
+                        String s = a.getSignature();
+                        if (s.endsWith(RES_SUFFIX)) {
+                            String typeString = s.substring(SUPPORT_ANNOTATIONS_PREFIX.length(),
+                                    s.length() - RES_SUFFIX.length()).toLowerCase(Locale.US);
+                            ResourceType type = ResourceType.getEnum(typeString);
+                            if (type != null) {
+                                if (types == null) {
+                                    types = EnumSet.of(type);
+                                } else {
+                                    types.add(type);
+                                }
+                            } else if (typeString.equals("any")) { // @AnyRes
+                                types = getAnyRes();
+                                break;
+                            }
+                        }
+                    }
+
+                    if (types != null) {
+                        checkResourceType(context, argument, types);
+                    }
                 }
             }
         }
     }
 
+    private static EnumSet<ResourceType> getAnyRes() {
+        EnumSet<ResourceType> types = EnumSet.allOf(ResourceType.class);
+        types.remove(COLOR_INT_MARKER_TYPE);
+        return types;
+    }
+
     private void checkParameterPermission(
             @NonNull JavaContext context,
             @NonNull String signature,
@@ -385,9 +425,10 @@
             return;
         }
 
-        List<ResourceType> types = getResourceTypes(context, argument);
+        EnumSet<ResourceType> types = getResourceTypes(context, argument);
 
-        if (types != null && types.contains(ResourceType.COLOR)) {
+        if (types != null && types.contains(ResourceType.COLOR)
+                && !isIgnoredInIde(COLOR_USAGE, context, argument)) {
             String message = String.format(
                     "Should pass resolved color instead of resource id here: " +
                             "`getResources().getColor(%1$s)`", argument.toString());
@@ -395,6 +436,17 @@
         }
     }
 
+    private static boolean isIgnoredInIde(@NonNull Issue issue, @NonNull JavaContext context,
+            @NonNull Node node) {
+        // Historically, the IDE would treat *all* support annotation warnings as
+        // handled by the id "ResourceType", so look for that id too for issues
+        // deliberately suppressed prior to Android Studio 2.0.
+        Issue synonym = Issue.create("ResourceType", issue.getBriefDescription(TextFormat.RAW),
+                issue.getExplanation(TextFormat.RAW), issue.getCategory(), issue.getPriority(),
+                issue.getDefaultSeverity(), issue.getImplementation());
+        return context.getDriver().isSuppressed(context, synonym, node);
+    }
+
     private void checkPermission(
             @NonNull JavaContext context,
             @NonNull Node node,
@@ -410,6 +462,9 @@
             // annotations in the surrounding context
             permissions  = addLocalPermissions(context, permissions, node);
             if (!requirement.isSatisfied(permissions)) {
+                if (isIgnoredInIde(MISSING_PERMISSION, context, node)) {
+                    return;
+                }
                 Operation operation;
                 String name;
                 if (result != null) {
@@ -436,13 +491,14 @@
                 if (tryCatch == null) {
                     break;
                 } else {
+                    JavaParser parser = context.getParser();
                     for (Catch aCatch : tryCatch.astCatches()) {
-                        TypeReference catchType = aCatch.astExceptionDeclaration().
-                                astTypeReference();
-                        if (isSecurityException(context,
-                                catchType)) {
-                            handlesMissingPermission = true;
-                            break;
+                        for (TypeDescriptor catchType : parser.getCatchTypes(context, aCatch)) {
+                            if (isSecurityException(context,
+                                    catchType)) {
+                                handlesMissingPermission = true;
+                                break;
+                            }
                         }
                     }
                     parent = tryCatch;
@@ -455,7 +511,7 @@
                 MethodDeclaration declaration = getParentOfType(parent, MethodDeclaration.class);
                 if (declaration != null) {
                     for (TypeReference typeReference : declaration.astThrownTypeReferences()) {
-                        if (isSecurityException(context, typeReference)) {
+                        if (isSecurityException(context, context.getType(typeReference))) {
                             handlesMissingPermission = true;
                             break;
                         }
@@ -474,7 +530,7 @@
                 }
             }
 
-            if (!handlesMissingPermission) {
+            if (!handlesMissingPermission && !isIgnoredInIde(MISSING_PERMISSION, context, node)) {
                 String message = getUnhandledPermissionMessage();
                 context.report(MISSING_PERMISSION, node, context.getLocation(node), message);
             }
@@ -527,8 +583,8 @@
     /** Returns the error message shown when a revocable permission call is not properly handled */
     public static String getUnhandledPermissionMessage() {
         return "Call requires permission which may be rejected by user: code should explicitly "
-                + "check to see if permission is available (with `checkPermission`) or handle "
-                + "a potential `SecurityException`";
+                + "check to see if permission is available (with `checkPermission`) or explicitly "
+                + "handle a potential `SecurityException`";
     }
 
     /**
@@ -579,12 +635,11 @@
 
     private static boolean isSecurityException(
             @NonNull JavaContext context,
-            @NonNull TypeReference typeReference) {
-        TypeDescriptor type = context.getType(typeReference);
-        return type != null && (type.matchesSignature("java.lang.SecurityException") ||
-                type.matchesSignature("java.lang.RuntimeException") ||
-                type.matchesSignature("java.lang.Exception") ||
-                type.matchesSignature("java.lang.Throwable"));
+            @Nullable TypeDescriptor type) {
+        // In earlier versions we checked not just for java.lang.SecurityException but
+        // any super type as well, however that probably hides warnings in cases where
+        // users don't want that; see http://b.android.com/182165
+        return type != null && type.matchesSignature("java.lang.SecurityException");
     }
 
     private PermissionHolder mPermissions;
@@ -635,7 +690,9 @@
                 continue;
             }
             String nodeName = item.getNodeName();
-            if (nodeName.equals(TAG_USES_PERMISSION)) {
+            if (nodeName.equals(TAG_USES_PERMISSION)
+                || nodeName.equals(TAG_USES_PERMISSION_SDK_23)
+                || nodeName.equals(TAG_USES_PERMISSION_SDK_M)) {
                 Element element = (Element)item;
                 String name = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
                 if (!name.isEmpty()) {
@@ -671,6 +728,10 @@
                 issue = CHECK_PERMISSION;
             }
 
+            if (isIgnoredInIde(issue, context, node)) {
+                return;
+            }
+
             String message = String.format("The result of `%1$s` is not used",
                     methodName);
             if (suggested != null) {
@@ -690,7 +751,8 @@
             @NonNull ResolvedMethod method,
             @NonNull String annotation) {
         String threadContext = getThreadContext(context, node);
-        if (threadContext != null && !isCompatibleThread(threadContext, annotation)) {
+        if (threadContext != null && !isCompatibleThread(threadContext, annotation)
+                && !isIgnoredInIde(THREAD, context, node)) {
             String message = String.format("Method %1$s must be called from the `%2$s` thread, currently inferred thread is `%3$s` thread",
                     method.getName(), describeThread(annotation), describeThread(threadContext));
             context.report(THREAD, node, context.getLocation(node), message);
@@ -802,25 +864,29 @@
     private static void checkResourceType(
             @NonNull JavaContext context,
             @NonNull Node argument,
-            @Nullable ResourceType expectedType) {
-        List<ResourceType> actual = getResourceTypes(context, argument);
+            @NonNull EnumSet<ResourceType> expectedType) {
+        EnumSet<ResourceType> actual = getResourceTypes(context, argument);
         if (actual == null && (!isNumber(argument) || isZero(argument) || isMinusOne(argument)) ) {
             return;
-        } else if (actual != null && (expectedType == null
-                || actual.contains(expectedType)
-                || expectedType == DRAWABLE && (actual.contains(COLOR) || actual.contains(MIPMAP)))) {
+        } else if (actual != null && (!Sets.intersection(actual, expectedType).isEmpty()
+                || expectedType.contains(DRAWABLE)
+                && (actual.contains(COLOR) || actual.contains(MIPMAP)))) {
+            return;
+        }
+
+        if (isIgnoredInIde(RESOURCE_TYPE, context, argument)) {
             return;
         }
 
         String message;
-        if (actual != null && actual.size() == 1 && actual.get(0) == COLOR_INT_MARKER_TYPE) {
+        if (actual != null && actual.size() == 1 && actual.contains(COLOR_INT_MARKER_TYPE)) {
             message = "Expected a color resource id (`R.color.`) but received an RGB integer";
-        } else if (expectedType == COLOR_INT_MARKER_TYPE) {
+        } else if (expectedType.contains(COLOR_INT_MARKER_TYPE)) {
             message = String.format("Should pass resolved color instead of resource id here: " +
                     "`getResources().getColor(%1$s)`", argument.toString());
-        } else if (expectedType != null) {
-            message = String.format(
-                    "Expected resource of type %1$s", expectedType.getName());
+        } else if (expectedType.size() < ResourceType.getNames().length - 1) {
+            message = String.format("Expected resource of type %1$s",
+                    Joiner.on(" or ").join(expectedType));
         } else {
             message = "Expected resource identifier (`R`.type.`name`)";
         }
@@ -828,7 +894,7 @@
     }
 
     @Nullable
-    private static List<ResourceType> getResourceTypes(@NonNull JavaContext context,
+    private static EnumSet<ResourceType> getResourceTypes(@NonNull JavaContext context,
             @NonNull Node argument) {
         if (argument instanceof Select) {
             Select node = (Select) argument;
@@ -839,7 +905,7 @@
                     if (innerSelect.astIdentifier().astValue().equals(R_CLASS)) {
                         String typeName = select.astIdentifier().astValue();
                         ResourceType type = ResourceType.getEnum(typeName);
-                        return type != null ? Collections.singletonList(type) : null;
+                        return type != null ? EnumSet.of(type) : null;
                     }
                 }
                 if (select.astOperand() instanceof VariableReference) {
@@ -847,7 +913,7 @@
                     if (reference.astIdentifier().astValue().equals(R_CLASS)) {
                         String typeName = select.astIdentifier().astValue();
                         ResourceType type = ResourceType.getEnum(typeName);
-                        return type != null ? Collections.singletonList(type) : null;
+                        return type != null ? EnumSet.of(type) : null;
                     }
                 }
             }
@@ -864,7 +930,7 @@
                             Select typeSelect = (Select) typeOperand;
                             String typeName = typeSelect.astIdentifier().astValue();
                             ResourceType type = ResourceType.getEnum(typeName);
-                            return type != null ? Collections.singletonList(type) : null;
+                            return type != null ? EnumSet.of(type) : null;
                         }
                     }
                 }
@@ -915,7 +981,7 @@
                 for (ResolvedAnnotation annotation : resolved.getAnnotations()) {
                     String signature = annotation.getSignature();
                     if (signature.equals(COLOR_INT_ANNOTATION)) {
-                        return Collections.singletonList(COLOR_INT_MARKER_TYPE);
+                        return EnumSet.of(COLOR_INT_MARKER_TYPE);
                     }
                     if (signature.endsWith(RES_SUFFIX)
                             && signature.startsWith(SUPPORT_ANNOTATIONS_PREFIX)) {
@@ -923,18 +989,9 @@
                                 signature.length() - RES_SUFFIX.length()).toLowerCase(Locale.US);
                         ResourceType type = ResourceType.getEnum(typeString);
                         if (type != null) {
-                            return Collections.singletonList(type);
+                            return EnumSet.of(type);
                         } else if (typeString.equals("any")) { // @AnyRes
-                            ResourceType[] types = ResourceType.values();
-                            List<ResourceType> result = Lists.newArrayListWithExpectedSize(
-                                    types.length);
-                            for (ResourceType t : types) {
-                                if (t != COLOR_INT_MARKER_TYPE) {
-                                    result.add(t);
-                                }
-                            }
-
-                            return result;
+                            return getAnyRes();
                         }
                     }
                 }
@@ -958,6 +1015,10 @@
                 return;
             }
 
+            if (isIgnoredInIde(RANGE, context, argument)) {
+                return;
+            }
+
             context.report(RANGE, argument, context.getLocation(argument), message);
         }
     }
@@ -967,6 +1028,20 @@
             @NonNull JavaContext context,
             @NonNull ResolvedAnnotation annotation,
             @NonNull Node argument) {
+        if (argument instanceof ArrayCreation) {
+            ArrayCreation creation = (ArrayCreation)argument;
+            ArrayInitializer initializer = creation.astInitializer();
+            if (initializer != null) {
+                for (Expression expression : initializer.astExpressions()) {
+                    String error = getIntRangeError(context, annotation, expression);
+                    if (error != null) {
+                        return error;
+                    }
+                }
+            }
+
+            return null;
+        }
         Object object = ConstantEvaluator.evaluate(context, argument);
         if (!(object instanceof Number)) {
             return null;
@@ -1015,7 +1090,7 @@
         boolean toInclusive = getBoolean(annotation, ATTR_TO_INCLUSIVE, true);
 
         String message = getFloatRangeError(value, from, to, fromInclusive, toInclusive, argument);
-        if (message != null) {
+        if (message != null && !isIgnoredInIde(RANGE, context, argument)) {
             context.report(RANGE, argument, context.getLocation(argument), message);
         }
     }
@@ -1128,7 +1203,7 @@
             unit = "size";
         }
         String message = getSizeError(actual, exact, min, max, multiple, unit);
-        if (message != null) {
+        if (message != null && !isIgnoredInIde(RANGE, context, argument)) {
             context.report(RANGE, argument, context.getLocation(argument), message);
         }
     }
@@ -1179,7 +1254,7 @@
     }
 
     @Nullable
-    private static ResolvedAnnotation findIntDef(
+    static ResolvedAnnotation findIntDef(
             @NonNull Iterable<ResolvedAnnotation> annotations) {
         for (ResolvedAnnotation annotation : annotations) {
             if (INT_DEF_ANNOTATION.equals(annotation.getName())) {
@@ -1254,6 +1329,9 @@
                 checkTypeDefConstant(context, annotation, expression.astOperand(), errorNode, true,
                         allAnnotations);
             } else if (operator == UnaryOperator.BINARY_NOT) {
+                if (isIgnoredInIde(TYPE_DEF, context, expression)) {
+                    return;
+                }
                 context.report(TYPE_DEF, expression, context.getLocation(expression),
                         "Flag not allowed here");
             } else if (operator == UnaryOperator.UNARY_MINUS) {
@@ -1280,15 +1358,42 @@
                 if (operator == BinaryOperator.BITWISE_AND
                         || operator == BinaryOperator.BITWISE_OR
                         || operator == BinaryOperator.BITWISE_XOR) {
+                    if (isIgnoredInIde(TYPE_DEF, context, expression)) {
+                        return;
+                    }
                     context.report(TYPE_DEF, expression, context.getLocation(expression),
                             "Flag not allowed here");
                 }
             }
+        } else if (argument instanceof ArrayCreation) {
+            ArrayCreation creation = (ArrayCreation) argument;
+            TypeReference typeReference = creation.astComponentTypeReference();
+            ArrayInitializer initializer = creation.astInitializer();
+            if (initializer != null && (TYPE_INT.equals(typeReference.getTypeName())
+                    || TYPE_LONG.equals(typeReference.getTypeName()))) {
+                for (Expression expression : initializer.astExpressions()) {
+                    checkTypeDefConstant(context, annotation, expression, errorNode, flag,
+                            allAnnotations);
+                }
+            }
         } else {
             ResolvedNode resolved = context.resolve(argument);
             if (resolved instanceof ResolvedField) {
-                checkTypeDefConstant(context, annotation, argument, errorNode, flag, resolved,
-                        allAnnotations);
+                ResolvedField field = (ResolvedField) resolved;
+                if (field.getType().isArray()) {
+                    // It's pointing to an array reference; we can't check these individual
+                    // elements (because we can't jump from ResolvedNodes to AST elements; this
+                    // is part of the motivation for the PSI change in lint 2.0), but we also
+                    // don't want to flag it as invalid.
+                    return;
+                }
+                int modifiers = field.getModifiers();
+                // If it's a constant (static/final) check that it's one of the allowed ones
+                if ((modifiers & (Modifier.FINAL|Modifier.STATIC))
+                        == (Modifier.FINAL|Modifier.STATIC)) {
+                    checkTypeDefConstant(context, annotation, argument, errorNode, flag, resolved,
+                            allAnnotations);
+                }
             } else if (argument instanceof VariableReference) {
                 Statement statement = getParentOfType(argument, Statement.class, false);
                 if (statement != null) {
@@ -1370,6 +1475,13 @@
     private static void reportTypeDef(@NonNull JavaContext context, @NonNull Node node,
             @Nullable Node errorNode, boolean flag, @NonNull Object[] allowedValues,
             @NonNull Iterable<ResolvedAnnotation> allAnnotations) {
+        if (errorNode == null) {
+            errorNode = node;
+        }
+        if (isIgnoredInIde(TYPE_DEF, context, errorNode)) {
+            return;
+        }
+
         String values = listAllowedValues(allowedValues);
         String message;
         if (flag) {
@@ -1388,9 +1500,6 @@
             }
         }
 
-        if (errorNode == null) {
-            errorNode = node;
-        }
         context.report(TYPE_DEF, errorNode, context.getLocation(errorNode), message);
     }
 
@@ -1405,6 +1514,9 @@
                 if (node instanceof ResolvedField) {
                     ResolvedField field = (ResolvedField) node;
                     String containingClassName = field.getContainingClassName();
+                    if (containingClassName == null) {
+                        continue;
+                    }
                     containingClassName = containingClassName.substring(containingClassName.lastIndexOf('.') + 1);
                     s = containingClassName + "." + field.getName();
                 } else {
@@ -1491,7 +1603,8 @@
             // Don't need to compute this if performing @IntDef or @StringDef lookup
             ResolvedClass type = annotation.getClassType();
             if (type != null) {
-                Iterator<ResolvedAnnotation> iterator2 = type.getAnnotations().iterator();
+                Iterable<ResolvedAnnotation> innerAnnotations = type.getAnnotations();
+                Iterator<ResolvedAnnotation> iterator2 = innerAnnotations.iterator();
                 while (iterator2.hasNext()) {
                     ResolvedAnnotation inner = iterator2.next();
                     if (inner.matches(INT_DEF_ANNOTATION)
@@ -1499,7 +1612,7 @@
                             || inner.matches(INT_RANGE_ANNOTATION)
                             || inner.matches(STRING_DEF_ANNOTATION)) {
                         if (!iterator.hasNext() && !iterator2.hasNext() && index == 1) {
-                            return annotations;
+                            return innerAnnotations;
                         }
                         if (result == null) {
                             result = new ArrayList<ResolvedAnnotation>(2);
@@ -1595,10 +1708,11 @@
 
                 annotations = method.getParameterAnnotations(i);
                 annotations = filterRelevantAnnotations(annotations);
-                for (ResolvedAnnotation annotation : annotations) {
-                    checkParameterAnnotation(mContext, argument, call, method, annotation,
-                            annotations);
-                }
+                checkParameterAnnotations(mContext, argument, call, method, annotations);
+            }
+            while (arguments.hasNext()) { // last parameter is varargs (same parameter annotations)
+                Expression argument = arguments.next();
+                checkParameterAnnotations(mContext, argument, call, method, annotations);
             }
         }
     }
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TranslationDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TranslationDetector.java
index 2eaab73..23b7c57 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TranslationDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TranslationDetector.java
@@ -174,7 +174,7 @@
 
         // Convention seen in various projects
         mIgnoreFile = context.file.getName().startsWith("donottranslate") //$NON-NLS-1$
-                        || UnusedResourceDetector.isAnalyticsFile(context);
+                        || ResourceUsageModel.isAnalyticsFile(context.file);
 
         if (!context.getProject().getReportIssues()) {
             mIgnoreFile = true;
@@ -601,10 +601,11 @@
                     mNonTranslatable.add(name);
                 }
                 return;
-            } else if (name.equals("google_maps_key")                  //$NON-NLS-1$
+            } else if (isServiceKey(name)
+                    // Older versions of the templates shipped with these not marked as
+                    // non-translatable; don't flag them
+                    || name.equals("google_maps_key")                  //$NON-NLS-1$
                     || name.equals("google_maps_key_instructions")) {  //$NON-NLS-1$
-                // Older versions of the templates shipped with these not marked as
-                // non-translatable; don't flag them
                 if (mNonTranslatable == null) {
                     mNonTranslatable = new HashSet<String>();
                 }
@@ -651,6 +652,19 @@
         }
     }
 
+    public static boolean isServiceKey(@NonNull String name) {
+        // These are keys used by misc developer services.
+        // Configuration files provided by for example
+        //   https://developers.google.com/cloud-messaging/android/client
+        // in earlier versions would omit translatable="false", which meant users
+        // would run into fatal translation errors at build time.
+        // See for example
+        //    https://code.google.com/p/android/issues/detail?id=195824
+        return name.equals("gcm_defaultSenderId")
+                || name.equals("google_app_id")
+                || name.equals("ga_trackingID");
+    }
+
     private static boolean allItemsAreReferences(Element element) {
         assert element.getTagName().equals(TAG_STRING_ARRAY);
         NodeList childNodes = element.getChildNodes();
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TrustAllX509TrustManagerDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TrustAllX509TrustManagerDetector.java
new file mode 100644
index 0000000..7e92f89
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TrustAllX509TrustManagerDetector.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2015 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.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ClassContext;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Detector.ClassScanner;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+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 org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+
+import lombok.ast.ClassDeclaration;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.Node;
+import lombok.ast.NormalTypeBody;
+import lombok.ast.Return;
+import lombok.ast.Statement;
+
+public class TrustAllX509TrustManagerDetector extends Detector implements JavaScanner,
+        ClassScanner {
+
+    @SuppressWarnings("unchecked")
+    private static final Implementation IMPLEMENTATION =
+            new Implementation(TrustAllX509TrustManagerDetector.class,
+                    EnumSet.of(Scope.JAVA_LIBRARIES, Scope.JAVA_FILE),
+                    Scope.JAVA_FILE_SCOPE);
+
+    public static final Issue ISSUE = Issue.create("TrustAllX509TrustManager",
+            "Insecure TLS/SSL trust manager",
+            "This check looks for X509TrustManager implementations whose `checkServerTrusted` or " +
+            "`checkClientTrusted` methods do nothing (thus trusting any certificate chain) " +
+            "which could result in insecure network traffic caused by trusting arbitrary " +
+            "TLS/SSL certificates presented by peers.",
+            Category.SECURITY,
+            6,
+            Severity.WARNING,
+            IMPLEMENTATION);
+
+    public TrustAllX509TrustManagerDetector() {
+    }
+
+    // ---- Implements JavaScanner ----
+
+    @Nullable
+    @Override
+    public List<String> applicableSuperClasses() {
+        return Collections.singletonList("javax.net.ssl.X509TrustManager");
+    }
+
+    @Override
+    public void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration node,
+            @NonNull Node declarationOrAnonymous, @NonNull ResolvedClass cls) {
+        NormalTypeBody body;
+        if (declarationOrAnonymous instanceof NormalTypeBody) {
+            body = (NormalTypeBody) declarationOrAnonymous;
+        } else if (node != null) {
+            body = node.astBody();
+        } else {
+            return;
+        }
+
+        for (Node member : body.astMembers()) {
+            if (member instanceof MethodDeclaration) {
+                MethodDeclaration declaration = (MethodDeclaration)member;
+                String methodName = declaration.astMethodName().astValue();
+                if ("checkServerTrusted".equals(methodName)
+                        || "checkClientTrusted".equals(methodName)) {
+
+                    // For now very simple; only checks if nothing is done.
+                    // Future work: Improve this check to be less sensitive to irrelevant
+                    // instructions/statements/invocations (e.g. System.out.println) by
+                    // looking for calls that could lead to a CertificateException being
+                    // thrown, e.g. throw statement within the method itself or invocation
+                    // of another method that may throw a CertificateException, and only
+                    // reporting an issue if none of these calls are found. ControlFlowGraph
+                    // may be useful here.
+
+                    boolean complex = false;
+                    for (Statement statement : declaration.astBody().astContents()) {
+                        if (!(statement instanceof Return)) {
+                            complex = true;
+                            break;
+                        }
+                    }
+
+                    if (!complex) {
+                        Location location = context.getNameLocation(declaration);
+                        String message = getErrorMessage(methodName);
+                        context.report(ISSUE, declaration, location, message);
+                    }
+                }
+            }
+        }
+    }
+
+    @NonNull
+    private static String getErrorMessage(String methodName) {
+        return "`" + methodName + "` is empty, which could cause " +
+                "insecure network traffic due to trusting arbitrary TLS/SSL " +
+                "certificates presented by peers";
+    }
+
+    // ---- Implements ClassScanner ----
+    // Only used for libraries where we have to analyze bytecode
+
+    @Override
+    @SuppressWarnings("rawtypes")
+    public void checkClass(@NonNull final ClassContext context,
+            @NonNull ClassNode classNode) {
+        if (!context.isFromClassLibrary()) {
+            // Non-library code checked at the AST level
+            return;
+        }
+        if (!classNode.interfaces.contains("javax/net/ssl/X509TrustManager")) {
+            return;
+        }
+        List methodList = classNode.methods;
+        for (Object m : methodList) {
+            MethodNode method = (MethodNode) m;
+            if ("checkServerTrusted".equals(method.name) ||
+                    "checkClientTrusted".equals(method.name)) {
+                InsnList nodes = method.instructions;
+                boolean emptyMethod = true; // Stays true if method doesn't perform any "real"
+                                            // operations
+                for (int i = 0, n = nodes.size(); i < n; i++) {
+                    // Future work: Improve this check to be less sensitive to irrelevant
+                    // instructions/statements/invocations (e.g. System.out.println) by
+                    // looking for calls that could lead to a CertificateException being
+                    // thrown, e.g. throw statement within the method itself or invocation
+                    // of another method that may throw a CertificateException, and only
+                    // reporting an issue if none of these calls are found. ControlFlowGraph
+                    // may be useful here.
+                    AbstractInsnNode instruction = nodes.get(i);
+                    int type = instruction.getType();
+                    if (type != AbstractInsnNode.LABEL && type != AbstractInsnNode.LINE &&
+                            !(type == AbstractInsnNode.INSN &&
+                                    instruction.getOpcode() == Opcodes.RETURN)) {
+                        emptyMethod = false;
+                        break;
+                    }
+                }
+                if (emptyMethod) {
+                    Location location = context.getLocation(method, classNode);
+                    context.report(ISSUE, location, getErrorMessage(method.name));
+                }
+            }
+        }
+    }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoDetector.java
index 5d76e84..fbb6dee 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoDetector.java
@@ -435,25 +435,52 @@
         context.report(ISSUE, node, location, message);
     }
 
-    /** Returns the suggested replacements, if any, for the given typo. The error
-     * message <b>must</b> be one supplied by lint.
+    /** Simple data holder used to return several values from
+     * {@link #getSuggestions(String, TextFormat)}
+     */
+    public static class TypoSuggestionInfo {
+        @NonNull
+        private final String mOriginal;
+        @NonNull
+        private final List<String> mReplacements;
+
+        public TypoSuggestionInfo(@NonNull String original, @NonNull List<String> replacements) {
+            mOriginal = original;
+            mReplacements = replacements;
+        }
+
+        @NonNull
+        public String getOriginal() {
+            return mOriginal;
+        }
+
+        @NonNull
+        public List<String> getReplacements() {
+            return mReplacements;
+        }
+    }
+
+    /** Returns the suggested replacements and original string, for the given typo.
+     * The error message <b>must</b> be one supplied by lint.
      *
      * @param errorMessage the error message
      * @param format the format of the error message
-     * @return a list of replacement words suggested by the error message
+     * @return {@link TypoSuggestionInfo}
      */
-    @Nullable
-    public static List<String> getSuggestions(@NonNull String errorMessage,
+    @NonNull
+    public static TypoSuggestionInfo getSuggestions(@NonNull String errorMessage,
             @NonNull TextFormat format) {
         errorMessage = format.toText(errorMessage);
 
         // The words are all in quotes; the first word is the misspelling,
         // the other words are the suggested replacements
-        List<String> words = new ArrayList<String>();
+        List<String> replacements = new ArrayList<String>();
         // Skip the typo
         int index = errorMessage.indexOf('"');
-        index = errorMessage.indexOf('"', index + 1);
-        index++;
+        int originalEndIndex = errorMessage.indexOf('"', index + 1);
+        String original = errorMessage.substring(index + 1, originalEndIndex);
+
+        index = originalEndIndex + 1;
 
         while (true) {
             index = errorMessage.indexOf('"', index);
@@ -466,11 +493,11 @@
             if (index == -1) {
                 index = errorMessage.length();
             }
-            words.add(errorMessage.substring(start, index));
+            replacements.add(errorMessage.substring(start, index));
             index++;
         }
 
-        return words;
+        return new TypoSuggestionInfo(original, replacements);
     }
 
     /**
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoLookup.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoLookup.java
index 56ede7e..f9da908 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoLookup.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoLookup.java
@@ -26,10 +26,10 @@
 import com.android.tools.lint.detector.api.LintUtils;
 import com.google.common.base.Charsets;
 import com.google.common.base.Splitter;
+import com.google.common.io.ByteSink;
 import com.google.common.io.Files;
 
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
@@ -409,9 +409,8 @@
         byte[] b = new byte[size];
         buffer.rewind();
         buffer.get(b);
-        FileOutputStream output = Files.newOutputStreamSupplier(file).getOutput();
-        output.write(b);
-        output.close();
+        ByteSink sink = Files.asByteSink(file);
+        sink.write(b);
     }
 
     // For debugging only
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnsafeBroadcastReceiverDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnsafeBroadcastReceiverDetector.java
new file mode 100644
index 0000000..1287465
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnsafeBroadcastReceiverDetector.java
@@ -0,0 +1,583 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_PERMISSION;
+import static com.android.SdkConstants.CLASS_BROADCASTRECEIVER;
+import static com.android.SdkConstants.CLASS_CONTEXT;
+import static com.android.SdkConstants.CLASS_INTENT;
+import static com.android.SdkConstants.TAG_INTENT_FILTER;
+import static com.android.SdkConstants.TAG_RECEIVER;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.client.api.JavaParser.ResolvedVariable;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Detector.XmlScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.LintUtils;
+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.XmlContext;
+import com.google.common.collect.Sets;
+
+import org.w3c.dom.Element;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import lombok.ast.ClassDeclaration;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+import lombok.ast.VariableReference;
+
+public class UnsafeBroadcastReceiverDetector extends Detector
+        implements JavaScanner, XmlScanner {
+
+    /* Description of check implementations:
+     *
+     * UnsafeProtectedBroadcastReceiver check
+     *
+     * If a receiver is declared in the application manifest that has an intent-filter
+     * with an action string that matches a protected-broadcast action string,
+     * then if that receiver has an onReceive method, ensure that the method calls
+     * getAction at least once.
+     *
+     * With this check alone, false positives will occur if the onReceive method
+     * passes the received intent to another method that calls getAction.
+     * We look for any calls to aload_2 within the method bytecode, which could
+     * indicate loading the inputted intent onto the stack to use in a call
+     * to another method. In those cases, still report the issue, but
+     * report in the description that the finding may be a false positive.
+     * An alternative implementation option would be to omit reporting the issue
+     * at all when a call to aload_2 exists.
+     *
+     * UnprotectedSMSBroadcastReceiver check
+     *
+     * If a receiver is declared in AndroidManifest that has an intent-filter
+     * with action string SMS_DELIVER or SMS_RECEIVED, ensure that the
+     * receiver requires callers to have the BROADCAST_SMS permission.
+     *
+     * It is possible that the receiver may check the sender's permission by
+     * calling checkCallingPermission, which could cause a false positive.
+     * However, application developers should still be encouraged to declare
+     * the permission requirement in the manifest where it can be easily
+     * audited.
+     *
+     * Future work: Add checks for other action strings that should require
+     * particular permissions be checked, such as
+     * android.provider.Telephony.WAP_PUSH_DELIVER
+     *
+     * Note that neither of these checks address receivers dynamically created at runtime,
+     * only ones that are declared in the application manifest.
+     */
+
+    public static final Issue ACTION_STRING = Issue.create(
+            "UnsafeProtectedBroadcastReceiver",
+            "Unsafe Protected BroadcastReceiver",
+            "BroadcastReceivers that declare an intent-filter for a protected-broadcast action " +
+            "string must check that the received intent's action string matches the expected " +
+            "value, otherwise it is possible for malicious actors to spoof intents.",
+            Category.SECURITY,
+            6,
+            Severity.WARNING,
+            new Implementation(UnsafeBroadcastReceiverDetector.class,
+                    EnumSet.of(Scope.MANIFEST, Scope.JAVA_FILE)));
+
+    public static final Issue BROADCAST_SMS = Issue.create(
+            "UnprotectedSMSBroadcastReceiver",
+            "Unprotected SMS BroadcastReceiver",
+            "BroadcastReceivers that declare an intent-filter for SMS_DELIVER or " +
+            "SMS_RECEIVED must ensure that the caller has the BROADCAST_SMS permission, " +
+            "otherwise it is possible for malicious actors to spoof intents.",
+            Category.SECURITY,
+            6,
+            Severity.WARNING,
+            new Implementation(UnsafeBroadcastReceiverDetector.class,
+                    Scope.MANIFEST_SCOPE));
+
+    /* List of protected broadcast strings. This list must be sorted alphabetically.
+     * Protected broadcast strings are defined by <protected-broadcast> entries in the
+     * manifest of system-level components or applications.
+     * The below list is copied from frameworks/base/core/res/AndroidManifest.xml
+     * and packages/services/Telephony/AndroidManifest.xml .
+     * It should be periodically updated. This list will likely not be complete, since
+     * protected-broadcast entries can be defined elsewhere, but should address
+     * most situations.
+     */
+    @VisibleForTesting
+    static final String[] PROTECTED_BROADCASTS = new String[] {
+            "android.app.action.DEVICE_OWNER_CHANGED",
+            "android.app.action.ENTER_CAR_MODE",
+            "android.app.action.ENTER_DESK_MODE",
+            "android.app.action.EXIT_CAR_MODE",
+            "android.app.action.EXIT_DESK_MODE",
+            "android.app.action.NEXT_ALARM_CLOCK_CHANGED",
+            "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED",
+            "android.appwidget.action.APPWIDGET_DELETED",
+            "android.appwidget.action.APPWIDGET_DISABLED",
+            "android.appwidget.action.APPWIDGET_ENABLED",
+            "android.appwidget.action.APPWIDGET_HOST_RESTORED",
+            "android.appwidget.action.APPWIDGET_RESTORED",
+            "android.appwidget.action.APPWIDGET_UPDATE_OPTIONS",
+            "android.backup.intent.CLEAR",
+            "android.backup.intent.INIT",
+            "android.backup.intent.RUN",
+            "android.bluetooth.a2dp-sink.profile.action.AUDIO_CONFIG_CHANGED",
+            "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED",
+            "android.bluetooth.a2dp-sink.profile.action.PLAYING_STATE_CHANGED",
+            "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED",
+            "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED",
+            "android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED",
+            "android.bluetooth.adapter.action.DISCOVERY_FINISHED",
+            "android.bluetooth.adapter.action.DISCOVERY_STARTED",
+            "android.bluetooth.adapter.action.LOCAL_NAME_CHANGED",
+            "android.bluetooth.adapter.action.SCAN_MODE_CHANGED",
+            "android.bluetooth.adapter.action.STATE_CHANGED",
+            "android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED",
+            "android.bluetooth.device.action.ACL_CONNECTED",
+            "android.bluetooth.device.action.ACL_DISCONNECTED",
+            "android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED",
+            "android.bluetooth.device.action.ALIAS_CHANGED",
+            "android.bluetooth.device.action.BOND_STATE_CHANGED",
+            "android.bluetooth.device.action.CLASS_CHANGED",
+            "android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL",
+            "android.bluetooth.device.action.CONNECTION_ACCESS_REPLY",
+            "android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST",
+            "android.bluetooth.device.action.DISAPPEARED",
+            "android.bluetooth.device.action.FOUND",
+            "android.bluetooth.device.action.MAS_INSTANCE",
+            "android.bluetooth.device.action.NAME_CHANGED",
+            "android.bluetooth.device.action.NAME_FAILED",
+            "android.bluetooth.device.action.PAIRING_CANCEL",
+            "android.bluetooth.device.action.PAIRING_REQUEST",
+            "android.bluetooth.device.action.UUID",
+            "android.bluetooth.devicepicker.action.DEVICE_SELECTED",
+            "android.bluetooth.devicepicker.action.LAUNCH",
+            "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT",
+            "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED",
+            "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED",
+            "android.bluetooth.headsetclient.profile.action.AG_CALL_CHANGED",
+            "android.bluetooth.headsetclient.profile.action.AG_EVENT",
+            "android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED",
+            "android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED",
+            "android.bluetooth.headsetclient.profile.action.LAST_VTAG",
+            "android.bluetooth.headsetclient.profile.action.RESULT",
+            "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED",
+            "android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED",
+            "android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS",
+            "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED",
+            "android.bluetooth.pbap.intent.action.PBAP_STATE_CHANGED",
+            "android.btopp.intent.action.CONFIRM",
+            "android.btopp.intent.action.HIDE",
+            "android.btopp.intent.action.HIDE_COMPLETE",
+            "android.btopp.intent.action.INCOMING_FILE_NOTIFICATION",
+            "android.btopp.intent.action.LIST",
+            "android.btopp.intent.action.OPEN",
+            "android.btopp.intent.action.OPEN_INBOUND",
+            "android.btopp.intent.action.OPEN_OUTBOUND",
+            "android.btopp.intent.action.RETRY",
+            "android.btopp.intent.action.USER_CONFIRMATION_TIMEOUT",
+            "android.hardware.display.action.WIFI_DISPLAY_STATUS_CHANGED",
+            "android.hardware.usb.action.USB_ACCESSORY_ATTACHED",
+            "android.hardware.usb.action.USB_ACCESSORY_DETACHED",
+            "android.hardware.usb.action.USB_DEVICE_ATTACHED",
+            "android.hardware.usb.action.USB_DEVICE_DETACHED",
+            "android.hardware.usb.action.USB_PORT_CHANGED",
+            "android.hardware.usb.action.USB_STATE",
+            "android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED",
+            "android.intent.action.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED",
+            "android.intent.action.ACTION_DEFAULT_SUBSCRIPTION_CHANGED",
+            "android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED",
+            "android.intent.action.ACTION_IDLE_MAINTENANCE_END",
+            "android.intent.action.ACTION_IDLE_MAINTENANCE_START",
+            "android.intent.action.ACTION_POWER_CONNECTED",
+            "android.intent.action.ACTION_POWER_DISCONNECTED",
+            "android.intent.action.ACTION_SET_RADIO_CAPABILITY_DONE",
+            "android.intent.action.ACTION_SET_RADIO_CAPABILITY_FAILED",
+            "android.intent.action.ACTION_SHUTDOWN",
+            "android.intent.action.ACTION_SUBINFO_CONTENT_CHANGE",
+            "android.intent.action.ACTION_SUBINFO_RECORD_UPDATED",
+            "android.intent.action.ADVANCED_SETTINGS",
+            "android.intent.action.AIRPLANE_MODE",
+            "android.intent.action.ANY_DATA_STATE",
+            "android.intent.action.APPLICATION_RESTRICTIONS_CHANGED",
+            "android.intent.action.BATTERY_CHANGED",
+            "android.intent.action.BATTERY_LOW",
+            "android.intent.action.BATTERY_OKAY",
+            "android.intent.action.BOOT_COMPLETED",
+            "android.intent.action.BUGREPORT_FINISHED",
+            "android.intent.action.CHARGING",
+            "android.intent.action.CLEAR_DNS_CACHE",
+            "android.intent.action.CONFIGURATION_CHANGED",
+            "android.intent.action.DATE_CHANGED",
+            "android.intent.action.DEVICE_STORAGE_FULL",
+            "android.intent.action.DEVICE_STORAGE_LOW",
+            "android.intent.action.DEVICE_STORAGE_NOT_FULL",
+            "android.intent.action.DEVICE_STORAGE_OK",
+            "android.intent.action.DISCHARGING",
+            "android.intent.action.DOCK_EVENT",
+            "android.intent.action.DREAMING_STARTED",
+            "android.intent.action.DREAMING_STOPPED",
+            "android.intent.action.EXTERNAL_APPLICATIONS_AVAILABLE",
+            "android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE",
+            "android.intent.action.HDMI_PLUGGED",
+            "android.intent.action.HEADSET_PLUG",
+            "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION",
+            "android.intent.action.LOCALE_CHANGED",
+            "android.intent.action.MASTER_CLEAR_NOTIFICATION",
+            "android.intent.action.MEDIA_BAD_REMOVAL",
+            "android.intent.action.MEDIA_CHECKING",
+            "android.intent.action.MEDIA_EJECT",
+            "android.intent.action.MEDIA_MOUNTED",
+            "android.intent.action.MEDIA_NOFS",
+            "android.intent.action.MEDIA_REMOVED",
+            "android.intent.action.MEDIA_SHARED",
+            "android.intent.action.MEDIA_UNMOUNTABLE",
+            "android.intent.action.MEDIA_UNMOUNTED",
+            "android.intent.action.MEDIA_UNSHARED",
+            "android.intent.action.MY_PACKAGE_REPLACED",
+            "android.intent.action.NEW_OUTGOING_CALL",
+            "android.intent.action.PACKAGE_ADDED",
+            "android.intent.action.PACKAGE_CHANGED",
+            "android.intent.action.PACKAGE_DATA_CLEARED",
+            "android.intent.action.PACKAGE_FIRST_LAUNCH",
+            "android.intent.action.PACKAGE_FULLY_REMOVED",
+            "android.intent.action.PACKAGE_INSTALL",
+            "android.intent.action.PACKAGE_NEEDS_VERIFICATION",
+            "android.intent.action.PACKAGE_REMOVED",
+            "android.intent.action.PACKAGE_REPLACED",
+            "android.intent.action.PACKAGE_RESTARTED",
+            "android.intent.action.PACKAGE_VERIFIED",
+            "android.intent.action.PERMISSION_RESPONSE_RECEIVED",
+            "android.intent.action.PHONE_STATE",
+            "android.intent.action.PROXY_CHANGE",
+            "android.intent.action.QUERY_PACKAGE_RESTART",
+            "android.intent.action.REBOOT",
+            "android.intent.action.REQUEST_PERMISSION",
+            "android.intent.action.SCREEN_OFF",
+            "android.intent.action.SCREEN_ON",
+            "android.intent.action.SUB_DEFAULT_CHANGED",
+            "android.intent.action.THERMAL_EVENT",
+            "android.intent.action.TIMEZONE_CHANGED",
+            "android.intent.action.TIME_SET",
+            "android.intent.action.TIME_TICK",
+            "android.intent.action.UID_REMOVED",
+            "android.intent.action.USER_ADDED",
+            "android.intent.action.USER_BACKGROUND",
+            "android.intent.action.USER_FOREGROUND",
+            "android.intent.action.USER_PRESENT",
+            "android.intent.action.USER_REMOVED",
+            "android.intent.action.USER_STARTED",
+            "android.intent.action.USER_STARTING",
+            "android.intent.action.USER_STOPPED",
+            "android.intent.action.USER_STOPPING",
+            "android.intent.action.USER_SWITCHED",
+            "android.internal.policy.action.BURN_IN_PROTECTION",
+            "android.location.GPS_ENABLED_CHANGE",
+            "android.location.GPS_FIX_CHANGE",
+            "android.location.MODE_CHANGED",
+            "android.location.PROVIDERS_CHANGED",
+            "android.media.ACTION_SCO_AUDIO_STATE_UPDATED",
+            "android.media.AUDIO_BECOMING_NOISY",
+            "android.media.MASTER_MUTE_CHANGED_ACTION",
+            "android.media.MASTER_VOLUME_CHANGED_ACTION",
+            "android.media.RINGER_MODE_CHANGED",
+            "android.media.SCO_AUDIO_STATE_CHANGED",
+            "android.media.VIBRATE_SETTING_CHANGED",
+            "android.media.VOLUME_CHANGED_ACTION",
+            "android.media.action.HDMI_AUDIO_PLUG",
+            "android.net.ConnectivityService.action.PKT_CNT_SAMPLE_INTERVAL_ELAPSED",
+            "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED",
+            "android.net.conn.CAPTIVE_PORTAL",
+            "android.net.conn.CAPTIVE_PORTAL_TEST_COMPLETED",
+            "android.net.conn.CONNECTIVITY_CHANGE",
+            "android.net.conn.CONNECTIVITY_CHANGE_IMMEDIATE",
+            "android.net.conn.DATA_ACTIVITY_CHANGE",
+            "android.net.conn.INET_CONDITION_ACTION",
+            "android.net.conn.NETWORK_CONDITIONS_MEASURED",
+            "android.net.conn.TETHER_STATE_CHANGED",
+            "android.net.nsd.STATE_CHANGED",
+            "android.net.proxy.PAC_REFRESH",
+            "android.net.scoring.SCORER_CHANGED",
+            "android.net.scoring.SCORE_NETWORKS",
+            "android.net.wifi.CONFIGURED_NETWORKS_CHANGE",
+            "android.net.wifi.LINK_CONFIGURATION_CHANGED",
+            "android.net.wifi.RSSI_CHANGED",
+            "android.net.wifi.SCAN_RESULTS",
+            "android.net.wifi.STATE_CHANGE",
+            "android.net.wifi.WIFI_AP_STATE_CHANGED",
+            "android.net.wifi.WIFI_CREDENTIAL_CHANGED",
+            "android.net.wifi.WIFI_SCAN_AVAILABLE",
+            "android.net.wifi.WIFI_STATE_CHANGED",
+            "android.net.wifi.p2p.CONNECTION_STATE_CHANGE",
+            "android.net.wifi.p2p.DISCOVERY_STATE_CHANGE",
+            "android.net.wifi.p2p.PEERS_CHANGED",
+            "android.net.wifi.p2p.PERSISTENT_GROUPS_CHANGED",
+            "android.net.wifi.p2p.STATE_CHANGED",
+            "android.net.wifi.p2p.THIS_DEVICE_CHANGED",
+            "android.net.wifi.supplicant.CONNECTION_CHANGE",
+            "android.net.wifi.supplicant.STATE_CHANGE",
+            "android.nfc.action.LLCP_LINK_STATE_CHANGED",
+            "android.nfc.action.TRANSACTION_DETECTED",
+            "android.nfc.handover.intent.action.HANDOVER_STARTED",
+            "android.nfc.handover.intent.action.TRANSFER_DONE",
+            "android.nfc.handover.intent.action.TRANSFER_PROGRESS",
+            "android.os.UpdateLock.UPDATE_LOCK_CHANGED",
+            "android.os.action.DEVICE_IDLE_MODE_CHANGED",
+            "android.os.action.POWER_SAVE_MODE_CHANGED",
+            "android.os.action.POWER_SAVE_MODE_CHANGING",
+            "android.os.action.POWER_SAVE_TEMP_WHITELIST_CHANGED",
+            "android.os.action.POWER_SAVE_WHITELIST_CHANGED",
+            "android.os.action.SCREEN_BRIGHTNESS_BOOST_CHANGED",
+            "android.os.action.SETTING_RESTORED",
+            "android.telecom.action.DEFAULT_DIALER_CHANGED",
+            "com.android.bluetooth.pbap.authcancelled",
+            "com.android.bluetooth.pbap.authchall",
+            "com.android.bluetooth.pbap.authresponse",
+            "com.android.bluetooth.pbap.userconfirmtimeout",
+            "com.android.nfc_extras.action.AID_SELECTED",
+            "com.android.nfc_extras.action.RF_FIELD_OFF_DETECTED",
+            "com.android.nfc_extras.action.RF_FIELD_ON_DETECTED",
+            "com.android.server.WifiManager.action.DELAYED_DRIVER_STOP",
+            "com.android.server.WifiManager.action.START_PNO",
+            "com.android.server.WifiManager.action.START_SCAN",
+            "com.android.server.connectivityservice.CONNECTED_TO_PROVISIONING_NETWORK_ACTION",
+    };
+
+    private static final Set<String> PROTECTED_BROADCAST_SET =
+            Sets.newHashSet(PROTECTED_BROADCASTS);
+
+    private Set<String> mReceiversWithProtectedBroadcastIntentFilter = new HashSet<String>();
+
+    public UnsafeBroadcastReceiverDetector() {
+    }
+
+    // ---- Implements XmlScanner ----
+
+    @Override
+    public Collection<String> getApplicableElements() {
+        return Collections.singletonList(TAG_RECEIVER);
+    }
+
+    @Override
+    public void visitElement(@NonNull XmlContext context,
+            @NonNull Element element) {
+        String tag = element.getTagName();
+        if (TAG_RECEIVER.equals(tag)) {
+            String name = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
+            String permission = element.getAttributeNS(ANDROID_URI, ATTR_PERMISSION);
+            // If no permission attribute, then if any exists at the application
+            // element, it applies
+            if (permission == null || permission.isEmpty()) {
+                Element parent = (Element) element.getParentNode();
+                permission = parent.getAttributeNS(ANDROID_URI, ATTR_PERMISSION);
+            }
+            List<Element> children = LintUtils.getChildren(element);
+            for (Element child : children) {
+                String tagName = child.getTagName();
+                if (TAG_INTENT_FILTER.equals(tagName)) {
+                    if (name.startsWith(".")) {
+                        name = context.getProject().getPackage() + name;
+                    }
+                    name = name.replace('$', '.');
+                    List<Element> children2 = LintUtils.getChildren(child);
+                    for (Element child2 : children2) {
+                        if ("action".equals(child2.getTagName())) {
+                            String actionName = child2.getAttributeNS(
+                                    ANDROID_URI, ATTR_NAME);
+                            if (("android.provider.Telephony.SMS_DELIVER".equals(actionName) ||
+                                    "android.provider.Telephony.SMS_RECEIVED".
+                                        equals(actionName)) &&
+                                    !"android.permission.BROADCAST_SMS".equals(permission)) {
+                                context.report(
+                                        BROADCAST_SMS,
+                                        element,
+                                        context.getLocation(element),
+                                        "BroadcastReceivers that declare an intent-filter for " +
+                                        "SMS_DELIVER or SMS_RECEIVED must ensure that the " +
+                                        "caller has the BROADCAST_SMS permission, otherwise it " +
+                                        "is possible for malicious actors to spoof intents.");
+                            }
+                            else if (PROTECTED_BROADCAST_SET.contains(actionName)) {
+                                mReceiversWithProtectedBroadcastIntentFilter.add(name);
+                            }
+                        }
+                    }
+                    break;
+                }
+            }
+        }
+    }
+
+    // ---- Implements JavaScanner ----
+
+    @Nullable
+    @Override
+    public List<String> applicableSuperClasses() {
+        return mReceiversWithProtectedBroadcastIntentFilter.isEmpty()
+                ? null : Collections.singletonList(CLASS_BROADCASTRECEIVER);
+    }
+
+    @Override
+    public void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration node,
+            @NonNull Node declarationOrAnonymous, @NonNull ResolvedClass cls) {
+        if (node == null) { // anonymous classes can't be the ones referenced in the manifest
+            return;
+        }
+        String name = cls.getName();
+        if (!mReceiversWithProtectedBroadcastIntentFilter.contains(name)) {
+            return;
+        }
+        for (Node member : node.astBody().astMembers()) {
+            if (member instanceof MethodDeclaration) {
+                MethodDeclaration declaration = (MethodDeclaration)member;
+                if ("onReceive".equals(declaration.astMethodName().astValue())
+                        && declaration.astParameters().size() == 2) {
+                    ResolvedNode resolved = context.resolve(declaration);
+                    if (resolved instanceof ResolvedMethod) {
+                        ResolvedMethod method = (ResolvedMethod) resolved;
+                        if (method.getArgumentCount() == 2
+                                && method.getArgumentType(0).matchesName(CLASS_CONTEXT)
+                                && method.getArgumentType(1).matchesName(CLASS_INTENT)) {
+                            checkOnReceive(context, declaration, method);
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private static void checkOnReceive(@NonNull JavaContext context,
+            @NonNull MethodDeclaration declaration,
+            @NonNull ResolvedMethod method) {
+        // Search for call to getAction but also search for references to aload_2,
+        // which indicates that the method is making use of the received intent in
+        // some way.
+        //
+        // If the onReceive method doesn't call getAction but does make use of
+        // the received intent, it is possible that it is passing it to another
+        // method that might be performing the getAction check, so we warn that the
+        // finding may be a false positive. (An alternative option would be to not
+        // report a finding at all in this case.)
+        assert method.getArgumentCount() == 2;
+        ResolvedVariable parameter = null;
+        if (declaration.astParameters().size() == 2) {
+            ResolvedNode resolved = context.resolve(declaration.astParameters().last());
+            if (resolved instanceof ResolvedVariable) {
+                parameter = (ResolvedVariable) resolved;
+            }
+        }
+        OnReceiveVisitor visitor = new OnReceiveVisitor(context, parameter);
+        declaration.accept(visitor);
+        if (!visitor.getCallsGetAction()) {
+            String report;
+            if (!visitor.getUsesIntent()) {
+                report = "This broadcast receiver declares an intent-filter for a protected " +
+                        "broadcast action string, which can only be sent by the system, " +
+                        "not third-party applications. However, the receiver's onReceive " +
+                        "method does not appear to call getAction to ensure that the " +
+                        "received Intent's action string matches the expected value, " +
+                        "potentially making it possible for another actor to send a " +
+                        "spoofed intent with no action string or a different action " +
+                        "string and cause undesired behavior.";
+            } else {
+                // An alternative implementation option is to not report a finding at all in
+                // this case, if we are worried about false positives causing confusion or
+                // resulting in developers ignoring other lint warnings.
+                report = "This broadcast receiver declares an intent-filter for a protected " +
+                        "broadcast action string, which can only be sent by the system, " +
+                        "not third-party applications. However, the receiver's onReceive " +
+                        "method does not appear to call getAction to ensure that the " +
+                        "received Intent's action string matches the expected value, " +
+                        "potentially making it possible for another actor to send a " +
+                        "spoofed intent with no action string or a different action " +
+                        "string and cause undesired behavior. In this case, it is " +
+                        "possible that the onReceive method passed the received Intent " +
+                        "to another method that checked the action string. If so, this " +
+                        "finding can safely be ignored.";
+            }
+            Location location = context.getNameLocation(declaration);
+            context.report(ACTION_STRING, declaration, location, report);
+        }
+    }
+
+    private static class OnReceiveVisitor extends ForwardingAstVisitor {
+        @NonNull private final JavaContext mContext;
+        @Nullable private final ResolvedVariable mParameter;
+        private boolean mCallsGetAction;
+        private boolean mUsesIntent;
+
+        public OnReceiveVisitor(@NonNull JavaContext context, @Nullable ResolvedVariable parameter) {
+            mContext = context;
+            mParameter = parameter;
+        }
+
+        public boolean getCallsGetAction() {
+            return mCallsGetAction;
+        }
+
+        public boolean getUsesIntent() {
+            return mUsesIntent;
+        }
+
+        @Override
+        public boolean visitMethodInvocation(@NonNull MethodInvocation node) {
+            if (!mCallsGetAction) {
+                ResolvedNode resolved = mContext.resolve(node);
+                if (resolved instanceof ResolvedMethod) {
+                    ResolvedMethod method = (ResolvedMethod) resolved;
+                    if (method.getName().equals("getAction") &&
+                            method.getContainingClass().isSubclassOf(CLASS_INTENT, false)) {
+                        mCallsGetAction = true;
+                    }
+                }
+            }
+            return super.visitMethodInvocation(node);
+        }
+
+        @Override
+        public boolean visitVariableReference(@NonNull VariableReference node) {
+            if (!mUsesIntent && mParameter != null) {
+                ResolvedNode resolved = mContext.resolve(node);
+                if (mParameter.equals(resolved)) {
+                    mUsesIntent = true;
+                }
+            }
+
+            return super.visitVariableReference(node);
+        }
+    }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnsafeNativeCodeDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnsafeNativeCodeDetector.java
new file mode 100644
index 0000000..6033693
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnsafeNativeCodeDetector.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.DOT_NATIVE_LIBS;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Project;
+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 java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.MethodInvocation;
+
+public class UnsafeNativeCodeDetector extends Detector
+        implements Detector.JavaScanner {
+
+    private static final Implementation IMPLEMENTATION = new Implementation(
+            UnsafeNativeCodeDetector.class,
+            Scope.JAVA_FILE_SCOPE);
+
+    public static final Issue LOAD = Issue.create(
+            "UnsafeDynamicallyLoadedCode",
+            "`load` used to dynamically load code",
+            "Dynamically loading code from locations other than the application's library " +
+            "directory or the Android platform's built-in library directories is dangerous, " +
+            "as there is an increased risk that the code could have been tampered with. " +
+            "Applications should use `loadLibrary` when possible, which provides increased " +
+            "assurance that libraries are loaded from one of these safer locations. " +
+            "Application developers should use the features of their development " +
+            "environment to place application native libraries into the lib directory " +
+            "of their compiled APKs.",
+            Category.SECURITY,
+            4,
+            Severity.WARNING,
+            IMPLEMENTATION);
+
+    public static final Issue UNSAFE_NATIVE_CODE_LOCATION = Issue.create(
+            "UnsafeNativeCodeLocation", //$NON-NLS-1$
+            "Native code outside library directory",
+            "In general, application native code should only be placed in the application's " +
+            "library directory, not in other locations such as the res or assets directories. " +
+            "Placing the code in the library directory provides increased assurance that the " +
+            "code will not be tampered with after application installation. Application " +
+            "developers should use the features of their development environment to place " +
+            "application native libraries into the lib directory of their compiled " +
+            "APKs. Embedding non-shared library native executables into applications should " +
+            "be avoided when possible.",
+            Category.SECURITY,
+            4,
+            Severity.WARNING,
+            IMPLEMENTATION);
+
+    private static final String RUNTIME_CLASS = "java.lang.Runtime"; //$NON-NLS-1$
+    private static final String SYSTEM_CLASS = "java.lang.System"; //$NON-NLS-1$
+
+    private static final byte[] ELF_MAGIC_VALUE = { (byte) 0x7F, (byte) 0x45, (byte) 0x4C, (byte) 0x46 };
+
+    @NonNull
+    @Override
+    public Speed getSpeed() {
+        return Speed.NORMAL;
+    }
+
+    // ---- Implements Detector.JavaScanner ----
+
+    @Override
+    public List<String> getApplicableMethodNames() {
+        // Identify calls to Runtime.load() and System.load()
+        return Collections.singletonList("load");
+    }
+
+    @Override
+    public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+            @NonNull MethodInvocation node) {
+        ResolvedNode resolved = context.resolve(node);
+        if (resolved instanceof ResolvedMethod) {
+            String methodName = node.astName().astValue();
+            ResolvedClass resolvedClass = ((ResolvedMethod) resolved).getContainingClass();
+            if ((resolvedClass.isSubclassOf(RUNTIME_CLASS, false)) ||
+                    (resolvedClass.matches(SYSTEM_CLASS))) {
+                // Report calls to Runtime.load() and System.load()
+                if ("load".equals(methodName)) {
+                    context.report(LOAD, node, context.getLocation(node),
+                            "Dynamically loading code using `load` is risky, please use " +
+                                    "`loadLibrary` instead when possible");
+                }
+            }
+        }
+    }
+
+    // ---- Look for code in resource and asset directories ----
+
+    @Override
+    public void afterCheckLibraryProject(@NonNull Context context) {
+        if (!context.getProject().getReportIssues()) {
+            // If this is a library project not being analyzed, ignore it
+            return;
+        }
+
+        checkResourceFolders(context, context.getProject());
+    }
+
+    @Override
+    public void afterCheckProject(@NonNull Context context) {
+        if (!context.getProject().getReportIssues()) {
+            // If this is a library project not being analyzed, ignore it
+            return;
+        }
+
+        checkResourceFolders(context, context.getProject());
+    }
+
+    private static boolean isNativeCode(File file) {
+        if (!file.isFile()) {
+            return false;
+        }
+
+        try {
+            FileInputStream fis = new FileInputStream(file);
+            try {
+                byte[] bytes = new byte[4];
+                int length = fis.read(bytes);
+                return (length == 4) && (Arrays.equals(ELF_MAGIC_VALUE, bytes));
+            } finally {
+                fis.close();
+            }
+        } catch (IOException ex) {
+            return false;
+        }
+    }
+
+    private static void checkResourceFolders(Context context, @NonNull Project project) {
+        if (!context.getScope().contains(Scope.RESOURCE_FOLDER)) {
+            // Don't do work when doing in-editor analysis of Java files
+            return;
+        }
+        List<File> resourceFolders = project.getResourceFolders();
+        for (File res : resourceFolders) {
+            File[] folders = res.listFiles();
+            if (folders != null) {
+                for (File typeFolder : folders) {
+                    if (typeFolder.getName().startsWith(SdkConstants.FD_RES_RAW)) {
+                        File[] rawFiles = typeFolder.listFiles();
+                        if (rawFiles != null) {
+                            for (File rawFile : rawFiles) {
+                                checkFile(context, rawFile);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        List<File> assetFolders = project.getAssetFolders();
+        for (File assetFolder : assetFolders) {
+            File[] assets = assetFolder.listFiles();
+            if (assets != null) {
+                for (File asset : assets) {
+                    checkFile(context, asset);
+                }
+            }
+        }
+    }
+
+    private static void checkFile(@NonNull Context context, @NonNull File file) {
+        if (isNativeCode(file)) {
+            if (LintUtils.endsWith(file.getPath(), DOT_NATIVE_LIBS)) {
+                context.report(UNSAFE_NATIVE_CODE_LOCATION, Location.create(file),
+                        "Shared libraries should not be placed in the res or assets " +
+                        "directories. Please use the features of your development " +
+                        "environment to place shared libraries in the lib directory of " +
+                        "the compiled APK.");
+            } else {
+                context.report(UNSAFE_NATIVE_CODE_LOCATION, Location.create(file),
+                        "Embedding non-shared library native executables into applications " +
+                        "should be avoided when possible, as there is an increased risk that " +
+                        "the executables could be tampered with after installation. Instead, " +
+                        "native code should be placed in a shared library, and the features of " +
+                        "the development environment should be used to place the shared library " +
+                        "in the lib directory of the compiled APK.");
+            }
+        }
+    }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnusedResourceDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnusedResourceDetector.java
index 4117067..e5b37a1 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnusedResourceDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnusedResourceDetector.java
@@ -16,85 +16,85 @@
 
 package com.android.tools.lint.checks;
 
-import static com.android.SdkConstants.ANDROID_URI;
 import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_REF_PREFIX;
-import static com.android.SdkConstants.ATTR_TYPE;
+import static com.android.SdkConstants.DOT_JAVA;
 import static com.android.SdkConstants.DOT_XML;
-import static com.android.SdkConstants.RESOURCE_CLR_STYLEABLE;
-import static com.android.SdkConstants.RESOURCE_CLZ_ARRAY;
-import static com.android.SdkConstants.RESOURCE_CLZ_ID;
-import static com.android.SdkConstants.R_ATTR_PREFIX;
 import static com.android.SdkConstants.R_CLASS;
-import static com.android.SdkConstants.R_ID_PREFIX;
-import static com.android.SdkConstants.R_PREFIX;
-import static com.android.SdkConstants.TAG_ARRAY;
-import static com.android.SdkConstants.TAG_INTEGER_ARRAY;
-import static com.android.SdkConstants.TAG_ITEM;
-import static com.android.SdkConstants.TAG_PLURALS;
-import static com.android.SdkConstants.TAG_RESOURCES;
-import static com.android.SdkConstants.TAG_STRING_ARRAY;
-import static com.android.SdkConstants.TAG_STYLE;
-import static com.android.utils.SdkUtils.getResourceFieldName;
+import static com.android.tools.lint.detector.api.LintUtils.findSubstring;
+import static com.android.utils.SdkUtils.endsWithIgnoreCase;
+import static com.google.common.base.Charsets.UTF_8;
 
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.BuildTypeContainer;
+import com.android.builder.model.ClassField;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.ProductFlavorContainer;
+import com.android.builder.model.SourceProvider;
+import com.android.builder.model.Variant;
+import com.android.resources.ResourceFolderType;
 import com.android.resources.ResourceType;
+import com.android.tools.lint.checks.ResourceUsageModel.Resource;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedField;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
 import com.android.tools.lint.detector.api.Category;
 import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Detector.BinaryResourceScanner;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Detector.XmlScanner;
 import com.android.tools.lint.detector.api.Implementation;
 import com.android.tools.lint.detector.api.Issue;
 import com.android.tools.lint.detector.api.JavaContext;
 import com.android.tools.lint.detector.api.LintUtils;
 import com.android.tools.lint.detector.api.Location;
 import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.ResourceContext;
 import com.android.tools.lint.detector.api.ResourceXmlDetector;
 import com.android.tools.lint.detector.api.Scope;
 import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.TextFormat;
 import com.android.tools.lint.detector.api.XmlContext;
+import com.android.utils.XmlUtils;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
 
-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.io.IOException;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
 import lombok.ast.AstVisitor;
 import lombok.ast.ClassDeclaration;
+import lombok.ast.CompilationUnit;
 import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.NormalTypeBody;
-import lombok.ast.VariableDeclaration;
-import lombok.ast.VariableDefinition;
+import lombok.ast.Identifier;
+import lombok.ast.ImportDeclaration;
+import lombok.ast.VariableReference;
 
 /**
  * Finds unused resources.
- * <p>
- * Note: This detector currently performs *string* analysis to check Java files.
- * The Lint API needs an official Java AST API (or map to an existing one like
- * BCEL for bytecode analysis etc) and once it does this should be updated to
- * use it.
  */
-public class UnusedResourceDetector extends ResourceXmlDetector implements Detector.JavaScanner {
+public class UnusedResourceDetector extends ResourceXmlDetector implements JavaScanner,
+        BinaryResourceScanner, XmlScanner {
 
     private static final Implementation IMPLEMENTATION = new Implementation(
             UnusedResourceDetector.class,
             EnumSet.of(Scope.MANIFEST, Scope.ALL_RESOURCE_FILES, Scope.ALL_JAVA_FILES,
-                    Scope.TEST_SOURCES));
+                    Scope.BINARY_RESOURCE_FILE, Scope.TEST_SOURCES));
 
     /** Unused resources (other than ids). */
     public static final Issue ISSUE = Issue.create(
@@ -120,9 +120,12 @@
             IMPLEMENTATION)
             .setEnabledByDefault(false);
 
-    private Set<String> mDeclarations;
-    private Set<String> mReferences;
-    private Map<String, Location> mUnused;
+    private final UnusedResourceDetectorUsageModel mModel =
+            new UnusedResourceDetectorUsageModel();
+
+    /** Whether the resource detector will look for inactive resources (e.g. resource and code references
+     * in source sets that are not the primary/active variant) */
+    public static boolean sIncludeInactiveReferences = true;
 
     /**
      * Constructs a new {@link UnusedResourceDetector}
@@ -130,70 +133,41 @@
     public UnusedResourceDetector() {
     }
 
-    @Override
-    public void run(@NonNull Context context) {
-        assert false;
-    }
-
-    @Override
-    public boolean appliesTo(@NonNull Context context, @NonNull File file) {
-        return true;
-    }
-
-    @Override
-    public void beforeCheckProject(@NonNull Context context) {
-        if (context.getPhase() == 1) {
-            mDeclarations = new HashSet<String>(300);
-            mReferences = new HashSet<String>(300);
+    private void addDynamicResources(
+            @NonNull Context context) {
+        Project project = context.getProject();
+        AndroidProject model = project.getGradleProjectModel();
+        if (model != null) {
+            Variant selectedVariant = project.getCurrentVariant();
+            if (selectedVariant != null) {
+                for (BuildTypeContainer container : model.getBuildTypes()) {
+                    if (selectedVariant.getBuildType().equals(container.getBuildType().getName())) {
+                        addDynamicResources(project, container.getBuildType().getResValues());
+                    }
+                }
+            }
+            ProductFlavor flavor = model.getDefaultConfig().getProductFlavor();
+            addDynamicResources(project, flavor.getResValues());
         }
     }
 
-    // ---- Implements JavaScanner ----
-
-    @Override
-    public void beforeCheckFile(@NonNull Context context) {
-        File file = context.file;
-
-        boolean isXmlFile = LintUtils.isXmlFile(file);
-        if (isXmlFile || LintUtils.isBitmapFile(file)) {
-            String fileName = file.getName();
-            String parentName = file.getParentFile().getName();
-            int dash = parentName.indexOf('-');
-            String typeName = parentName.substring(0, dash == -1 ? parentName.length() : dash);
-            ResourceType type = ResourceType.getEnum(typeName);
-            if (type != null && LintUtils.isFileBasedResourceType(type)) {
-                String baseName = fileName.substring(0, fileName.length() - DOT_XML.length());
-                String resource = R_PREFIX + typeName + '.' + baseName;
-                if (context.getPhase() == 1) {
-                    mDeclarations.add(resource);
-                } else {
-                    assert context.getPhase() == 2;
-                    if (mUnused.containsKey(resource)) {
-                        // Check whether this is an XML document that has a tools:ignore attribute
-                        // on the document element: if so don't record it as a declaration.
-                        if (isXmlFile && context instanceof XmlContext) {
-                            XmlContext xmlContext = (XmlContext) context;
-                            if (xmlContext.document != null
-                                    && xmlContext.document.getDocumentElement() != null) {
-                                Element root = xmlContext.document.getDocumentElement();
-                                if (xmlContext.getDriver().isSuppressed(xmlContext, ISSUE, root)) {
-                                    //  Also remove it from consideration such that even the
-                                    // presence of this field in the R file is ignored.
-                                    mUnused.remove(resource);
-                                    return;
-                                }
-                            }
-                        }
-
-                        if (!context.getProject().getReportIssues()) {
-                            // If this is a library project not being analyzed, ignore it
-                            mUnused.remove(resource);
-                            return;
-                        }
-
-                        recordLocation(resource, Location.create(file));
-                    }
+    private void addDynamicResources(@NonNull Project project,
+            @NonNull Map<String, ClassField> resValues) {
+        Set<String> keys = resValues.keySet();
+        if (!keys.isEmpty()) {
+            Location location = LintUtils.guessGradleLocation(project);
+            for (String name : keys) {
+                ClassField field = resValues.get(name);
+                ResourceType type = ResourceType.getEnum(field.getType());
+                if (type == null) {
+                    // Highly unlikely. This would happen if in the future we add
+                    // some new ResourceType, that the Gradle plugin (and the user's
+                    // Gradle file is creating) and it's an older version of Studio which
+                    // doesn't yet have this ResourceType in its enum.
+                    continue;
                 }
+                Resource resource = mModel.declareResource(type, name, null);
+                resource.recordLocation(location);
             }
         }
     }
@@ -201,28 +175,39 @@
     @Override
     public void afterCheckProject(@NonNull Context context) {
         if (context.getPhase() == 1) {
-            mDeclarations.removeAll(mReferences);
-            Set<String> unused = mDeclarations;
-            mReferences = null;
-            mDeclarations = null;
+            Project project = context.getProject();
 
-            // Remove styles and attributes: they may be used, analysis isn't complete for these
-            List<String> styles = new ArrayList<String>();
-            for (String resource : unused) {
-                // R.style.x, R.styleable.x, R.attr
-                if (resource.startsWith("R.style")          //$NON-NLS-1$
-                        || resource.startsWith("R.attr")) { //$NON-NLS-1$
-                    styles.add(resource);
+            // Look for source sets that aren't part of the active variant;
+            // we need to make sure we find references in those source sets as well
+            // such that we don't incorrectly remove resources that are
+            // used by some other source set.
+            if (sIncludeInactiveReferences && project.isGradleProject() && !project.isLibrary()) {
+                AndroidProject model = project.getGradleProjectModel();
+                Variant variant = project.getCurrentVariant();
+                if (model != null && variant != null) {
+                    addInactiveReferences(model, variant);
                 }
             }
-            unused.removeAll(styles);
+
+            addDynamicResources(context);
+            mModel.processToolsAttributes();
+
+            List<Resource> unusedResources = mModel.findUnused();
+            Set<Resource> unused = Sets.newHashSetWithExpectedSize(unusedResources.size());
+            for (Resource resource : unusedResources) {
+                if (resource.isDeclared()
+                        && !resource.isPublic()
+                        && resource.type != ResourceType.PUBLIC) {
+                    unused.add(resource);
+                }
+            }
 
             // Remove id's if the user has disabled reporting issue ids
             if (!unused.isEmpty() && !context.isEnabled(ISSUE_IDS)) {
                 // Remove all R.id references
-                List<String> ids = new ArrayList<String>();
-                for (String resource : unused) {
-                    if (resource.startsWith(R_ID_PREFIX)) {
+                List<Resource> ids = Lists.newArrayList();
+                for (Resource resource : unused) {
+                    if (resource.type == ResourceType.ID) {
                         ids.add(resource);
                     }
                 }
@@ -230,10 +215,7 @@
             }
 
             if (!unused.isEmpty() && !context.getDriver().hasParserErrors()) {
-                mUnused = new HashMap<String, Location>(unused.size());
-                for (String resource : unused) {
-                    mUnused.put(resource, null);
-                }
+                mModel.unused = unused;
 
                 // Request another pass, and in the second pass we'll gather location
                 // information for all declaration locations we've found
@@ -244,11 +226,21 @@
 
             // Report any resources that we (for some reason) could not find a declaration
             // location for
-            if (!mUnused.isEmpty()) {
+            Collection<Resource> unused = mModel.unused;
+            if (!unused.isEmpty()) {
+                // Final pass: we may have marked a few resource declarations with
+                // tools:ignore; we don't check that on every single element, only those
+                // first thought to be unused. We don't just remove the elements explicitly
+                // marked as unused, we revisit everything transitively such that resources
+                // referenced from the ignored/kept resource are also kept.
+                unused = mModel.findUnused(Lists.newArrayList(unused));
+                if (unused.isEmpty()) {
+                    return;
+                }
+
                 // Fill in locations for files that we didn't encounter in other ways
-                for (Map.Entry<String, Location> entry : mUnused.entrySet()) {
-                    String resource = entry.getKey();
-                    Location location = entry.getValue();
+                for (Resource resource : unused) {
+                    Location location = resource.locations;
                     //noinspection VariableNotUsedInsideIf
                     if (location != null) {
                         continue;
@@ -258,11 +250,9 @@
                     // in that case we can figure out the filename since it has a simple mapping
                     // from the resource name (though the presence of qualifiers like -land etc
                     // makes it a little tricky if there's no base file provided)
-                    int secondDot = resource.indexOf('.', 2);
-                    String typeName = resource.substring(2, secondDot); // 2: Skip R.
-                    ResourceType type = ResourceType.getEnum(typeName);
+                    ResourceType type = resource.type;
                     if (type != null && LintUtils.isFileBasedResourceType(type)) {
-                        String name = resource.substring(secondDot + 1);
+                        String name = resource.name;
 
                         List<File> folders = Lists.newArrayList();
                         List<File> resourceFolders = context.getProject().getResourceFolders();
@@ -272,7 +262,7 @@
                                 folders.addAll(Arrays.asList(f));
                             }
                         }
-                        if (folders != null) {
+                        if (!folders.isEmpty()) {
                             // Process folders in alphabetical order such that we process
                             // based folders first: we want the locations in base folder
                             // order
@@ -283,7 +273,7 @@
                                 }
                             });
                             for (File folder : folders) {
-                                if (folder.getName().startsWith(typeName)) {
+                                if (folder.getName().startsWith(type.getName())) {
                                     File[] files = folder.listFiles();
                                     if (files != null) {
                                         Arrays.sort(files);
@@ -292,7 +282,7 @@
                                             if (fileName.startsWith(name)
                                                     && fileName.startsWith(".", //$NON-NLS-1$
                                                             name.length())) {
-                                                recordLocation(resource, Location.create(file));
+                                                resource.recordLocation(Location.create(file));
                                             }
                                         }
                                     }
@@ -302,13 +292,13 @@
                     }
                 }
 
-                List<String> sorted = new ArrayList<String>(mUnused.keySet());
+                List<Resource> sorted = Lists.newArrayList(unused);
                 Collections.sort(sorted);
 
                 Boolean skippedLibraries = null;
 
-                for (String resource : sorted) {
-                    Location location = mUnused.get(resource);
+                for (Resource resource : sorted) {
+                    Location location = resource.locations;
                     if (location != null) {
                         // We were prepending locations, but we want to prefer the base folders
                         location = Location.reverse(location);
@@ -333,194 +323,198 @@
                         }
                     }
 
+                    // Keep in sync with getUnusedResource() below
                     String message = String.format("The resource `%1$s` appears to be unused",
-                            resource);
-                    Issue issue = getIssue(resource);
-                    // TODO: Compute applicable node scope
-                    context.report(issue, location, message);
+                            resource.getField());
+                    context.report(getIssue(resource), location, message);
                 }
             }
         }
     }
 
-    private static Issue getIssue(String resource) {
-        return resource.startsWith(R_ID_PREFIX) ? ISSUE_IDS : ISSUE;
-    }
+    /** Returns source providers that are <b>not</b> part of the given variant */
+    @NonNull
+    private static List<SourceProvider> getInactiveSourceProviders(
+            @NonNull AndroidProject project,
+            @NonNull Variant variant) {
+        Collection<Variant> variants = project.getVariants();
+        List<SourceProvider> providers = Lists.newArrayList();
 
-    private void recordLocation(String resource, Location location) {
-        Location oldLocation = mUnused.get(resource);
-        if (oldLocation != null) {
-            location.setSecondary(oldLocation);
+        // Add other flavors
+        Collection<ProductFlavorContainer> flavors = project.getProductFlavors();
+        for (ProductFlavorContainer pfc : flavors) {
+            if (variant.getProductFlavors().contains(pfc.getProductFlavor().getName())) {
+                continue;
+            }
+            providers.add(pfc.getSourceProvider());
         }
-        mUnused.put(resource, location);
+
+        // Add other multi-flavor source providers
+        for (Variant v : variants) {
+            if (variant.getName().equals(v.getName())) {
+                continue;
+            }
+            SourceProvider provider = v.getMainArtifact().getMultiFlavorSourceProvider();
+            if (provider != null) {
+                providers.add(provider);
+            }
+        }
+
+        // Add other the build types
+        Collection<BuildTypeContainer> buildTypes = project.getBuildTypes();
+        for (BuildTypeContainer btc : buildTypes) {
+            if (variant.getBuildType().equals(btc.getBuildType().getName())) {
+                continue;
+            }
+            providers.add(btc.getSourceProvider());
+        }
+
+        // Add other the other variant source providers
+        for (Variant v : variants) {
+            if (variant.getName().equals(v.getName())) {
+                continue;
+            }
+            SourceProvider provider = v.getMainArtifact().getVariantSourceProvider();
+            if (provider != null) {
+                providers.add(provider);
+            }
+        }
+
+        return providers;
     }
 
-    @Override
-    public Collection<String> getApplicableAttributes() {
-        return ALL;
-    }
-
-    @Override
-    public Collection<String> getApplicableElements() {
-        return Arrays.asList(
-                TAG_STYLE,
-                TAG_RESOURCES,
-                TAG_ARRAY,
-                TAG_STRING_ARRAY,
-                TAG_INTEGER_ARRAY,
-                TAG_PLURALS
-        );
-    }
-
-    @Override
-    public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
-        if (TAG_RESOURCES.equals(element.getTagName())) {
-            for (Element item : LintUtils.getChildren(element)) {
-                Attr nameAttribute = item.getAttributeNode(ATTR_NAME);
-                if (nameAttribute != null) {
-                    String name = getResourceFieldName(nameAttribute.getValue());
-                    String type = item.getTagName();
-                    if (type.equals(TAG_ITEM)) {
-                        type = item.getAttribute(ATTR_TYPE);
-                        if (type == null || type.isEmpty()) {
-                            type = RESOURCE_CLZ_ID;
-                        }
-                    } else if (type.equals("declare-styleable")) {   //$NON-NLS-1$
-                        type = RESOURCE_CLR_STYLEABLE;
-                    } else if (type.contains("array")) {             //$NON-NLS-1$
-                        // <string-array> etc
-                        type = RESOURCE_CLZ_ARRAY;
+    private void recordInactiveJavaReferences(@NonNull File resDir) {
+        File[] files = resDir.listFiles();
+        if (files != null) {
+            for (File file : files) {
+                if (file.isDirectory()) {
+                    recordInactiveJavaReferences(file);
+                } else if (file.getName().endsWith(DOT_JAVA)) {
+                    try {
+                        String java = Files.toString(file, UTF_8);
+                        mModel.tokenizeJavaCode(java);
+                    } catch (Throwable ignore) {
+                        // Tolerate parsing errors etc in these files; they're user
+                        // sources, and this is even for inactive source sets.
                     }
-                    String resource = R_PREFIX + type + '.' + name;
+                }
+            }
+        }
+    }
 
-                    if (context.getPhase() == 1) {
-                        mDeclarations.add(resource);
-                        checkChildRefs(item);
+    private void recordInactiveXmlResources(@NonNull File resDir) {
+        File[] resourceFolders = resDir.listFiles();
+        if (resourceFolders != null) {
+            for (File folder : resourceFolders) {
+                ResourceFolderType folderType = ResourceFolderType.getFolderType(folder.getName());
+                if (folderType != null) {
+                    recordInactiveXmlResources(folderType, folder);
+                }
+            }
+        }
+    }
+
+    // Used for traversing resource folders *outside* of the normal Gradle variant
+    // folders: these are not necessarily on the project path, so we don't have PSI files
+    // for them
+    private void recordInactiveXmlResources(@NonNull ResourceFolderType folderType,
+      @NonNull File folder) {
+        File[] files = folder.listFiles();
+        if (files != null) {
+            for (File file : files) {
+                String path = file.getPath();
+                boolean isXml = endsWithIgnoreCase(path, DOT_XML);
+                try {
+                    if (isXml) {
+                        String xml = Files.toString(file, UTF_8);
+                        Document document = XmlUtils.parseDocument(xml, true);
+                        mModel.visitXmlDocument(file, folderType, document);
                     } else {
-                        assert context.getPhase() == 2;
-                        if (mUnused.containsKey(resource)) {
-                            if (context.getDriver().isSuppressed(context, getIssue(resource),
-                                    item)) {
-                                mUnused.remove(resource);
-                                continue;
-                            }
-                            if (!context.getProject().getReportIssues()) {
-                                mUnused.remove(resource);
-                                continue;
-                            }
-                            if (isAnalyticsFile(context)) {
-                                mUnused.remove(resource);
-                                continue;
-                            }
-
-                            recordLocation(resource, context.getLocation(nameAttribute));
-                        }
+                        mModel.visitBinaryResource(folderType, file);
                     }
+                } catch (Throwable ignore) {
+                    // Tolerate parsing errors etc in these files; they're user
+                    // sources, and this is even for inactive source sets.
                 }
             }
-        } else //noinspection VariableNotUsedInsideIf
-            if (mReferences != null) {
-            assert TAG_STYLE.equals(element.getTagName())
-                || TAG_ARRAY.equals(element.getTagName())
-                || TAG_PLURALS.equals(element.getTagName())
-                || TAG_INTEGER_ARRAY.equals(element.getTagName())
-                || TAG_STRING_ARRAY.equals(element.getTagName());
-            for (Element item : LintUtils.getChildren(element)) {
-                checkChildRefs(item);
-            }
         }
     }
 
-    private static final String ANALYTICS_FILE = "analytics.xml"; //$NON-NLS-1$
+    private void addInactiveReferences(@NonNull AndroidProject model,
+                              @NonNull Variant variant) {
+        for (SourceProvider provider : getInactiveSourceProviders(model, variant)) {
+            for (File res : provider.getResDirectories()) {
+                // Scan resource directory
+                if (res.isDirectory()) {
+                    recordInactiveXmlResources(res);
+                }
+            }
+            for (File file : provider.getJavaDirectories()) {
+                // Scan Java directory
+                if (file.isDirectory()) {
+                    recordInactiveJavaReferences(file);
+                }
+            }
+        }
+    }
 
     /**
-     * Returns true if this XML file corresponds to an Analytics configuration file;
-     * these contain some attributes read by the library which won't be flagged as
-     * used by the application
+     * Given an error message created by this lint check, return the corresponding
+     * resource field name for the resource that is described as unused.
+     * (Intended to support quickfix implementations for this lint check.)
      *
-     * @param context the context used for scanning
-     * @return true if the file represents an analytics file
+     * @param errorMessage the error message originally produced by this detector
+     * @param format the format of the error message
+     * @return the corresponding resource field name, e.g. {@code R.string.foo}
      */
-    public static boolean isAnalyticsFile(Context context) {
-        File file = context.file;
-        return file.getPath().endsWith(ANALYTICS_FILE) && file.getName().equals(ANALYTICS_FILE);
+    @Nullable
+    public static String getUnusedResource(@NonNull String errorMessage, @NonNull TextFormat format) {
+        errorMessage = format.toText(errorMessage);
+        return findSubstring(errorMessage, "The resource ", " appears ");
     }
 
-    private void checkChildRefs(Element item) {
-        // Look for ?attr/ and @dimen/foo etc references in the item children
-        NodeList childNodes = item.getChildNodes();
-        for (int i = 0, n = childNodes.getLength(); i < n; i++) {
-            Node child = childNodes.item(i);
-            if (child.getNodeType() == Node.TEXT_NODE) {
-                String text = child.getNodeValue();
-
-                int index = text.indexOf(ATTR_REF_PREFIX);
-                if (index != -1) {
-                    String name = text.substring(index + ATTR_REF_PREFIX.length()).trim();
-                    mReferences.add(R_ATTR_PREFIX + name);
-                } else {
-                    index = text.indexOf('@');
-                    if (index != -1 && text.indexOf('/', index) != -1
-                            && !text.startsWith("@android:", index)) {  //$NON-NLS-1$
-                        // Compute R-string, e.g. @string/foo => R.string.foo
-                        String token = text.substring(index + 1).trim().replace('/', '.');
-                        String r = R_PREFIX + token;
-                        mReferences.add(r);
-                    }
-                }
-            }
-        }
+    private static Issue getIssue(@NonNull Resource resource) {
+        return resource.type != ResourceType.ID ? ISSUE : ISSUE_IDS;
     }
 
     @Override
-    public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
-        String value = attribute.getValue();
-
-        if (value.startsWith("@+") && !value.startsWith("@+android")) { //$NON-NLS-1$ //$NON-NLS-2$
-            String resource = R_PREFIX + value.substring(2).replace('/', '.');
-            // We already have the declarations when we scan the R file, but we're tracking
-            // these here to get attributes for position info
-
-            if (context.getPhase() == 1) {
-                mDeclarations.add(resource);
-            } else if (mUnused.containsKey(resource)) {
-                if (context.getDriver().isSuppressed(context, getIssue(resource), attribute)) {
-                    mUnused.remove(resource);
-                    return;
-                }
-                if (!context.getProject().getReportIssues()) {
-                    mUnused.remove(resource);
-                    return;
-                }
-                recordLocation(resource, context.getLocation(attribute));
-                return;
-            }
-        } else if (mReferences != null) {
-            if (value.startsWith("@")              //$NON-NLS-1$
-                    && !value.startsWith("@android:")) {  //$NON-NLS-1$
-                // Compute R-string, e.g. @string/foo => R.string.foo
-                String r = R_PREFIX + value.substring(1).replace('/', '.');
-                mReferences.add(r);
-            } else if (value.startsWith(ATTR_REF_PREFIX)) {
-                mReferences.add(R_ATTR_PREFIX + value.substring(ATTR_REF_PREFIX.length()));
-            }
-        }
-
-        if (attribute.getNamespaceURI() != null
-                && !ANDROID_URI.equals(attribute.getNamespaceURI()) && mReferences != null) {
-            mReferences.add(R_ATTR_PREFIX + attribute.getLocalName());
-        }
+    public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+        return true;
     }
 
-    @NonNull
+    // ---- Implements BinaryResourceScanner ----
+
     @Override
-    public Speed getSpeed() {
-        return Speed.SLOW;
+    public void checkBinaryResource(@NonNull ResourceContext context) {
+        mModel.context = context;
+        try {
+            mModel.visitBinaryResource(context.getResourceFolderType(), context.file);
+        } finally {
+            mModel.context = null;
+        }
     }
 
+    // ---- Implements XmlScanner ----
+
+    @Override
+    public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
+        mModel.context = mModel.xmlContext = context;
+        try {
+            mModel.visitXmlDocument(context.file, context.getResourceFolderType(),
+                    document);
+        } finally {
+            mModel.context = mModel.xmlContext = null;
+        }
+    }
+
+    // ---- Implements JavaScanner ----
+
     @Override
     public List<Class<? extends lombok.ast.Node>> getApplicableNodeTypes() {
-        return Collections.<Class<? extends lombok.ast.Node>>singletonList(ClassDeclaration.class);
+        //noinspection unchecked
+        return Arrays.<Class<? extends lombok.ast.Node>>asList(
+                ClassDeclaration.class,
+                ImportDeclaration.class);
     }
 
     @Override
@@ -532,16 +526,17 @@
     public void visitResourceReference(@NonNull JavaContext context, @Nullable AstVisitor visitor,
             @NonNull lombok.ast.Node node, @NonNull String type, @NonNull String name,
             boolean isFramework) {
-        if (mReferences != null && !isFramework) {
-            String reference = R_PREFIX + type + '.' + name;
-            mReferences.add(reference);
+        if (!isFramework) {
+            ResourceType t = ResourceType.getEnum(type);
+            assert t != null : type;
+            ResourceUsageModel.markReachable(mModel.addResource(t, name, null));
         }
     }
 
     @Override
     public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
-        if (mReferences != null) {
-            return new UnusedResourceVisitor();
+        if (context.getDriver().getPhase() == 1) {
+            return new UnusedResourceVisitor(context);
         } else {
             // Second pass, computing resource declaration locations: No need to look at Java
             return null;
@@ -550,53 +545,167 @@
 
     // Look for references and declarations
     private class UnusedResourceVisitor extends ForwardingAstVisitor {
+        private final JavaContext mContext;
+
+        public UnusedResourceVisitor(JavaContext context) {
+            mContext = context;
+        }
+
         @Override
         public boolean visitClassDeclaration(ClassDeclaration node) {
-            // Look for declarations of R class fields and store them in
-            // mDeclarations
+            // Look for declarations of R class fields and record them as declarations
             String description = node.astName().astValue();
             if (description.equals(R_CLASS)) {
-                // This is an R class. We can process this class very deliberately.
-                // The R class has a very specific AST format:
-                // ClassDeclaration ("R")
-                //    NormalTypeBody
-                //        ClassDeclaration (e.g. "drawable")
-                //             NormalTypeBody
-                //                 VariableDeclaration
-                //                     VariableDefinition (e.g. "ic_launcher")
-                for (lombok.ast.Node body : node.getChildren()) {
-                    if (body instanceof NormalTypeBody) {
-                        for (lombok.ast.Node subclass : body.getChildren()) {
-                            if (subclass instanceof ClassDeclaration) {
-                                String className = ((ClassDeclaration) subclass).astName().astValue();
-                                for (lombok.ast.Node innerBody : subclass.getChildren()) {
-                                    if (innerBody instanceof NormalTypeBody) {
-                                        for (lombok.ast.Node field : innerBody.getChildren()) {
-                                            if (field instanceof VariableDeclaration) {
-                                                for (lombok.ast.Node child : field.getChildren()) {
-                                                    if (child instanceof VariableDefinition) {
-                                                        VariableDefinition def =
-                                                                (VariableDefinition) child;
-                                                        String name = def.astVariables().first()
-                                                                .astName().astValue();
-                                                        String resource = R_PREFIX + className
-                                                                + '.' + name;
-                                                        mDeclarations.add(resource);
-                                                    } // Else: It could be a comment node
-                                                }
-                                            }
-                                        }
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-
+                // Don't visit R class declarations; we don't need to look at R declarations
+                // since we now look for all the same resource declarations that the R
+                // class itself was derived from.
                 return true;
             }
 
             return false;
         }
+
+        @Override
+        public boolean visitImportDeclaration(ImportDeclaration node) {
+            if (node.astStaticImport()) {
+                // import static pkg.R.type.name;
+                //   or
+                // import static pkg.R.type.*;
+                Iterator<Identifier> iterator = node.astParts().iterator();
+                while (iterator.hasNext()) {
+                    String identifier = iterator.next().astValue();
+                    if (identifier.equals(R_CLASS)) {
+                        if (iterator.hasNext()) {
+                            String typeString = iterator.next().astValue();
+                            ResourceType type = ResourceType.getEnum(typeString);
+                            if (type != null) {
+                                if (iterator.hasNext()) {
+                                    // import static pkg.R.type.name;
+                                    String name = iterator.next().astValue();
+                                    Resource resource = mModel.addResource(type, name, null);
+                                    ResourceUsageModel.markReachable(resource);
+                                } else if (!mScannedForStaticImports) {
+                                    // wildcard import of whole type:
+                                    // import static pkg.R.type.*;
+                                    // We have to do a more expensive analysis here to
+                                    // for example recognize "x" as a reference to R.string.x
+                                    mScannedForStaticImports = true;
+                                    CompilationUnit unit = node.upToCompilationUnit();
+                                    scanForStaticImportReferences(unit);
+                                }
+                            }
+                        }
+                        break;
+                    }
+                }
+            } else if (!mScannedForStaticImports) {
+                String last = node.astParts().last().astValue();
+                if (Character.isLowerCase(last.charAt(0))) {
+                    Iterator<Identifier> iterator = node.astParts().iterator();
+                    while (iterator.hasNext()) {
+                        String identifier = iterator.next().astValue();
+                        if (identifier.equals(R_CLASS)) {
+                            if (iterator.hasNext()) {
+                                String typeString = iterator.next().astValue();
+                                ResourceType type = ResourceType.getEnum(typeString);
+                                if (type != null && !iterator.hasNext()) {
+                                    // Import a type as a class:
+                                    // import pkg.R.type;
+                                    // We have to do a more expensive analysis here to
+                                    // for example recognize "string.x" as a reference to R.string.x
+                                    mScannedForStaticImports = true;
+                                    CompilationUnit unit = node.upToCompilationUnit();
+                                    scanForStaticImportReferences(unit);
+                                }
+                            }
+                            break;
+                        }
+                    }
+                }
+            }
+
+            return false;
+        }
+
+        private void scanForStaticImportReferences(@Nullable CompilationUnit unit) {
+            if (unit == null) {
+                return;
+            }
+            unit.accept(new ForwardingAstVisitor() {
+                @Override
+                public boolean visitVariableReference(
+                        VariableReference node) {
+                    ResolvedNode resolved = mContext.resolve(node);
+                    if (resolved instanceof ResolvedField) {
+                        ResolvedField field = (ResolvedField) resolved;
+                        ResolvedClass typeClass = field.getContainingClass();
+                        if (typeClass != null) {
+                            ResolvedClass rClass = typeClass.getContainingClass();
+                            if (rClass != null && R_CLASS.equals(rClass.getSimpleName())) {
+                                ResourceType type = ResourceType.getEnum(typeClass.getSimpleName());
+                                if (type != null) {
+                                    Resource resource = mModel.addResource(type, field.getName(),
+                                            null);
+                                    ResourceUsageModel.markReachable(resource);
+                                }
+                            }
+                        }
+
+                    }
+                    return super.visitVariableReference(node);
+                }
+            });
+        }
+
+        private boolean mScannedForStaticImports;
+    }
+
+    private static class UnusedResourceDetectorUsageModel extends ResourceUsageModel {
+        public XmlContext xmlContext;
+        public Context context;
+        public Set<Resource> unused = Sets.newHashSet();
+
+        @NonNull
+        @Override
+        protected String readText(@NonNull File file) {
+            if (context != null) {
+                return context.getClient().readFile(file);
+            }
+            try {
+                return Files.toString(file, UTF_8);
+            } catch (IOException e) {
+                return ""; // Lint API
+            }
+        }
+
+        @Override
+        protected Resource declareResource(ResourceType type, String name, Node node) {
+            Resource resource = super.declareResource(type, name, node);
+            if (context != null) {
+                resource.setDeclared(context.getProject().getReportIssues());
+                if (context.getPhase() == 2 && unused.contains(resource)) {
+                    if (xmlContext != null && xmlContext.getDriver().isSuppressed(xmlContext,
+                            getIssue(resource), node)) {
+                        resource.setKeep(true);
+                    } else {
+                        // For positions we try to use the name node rather than the
+                        // whole declaration element
+                        if (node == null || xmlContext == null) {
+                            resource.recordLocation(Location.create(context.file));
+                        } else {
+                            if (node instanceof Element) {
+                                Node attribute = ((Element) node).getAttributeNode(ATTR_NAME);
+                                if (attribute != null) {
+                                    node = attribute;
+                                }
+                            }
+                            resource.recordLocation(xmlContext.getLocation(node));
+                        }
+                    }
+                }
+            }
+
+            return resource;
+        }
     }
 }
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/VectorDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/VectorDetector.java
new file mode 100644
index 0000000..d19c3de
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/VectorDetector.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_URI;
+
+import com.android.annotations.NonNull;
+import com.android.builder.model.AndroidProject;
+import com.android.ide.common.resources.ResourceUrl;
+import com.android.resources.ResourceFolderType;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.XmlContext;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * Checks for unreachable states in an Android state list definition
+ */
+public class VectorDetector extends ResourceXmlDetector {
+    /** The main issue discovered by this detector */
+    public static final Issue ISSUE = Issue.create(
+            "VectorRaster", //$NON-NLS-1$
+            "Vector Image Generation",
+            "Vector icons require API 21, but when using Android Gradle plugin 1.4 or higher, " +
+            "vectors placed in the `drawable` folder are automatically moved to `drawable-*dpi-v21` " +
+            "and a bitmap image is generated each `drawable-*dpi` folder instead, for backwards " +
+            "compatibility (provided `minSdkVersion` is less than 21.).\n" +
+            "\n" +
+            "However, there are some limitations to this vector image generation, and this " +
+            "lint check flags elements and attributes that are not fully supported. " +
+            "You should manually check whether the generated output is acceptable for those " +
+            "older devices.",
+
+            Category.CORRECTNESS,
+            5,
+            Severity.WARNING,
+            new Implementation(
+                    VectorDetector.class,
+                    Scope.RESOURCE_FILE_SCOPE));
+
+    /** Constructs a new {@link VectorDetector} */
+    public VectorDetector() {
+    }
+
+    @Override
+    public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+        return folderType == ResourceFolderType.DRAWABLE;
+    }
+
+    @NonNull
+    @Override
+    public Speed getSpeed() {
+        return Speed.FAST;
+    }
+
+
+    /**
+     * Returns true if the given Gradle project model supports vector image generation
+     *
+     * @param project the project to check
+     * @return true if the plugin supports vector image generation
+     */
+    public static boolean isVectorGenerationSupported(@NonNull AndroidProject project) {
+        String modelVersion = project.getModelVersion();
+
+        // Requires 1.4.x or higher. Rather than doing string => x.y.z decomposition and then
+        // checking higher than 1.4.0, we'll just exclude the 4 possible prefixes that don't satisfy
+        // this requirement.
+        return !(modelVersion.startsWith("1.0")
+                 || modelVersion.startsWith("1.1")
+                 || modelVersion.startsWith("1.2")
+                 || modelVersion.startsWith("1.3"));
+    }
+
+
+    @Override
+    public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
+        // If minSdkVersion >= 21, we're not generating compatibility vector icons
+        Project project = context.getMainProject();
+        if (project.getMinSdkVersion().getFeatureLevel() >= 21) {
+            return;
+        }
+
+        // Vector generation is only done for Gradle projects
+        if (!project.isGradleProject()) {
+            return;
+        }
+
+        // Not using a plugin that supports vector image generation?
+        AndroidProject model = project.getGradleProjectModel();
+        if (model == null || !isVectorGenerationSupported(model)) {
+            return;
+        }
+
+        Element root = document.getDocumentElement();
+        // If this is not actually a vector icon, nothing to do in this detector
+        if (root == null || !root.getTagName().equals("vector")) { //$NON-NLS-1$
+            return;
+        }
+
+        // If this vector asset is in a -v21 folder, we're not generating vector assets
+        if (context.getFolderVersion() >= 21) {
+            return;
+        }
+
+        // TODO: Check to see if there already is a -?dpi version of the file; if so,
+        // we also won't be generating a vector image
+
+
+        checkSupported(context, root);
+    }
+
+    /** Recursive element check for unsupported attributes and tags */
+    private static void checkSupported(@NonNull XmlContext context, @NonNull Element element) {
+        // Unsupported tags
+        String tag = element.getTagName();
+        if ("clip-path".equals(tag)) {
+            String message = "This tag is not supported in images generated from this "
+                    + "vector icon for API < 21; check generated icon to make sure it looks "
+                    + "acceptable";
+            context.report(ISSUE, element, context.getLocation(element), message);
+        } else if ("group".equals(tag)) {
+            AndroidProject model = context.getMainProject().getGradleProjectModel();
+            if (model != null && model.getModelVersion().startsWith("1.4.")) {
+                String message = "Update Gradle plugin version to 1.5+ to correctly handle "
+                        + "`<group>` tags in generated bitmaps";
+                context.report(ISSUE, element, context.getLocation(element), message);
+            }
+        }
+
+        NamedNodeMap attributes = element.getAttributes();
+        for (int i = 0, n = attributes.getLength(); i < n; i++) {
+            Attr attr = (Attr)attributes.item(i);
+            String name = attr.getLocalName();
+            if (("autoMirrored".equals(name)
+                    || "trimPathStart".equals(name)
+                    || "trimPathEnd".equals(name)
+                    || "trimPathOffset".equals(name))
+                    && ANDROID_URI.equals(attr.getNamespaceURI())) {
+                String message = "This attribute is not supported in images generated from this "
+                        + "vector icon for API < 21; check generated icon to make sure it looks "
+                        + "acceptable";
+                context.report(ISSUE, attr, context.getNameLocation(attr), message);
+            }
+
+            String value = attr.getValue();
+            if (ResourceUrl.parse(value) != null) {
+                String message = "Resource references will not work correctly in images generated "
+                        + "for this vector icon for API < 21; check generated icon to make sure "
+                        + "it looks acceptable";
+                context.report(ISSUE, attr, context.getValueLocation(attr), message);
+            }
+        }
+
+        NodeList children = element.getChildNodes();
+        for (int i = 0, n = children.getLength(); i < n; i++) {
+            Node child = children.item(i);
+            if (child.getNodeType() == Node.ELEMENT_NODE) {
+                checkSupported(context, ((Element) child));
+            }
+        }
+    }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTagDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTagDetector.java
index 77f5fe7..f4a15c7 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTagDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTagDetector.java
@@ -16,39 +16,35 @@
 
 package com.android.tools.lint.checks;
 
+import static com.android.SdkConstants.CLASS_VIEW;
+import static com.android.tools.lint.checks.CleanupDetector.CURSOR_CLS;
+
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.LintDriver;
-import com.android.tools.lint.client.api.SdkInfo;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
 import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
 import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Detector.ClassScanner;
 import com.android.tools.lint.detector.api.Implementation;
 import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.JavaContext;
 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.objectweb.asm.Type;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.InsnList;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.objectweb.asm.tree.analysis.Analyzer;
-import org.objectweb.asm.tree.analysis.AnalyzerException;
-import org.objectweb.asm.tree.analysis.BasicInterpreter;
-import org.objectweb.asm.tree.analysis.BasicValue;
-import org.objectweb.asm.tree.analysis.Frame;
+import com.android.tools.lint.detector.api.TypeEvaluator;
 
 import java.util.Collections;
 import java.util.List;
 
+import lombok.ast.AstVisitor;
+import lombok.ast.Expression;
+import lombok.ast.MethodInvocation;
+
 /**
  * Checks for missing view tag detectors
  */
-public class ViewTagDetector extends Detector implements ClassScanner {
+public class ViewTagDetector extends Detector implements Detector.JavaScanner {
     /** Using setTag and leaking memory */
     public static final Issue ISSUE = Issue.create(
             "ViewTag", //$NON-NLS-1$
@@ -67,110 +63,69 @@
             Severity.WARNING,
             new Implementation(
                     ViewTagDetector.class,
-                    Scope.CLASS_FILE_SCOPE));
+                    Scope.JAVA_FILE_SCOPE));
 
     /** Constructs a new {@link ViewTagDetector} */
     public ViewTagDetector() {
     }
 
-    @NonNull
-    @Override
-    public Speed getSpeed() {
-        return Speed.FAST;
-    }
+    // ---- Implements JavaScanner ----
 
-    // ---- Implements ClassScanner ----
-
-    @Override
     @Nullable
-    public List<String> getApplicableCallNames() {
-        return Collections.singletonList("setTag"); //$NON-NLS-1$
+    @Override
+    public List<String> getApplicableMethodNames() {
+        return Collections.singletonList("setTag");
     }
 
     @Override
-    public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
-            @NonNull MethodNode method, @NonNull MethodInsnNode call) {
+    public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+            @NonNull MethodInvocation call) {
         // The leak behavior is fixed in ICS:
         // http://code.google.com/p/android/issues/detail?id=18273
         if (context.getMainProject().getMinSdk() >= 14) {
             return;
         }
 
-        String owner = call.owner;
-        String desc = call.desc;
-        if (owner.equals("android/view/View")                 //$NON-NLS-1$
-                && desc.equals("(ILjava/lang/Object;)V")) {   //$NON-NLS-1$
-            Analyzer analyzer = new Analyzer(new BasicInterpreter() {
-                @Override
-                public BasicValue newValue(Type type) {
-                    if (type == null) {
-                        return BasicValue.UNINITIALIZED_VALUE;
-                    } else if (type.getSort() == Type.VOID) {
-                        return null;
-                    } else {
-                        return new BasicValue(type);
-                    }
-                }
-            });
-            try {
-                Frame[] frames = analyzer.analyze(classNode.name, method);
-                InsnList instructions = method.instructions;
-                Frame frame = frames[instructions.indexOf(call)];
-                if (frame.getStackSize() < 3) {
-                    return;
-                }
-                BasicValue stackValue = (BasicValue) frame.getStack(2);
-                Type type = stackValue.getType();
-                if (type == null) {
-                    return;
-                }
-
-                String internalName = type.getInternalName();
-                String className = type.getClassName();
-                LintDriver driver = context.getDriver();
-
-                SdkInfo sdkInfo = context.getClient().getSdkInfo(context.getMainProject());
-                String objectType = null;
-                while (className != null) {
-                    if (className.equals("android.view.View")) {         //$NON-NLS-1$
-                        objectType = "views";
-                        break;
-                    } else if (className.endsWith("ViewHolder")) {       //$NON-NLS-1$
-                        objectType = "view holders";
-                        break;
-                    } else if (className.endsWith("Cursor")              //$NON-NLS-1$
-                                && className.startsWith("android.")) {   //$NON-NLS-1$
-                        objectType = "cursors";
-                        break;
-                    }
-
-                    // TBD: Bitmaps, drawables? That's tricky, because as explained in
-                    // http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html
-                    // apparently these are used along with nulling out the callbacks,
-                    // and that's harder to detect here
-
-                    String parent = sdkInfo.getParentViewClass(className);
-                    if (parent == null) {
-                        if (internalName == null) {
-                            internalName = className.replace('.', '/');
-                        }
-                        assert internalName != null;
-                        parent = driver.getSuperClass(internalName);
-                    }
-                    className = parent;
-                    internalName = null;
-                }
-
-                if (objectType != null) {
-                    Location location = context.getLocation(call);
-                    String message = String.format("Avoid setting %1$s as values for `setTag`: " +
-                        "Can lead to memory leaks in versions older than Android 4.0",
-                        objectType);
-                    context.report(ISSUE, method, call, location, message);
-                }
-            } catch (AnalyzerException e) {
-                context.log(e, null);
-            }
+        ResolvedNode resolved = context.resolve(call);
+        if (!(resolved instanceof ResolvedMethod)) {
+            return;
         }
+        ResolvedMethod method = (ResolvedMethod) resolved;
+        if (method.getArgumentCount() != 2) {
+            return;
+        }
+        Expression tagArgument = call.astArguments().last();
+        if (tagArgument == null) {
+            return;
+        }
+        ResolvedClass containingClass = method.getContainingClass();
+        if (!containingClass.matches(CLASS_VIEW)) {
+            return;
+        }
+
+        TypeDescriptor type = TypeEvaluator.evaluate(context, tagArgument);
+        if (type == null) {
+            return;
+        }
+        ResolvedClass typeClass = type.getTypeClass();
+        if (typeClass == null) {
+            return;
+        }
+
+        String objectType;
+        if (typeClass.isSubclassOf(CLASS_VIEW, false)) {
+            objectType = "views";
+        } else if (typeClass.isImplementing(CURSOR_CLS, false)) {
+            objectType = "cursors";
+        } else if (typeClass.getSimpleName().endsWith("ViewHolder")) {
+            objectType = "view holders";
+        } else {
+            return;
+        }
+        String message = String.format("Avoid setting %1$s as values for `setTag`: " +
+                        "Can lead to memory leaks in versions older than Android 4.0",
+                objectType);
+
+        context.report(ISSUE, call, context.getLocation(tagArgument), message);
     }
 }
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTypeDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTypeDetector.java
index ec8edd3..6718c43 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTypeDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTypeDetector.java
@@ -34,7 +34,7 @@
 import com.android.tools.lint.client.api.LintClient;
 import com.android.tools.lint.detector.api.Category;
 import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
 import com.android.tools.lint.detector.api.Implementation;
 import com.android.tools.lint.detector.api.Issue;
 import com.android.tools.lint.detector.api.JavaContext;
@@ -87,7 +87,7 @@
  * check its name or class attributes to make sure the cast is compatible with
  * the named fragment class!
  */
-public class ViewTypeDetector extends ResourceXmlDetector implements Detector.JavaScanner {
+public class ViewTypeDetector extends ResourceXmlDetector implements JavaScanner {
     /** Mismatched view types */
     @SuppressWarnings("unchecked")
     public static final Issue ISSUE = Issue.create(
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WakelockDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WakelockDetector.java
index 7ec48e1..0fb736c 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WakelockDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WakelockDetector.java
@@ -16,8 +16,6 @@
 
 package com.android.tools.lint.checks;
 
-import static com.android.SdkConstants.ANDROID_APP_ACTIVITY;
-
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
 import com.android.tools.lint.checks.ControlFlowGraph.Node;
@@ -50,6 +48,7 @@
  * which can lead to unnecessary battery usage.
  */
 public class WakelockDetector extends Detector implements ClassScanner {
+    public static final String ANDROID_APP_ACTIVITY = "android/app/Activity";        //$NON-NLS-1$
 
     /** Problems using wakelocks */
     public static final Issue ISSUE = Issue.create(
@@ -187,8 +186,6 @@
 
     private static void checkFlow(@NonNull ClassContext context, @NonNull ClassNode classNode,
             @NonNull MethodNode method, @NonNull MethodInsnNode acquire) {
-        // Track allocations such that we know whether the type of the call
-        // is on a SecureRandom rather than a Random
         final InsnList instructions = method.instructions;
         MethodInsnNode release = null;
 
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongCallDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongCallDetector.java
index 5a7c1d7..77acff3 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongCallDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongCallDetector.java
@@ -23,7 +23,9 @@
 
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
 import com.android.tools.lint.detector.api.Category;
 import com.android.tools.lint.detector.api.Detector;
 import com.android.tools.lint.detector.api.Implementation;
@@ -109,10 +111,10 @@
 
     private static void report(JavaContext context, MethodInvocation node) {
         // Make sure the call is on a view
-        JavaParser.ResolvedNode resolved = context.resolve(node);
-        if (resolved instanceof JavaParser.ResolvedMethod) {
-            JavaParser.ResolvedMethod method = (JavaParser.ResolvedMethod) resolved;
-            JavaParser.ResolvedClass containingClass = method.getContainingClass();
+        ResolvedNode resolved = context.resolve(node);
+        if (resolved instanceof ResolvedMethod) {
+            ResolvedMethod method = (ResolvedMethod) resolved;
+            ResolvedClass containingClass = method.getContainingClass();
             if (!containingClass.isSubclassOf(CLASS_VIEW, false)) {
                 return;
             }
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongIdDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongIdDetector.java
index 6041f63..c02bc08 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongIdDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongIdDetector.java
@@ -27,6 +27,7 @@
 import static com.android.SdkConstants.RELATIVE_LAYOUT;
 import static com.android.SdkConstants.TAG_ITEM;
 import static com.android.SdkConstants.VALUE_ID;
+import static com.android.tools.lint.checks.RequiredAttributeDetector.PERCENT_RELATIVE_LAYOUT;
 import static com.android.tools.lint.detector.api.LintUtils.editDistance;
 import static com.android.tools.lint.detector.api.LintUtils.getChildren;
 import static com.android.tools.lint.detector.api.LintUtils.isSameResourceFile;
@@ -187,7 +188,7 @@
 
     @Override
     public Collection<String> getApplicableElements() {
-        return Arrays.asList(RELATIVE_LAYOUT, TAG_ITEM);
+        return Arrays.asList(RELATIVE_LAYOUT, TAG_ITEM, PERCENT_RELATIVE_LAYOUT);
     }
 
     @Override
@@ -371,13 +372,8 @@
 
     @Override
     public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
-        if (element.getTagName().equals(RELATIVE_LAYOUT)) {
-            if (mRelativeLayouts == null) {
-                mRelativeLayouts = new ArrayList<Element>();
-            }
-            mRelativeLayouts.add(element);
-        } else {
-            assert element.getTagName().equals(TAG_ITEM);
+        String tagName = element.getTagName();
+        if (tagName.equals(TAG_ITEM)) {
             String type = element.getAttribute(ATTR_TYPE);
             if (VALUE_ID.equals(type)) {
                 String name = element.getAttribute(ATTR_NAME);
@@ -388,6 +384,13 @@
                     mDeclaredIds.add(ID_PREFIX + name);
                 }
             }
+        } else {
+            assert tagName.equals(RELATIVE_LAYOUT) || tagName.equals(
+                    PERCENT_RELATIVE_LAYOUT);
+            if (mRelativeLayouts == null) {
+                mRelativeLayouts = new ArrayList<Element>();
+            }
+            mRelativeLayouts.add(element);
         }
     }
 
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/api-versions-support-library.xml b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/api-versions-support-library.xml
new file mode 100644
index 0000000..faa8970
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/api-versions-support-library.xml
@@ -0,0 +1,275 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 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.
+-->
+<!-- This file is generated by ApiLookupTest#generateSupportLibraryFile() -->
+<api version="2">
+    <class name="android/support/design/internal/ForegroundLinearLayout" since="7">
+        <extends name="android/support/v7/widget/LinearLayoutCompat" />
+        <method name="drawableHotspotChanged(FF)V" since="7" />
+        <method name="getForeground()Landroid/graphics/drawable/Drawable;" since="7" />
+        <method name="getForegroundGravity()I" since="7" />
+        <method name="jumpDrawablesToCurrentState()V" since="7" />
+        <method name="setForeground(Landroid/graphics/drawable/Drawable;)V" since="7" />
+        <method name="setForegroundGravity(I)V" since="7" />
+    </class>
+    <class name="android/support/design/widget/CoordinatorLayout" since="7">
+        <extends name="android/view/ViewGroup" />
+        <method name="getNestedScrollAxes()I" since="7" />
+        <method name="onNestedFling(Landroid/view/View;FFZ)Z" since="7" />
+        <method name="onNestedPreFling(Landroid/view/View;FF)Z" since="7" />
+        <method name="onNestedPreScroll(Landroid/view/View;II[I)V" since="7" />
+        <method name="onNestedScroll(Landroid/view/View;IIII)V" since="7" />
+        <method name="onNestedScrollAccepted(Landroid/view/View;Landroid/view/View;I)V" since="7" />
+        <method name="onStartNestedScroll(Landroid/view/View;Landroid/view/View;I)Z" since="7" />
+        <method name="onStopNestedScroll(Landroid/view/View;)V" since="7" />
+    </class>
+    <class name="android/support/design/widget/FloatingActionButton" since="7">
+        <extends name="android/widget/ImageButton" />
+        <method name="getBackgroundTintList()Landroid/content/res/ColorStateList;" since="7" />
+        <method name="getBackgroundTintMode()Landroid/graphics/PorterDuff$Mode;" since="7" />
+        <method name="jumpDrawablesToCurrentState()V" since="7" />
+        <method name="setBackgroundTintList(Landroid/content/res/ColorStateList;)V" since="7" />
+        <method name="setBackgroundTintMode(Landroid/graphics/PorterDuff$Mode;)V" since="7" />
+    </class>
+    <class name="android/support/design/widget/TextInputLayout" since="7">
+        <extends name="android/widget/LinearLayout" />
+        <method name="&lt;init>(Landroid/content/Context;Landroid/util/AttributeSet;I)V" since="7" />
+    </class>
+    <class name="android/support/v17/leanback/transition/FadeAndShortSlide" since="17">
+        <extends name="android/transition/Visibility" />
+        <method name="&lt;init>()V" since="17" />
+        <method name="addListener(Landroid/transition/Transition$TransitionListener;)Landroid/transition/Transition;" since="17" />
+        <method name="captureEndValues(Landroid/transition/TransitionValues;)V" since="17" />
+        <method name="captureStartValues(Landroid/transition/TransitionValues;)V" since="17" />
+        <method name="clone()Landroid/transition/Transition;" since="17" />
+        <method name="clone()Ljava/lang/Object;" since="17" />
+        <method name="onAppear(Landroid/view/ViewGroup;Landroid/view/View;Landroid/transition/TransitionValues;Landroid/transition/TransitionValues;)Landroid/animation/Animator;" since="17" />
+        <method name="onDisappear(Landroid/view/ViewGroup;Landroid/view/View;Landroid/transition/TransitionValues;Landroid/transition/TransitionValues;)Landroid/animation/Animator;" since="17" />
+        <method name="removeListener(Landroid/transition/Transition$TransitionListener;)Landroid/transition/Transition;" since="17" />
+        <method name="setEpicenterCallback(Landroid/transition/Transition$EpicenterCallback;)V" since="17" />
+    </class>
+    <class name="android/support/v17/leanback/transition/SlideNoPropagation" since="17">
+        <extends name="android/transition/Slide" />
+        <method name="&lt;init>()V" since="17" />
+        <method name="&lt;init>(I)V" since="17" />
+        <method name="&lt;init>(Landroid/content/Context;Landroid/util/AttributeSet;)V" since="17" />
+        <method name="setSlideEdge(I)V" since="17" />
+    </class>
+    <class name="android/support/v4/view/ViewPager" since="4">
+        <extends name="android/view/ViewGroup" />
+        <method name="canScrollHorizontally(I)Z" since="4" />
+    </class>
+    <class name="android/support/v4/widget/DrawerLayout" since="4">
+        <extends name="android/view/ViewGroup" />
+        <method name="onRtlPropertiesChanged(I)V" since="4" />
+    </class>
+    <class name="android/support/v4/widget/NestedScrollView" since="4">
+        <extends name="android/widget/FrameLayout" />
+        <method name="dispatchNestedFling(FFZ)Z" since="4" />
+        <method name="dispatchNestedPreFling(FF)Z" since="4" />
+        <method name="dispatchNestedPreScroll(II[I[I)Z" since="4" />
+        <method name="dispatchNestedScroll(IIII[I)Z" since="4" />
+        <method name="getNestedScrollAxes()I" since="4" />
+        <method name="hasNestedScrollingParent()Z" since="4" />
+        <method name="isNestedScrollingEnabled()Z" since="4" />
+        <method name="onGenericMotionEvent(Landroid/view/MotionEvent;)Z" since="4" />
+        <method name="onNestedFling(Landroid/view/View;FFZ)Z" since="4" />
+        <method name="onNestedPreFling(Landroid/view/View;FF)Z" since="4" />
+        <method name="onNestedPreScroll(Landroid/view/View;II[I)V" since="4" />
+        <method name="onNestedScroll(Landroid/view/View;IIII)V" since="4" />
+        <method name="onNestedScrollAccepted(Landroid/view/View;Landroid/view/View;I)V" since="4" />
+        <method name="onOverScrolled(IIZZ)V" since="4" />
+        <method name="onStartNestedScroll(Landroid/view/View;Landroid/view/View;I)Z" since="4" />
+        <method name="onStopNestedScroll(Landroid/view/View;)V" since="4" />
+        <method name="setNestedScrollingEnabled(Z)V" since="4" />
+        <method name="shouldDelayChildPressedState()Z" since="4" />
+        <method name="startNestedScroll(I)Z" since="4" />
+        <method name="stopNestedScroll()V" since="4" />
+    </class>
+    <class name="android/support/v4/widget/SwipeRefreshLayout" since="4">
+        <extends name="android/view/ViewGroup" />
+        <method name="dispatchNestedFling(FFZ)Z" since="4" />
+        <method name="dispatchNestedPreFling(FF)Z" since="4" />
+        <method name="dispatchNestedPreScroll(II[I[I)Z" since="4" />
+        <method name="dispatchNestedScroll(IIII[I)Z" since="4" />
+        <method name="getNestedScrollAxes()I" since="4" />
+        <method name="hasNestedScrollingParent()Z" since="4" />
+        <method name="isNestedScrollingEnabled()Z" since="4" />
+        <method name="onNestedFling(Landroid/view/View;FFZ)Z" since="4" />
+        <method name="onNestedPreFling(Landroid/view/View;FF)Z" since="4" />
+        <method name="onNestedPreScroll(Landroid/view/View;II[I)V" since="4" />
+        <method name="onNestedScroll(Landroid/view/View;IIII)V" since="4" />
+        <method name="onNestedScrollAccepted(Landroid/view/View;Landroid/view/View;I)V" since="4" />
+        <method name="onStartNestedScroll(Landroid/view/View;Landroid/view/View;I)Z" since="4" />
+        <method name="onStopNestedScroll(Landroid/view/View;)V" since="4" />
+        <method name="setNestedScrollingEnabled(Z)V" since="4" />
+        <method name="startNestedScroll(I)Z" since="4" />
+        <method name="stopNestedScroll()V" since="4" />
+    </class>
+    <class name="android/support/v7/app/AppCompatDialog" since="7">
+        <extends name="android/app/Dialog" />
+        <method name="invalidateOptionsMenu()V" since="7" />
+    </class>
+    <class name="android/support/v7/app/MediaRouteButton" since="7">
+        <extends name="android/view/View" />
+        <method name="jumpDrawablesToCurrentState()V" since="7" />
+    </class>
+    <class name="android/support/v7/graphics/drawable/DrawableWrapper" since="7">
+        <extends name="android/graphics/drawable/Drawable" />
+        <method name="isAutoMirrored()Z" since="7" />
+        <method name="jumpToCurrentState()V" since="7" />
+        <method name="setAutoMirrored(Z)V" since="7" />
+        <method name="setHotspot(FF)V" since="7" />
+        <method name="setHotspotBounds(IIII)V" since="7" />
+        <method name="setTint(I)V" since="7" />
+        <method name="setTintList(Landroid/content/res/ColorStateList;)V" since="7" />
+        <method name="setTintMode(Landroid/graphics/PorterDuff$Mode;)V" since="7" />
+    </class>
+    <class name="android/support/v7/internal/widget/PreferenceImageView" since="7">
+        <extends name="android/widget/ImageView" />
+        <method name="getMaxHeight()I" since="7" />
+        <method name="getMaxWidth()I" since="7" />
+    </class>
+    <class name="android/support/v7/view/SupportActionModeWrapper" since="7">
+        <extends name="android/view/ActionMode" />
+        <method name="finish()V" since="7" />
+        <method name="getCustomView()Landroid/view/View;" since="7" />
+        <method name="getMenu()Landroid/view/Menu;" since="7" />
+        <method name="getMenuInflater()Landroid/view/MenuInflater;" since="7" />
+        <method name="getSubtitle()Ljava/lang/CharSequence;" since="7" />
+        <method name="getTag()Ljava/lang/Object;" since="7" />
+        <method name="getTitle()Ljava/lang/CharSequence;" since="7" />
+        <method name="getTitleOptionalHint()Z" since="7" />
+        <method name="invalidate()V" since="7" />
+        <method name="isTitleOptional()Z" since="7" />
+        <method name="setCustomView(Landroid/view/View;)V" since="7" />
+        <method name="setSubtitle(I)V" since="7" />
+        <method name="setSubtitle(Ljava/lang/CharSequence;)V" since="7" />
+        <method name="setTag(Ljava/lang/Object;)V" since="7" />
+        <method name="setTitle(I)V" since="7" />
+        <method name="setTitle(Ljava/lang/CharSequence;)V" since="7" />
+        <method name="setTitleOptionalHint(Z)V" since="7" />
+    </class>
+    <class name="android/support/v7/view/menu/ActionMenuItemView" since="7">
+        <extends name="android/support/v7/widget/AppCompatTextView" />
+        <method name="onConfigurationChanged(Landroid/content/res/Configuration;)V" since="7" />
+    </class>
+    <class name="android/support/v7/view/menu/ListMenuItemView" since="7">
+        <extends name="android/widget/LinearLayout" />
+        <method name="&lt;init>(Landroid/content/Context;Landroid/util/AttributeSet;I)V" since="7" />
+    </class>
+    <class name="android/support/v7/widget/ActionBarContainer" since="7">
+        <extends name="android/widget/FrameLayout" />
+        <method name="jumpDrawablesToCurrentState()V" since="7" />
+        <method name="startActionModeForChild(Landroid/view/View;Landroid/view/ActionMode$Callback;)Landroid/view/ActionMode;" since="7" />
+    </class>
+    <class name="android/support/v7/widget/ActionBarContextView" since="7">
+        <extends name="android/support/v7/widget/AbsActionBarView" />
+        <method name="onHoverEvent(Landroid/view/MotionEvent;)Z" since="7" />
+        <method name="onInitializeAccessibilityEvent(Landroid/view/accessibility/AccessibilityEvent;)V" since="7" />
+        <method name="shouldDelayChildPressedState()Z" since="7" />
+    </class>
+    <class name="android/support/v7/widget/ActionBarOverlayLayout" since="7">
+        <extends name="android/view/ViewGroup" />
+        <method name="getNestedScrollAxes()I" since="7" />
+        <method name="onConfigurationChanged(Landroid/content/res/Configuration;)V" since="7" />
+        <method name="onNestedFling(Landroid/view/View;FFZ)Z" since="7" />
+        <method name="onNestedPreFling(Landroid/view/View;FF)Z" since="7" />
+        <method name="onNestedPreScroll(Landroid/view/View;II[I)V" since="7" />
+        <method name="onNestedScroll(Landroid/view/View;IIII)V" since="7" />
+        <method name="onNestedScrollAccepted(Landroid/view/View;Landroid/view/View;I)V" since="7" />
+        <method name="onStartNestedScroll(Landroid/view/View;Landroid/view/View;I)Z" since="7" />
+        <method name="onStopNestedScroll(Landroid/view/View;)V" since="7" />
+        <method name="onWindowSystemUiVisibilityChanged(I)V" since="7" />
+        <method name="shouldDelayChildPressedState()Z" since="7" />
+    </class>
+    <class name="android/support/v7/widget/ActionMenuView" since="7">
+        <extends name="android/support/v7/widget/LinearLayoutCompat" />
+        <method name="onConfigurationChanged(Landroid/content/res/Configuration;)V" since="7" />
+    </class>
+    <class name="android/support/v7/widget/AppCompatButton" since="7">
+        <extends name="android/widget/Button" />
+        <method name="onInitializeAccessibilityEvent(Landroid/view/accessibility/AccessibilityEvent;)V" since="7" />
+        <method name="onInitializeAccessibilityNodeInfo(Landroid/view/accessibility/AccessibilityNodeInfo;)V" since="7" />
+    </class>
+    <class name="android/support/v7/widget/AppCompatPopupWindow" since="7">
+        <extends name="android/widget/PopupWindow" />
+        <method name="showAsDropDown(Landroid/view/View;III)V" since="7" />
+    </class>
+    <class name="android/support/v7/widget/AppCompatSpinner" since="7">
+        <extends name="android/widget/Spinner" />
+        <method name="&lt;init>(Landroid/content/Context;I)V" since="7" />
+        <method name="&lt;init>(Landroid/content/Context;Landroid/util/AttributeSet;II)V" since="7" />
+        <method name="getDropDownHorizontalOffset()I" since="7" />
+        <method name="getDropDownVerticalOffset()I" since="7" />
+        <method name="getDropDownWidth()I" since="7" />
+        <method name="getPopupBackground()Landroid/graphics/drawable/Drawable;" since="7" />
+        <method name="getPopupContext()Landroid/content/Context;" since="7" />
+        <method name="setDropDownHorizontalOffset(I)V" since="7" />
+        <method name="setDropDownVerticalOffset(I)V" since="7" />
+        <method name="setDropDownWidth(I)V" since="7" />
+        <method name="setPopupBackgroundDrawable(Landroid/graphics/drawable/Drawable;)V" since="7" />
+        <method name="setPopupBackgroundResource(I)V" since="7" />
+    </class>
+    <class name="android/support/v7/widget/CardView" since="7">
+        <extends name="android/widget/FrameLayout" />
+        <method name="setPaddingRelative(IIII)V" since="7" />
+    </class>
+    <class name="android/support/v7/widget/LinearLayoutCompat" since="7">
+        <extends name="android/view/ViewGroup" />
+        <method name="onInitializeAccessibilityEvent(Landroid/view/accessibility/AccessibilityEvent;)V" since="7" />
+        <method name="onInitializeAccessibilityNodeInfo(Landroid/view/accessibility/AccessibilityNodeInfo;)V" since="7" />
+        <method name="shouldDelayChildPressedState()Z" since="7" />
+    </class>
+    <class name="android/support/v7/widget/RecyclerView" since="7">
+        <extends name="android/view/ViewGroup" />
+        <method name="dispatchNestedFling(FFZ)Z" since="7" />
+        <method name="dispatchNestedPreFling(FF)Z" since="7" />
+        <method name="dispatchNestedPreScroll(II[I[I)Z" since="7" />
+        <method name="dispatchNestedScroll(IIII[I)Z" since="7" />
+        <method name="hasNestedScrollingParent()Z" since="7" />
+        <method name="isAttachedToWindow()Z" since="7" />
+        <method name="isNestedScrollingEnabled()Z" since="7" />
+        <method name="onGenericMotionEvent(Landroid/view/MotionEvent;)Z" since="7" />
+        <method name="setNestedScrollingEnabled(Z)V" since="7" />
+        <method name="startNestedScroll(I)Z" since="7" />
+        <method name="stopNestedScroll()V" since="7" />
+    </class>
+    <class name="android/support/v7/widget/ScrollingTabContainerView" since="7">
+        <extends name="android/widget/HorizontalScrollView" />
+        <method name="onConfigurationChanged(Landroid/content/res/Configuration;)V" since="7" />
+    </class>
+    <class name="android/support/v7/widget/SwitchCompat" since="7">
+        <extends name="android/widget/CompoundButton" />
+        <method name="drawableHotspotChanged(FF)V" since="7" />
+        <method name="jumpDrawablesToCurrentState()V" since="7" />
+        <method name="onInitializeAccessibilityEvent(Landroid/view/accessibility/AccessibilityEvent;)V" since="7" />
+        <method name="onInitializeAccessibilityNodeInfo(Landroid/view/accessibility/AccessibilityNodeInfo;)V" since="7" />
+        <method name="onPopulateAccessibilityEvent(Landroid/view/accessibility/AccessibilityEvent;)V" since="7" />
+    </class>
+    <class name="android/support/v7/widget/Toolbar" since="7">
+        <extends name="android/view/ViewGroup" />
+        <method name="onHoverEvent(Landroid/view/MotionEvent;)Z" since="7" />
+        <method name="onRtlPropertiesChanged(I)V" since="7" />
+    </class>
+    <!-- Referenced Super Classes -->
+    <class name="android/support/v7/widget/AbsActionBarView" since="7">
+        <extends name="android/view/ViewGroup" />
+    </class>
+    <class name="android/support/v7/widget/AppCompatTextView" since="7">
+        <extends name="android/widget/TextView" />
+    </class>
+</api>
\ No newline at end of file
diff --git a/lint/libs/lint-tests/lint-tests.iml b/lint/libs/lint-tests/lint-tests.iml
index 66d4ddf..be909e6 100644
--- a/lint/libs/lint-tests/lint-tests.iml
+++ b/lint/libs/lint-tests/lint-tests.iml
@@ -17,12 +17,12 @@
     <orderEntry type="module" module-name="lint-cli" exported="" />
     <orderEntry type="module" module-name="testutils" exported="" />
     <orderEntry type="module" module-name="lint-api-base" exported="" />
-    <orderEntry type="module" module-name="sdk-common-base" exported="" />
     <orderEntry type="library" exported="" name="JUnit4" level="project" />
     <orderEntry type="module" module-name="layoutlib-api-base" exported="" />
-    <orderEntry type="module" module-name="sdklib-base" exported="" />
     <orderEntry type="library" exported="" name="intellij-annotations" level="project" />
     <orderEntry type="library" scope="TEST" name="groovy" level="project" />
     <orderEntry type="library" scope="TEST" name="mockito" level="project" />
+    <orderEntry type="library" name="ecj" level="project" />
+    <orderEntry type="module" module-name="sdk-common-base" />
   </component>
 </module>
\ No newline at end of file
diff --git a/lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/LintDetectorTest.java b/lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/LintDetectorTest.java
index e103a08..02ac248 100644
--- a/lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/LintDetectorTest.java
+++ b/lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/LintDetectorTest.java
@@ -18,7 +18,10 @@
 
 import static com.android.SdkConstants.ANDROID_URI;
 import static com.android.SdkConstants.ATTR_ID;
+import static com.android.SdkConstants.DOT_JAVA;
+import static com.android.SdkConstants.FN_ANDROID_MANIFEST_XML;
 import static com.android.SdkConstants.NEW_ID_PREFIX;
+import static com.android.utils.SdkUtils.escapePropertyValue;
 
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
@@ -34,6 +37,7 @@
 import com.android.resources.ResourceType;
 import com.android.sdklib.IAndroidTarget;
 import com.android.testutils.SdkTestCase;
+import com.android.tools.lint.EcjParser;
 import com.android.tools.lint.ExternalAnnotationRepository;
 import com.android.tools.lint.LintCliClient;
 import com.android.tools.lint.LintCliFlags;
@@ -44,32 +48,43 @@
 import com.android.tools.lint.client.api.Configuration;
 import com.android.tools.lint.client.api.DefaultConfiguration;
 import com.android.tools.lint.client.api.IssueRegistry;
+import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.JavaVisitor;
 import com.android.tools.lint.client.api.LintClient;
 import com.android.tools.lint.client.api.LintDriver;
 import com.android.tools.lint.client.api.LintRequest;
 import com.android.tools.lint.detector.api.Context;
 import com.android.tools.lint.detector.api.Detector;
 import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
 import com.android.tools.lint.detector.api.LintUtils;
 import com.android.tools.lint.detector.api.Location;
 import com.android.tools.lint.detector.api.Project;
 import com.android.tools.lint.detector.api.Scope;
 import com.android.tools.lint.detector.api.Severity;
 import com.android.tools.lint.detector.api.TextFormat;
+import com.android.utils.FileUtils;
 import com.android.utils.ILogger;
 import com.android.utils.SdkUtils;
 import com.android.utils.StdLogger;
 import com.android.utils.XmlUtils;
 import com.google.common.annotations.Beta;
 import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
+import com.google.common.io.ByteStreams;
 import com.google.common.io.Files;
 
+import org.eclipse.jdt.core.compiler.CategorizedProblem;
+import org.eclipse.jdt.core.compiler.IProblem;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
 import org.intellij.lang.annotations.Language;
+import org.objectweb.asm.Opcodes;
 import org.w3c.dom.Attr;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
@@ -77,10 +92,10 @@
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 
-import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
@@ -89,12 +104,21 @@
 import java.net.URL;
 import java.security.CodeSource;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.EnumSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.jar.Attributes;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+
+import javax.xml.bind.DatatypeConverter;
 
 /**
  * Test case for lint detectors.
@@ -105,10 +129,32 @@
 @Beta
 @SuppressWarnings("javadoc")
 public abstract class LintDetectorTest extends SdkTestCase {
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
         BuiltinIssueRegistry.reset();
+        JavaVisitor.clearCrashCount();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        List<Issue> issues;
+        try {
+            // Some detectors extend LintDetectorTest but don't actually
+            // provide issues and assert instead; gracefully ignore those
+            // here
+            issues = getIssues();
+        } catch (Throwable t) {
+            issues = Collections.emptyList();
+        }
+        for (Issue issue : issues) {
+            if (issue.getImplementation().getScope().contains(Scope.JAVA_FILE)) {
+                assertEquals(0, JavaVisitor.getCrashCount());
+                break;
+            }
+        }
     }
 
     protected abstract Detector getDetector();
@@ -123,6 +169,10 @@
         return mDetector;
     }
 
+    protected boolean allowCompilationErrors() {
+        return false;
+    }
+
     protected abstract List<Issue> getIssues();
 
     public class CustomIssueRegistry extends IssueRegistry {
@@ -211,8 +261,9 @@
 
     /**
      * Run lint on the given files when constructed as a separate project
-     * @return The output of the lint check. On Windows, this transforms all directory
-     *   separators to the unix-style forward slash.
+     *
+     * @return The output of the lint check. On Windows, this transforms all directory separators to
+     * the unix-style forward slash.
      */
     protected String lintProject(String... relativePaths) throws Exception {
         File projectDir = getProjectDir(null, relativePaths);
@@ -271,6 +322,24 @@
         return file().to(to).withSource(source);
     }
 
+    private static final Pattern PACKAGE_PATTERN = Pattern.compile("package\\s+(.*)\\s*;");
+    private static final Pattern CLASS_PATTERN = Pattern
+            .compile("\\s*(\\S+)\\s*(extends.*)?(implements.*)?\\{");
+
+    @NonNull
+    public TestFile java(@NonNull @Language("JAVA") String source) {
+        // Figure out the "to" path: the package plus class name + java in the src/ folder
+        Matcher matcher = PACKAGE_PATTERN.matcher(source);
+        assertTrue("Couldn't find package declaration in source", matcher.find());
+        String pkg = matcher.group(1).trim();
+        matcher = CLASS_PATTERN.matcher(source);
+        assertTrue("Couldn't find class declaration in source", matcher.find());
+        String cls = matcher.group(1).trim();
+        String to = "src/" + pkg.replace('.', '/') + '/' + cls + DOT_JAVA;
+
+        return file().to(to).withSource(source);
+    }
+
     @NonNull
     public TestFile xml(@NonNull String to, @NonNull @Language("XML") String source) {
         return file().to(to).withSource(source);
@@ -282,6 +351,372 @@
     }
 
     @NonNull
+    public ManifestTestFile manifest() {
+        return new ManifestTestFile();
+    }
+
+    public class ManifestTestFile extends TestFile {
+        public String pkg = "test.pkg";
+        public int minSdk;
+        public int targetSdk;
+        public String[] permissions;
+
+        public ManifestTestFile() {
+            to(FN_ANDROID_MANIFEST_XML);
+        }
+
+        public ManifestTestFile minSdk(int min) {
+            minSdk = min;
+            return this;
+        }
+
+        public ManifestTestFile targetSdk(int target) {
+            targetSdk = target;
+            return this;
+        }
+
+        public ManifestTestFile permissions(String... permissions) {
+            this.permissions = permissions;
+            return this;
+        }
+
+        @Override
+        @NonNull
+        public String getContents() {
+            if (contents == null) {
+                StringBuilder sb = new StringBuilder();
+                sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
+                sb.append("<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n");
+                sb.append("    package=\"").append(pkg).append("\"\n");
+                sb.append("    android:versionCode=\"1\"\n");
+                sb.append("    android:versionName=\"1.0\" >\n");
+                if (minSdk > 0 || targetSdk > 0) {
+                    sb.append("    <uses-sdk ");
+                    if (minSdk > 0) {
+                        sb.append(" android:minSdkVersion=\"").append(Integer.toString(minSdk))
+                                .append("\"");
+                    }
+                    if (targetSdk > 0) {
+                        sb.append(" android:targetSdkVersion=\"")
+                                .append(Integer.toString(targetSdk))
+                                .append("\"");
+                    }
+                    sb.append(" />\n");
+                }
+                if (permissions != null && permissions.length > 0) {
+                    StringBuilder permissionBlock = new StringBuilder();
+                    for (String permission : permissions) {
+                        permissionBlock
+                                .append("    <uses-permission android:name=\"")
+                                .append(permission)
+                                .append("\" />\n");
+                    }
+                    sb.append(permissionBlock);
+                    sb.append("\n");
+                }
+
+                sb.append(""
+                        + "\n"
+                        + "    <application\n"
+                        + "        android:icon=\"@drawable/ic_launcher\"\n"
+                        + "        android:label=\"@string/app_name\" >\n"
+                        + "    </application>\n"
+                        + "\n"
+                        + "</manifest>");
+                contents = sb.toString();
+            }
+
+            return contents;
+        }
+
+        @NonNull
+        @Override
+        public File createFile(@NonNull File targetDir) throws IOException {
+            getContents(); // lazy init
+            return super.createFile(targetDir);
+        }
+    }
+
+    @NonNull
+    public PropertyTestFile projectProperties() {
+        return new PropertyTestFile();
+    }
+
+    public class PropertyTestFile extends TestFile {
+        @SuppressWarnings("StringBufferField")
+        private StringBuilder mStringBuilder = new StringBuilder();
+
+        private int mNextLibraryIndex = 1;
+
+        public PropertyTestFile() {
+            to("project.properties");
+        }
+
+        public PropertyTestFile property(String key, String value) {
+            String escapedValue = escapePropertyValue(value);
+            mStringBuilder.append(key).append('=').append(escapedValue).append('\n');
+            return this;
+        }
+
+        public PropertyTestFile compileSdk(int target) {
+            mStringBuilder.append("target=android-").append(Integer.toString(target)).append('\n');
+            return this;
+        }
+
+        public PropertyTestFile library(boolean isLibrary) {
+            mStringBuilder.append("android.library=").append(Boolean.toString(isLibrary))
+                    .append('\n');
+            return this;
+        }
+
+        public PropertyTestFile manifestMerger(boolean merger) {
+            mStringBuilder.append("manifestmerger.enabled=").append(Boolean.toString(merger))
+                    .append('\n');
+            return this;
+        }
+
+        public PropertyTestFile dependsOn(String relative) {
+            assertTrue(relative.startsWith("../"));
+            mStringBuilder.append("android.library.reference.")
+                    .append(Integer.toString(mNextLibraryIndex++))
+                    .append("=").append(escapePropertyValue(relative))
+                    .append('\n');
+            return this;
+        }
+
+        @Override
+        public TestFile withSource(@NonNull String source) {
+            fail("Don't call withSource on " + this.getClass());
+            return this;
+        }
+
+        @Override
+        @NonNull
+        public String getContents() {
+            contents = mStringBuilder.toString();
+            return contents;
+        }
+
+        @NonNull
+        @Override
+        public File createFile(@NonNull File targetDir) throws IOException {
+            getContents(); // lazy init
+            return super.createFile(targetDir);
+        }
+    }
+
+    /** Produces byte arrays, for use with {@link BinaryTestFile} */
+    public static class ByteProducer {
+        @SuppressWarnings("MethodMayBeStatic") // intended for override
+        @NonNull
+        public byte[] produce() {
+            return new byte[0];
+        }
+    }
+
+    public static class BytecodeProducer extends ByteProducer implements Opcodes {
+        /**
+         *  Typically generated by first getting asm output like this:
+         * <pre>
+         *     assertEquals("", asmify(new File("/full/path/to/my/file.class")));
+         * </pre>
+         * ...and when the test fails, the actual test output is the necessary assembly
+         *
+         */
+        @Override
+        @SuppressWarnings("MethodMayBeStatic") // intended for override
+        @NonNull
+        public byte[] produce() {
+            return new byte[0];
+        }
+    }
+
+    @NonNull
+    public BinaryTestFile bytecode(@NonNull String to, @NonNull BytecodeProducer producer) {
+        return new BinaryTestFile(to, producer);
+    }
+
+    public static String toBase64(@NonNull byte[] bytes) {
+        String base64 = DatatypeConverter.printBase64Binary(bytes);
+        return Joiner.on("").join(Splitter.fixedLength(60).split(base64));
+    }
+
+    public static String toBase64(@NonNull File file) throws IOException {
+        return toBase64(Files.toByteArray(file));
+    }
+
+    /**
+     * Creates a test file from the given base64 data. To create this data, use {@link
+     * #toBase64(File)} or {@link #toBase64(byte[])}, for example via {@code assertEquals("",
+     * uuencode(new File("path/to/your.class")));} </pre>
+     *
+     * @param to      the file to write as
+     * @param encoded the encoded data
+     * @return the new test file
+     */
+    public BinaryTestFile base64(@NonNull String to, @NonNull String encoded) {
+        encoded = encoded.replaceAll("\n", "");
+        final byte[] bytes = DatatypeConverter.parseBase64Binary(encoded);
+        return new BinaryTestFile(to, new BytecodeProducer() {
+            @NonNull
+            @Override
+            public byte[] produce() {
+                return bytes;
+            }
+        });
+    }
+
+    public class BinaryTestFile extends TestFile {
+        private final BytecodeProducer mProducer;
+
+        public BinaryTestFile(@NonNull String to, @NonNull BytecodeProducer producer) {
+            to(to);
+            mProducer = producer;
+        }
+
+        @Override
+        public TestFile withSource(@NonNull String source) {
+            fail("Don't call withSource on " + this.getClass());
+            return this;
+        }
+
+        @Override
+        @NonNull
+        public String getContents() {
+            fail("Don't call getContents on binary " + this.getClass());
+            return null;
+        }
+
+        public byte[] getBinaryContents() {
+            return mProducer.produce();
+        }
+
+        @NonNull
+        @Override
+        public File createFile(@NonNull File targetDir) throws IOException {
+            int index = targetRelativePath.lastIndexOf('/');
+            String relative = null;
+            String name = targetRelativePath;
+            if (index != -1) {
+                name = targetRelativePath.substring(index + 1);
+                relative = targetRelativePath.substring(0, index);
+            }
+            InputStream stream = new ByteArrayInputStream(getBinaryContents());
+            return makeTestFile(targetDir, name, relative, stream);
+        }
+    }
+
+    @NonNull
+    public JarTestFile jar(@NonNull String to) {
+        return new JarTestFile(to);
+    }
+
+    @NonNull
+    public JarTestFile jar(@NonNull String to, @NonNull TestFile... files) {
+        JarTestFile jar = new JarTestFile(to);
+        jar.files(files);
+        return jar;
+    }
+
+    public class JarTestFile extends TestFile {
+        private List<TestFile> mFiles = Lists.newArrayList();
+        private Map<TestFile, String> mPath = Maps.newHashMap();
+
+        public JarTestFile(@NonNull String to) {
+            to(to);
+        }
+
+        public JarTestFile files(@NonNull TestFile... files) {
+            mFiles.addAll(Arrays.asList(files));
+            return this;
+        }
+
+        public JarTestFile add(@NonNull TestFile file, @NonNull String path) {
+            add(file);
+            mPath.put(file, path);
+            return this;
+        }
+
+        public JarTestFile add(@NonNull TestFile file) {
+            mFiles.add(file);
+            mPath.put(file, null);
+            return this;
+        }
+
+        @Override
+        public TestFile withSource(@NonNull String source) {
+            fail("Don't call withSource on " + this.getClass());
+            return this;
+        }
+
+        @Override
+        @NonNull
+        public String getContents() {
+            fail("Don't call getContents on binary " + this.getClass());
+            return null;
+        }
+
+        @NonNull
+        @Override
+        public File createFile(@NonNull File targetDir) throws IOException {
+            int index = targetRelativePath.lastIndexOf('/');
+            String relative = null;
+            String name = targetRelativePath;
+            if (index != -1) {
+                name = targetRelativePath.substring(index + 1);
+                relative = targetRelativePath.substring(0, index);
+            }
+
+            File dir = targetDir;
+            if (relative != null) {
+                dir = new File(dir, relative);
+                if (!dir.exists()) {
+                    boolean mkdir = dir.mkdirs();
+                    assertTrue(dir.getPath(), mkdir);
+                }
+            } else if (!dir.exists()) {
+                boolean mkdir = dir.mkdirs();
+                assertTrue(dir.getPath(), mkdir);
+            }
+            File tempFile = new File(dir, name);
+            if (tempFile.exists()) {
+                boolean deleted = tempFile.delete();
+                assertTrue(tempFile.getPath(), deleted);
+            }
+
+            Manifest manifest = new Manifest();
+            manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
+            JarOutputStream jarOutputStream = new JarOutputStream(
+                    new BufferedOutputStream(new FileOutputStream(tempFile)), manifest);
+
+            try {
+                for (TestFile file : mFiles) {
+                    String path = mPath.get(file);
+                    if (path == null) {
+                        path = file.targetRelativePath;
+                    }
+                    jarOutputStream.putNextEntry(new ZipEntry(path));
+                    if (file instanceof BinaryTestFile) {
+                        byte[] bytes = ((BinaryTestFile)file).getBinaryContents();
+                        assertNotNull(file.targetRelativePath, bytes);
+                        ByteStreams.copy(new ByteArrayInputStream(bytes), jarOutputStream);
+                    } else {
+                        String contents = file.getContents();
+                        assertNotNull(file.targetRelativePath, contents);
+                        byte[] bytes = contents.getBytes(Charsets.UTF_8);
+                        ByteStreams.copy(new ByteArrayInputStream(bytes), jarOutputStream);
+                    }
+                    jarOutputStream.closeEntry();
+                }
+            } finally {
+                jarOutputStream.close();
+            }
+
+            return tempFile;
+        }
+    }
+
+    @NonNull
     public TestFile copy(@NonNull String from) {
         return file().from(from).to(from);
     }
@@ -341,7 +776,7 @@
     @Override
     protected InputStream getTestResource(String relativePath, boolean expectExists) {
         String path = "data" + File.separator + relativePath; //$NON-NLS-1$
-        InputStream stream = LintDetectorTest.class.getResourceAsStream(path);
+        InputStream stream = getClass().getResourceAsStream(path);
         if (!expectExists && stream == null) {
             return null;
         }
@@ -354,8 +789,10 @@
             return true;
         }
 
-        if (issue == IssueRegistry.LINT_ERROR || issue == IssueRegistry.PARSER_ERROR) {
+        if (issue == IssueRegistry.LINT_ERROR) {
             return !ignoreSystemErrors();
+        } else if (issue == IssueRegistry.PARSER_ERROR) {
+            return !allowCompilationErrors();
         }
 
         return false;
@@ -382,7 +819,7 @@
         private File mIncrementalCheck;
 
         public TestLintClient() {
-            super(new LintCliFlags());
+            super(new LintCliFlags(), "test");
             mFlags.getReporters().add(new TextReporter(this, mFlags, mWriter, false));
         }
 
@@ -469,6 +906,44 @@
         }
 
         @Override
+        public JavaParser getJavaParser(@Nullable Project project) {
+            return new EcjParser(this, project) {
+                @Override
+                public void prepareJavaParse(@NonNull List<JavaContext> contexts) {
+                    super.prepareJavaParse(contexts);
+                    if (!allowCompilationErrors() && mEcjResult != null) {
+                        StringBuilder sb = new StringBuilder();
+                        for (CompilationUnitDeclaration unit : mEcjResult.getCompilationUnits()) {
+                            // so maybe I don't need my map!!
+                            CategorizedProblem[] problems = unit.compilationResult()
+                                    .getAllProblems();
+                            if (problems != null) {
+                                for (IProblem problem : problems) {
+                                    if (problem == null || !problem.isError()) {
+                                        continue;
+                                    }
+                                    String filename = new File(new String(
+                                            problem.getOriginatingFileName())).getName();
+                                    sb.append(filename)
+                                            .append(":")
+                                            .append(problem.isError() ? "Error" : "Warning")
+                                            .append(": ").append(problem.getSourceLineNumber())
+                                            .append(": ").append(problem.getMessage())
+                                            .append('\n');
+                                }
+                            }
+                        }
+                        if (sb.length() > 0) {
+                            fail("Found compilation problems in lint test not overriding "
+                                    + "allowCompilationErrors():\n" + sb);
+                        }
+
+                    }
+                }
+            };
+        }
+
+        @Override
         public void report(
                 @NonNull Context context,
                 @NonNull Issue issue,
@@ -481,7 +956,7 @@
             }
 
             // Use plain ascii in the test golden files for now. (This also ensures
-            // that the markup is wellformed, e.g. if we have a ` without a matching
+            // that the markup is well-formed, e.g. if we have a ` without a matching
             // closing `, the ` would show up in the plain text.)
             message = format.convertTo(message, TextFormat.TEXT);
 
@@ -729,7 +1204,10 @@
                                     } else if (qualifiers.equals("layout")) {
                                         qualifiers = "";
                                     }
-                                    idItem.setSource(new ResourceFile(file, item, qualifiers));
+
+                                    // Creating the resource file will set the source of
+                                    // idItem.
+                                    new ResourceFile(file, idItem, qualifiers);
                                     idMap.put(id, idItem);
                                 }
                             }
@@ -825,6 +1303,11 @@
                     if (lint.exists() && new File(lint, "cli").exists()) { //$NON-NLS-1$
                         return dir.getParentFile().getParentFile();
                     }
+
+                    File tools = new File(dir, "tools");
+                    if (tools.exists() && FileUtils.join(tools, "base", "lint", "cli").exists()) {
+                        return dir;
+                    }
                     dir = dir.getParentFile();
                 }
 
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/EcjParserTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/EcjParserTest.java
index 457a447..2b72080 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/EcjParserTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/EcjParserTest.java
@@ -23,6 +23,8 @@
 import static com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
 import static com.android.tools.lint.client.api.JavaParser.ResolvedNode;
 import static com.android.tools.lint.client.api.JavaParser.ResolvedVariable;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import com.android.annotations.NonNull;
 import com.android.tools.lint.checks.AbstractCheckTest;
@@ -40,7 +42,9 @@
 
 import java.io.File;
 import java.util.List;
+import java.util.Locale;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
 
 import lombok.ast.AnnotationElement;
 import lombok.ast.BinaryExpression;
@@ -64,6 +68,10 @@
 import lombok.ast.printer.SourcePrinter;
 import lombok.ast.printer.TextFormatter;
 
+// Disable code warnings that are applied to injected languages in ECJ sample code: these
+// are deliberately doing dodgy things to test parser scenarios
+@SuppressWarnings({"ClassNameDiffersFromFileName", "MethodMayBeStatic", "ImplicitArrayToString",
+        "UnnecessaryLocalVariable", "UnnecessarySemicolon"})
 public class EcjParserTest extends AbstractCheckTest {
     public void testTryCatchHang() throws Exception {
         // Ensure that we're really using this parser
@@ -80,6 +88,7 @@
     }
 
     public void testKitKatLanguageFeatures() throws Exception {
+        @Language("JAVA")
         String testClass = "" +
                 "package test.pkg;\n" +
                 "\n" +
@@ -243,7 +252,88 @@
         assertTrue(found.get());
     }
 
+    @SuppressWarnings("ClassNameDiffersFromFileName")
+    public void testRemoveDuplicates() throws Exception {
+        @SuppressWarnings("override")
+        @Language("JAVA")
+        String source = ""
+                + "package test.pkg;\n"
+                + "import java.lang.annotation.Retention;\n"
+                + "import java.lang.annotation.RetentionPolicy;\n"
+                + "\n"
+                + "@SuppressWarnings(\"unused\")\n"
+                + "public class AnnotationTest {\n"
+                + "    @Retention(RetentionPolicy.CLASS)\n"
+                + "    public @interface Annotation1 {};\n"
+                + "    @Retention(RetentionPolicy.CLASS)\n"
+                + "    public @interface Annotation2 {};\n"
+                + "    @Retention(RetentionPolicy.CLASS)\n"
+                + "    public @interface Annotation3 {};\n"
+                + "\n"
+                + "    public static void method(ChildClass child) {\n"
+                + "        child.method(0xff0000);\n"
+                + "    }\n"
+                + "\n"
+                + "    public static class ParentClass {\n"
+                + "        @Annotation1\n"
+                + "        void method(@Annotation2 int rgb) {\n"
+                + "        }\n"
+                + "    }\n"
+                + "\n"
+                + "    public static class ChildClass extends ParentClass {\n"
+                + "        @Annotation1\n"
+                + "        void method(@Annotation2 @Annotation3 int rgb) {\n"
+                + "        }\n"
+                + "    }\n"
+                + "}\n";
+
+        final JavaContext context = LintUtilsTest.parse(source,
+                new File("src/test/pkg/AnnotationTest.java"));
+        assertNotNull(context);
+
+        Node compilationUnit = context.getCompilationUnit();
+        assertNotNull(compilationUnit);
+        final AtomicBoolean found = new AtomicBoolean();
+        compilationUnit.accept(new ForwardingAstVisitor() {
+            @Override
+            public boolean visitMethodInvocation(MethodInvocation node) {
+                if (node.astName().astValue().equals("method")) {
+                    found.set(true);
+                    ResolvedNode resolved = context.resolve(node);
+                    assertNotNull(resolved);
+                    ResolvedMethod method = (ResolvedMethod) resolved;
+                    List<ResolvedAnnotation> methodAnnotations =
+                            Lists.newArrayList(method.getAnnotations());
+                    // This is the point of this test: make sure there is only *one* occurrence
+                    // of this annotation (prior to this fix, we'd accumulate up the hierarchy)
+                    assertEquals(1, methodAnnotations.size());
+                    assertEquals("test.pkg.AnnotationTest.Annotation1",
+                            methodAnnotations.get(0).getName());
+                    assertEquals(methodAnnotations.get(0).getName(),
+                            methodAnnotations.get(0).getSignature());
+
+                    List<ResolvedAnnotation> annotations =
+                            Lists.newArrayList(method.getParameterAnnotations(0));
+                    assertEquals(2, annotations.size());
+                    String first = annotations.get(0).getName();
+                    String second = annotations.get(1).getName();
+                    if (first.compareTo(second) > 0) {
+                        String temp = first;
+                        first = second;
+                        second = temp;
+                    }
+                    assertEquals("test.pkg.AnnotationTest.Annotation2", first);
+                    assertEquals("test.pkg.AnnotationTest.Annotation3", second);
+                }
+
+                return super.visitMethodInvocation(node);
+            }
+        });
+        assertTrue(found.get());
+    }
+
     public void testResolution() throws Exception {
+        @Language("JAVA")
         String source =
                 "package test.pkg;\n" +
                 "\n" +
@@ -269,6 +359,9 @@
                 "         private void client(int z) {\n" +
                 "             int x = z;\n" +
                 "             int y = x + 5;\n" +
+                "             {\n" +
+                "               int w = 5;\n" +
+                "             }\n" +
                 "             Inner inner = new Inner(null, null);\n" +
                 "             inner.myField = 6;\n" +
                 "             System.out.println(inner.myInts);\n" +
@@ -368,8 +461,8 @@
                 "              [Identifier File]\n" +
                 "                PROPERTY: name = File\n" +
                 "          [NormalTypeBody], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
-                "              [VariableDeclaration], type: float, resolved class: float \n" +
-                "                [VariableDefinition]\n" +
+                "              [VariableDeclaration], type: float, resolved field: myField test.pkg.TypeResolutionTest.Inner\n" +
+                "                [VariableDefinition], type: float, resolved field: myField test.pkg.TypeResolutionTest.Inner\n" +
                 "                  PROPERTY: varargs = false\n" +
                 "                  [Modifiers]\n" +
                 "                    [KeywordModifier public]\n" +
@@ -380,14 +473,14 @@
                 "                    [TypeReferencePart], type: float, resolved class: float \n" +
                 "                      [Identifier float]\n" +
                 "                        PROPERTY: name = float\n" +
-                "                  [VariableDefinitionEntry], resolved field: myField test.pkg.TypeResolutionTest.Inner\n" +
+                "                  [VariableDefinitionEntry], type: float, resolved field: myField test.pkg.TypeResolutionTest.Inner\n" +
                 "                    PROPERTY: arrayDimensions = 0\n" +
-                "                    varName: [Identifier myField], resolved field: myField test.pkg.TypeResolutionTest.Inner\n" +
+                "                    varName: [Identifier myField], type: float, resolved field: myField test.pkg.TypeResolutionTest.Inner\n" +
                 "                      PROPERTY: name = myField\n" +
                 "                    [FloatingPointLiteral 5.0], type: float\n" +
                 "                      PROPERTY: value = 5f\n" +
-                "              [VariableDeclaration], type: int[], resolved class: int[] \n" +
-                "                [VariableDefinition]\n" +
+                "              [VariableDeclaration], type: int[], resolved field: myInts test.pkg.TypeResolutionTest.Inner\n" +
+                "                [VariableDefinition], type: int[], resolved field: myInts test.pkg.TypeResolutionTest.Inner\n" +
                 "                  PROPERTY: varargs = false\n" +
                 "                  [Modifiers]\n" +
                 "                    [KeywordModifier public]\n" +
@@ -398,9 +491,9 @@
                 "                    [TypeReferencePart], type: int[], resolved class: int[] \n" +
                 "                      [Identifier int]\n" +
                 "                        PROPERTY: name = int\n" +
-                "                  [VariableDefinitionEntry], resolved field: myInts test.pkg.TypeResolutionTest.Inner\n" +
+                "                  [VariableDefinitionEntry], type: int[], resolved field: myInts test.pkg.TypeResolutionTest.Inner\n" +
                 "                    PROPERTY: arrayDimensions = 0\n" +
-                "                    varName: [Identifier myInts], resolved field: myInts test.pkg.TypeResolutionTest.Inner\n" +
+                "                    varName: [Identifier myInts], type: int[], resolved field: myInts test.pkg.TypeResolutionTest.Inner\n" +
                 "                      PROPERTY: name = myInts\n" +
                 "              [ConstructorDeclaration], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
                 "                [Modifiers], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
@@ -408,7 +501,7 @@
                 "                    PROPERTY: modifier = public\n" +
                 "                typeName: [Identifier Inner], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
                 "                  PROPERTY: name = Inner\n" +
-                "                parameter: [VariableDefinition], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
+                "                parameter: [VariableDefinition], type: java.io.File, resolved variable: dir java.io.File\n" +
                 "                  PROPERTY: varargs = false\n" +
                 "                  [Modifiers]\n" +
                 "                  type: [TypeReference File], type: java.io.File, resolved class: java.io.File \n" +
@@ -417,11 +510,11 @@
                 "                    [TypeReferencePart], type: java.io.File, resolved class: java.io.File \n" +
                 "                      [Identifier File]\n" +
                 "                        PROPERTY: name = File\n" +
-                "                  [VariableDefinitionEntry], resolved variable: dir java.io.File\n" +
+                "                  [VariableDefinitionEntry], type: java.io.File, resolved variable: dir java.io.File\n" +
                 "                    PROPERTY: arrayDimensions = 0\n" +
-                "                    varName: [Identifier dir], resolved variable: dir java.io.File\n" +
+                "                    varName: [Identifier dir], type: java.io.File, resolved variable: dir java.io.File\n" +
                 "                      PROPERTY: name = dir\n" +
-                "                parameter: [VariableDefinition], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
+                "                parameter: [VariableDefinition], type: java.lang.String, resolved variable: name java.lang.String\n" +
                 "                  PROPERTY: varargs = false\n" +
                 "                  [Modifiers]\n" +
                 "                  type: [TypeReference String], type: java.lang.String, resolved class: java.lang.String \n" +
@@ -430,9 +523,9 @@
                 "                    [TypeReferencePart], type: java.lang.String, resolved class: java.lang.String \n" +
                 "                      [Identifier String]\n" +
                 "                        PROPERTY: name = String\n" +
-                "                  [VariableDefinitionEntry], resolved variable: name java.lang.String\n" +
+                "                  [VariableDefinitionEntry], type: java.lang.String, resolved variable: name java.lang.String\n" +
                 "                    PROPERTY: arrayDimensions = 0\n" +
-                "                    varName: [Identifier name], resolved variable: name java.lang.String\n" +
+                "                    varName: [Identifier name], type: java.lang.String, resolved variable: name java.lang.String\n" +
                 "                      PROPERTY: name = name\n" +
                 "                [Block], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
                 "                    [SuperConstructorInvocation], resolved method: java.io.File java.io.File\n" +
@@ -454,7 +547,7 @@
                 "                      PROPERTY: name = void\n" +
                 "                methodName: [Identifier call], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
                 "                  PROPERTY: name = call\n" +
-                "                parameter: [VariableDefinition], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
+                "                parameter: [VariableDefinition], type: int, resolved variable: arg1 int\n" +
                 "                  PROPERTY: varargs = false\n" +
                 "                  [Modifiers]\n" +
                 "                  type: [TypeReference int], type: int, resolved class: int \n" +
@@ -463,11 +556,11 @@
                 "                    [TypeReferencePart], type: int, resolved class: int \n" +
                 "                      [Identifier int]\n" +
                 "                        PROPERTY: name = int\n" +
-                "                  [VariableDefinitionEntry], resolved variable: arg1 int\n" +
+                "                  [VariableDefinitionEntry], type: int, resolved variable: arg1 int\n" +
                 "                    PROPERTY: arrayDimensions = 0\n" +
-                "                    varName: [Identifier arg1], resolved variable: arg1 int\n" +
+                "                    varName: [Identifier arg1], type: int, resolved variable: arg1 int\n" +
                 "                      PROPERTY: name = arg1\n" +
-                "                parameter: [VariableDefinition], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
+                "                parameter: [VariableDefinition], type: double, resolved variable: arg2 double\n" +
                 "                  PROPERTY: varargs = false\n" +
                 "                  [Modifiers]\n" +
                 "                  type: [TypeReference double], type: double, resolved class: double \n" +
@@ -476,13 +569,13 @@
                 "                    [TypeReferencePart], type: double, resolved class: double \n" +
                 "                      [Identifier double]\n" +
                 "                        PROPERTY: name = double\n" +
-                "                  [VariableDefinitionEntry], resolved variable: arg2 double\n" +
+                "                  [VariableDefinitionEntry], type: double, resolved variable: arg2 double\n" +
                 "                    PROPERTY: arrayDimensions = 0\n" +
-                "                    varName: [Identifier arg2], resolved variable: arg2 double\n" +
+                "                    varName: [Identifier arg2], type: double, resolved variable: arg2 double\n" +
                 "                      PROPERTY: name = arg2\n" +
                 "                [Block], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
-                "                    [VariableDeclaration], type: boolean, resolved class: boolean \n" +
-                "                      [VariableDefinition]\n" +
+                "                    [VariableDeclaration], type: boolean, resolved variable: x boolean\n" +
+                "                      [VariableDefinition], type: boolean, resolved variable: x boolean\n" +
                 "                        PROPERTY: varargs = false\n" +
                 "                        [Modifiers]\n" +
                 "                        type: [TypeReference boolean], type: boolean, resolved class: boolean \n" +
@@ -491,9 +584,9 @@
                 "                          [TypeReferencePart], type: boolean, resolved class: boolean \n" +
                 "                            [Identifier boolean]\n" +
                 "                              PROPERTY: name = boolean\n" +
-                "                        [VariableDefinitionEntry], resolved variable: x boolean\n" +
+                "                        [VariableDefinitionEntry], type: boolean, resolved variable: x boolean\n" +
                 "                          PROPERTY: arrayDimensions = 0\n" +
-                "                          varName: [Identifier x], resolved variable: x boolean\n" +
+                "                          varName: [Identifier x], type: boolean, resolved variable: x boolean\n" +
                 "                            PROPERTY: name = x\n" +
                 "                          [MethodInvocation canRead], type: boolean, resolved method: canRead java.io.File\n" +
                 "                            operand: [Super], type: java.io.File\n" +
@@ -543,7 +636,7 @@
                 "                      PROPERTY: name = void\n" +
                 "                methodName: [Identifier client], type: void, resolved method: client test.pkg.TypeResolutionTest.Other\n" +
                 "                  PROPERTY: name = client\n" +
-                "                parameter: [VariableDefinition], type: void, resolved method: client test.pkg.TypeResolutionTest.Other\n" +
+                "                parameter: [VariableDefinition], type: int, resolved variable: z int\n" +
                 "                  PROPERTY: varargs = false\n" +
                 "                  [Modifiers]\n" +
                 "                  type: [TypeReference int], type: int, resolved class: int \n" +
@@ -552,13 +645,13 @@
                 "                    [TypeReferencePart], type: int, resolved class: int \n" +
                 "                      [Identifier int]\n" +
                 "                        PROPERTY: name = int\n" +
-                "                  [VariableDefinitionEntry], resolved variable: z int\n" +
+                "                  [VariableDefinitionEntry], type: int, resolved variable: z int\n" +
                 "                    PROPERTY: arrayDimensions = 0\n" +
-                "                    varName: [Identifier z], resolved variable: z int\n" +
+                "                    varName: [Identifier z], type: int, resolved variable: z int\n" +
                 "                      PROPERTY: name = z\n" +
                 "                [Block], type: void, resolved method: client test.pkg.TypeResolutionTest.Other\n" +
-                "                    [VariableDeclaration], type: int, resolved class: int \n" +
-                "                      [VariableDefinition]\n" +
+                "                    [VariableDeclaration], type: int, resolved variable: x int\n" +
+                "                      [VariableDefinition], type: int, resolved variable: x int\n" +
                 "                        PROPERTY: varargs = false\n" +
                 "                        [Modifiers]\n" +
                 "                        type: [TypeReference int], type: int, resolved class: int \n" +
@@ -567,15 +660,15 @@
                 "                          [TypeReferencePart], type: int, resolved class: int \n" +
                 "                            [Identifier int]\n" +
                 "                              PROPERTY: name = int\n" +
-                "                        [VariableDefinitionEntry], resolved variable: x int\n" +
+                "                        [VariableDefinitionEntry], type: int, resolved variable: x int\n" +
                 "                          PROPERTY: arrayDimensions = 0\n" +
-                "                          varName: [Identifier x], resolved variable: x int\n" +
+                "                          varName: [Identifier x], type: int, resolved variable: x int\n" +
                 "                            PROPERTY: name = x\n" +
                 "                          [VariableReference], type: int, resolved variable: z int\n" +
                 "                            [Identifier z], type: int, resolved variable: z int\n" +
                 "                              PROPERTY: name = z\n" +
-                "                    [VariableDeclaration], type: int, resolved class: int \n" +
-                "                      [VariableDefinition]\n" +
+                "                    [VariableDeclaration], type: int, resolved variable: y int\n" +
+                "                      [VariableDefinition], type: int, resolved variable: y int\n" +
                 "                        PROPERTY: varargs = false\n" +
                 "                        [Modifiers]\n" +
                 "                        type: [TypeReference int], type: int, resolved class: int \n" +
@@ -584,9 +677,9 @@
                 "                          [TypeReferencePart], type: int, resolved class: int \n" +
                 "                            [Identifier int]\n" +
                 "                              PROPERTY: name = int\n" +
-                "                        [VariableDefinitionEntry], resolved variable: y int\n" +
+                "                        [VariableDefinitionEntry], type: int, resolved variable: y int\n" +
                 "                          PROPERTY: arrayDimensions = 0\n" +
-                "                          varName: [Identifier y], resolved variable: y int\n" +
+                "                          varName: [Identifier y], type: int, resolved variable: y int\n" +
                 "                            PROPERTY: name = y\n" +
                 "                          [BinaryExpression +], type: int\n" +
                 "                            PROPERTY: operator = +\n" +
@@ -595,8 +688,25 @@
                 "                                PROPERTY: name = x\n" +
                 "                            right: [IntegralLiteral 5], type: int\n" +
                 "                              PROPERTY: value = 5\n" +
-                "                    [VariableDeclaration], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
-                "                      [VariableDefinition]\n" +
+                "                    [Block]\n" +
+                "                        [VariableDeclaration], type: int, resolved variable: w int\n" +
+                "                          [VariableDefinition], type: int, resolved variable: w int\n" +
+                "                            PROPERTY: varargs = false\n" +
+                "                            [Modifiers]\n" +
+                "                            type: [TypeReference int], type: int, resolved class: int \n" +
+                "                              PROPERTY: WildcardKind = NONE\n" +
+                "                              PROPERTY: arrayDimensions = 0\n" +
+                "                              [TypeReferencePart], type: int, resolved class: int \n" +
+                "                                [Identifier int]\n" +
+                "                                  PROPERTY: name = int\n" +
+                "                            [VariableDefinitionEntry], type: int, resolved variable: w int\n" +
+                "                              PROPERTY: arrayDimensions = 0\n" +
+                "                              varName: [Identifier w], type: int, resolved variable: w int\n" +
+                "                                PROPERTY: name = w\n" +
+                "                              [IntegralLiteral 5], type: int\n" +
+                "                                PROPERTY: value = 5\n" +
+                "                    [VariableDeclaration], type: test.pkg.TypeResolutionTest.Inner, resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
+                "                      [VariableDefinition], type: test.pkg.TypeResolutionTest.Inner, resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
                 "                        PROPERTY: varargs = false\n" +
                 "                        [Modifiers]\n" +
                 "                        type: [TypeReference Inner], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
@@ -605,9 +715,9 @@
                 "                          [TypeReferencePart], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
                 "                            [Identifier Inner]\n" +
                 "                              PROPERTY: name = Inner\n" +
-                "                        [VariableDefinitionEntry], resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
+                "                        [VariableDefinitionEntry], type: test.pkg.TypeResolutionTest.Inner, resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
                 "                          PROPERTY: arrayDimensions = 0\n" +
-                "                          varName: [Identifier inner], resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
+                "                          varName: [Identifier inner], type: test.pkg.TypeResolutionTest.Inner, resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
                 "                            PROPERTY: name = inner\n" +
                 "                          [ConstructorInvocation Inner], type: test.pkg.TypeResolutionTest.Inner, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
                 "                            type: [TypeReference Inner], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
@@ -732,6 +842,111 @@
                 }));
     }
 
+    // Regression test for https://code.google.com/p/android/issues/detail?id=172268
+    // This is a reduced version of the in-the-wild occurrence that happened when lint processed
+    // Chromium for Android sources.
+    public void testTypeInformationIsNotClearedAfterCompilation() throws Exception {
+        // We need to generate lots of classes to overflow default Compiler's TypeSystem internal array
+        // of types to trigger ArrayIndexOutOfBoundsException. This array initially has 256 entries.
+        int innerClassesNumber = 256;
+        String innerClassWithFieldTemplate = ""
+                + "  private static class Inner%1$d {}\n"
+                + "  private final HashMap<Inner%1$d, Integer> mMap%1$d;";
+
+        StringBuilder innerClasses = new StringBuilder();
+        for (int i = 0; i < innerClassesNumber; ++i) {
+            innerClasses.append(String.format(Locale.US, innerClassWithFieldTemplate, i));
+            innerClasses.append('\n');
+        }
+
+        String accessorTemplate = ""
+                + "    for (Map.Entry<Inner%1$d, Integer> entry : mMap%1$d.entrySet()) {}";
+        StringBuilder accessors = new StringBuilder();
+        for (int i = 0; i < innerClassesNumber; ++i) {
+            accessors.append(String.format(Locale.US, accessorTemplate, i)).append('\n');
+        }
+        @SuppressWarnings({"OnDemandImport", "ClassNameDiffersFromFileName"})
+        @Language("JAVA")
+        String source = ""
+                + "import java.util.*;\n"
+                + "public class TypeInfoTest {\n"
+                + innerClasses.toString()
+                + "  public void doSomething() {\n"
+                + accessors.toString()
+                + "  }"
+                + "}\n";
+
+        final JavaContext context = LintUtilsTest.parse(source,
+                new File("src/test/pkg/TypeInfoTest.java"));
+        assertNotNull(context);
+
+        Node compilationUnit = context.getCompilationUnit();
+        assertNotNull(compilationUnit);
+
+        // null means OK
+        final AtomicReference<String> result = new AtomicReference<String>();
+
+        compilationUnit.accept(new ForwardingAstVisitor() {
+            @Override
+            public boolean visitMethodInvocation(MethodInvocation call) {
+                // Snapshot of SupportAnnotationDetector
+                try {
+                    ResolvedNode resolved = context.resolve(call);
+                    if (resolved instanceof ResolvedMethod) {
+                        ResolvedMethod method = (ResolvedMethod) resolved;
+                        method.getAnnotations();
+                        method.getContainingClass().getAnnotations();
+                    } else {
+                        // Most likely target SDK isn't supplied (via ANDROID_HOME or by some other
+                        // means). The test is meaningless then.
+                        result.compareAndSet(null,
+                                "Cannot get resolved method, is test setup correct?");
+                    }
+                } catch (RuntimeException e) {
+                    // Exception can be swallowed by some upper-level code so we cannot rely on
+                    // compilationUnit.accept to fail test
+                    result.compareAndSet(null, "Got an exception " + e);
+                }
+                return false;
+            }
+        });
+
+        String failMessage = result.get();
+        assertNull(failMessage, result.get());
+    }
+
+    public void testGetClassNamesWithoutSignatures() {
+        @SuppressWarnings("TypeParameterExplicitlyExtendsObject")
+        @Language("JAVA")
+        String source = ""
+                + "package test.pkg;\n"
+                + "public abstract class Adapter<VH extends Object> {\n"
+                + "    public abstract void onBindViewHolder(VH holder, int position);\n"
+                + "}\n";
+        final JavaContext context = LintUtilsTest.parse(source,
+                new File("src/test/pkg/Adapter.java"));
+        assertNotNull(context);
+
+        Node compilationUnit = context.getCompilationUnit();
+        assertNotNull(compilationUnit);
+        final AtomicBoolean found = new AtomicBoolean();
+        compilationUnit.accept(new ForwardingAstVisitor() {
+            @Override
+            public boolean visitClassDeclaration(ClassDeclaration node) {
+                if (node.astName().astValue().startsWith("Adapter")) {
+                    found.set(true);
+                    ResolvedNode resolved = context.resolve(node);
+                    assertNotNull(resolved);
+                    ResolvedClass cls = (ResolvedClass) resolved;
+                    assertEquals("test.pkg.Adapter", cls.getName());
+                    assertEquals("Adapter", cls.getSimpleName());
+                }
+                return super.visitClassDeclaration(node);
+            }
+        });
+        assertTrue(found.get());
+    }
+
     @Override
     protected Detector getDetector() {
         return new SdCardDetector();
@@ -746,14 +961,45 @@
                 ClassPathInfo classPath = super.getClassPath(project);
                 // Insert fake classpath entries (non existent directories) to
                 // make sure the parser handles that gracefully. See issue 87740.
-                classPath.getLibraries().add(new File("nonexistent path"));
+                classPath.getLibraries(true).add(new File("nonexistent path"));
                 return classPath;
             }
         };
     }
 
+    public void testEnsureUnique() {
+        ResolvedAnnotation foo1 = createTestAnnotation("foo");
+        ResolvedAnnotation foo2 = createTestAnnotation("foo");
+        ResolvedAnnotation foo3 = createTestAnnotation("foo");
+        ResolvedAnnotation bar1 = createTestAnnotation("bar");
+        ResolvedAnnotation bar2 = createTestAnnotation("bar");
+
+        assertEquals("[]",
+                EcjParser.ensureUnique(Lists.<ResolvedAnnotation>newArrayList()).toString());
+        assertEquals("[foo, bar]",
+                EcjParser.ensureUnique(Lists.newArrayList(foo1, foo2, foo3, bar1)).toString());
+        assertEquals("[foo]",
+                EcjParser.ensureUnique(Lists.newArrayList(foo1, foo2)).toString());
+        assertEquals("[foo]",
+                EcjParser.ensureUnique(Lists.newArrayList(foo1, foo2, foo3)).toString());
+        assertEquals("[foo, bar]",
+                EcjParser.ensureUnique(Lists.newArrayList(foo1, foo2, foo3, bar1)).toString());
+        assertEquals("[bar, foo]",
+                EcjParser.ensureUnique(Lists.newArrayList(bar1, foo2, foo3)).toString());
+        assertEquals("[bar, foo]",
+                EcjParser.ensureUnique(Lists.newArrayList(bar1, bar2, foo2, foo3)).toString());
+    }
+
+    private ResolvedAnnotation createTestAnnotation(String name) {
+        ResolvedAnnotation annotation = mock(ResolvedAnnotation.class);
+        when(annotation.getName()).thenReturn(name);
+        when(annotation.toString()).thenReturn(name);
+        return annotation;
+    }
+
     public static class AstPrettyPrinter implements SourceFormatter {
 
+        @SuppressWarnings("StringBufferField") // Don't care about performance here
         private final StringBuilder mOutput = new StringBuilder(1000);
 
         private final JavaParser mResolver;
@@ -804,10 +1050,12 @@
 
             String typeDescription = "";
             String resolutionDescription = "";
+            @SuppressWarnings("ConstantConditions") // Hack for test pretty printer only
             JavaParser.TypeDescriptor t = mResolver.getType(null, node);
             if (t != null) {
                 typeDescription = ", type: " + t.getName();
             }
+            @SuppressWarnings("ConstantConditions") // Hack for test pretty printer only
             ResolvedNode resolved = mResolver.resolve(null, node);
             if (resolved != null) {
                 String c = "unknown";
@@ -821,7 +1069,10 @@
                 } else if (resolved instanceof ResolvedField) {
                     c = "field";
                     ResolvedField field = (ResolvedField) resolved;
-                    extra = field.getContainingClass().getName();
+                    ResolvedClass containingClass = field.getContainingClass();
+                    if (containingClass != null) {
+                        extra = containingClass.getName();
+                    }
                 } else if (resolved instanceof ResolvedVariable) {
                     c = "variable";
                     ResolvedVariable variable = (ResolvedVariable) resolved;
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/ExternalAnnotationRepositoryTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/ExternalAnnotationRepositoryTest.java
index 774a4c8..47eb31f 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/ExternalAnnotationRepositoryTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/ExternalAnnotationRepositoryTest.java
@@ -546,9 +546,9 @@
             // Can't find it when running from Gradle; ignore for now
             return;
         }
-        ResolvedMethod method = createMethod("android.view.LayoutInflater", "android.view.View",
-                "createView", "java.lang.String, java.lang.String, android.util.AttributeSet");
-        assertNotNull(manager.getAnnotation(method, 2, "android.support.annotation.NonNull"));
+        ResolvedMethod method = createMethod("android.view.View", "void",
+                "dispatchVisibilityChanged", "android.view.View, int");
+        assertNotNull(manager.getAnnotation(method, 0, "android.support.annotation.NonNull"));
     }
 
     private static ResolvedClass createClass(String name) {
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/MultiProjectHtmlReporterTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/MultiProjectHtmlReporterTest.java
index d53c03d..7d00b3d 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/MultiProjectHtmlReporterTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/MultiProjectHtmlReporterTest.java
@@ -35,7 +35,7 @@
 import java.util.Arrays;
 import java.util.List;
 
-public class MultiProjectHtmlReporterTest  extends AbstractCheckTest {
+public class MultiProjectHtmlReporterTest extends AbstractCheckTest {
     public void test() throws Exception {
         File dir = new File(getTargetDir(), "report");
         try {
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/WarningTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/WarningTest.java
index 46b567d..6466a01 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/WarningTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/WarningTest.java
@@ -91,4 +91,11 @@
             prev = warning;
         }
     }
+
+    @Override
+    protected boolean allowCompilationErrors() {
+        // Some of these unit tests are still relying on source code that references
+        // unresolved symbols etc.
+        return true;
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AllowAllHostnameVerifierDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AllowAllHostnameVerifierDetectorTest.java
new file mode 100644
index 0000000..5531e3c
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AllowAllHostnameVerifierDetectorTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2015 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.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+@SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName", "ImplicitArrayToString",
+        "MethodMayBeStatic"})
+public class AllowAllHostnameVerifierDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new AllowAllHostnameVerifierDetector();
+    }
+
+    public void testBroken() throws Exception {
+        assertEquals(""
+                + "src/test/pkg/InsecureHostnameVerifier.java:22: Warning: Using the AllowAllHostnameVerifier HostnameVerifier is unsafe because it always returns true, which could cause insecure network traffic due to trusting TLS/SSL server certificates for wrong hostnames [AllowAllHostnameVerifier]\n"
+                + "            connection.setHostnameVerifier(new AllowAllHostnameVerifier());\n"
+                + "                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/InsecureHostnameVerifier.java:23: Warning: Using the ALLOW_ALL_HOSTNAME_VERIFIER HostnameVerifier is unsafe because it always returns true, which could cause insecure network traffic due to trusting TLS/SSL server certificates for wrong hostnames [AllowAllHostnameVerifier]\n"
+                + "            connection.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);\n"
+                + "                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "0 errors, 2 warnings\n",
+                lintProject(
+                        xml("AndroidManifest.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                                + "    package=\"test.pkg\"\n"
+                                + "    android:versionCode=\"1\"\n"
+                                + "    android:versionName=\"1.0\" >\n"
+                                + "\n"
+                                + "    <uses-sdk android:minSdkVersion=\"14\" />\n"
+                                + "\n"
+                                + "    <application\n"
+                                + "        android:icon=\"@drawable/ic_launcher\"\n"
+                                + "        android:label=\"@string/app_name\" >\n"
+                                + "        <service\n"
+                                + "            android:name=\".InsecureHostnameVerifier\" >\n"
+                                + "        </service>\n"
+                                + "    </application>\n"
+                                + "\n"
+                                + "</manifest>\n"),
+                        java("src/test/pkg/InsecureHostnameVerifier.java", ""
+                                + "package test.pkg;\n"
+                                + "\n"
+                                + "import android.content.Intent;\n"
+                                + "\n"
+                                + "import java.io.IOException;\n"
+                                + "import java.net.URL;\n"
+                                + "import javax.net.ssl.HostnameVerifier;\n"
+                                + "import javax.net.ssl.HttpsURLConnection;\n"
+                                + "import javax.net.ssl.SSLContext;\n"
+                                + "import javax.net.ssl.SSLSession;\n"
+                                + "import javax.net.ssl.TrustManager;\n"
+                                + "import javax.net.ssl.X509TrustManager;\n"
+                                + "\n"
+                                + "import org.apache.http.conn.ssl.SSLSocketFactory;\n"
+                                + "import org.apache.http.conn.ssl.AllowAllHostnameVerifier;\n"
+                                + "\n"
+                                + "public class InsecureHostnameVerifier {\n"
+                                + "    protected void onHandleIntent(Intent intent) {\n"
+                                + "        try {\n"
+                                + "            URL url = new URL(\"https://www.google.com\");\n"
+                                + "            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();\n"
+                                + "            connection.setHostnameVerifier(new AllowAllHostnameVerifier());\n"
+                                + "            connection.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);\n"
+                                + "        } catch (IOException e) {\n"
+                                + "            System.out.println(e.getStackTrace());\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "}\n")
+                ));
+    }
+
+    public void testCorrect() throws Exception {
+        assertEquals(
+                "No warnings.",
+                lintProject(
+                        xml("AndroidManifest.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                                + "    package=\"test.pkg\"\n"
+                                + "    android:versionCode=\"1\"\n"
+                                + "    android:versionName=\"1.0\" >\n"
+                                + "\n"
+                                + "    <uses-sdk android:minSdkVersion=\"14\" />\n"
+                                + "\n"
+                                + "    <application\n"
+                                + "        android:icon=\"@drawable/ic_launcher\"\n"
+                                + "        android:label=\"@string/app_name\" >\n"
+                                + "        <service\n"
+                                + "            android:name=\".ExampleHostnameVerifier\" >\n"
+                                + "        </service>\n"
+                                + "    </application>\n"
+                                + "\n"
+                                + "</manifest>\n"),
+                        java("src/test/pkg/ExampleHostnameVerifier.java", ""
+                                + "package test.pkg;\n"
+                                + "\n"
+                                + "import android.content.Intent;\n"
+                                + "\n"
+                                + "import java.io.IOException;\n"
+                                + "import java.net.URL;\n"
+                                + "import javax.net.ssl.HostnameVerifier;\n"
+                                + "import javax.net.ssl.HttpsURLConnection;\n"
+                                + "import javax.net.ssl.SSLContext;\n"
+                                + "import javax.net.ssl.SSLSession;\n"
+                                + "import javax.net.ssl.TrustManager;\n"
+                                + "import javax.net.ssl.X509TrustManager;\n"
+                                + "\n"
+                                + "import org.apache.http.conn.ssl.SSLSocketFactory;\n"
+                                + "import org.apache.http.conn.ssl.StrictHostnameVerifier;\n"
+                                + "\n"
+                                + "public class ExampleHostnameVerifier {\n"
+                                + "    protected void onHandleIntent(Intent intent) {\n"
+                                + "        try {\n"
+                                + "            URL url = new URL(\"https://www.google.com\");\n"
+                                + "            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();\n"
+                                + "            connection.setHostnameVerifier(new StrictHostnameVerifier());\n"
+                                + "            connection.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);\n"
+                                + "        } catch (IOException e) {\n"
+                                + "            System.out.println(e.getStackTrace());\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "}\n")
+                ));
+    }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AndroidAutoDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AndroidAutoDetectorTest.java
new file mode 100644
index 0000000..b6424bd
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AndroidAutoDetectorTest.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.FN_ANDROID_MANIFEST_XML;
+
+import com.android.annotations.NonNull;
+import com.android.testutils.SdkTestCase;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Project;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+public class AndroidAutoDetectorTest extends AbstractCheckTest {
+
+    private final SdkTestCase.TestFile mValidAutomotiveDescriptor =
+            xml("res/xml/automotive_app_desc.xml", ""
+                    + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                    + "<automotiveApp>\n"
+                    + "    <uses name=\"media\"/>\n"
+                    + "</automotiveApp>\n");
+
+    private SdkTestCase.TestFile mValidAutoAndroidXml = xml(FN_ANDROID_MANIFEST_XML, ""
+            + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+            + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+            + "          xmlns:tools=\"http://schemas.android.com/tools\"\n"
+            + "          package=\"com.example.android.uamp\">\n"
+            + "\n"
+            + "    <application\n"
+            + "        android:name=\".UAMPApplication\"\n"
+            + "        android:label=\"@string/app_name\"\n"
+            + "        android:theme=\"@style/UAmpAppTheme\">\n"
+            + "\n"
+            + "    <meta-data\n"
+            + "        android:name=\"com.google.android.gms.car.application\"\n"
+            + "        android:resource=\"@xml/automotive_app_desc\"/>\n"
+            + "\n"
+            + "        <service\n"
+            + "            android:name=\".MusicService\"\n"
+            + "            android:exported=\"true\"\n"
+            + "            tools:ignore=\"ExportedService\">\n"
+            + "            <intent-filter>\n"
+            + "                <action android:name=\"android.media.browse.MediaBrowserService\"/>\n"
+            + "            </intent-filter>\n"
+            + "            <intent-filter>\n"
+            + "                <action android:name=\"android.media.action.MEDIA_PLAY_FROM_SEARCH\"/>\n"
+            + "                <category android:name=\"android.intent.category.DEFAULT\"/>\n"
+            + "            </intent-filter>\n"
+            + "        </service>\n"
+            + "\n"
+            + "    </application>\n"
+            + "\n"
+            + "</manifest>\n");
+
+    private Set<Issue> mEnabled = new HashSet<Issue>();
+
+    @Override
+    protected Detector getDetector() {
+        return new AndroidAutoDetector();
+    }
+
+    @Override
+    protected TestConfiguration getConfiguration(LintClient client, Project project) {
+        return new TestConfiguration(client, project, null) {
+            @Override
+            public boolean isEnabled(@NonNull Issue issue) {
+                return super.isEnabled(issue) && mEnabled.contains(issue);
+            }
+        };
+    }
+
+    public void testMissingIntentFilter() throws Exception {
+        mEnabled = Collections.singleton(
+                AndroidAutoDetector.MISSING_MEDIA_BROWSER_SERVICE_ACTION_ISSUE);
+        String expected = "AndroidManifest.xml:6: Error: Missing intent-filter for action android.media.browse.MediaBrowserService that is required for android auto support [MissingMediaBrowserServiceIntentFilter]\n"
+                + "    <application\n"
+                + "    ^\n"
+                + "1 errors, 0 warnings\n";
+        String result = lintProject(
+                xml(FN_ANDROID_MANIFEST_XML, ""
+                        + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                        + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                        + "          xmlns:tools=\"http://schemas.android.com/tools\"\n"
+                        + "          package=\"com.example.android.uamp\">\n"
+                        + "\n"
+                        + "    <application\n"
+                        + "        android:name=\".UAMPApplication\"\n"
+                        + "        android:icon=\"@drawable/ic_launcher\"\n"
+                        + "        android:label=\"@string/app_name\"\n"
+                        + "        android:theme=\"@style/UAmpAppTheme\"\n"
+                        + "        android:banner=\"@drawable/banner_tv\">\n"
+                        + "\n"
+                        + "        <meta-data\n"
+                        + "            android:name=\"com.google.android.gms.car.application\"\n"
+                        + "            android:resource=\"@xml/automotive_app_desc\"/>\n"
+                        + "        <service\n"
+                        + "            android:name=\".MusicService\"\n"
+                        + "            android:exported=\"true\"\n"
+                        + "            tools:ignore=\"ExportedService\">\n"
+                        + "        </service>\n"
+                        + "    </application>\n"
+                        + "\n"
+                        + "</manifest>\n"),
+                mValidAutomotiveDescriptor);
+
+        assertEquals(expected, result);
+    }
+
+    public void testInvalidUsesTagInMetadataFile() throws Exception {
+        mEnabled = Collections.singleton(AndroidAutoDetector.INVALID_USES_TAG_ISSUE);
+        String expected = "" +
+                "res/xml/automotive_app_desc.xml:3: Error: Expecting one of media or notification for the name attribute in uses tag. [InvalidUsesTagAttribute]\n"
+                + "    <uses name=\"medias\"/>\n"
+                + "          ~~~~~~~~~~~~~\n"
+                + "1 errors, 0 warnings\n";
+        String result = lintProject(
+                mValidAutoAndroidXml,
+                xml("res/xml/automotive_app_desc.xml", ""
+                        + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                        + "<automotiveApp>\n"
+                        + "    <uses name=\"medias\"/>\n"
+                        + "</automotiveApp>\n"));
+        assertEquals(expected, result);
+    }
+
+    public void testMissingMediaSearchIntent() throws Exception {
+        mEnabled = Collections.singleton(
+                AndroidAutoDetector.MISSING_INTENT_FILTER_FOR_MEDIA_SEARCH);
+        String expected = "AndroidManifest.xml:6: Error: Missing intent-filter for action android.media.action.MEDIA_PLAY_FROM_SEARCH. [MissingIntentFilterForMediaSearch]\n"
+                + "    <application\n"
+                + "    ^\n"
+                + "1 errors, 0 warnings\n";
+
+        String result = lintProject(
+                xml(FN_ANDROID_MANIFEST_XML, ""
+                        + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                        + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                        + "          xmlns:tools=\"http://schemas.android.com/tools\"\n"
+                        + "          package=\"com.example.android.uamp\">\n"
+                        + "\n"
+                        + "    <application\n"
+                        + "        android:name=\".UAMPApplication\"\n"
+                        + "        android:label=\"@string/app_name\"\n"
+                        + "        android:theme=\"@style/UAmpAppTheme\">\n"
+                        + "\n"
+                        + "    <meta-data\n"
+                        + "        android:name=\"com.google.android.gms.car.application\"\n"
+                        + "        android:resource=\"@xml/automotive_app_desc\"/>\n"
+                        + "\n"
+                        + "        <service\n"
+                        + "            android:name=\".MusicService\"\n"
+                        + "            android:exported=\"true\"\n"
+                        + "            tools:ignore=\"ExportedService\">\n"
+                        + "            <intent-filter>\n"
+                        + "                <action android:name=\"android.media.browse"
+                        + ".MediaBrowserService\"/>\n"
+                        + "            </intent-filter>\n"
+                        + "        </service>\n"
+                        + "\n"
+                        + "    </application>\n"
+                        + "\n"
+                        + "</manifest>\n"),
+                mValidAutomotiveDescriptor);
+        assertEquals(expected, result);
+    }
+
+    public void testMissingOnPlayFromSearch() throws Exception {
+        mEnabled = Collections.singleton(
+                AndroidAutoDetector.MISSING_ON_PLAY_FROM_SEARCH);
+
+        String expected = "src/com/example/android/uamp/MSessionCallback.java:5: Error: This class does not override onPlayFromSearch from MediaSession.Callback The method should be overridden and implemented to support Voice search on Android Auto. [MissingOnPlayFromSearch]\n"
+                + "public class MSessionCallback extends Callback {\n"
+                + "             ~~~~~~~~~~~~~~~~\n"
+                + "1 errors, 0 warnings\n";
+
+        String result = lintProject(
+                copy("bytecode/.classpath", ".classpath"),
+                mValidAutoAndroidXml,
+                mValidAutomotiveDescriptor,
+                java("src/com/example/android/uamp/MSessionCallback.java", ""
+                        + "package com.example.android.uamp;\n"
+                        + "\n"
+                        + "import android.media.session.MediaSession.Callback;\n"
+                        + "\n"
+                        + "public class MSessionCallback extends Callback {\n"
+                        + "    @Override\n"
+                        + "    public void onPlay() {\n"
+                        + "        // custom impl\n"
+                        + "    }\n"
+                        + "}\n"));
+        assertEquals(expected, result);
+    }
+
+    public void testValidOnPlayFromSearch() throws Exception {
+        mEnabled = Collections.singleton(AndroidAutoDetector.MISSING_ON_PLAY_FROM_SEARCH);
+
+        String expected = "No warnings.";
+
+        String result = lintProject(
+                copy("bytecode/.classpath", ".classpath"),
+                mValidAutoAndroidXml,
+                mValidAutomotiveDescriptor,
+                java("src/com/example/android/uamp/MSessionCallback.java", ""
+                        + "package com.example.android.uamp;\n"
+                        + "\n"
+                        + "import android.os.Bundle;\n"
+                        + "\n"
+                        + "import android.media.session.MediaSession.Callback;\n"
+                        + "\n"
+                        + "public class MSessionCallback extends Callback {\n"
+                        + "    @Override\n"
+                        + "    public void onPlayFromSearch(String query, Bundle bundle) {\n"
+                        + "        // custom impl\n"
+                        + "    }\n"
+                        + "}\n"));
+        assertEquals(expected, result);
+    }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AndroidTvDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AndroidTvDetectorTest.java
new file mode 100644
index 0000000..e5bcedf
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AndroidTvDetectorTest.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.FN_ANDROID_MANIFEST_XML;
+import static com.android.tools.lint.checks.AndroidTvDetector.MISSING_BANNER;
+import static com.android.tools.lint.checks.AndroidTvDetector.MISSING_LEANBACK_LAUNCHER;
+import static com.android.tools.lint.checks.AndroidTvDetector.MISSING_LEANBACK_SUPPORT;
+import static com.android.tools.lint.checks.AndroidTvDetector.PERMISSION_IMPLIES_UNSUPPORTED_HARDWARE;
+import static com.android.tools.lint.checks.AndroidTvDetector.UNSUPPORTED_TV_HARDWARE;
+
+import com.android.annotations.NonNull;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Project;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+@SuppressWarnings("javadoc")
+public class AndroidTvDetectorTest extends AbstractCheckTest {
+
+    private Set<Issue> mEnabled = new HashSet<Issue>();
+
+    @Override
+    protected Detector getDetector() {
+        return new AndroidTvDetector();
+    }
+
+    @Override
+    protected TestConfiguration getConfiguration(LintClient client, Project project) {
+        return new TestConfiguration(client, project, null) {
+            @Override
+            public boolean isEnabled(@NonNull Issue issue) {
+                return super.isEnabled(issue) && mEnabled.contains(issue);
+            }
+        };
+    }
+
+    public void testInvalidNoLeanbackActivity() throws Exception {
+        mEnabled = Collections.singleton(MISSING_LEANBACK_LAUNCHER);
+        String expected = "AndroidManifest.xml:2: Error: Expecting an activity to have android.intent.category.LEANBACK_LAUNCHER intent filter. [MissingLeanbackLauncher]\n"
+                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                + "^\n"
+                + "1 errors, 0 warnings\n";
+        String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                + "          xmlns:tools=\"http://schemas.android.com/tools\">\n"
+                + "    <uses-feature android:name=\"android.software.leanback\"/>\n"
+                + "    <application>\n"
+                + "        <!-- Application contains an activity, but it isn't a leanback launcher activity -->\n"
+                + "        <activity android:name=\"com.example.android.test.Activity\">\n"
+                + "            <intent-filter>\n"
+                + "                <action android:name=\"android.intent.action.SEND\"/>\n"
+                + "                <category android:name=\"android.intent.category.DEFAULT\"/>\n"
+                + "                <data android:mimeType=\"text/plain\"/>\n"
+                + "            </intent-filter>\n"
+                + "        </activity>\n"
+                + "    </application>\n"
+                + "</manifest>\n"));
+        assertEquals(expected, result);
+    }
+
+    public void testValidLeanbackActivity() throws Exception {
+        mEnabled = Collections.singleton(MISSING_LEANBACK_LAUNCHER);
+        String expected = "No warnings.";
+        String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                + "          xmlns:tools=\"http://schemas.android.com/tools\">\n"
+                + "    <application>\n"
+                + "        <activity android:name=\"com.example.android.TvActivity\">\n"
+                + "            <intent-filter>\n"
+                + "                <action android:name=\"android.intent.action.MAIN\" />\n"
+                + "                <category android:name=\"android.intent.category.LEANBACK_LAUNCHER\" />\n"
+                + "            </intent-filter>\n"
+                + "        </activity>\n"
+                + "    </application>\n"
+                + "</manifest>\n"));
+        assertEquals(expected, result);
+    }
+
+    public void testInvalidNoUsesFeatureLeanback() throws Exception {
+        mEnabled = Collections.singleton(MISSING_LEANBACK_SUPPORT);
+        String expected = "AndroidManifest.xml:2: Error: Expecting <uses-feature android:name=\"android.software.leanback\" android:required=\"false\" /> tag. [MissingLeanbackSupport]\n"
+                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                + "^\n"
+                + "1 errors, 0 warnings\n";
+        String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                + "          xmlns:tools=\"http://schemas.android.com/tools\">\n"
+                + "    <application>\n"
+                + "        <activity android:name=\"com.example.android.TvActivity\">\n"
+                + "            <intent-filter>\n"
+                + "                <action android:name=\"android.intent.action.MAIN\" />\n"
+                + "                <category android:name=\"android.intent.category.LEANBACK_LAUNCHER\" />\n"
+                + "            </intent-filter>\n"
+                + "        </activity>\n"
+                + "    </application>\n"
+                + "</manifest>\n"));
+        assertEquals(expected, result);
+    }
+
+    public void testValidUsesFeatureLeanback() throws Exception {
+        mEnabled = Collections.singleton(MISSING_LEANBACK_SUPPORT);
+        String expected = "No warnings.";
+        String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                + "          xmlns:tools=\"http://schemas.android.com/tools\">\n"
+                + "    <uses-feature android:name=\"android.software.leanback\" android:required=\"false\" />\n"
+                + "</manifest>\n"));
+        assertEquals(expected, result);
+    }
+
+    public void testInvalidUnsupportedHardware() throws Exception {
+        mEnabled = Collections.singleton(UNSUPPORTED_TV_HARDWARE);
+        String expected = "AndroidManifest.xml:6: Error: Expecting android:required=\"false\" for this hardware feature that may not be supported by all Android TVs. [UnsupportedTvHardware]\n"
+                + "        android:name=\"android.hardware.touchscreen\" android:required=\"true\"/>\n"
+                + "                                                    ~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "1 errors, 0 warnings\n";
+        String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                + "    xmlns:tools=\"http://schemas.android.com/tools\">\n"
+                + "    <uses-feature android:name=\"android.software.leanback\"/>\n"
+                + "    <uses-feature\n"
+                + "        android:name=\"android.hardware.touchscreen\" android:required=\"true\"/>\n"
+                + "\n"
+                + "</manifest>\n"));
+        assertEquals(expected, result);
+    }
+
+    public void testValidUnsupportedHardware() throws Exception {
+        mEnabled = Collections.singleton(UNSUPPORTED_TV_HARDWARE);
+        String expected = "No warnings.";
+        String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                + "          xmlns:tools=\"http://schemas.android.com/tools\">\n"
+                + "    <uses-feature\n"
+                + "        android:name=\"android.hardware.touchscreen\"\n"
+                + "        android:required=\"false\" />\n"
+                + "</manifest>\n"));
+        assertEquals(expected, result);
+    }
+
+    public void testValidPermissionImpliesNotMissingUnsupportedHardware() throws Exception {
+        mEnabled = Collections.singleton(PERMISSION_IMPLIES_UNSUPPORTED_HARDWARE);
+        String expected = "No warnings.";
+        String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                + "          xmlns:tools=\"http://schemas.android.com/tools\">\n"
+                + "    <uses-feature android:name=\"android.software.leanback\"/>\n"
+                + "    <uses-permission android:name=\"android.permission.CALL_PHONE\"/>\n"
+                + "    <uses-feature android:required=\"false\" android:name=\"android.hardware.telephony\"/>\n"
+                + "</manifest>\n"));
+        assertEquals(expected, result);
+    }
+
+    public void testInvalidPermissionImpliesNotMissingUnsupportedHardware() throws Exception {
+        mEnabled = Collections.singleton(PERMISSION_IMPLIES_UNSUPPORTED_HARDWARE);
+        String expected = "AndroidManifest.xml:5: Warning: Permission exists without corresponding hardware <uses-feature android:name=\"android.hardware.telephony\" required=\"false\"> tag. [PermissionImpliesUnsupportedHardware]\n"
+                + "    <uses-permission android:name=\"android.permission.CALL_PHONE\"/>\n"
+                + "    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "0 errors, 1 warnings\n";
+        String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                + "          xmlns:tools=\"http://schemas.android.com/tools\">\n"
+                + "    <uses-feature android:name=\"android.software.leanback\"/>\n"
+                + "    <uses-permission android:name=\"android.permission.CALL_PHONE\"/>\n"
+                + "</manifest>\n"));
+        assertEquals(expected, result);
+    }
+
+    public void testInvalidPermissionImpliesMissingUnsupportedHardware() throws Exception {
+        mEnabled = Collections.singleton(PERMISSION_IMPLIES_UNSUPPORTED_HARDWARE);
+        String expected = "AndroidManifest.xml:5: Warning: Permission exists without corresponding hardware <uses-feature android:name=\"android.hardware.telephony\" required=\"false\"> tag. [PermissionImpliesUnsupportedHardware]\n"
+                + "    <uses-permission android:name=\"android.permission.CALL_PHONE\"/>\n"
+                + "    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "0 errors, 1 warnings\n";
+        String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                + "          xmlns:tools=\"http://schemas.android.com/tools\">\n"
+                + "    <uses-feature android:name=\"android.software.leanback\"/>\n"
+                + "    <uses-permission android:name=\"android.permission.CALL_PHONE\"/>\n"
+                + "</manifest>\n"));
+        assertEquals(expected, result);
+    }
+
+    public void testValidPermissionImpliesUnsupportedHardware() throws Exception {
+        mEnabled = Collections.singleton(PERMISSION_IMPLIES_UNSUPPORTED_HARDWARE);
+        String expected = "No warnings.";
+        String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                + "          xmlns:tools=\"http://schemas.android.com/tools\">\n"
+                + "    <uses-feature android:name=\"android.software.leanback\"/>\n"
+                + "    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>\n"
+                + "</manifest>\n"));
+        assertEquals(expected, result);
+    }
+
+    public void testBannerMissingInApplicationTag() throws Exception {
+        mEnabled = Collections.singleton(MISSING_BANNER);
+        String expected = "AndroidManifest.xml:5: Warning: Expecting android:banner with the <application> tag or each Leanback launcher activity. [MissingTvBanner]\n"
+                + "    <application>\n"
+                + "    ^\n"
+                + "0 errors, 1 warnings\n";
+        String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                + "          xmlns:tools=\"http://schemas.android.com/tools\">\n"
+                + "    <uses-feature android:name=\"android.software.leanback\"/>\n"
+                + "    <application>\n"
+                + "        <activity>\n"
+                + "            <intent-filter>\n"
+                + "                <action android:name=\"android.intent.action.MAIN\"/>\n"
+                + "                <category android:name=\"android.intent.category.LEANBACK_LAUNCHER\"/>\n"
+                + "            </intent-filter>\n"
+                + "        </activity>\n"
+                + "    </application>\n"
+                + "</manifest>\n"));
+        assertEquals(expected, result);
+    }
+
+    public void testBannerInLeanbackLauncherActivity() throws Exception {
+        mEnabled = Collections.singleton(MISSING_BANNER);
+        String expected = "No warnings.";
+        String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                + "          xmlns:tools=\"http://schemas.android.com/tools\">\n"
+                + "    <uses-feature android:name=\"android.software.leanback\"/>\n"
+                + "    <application>\n"
+                + "        <activity android:banner=\"@drawable/banner\">\n"
+                + "            <intent-filter>\n"
+                + "                <action android:name=\"android.intent.action.MAIN\"/>\n"
+                + "                <category android:name=\"android.intent.category.LEANBACK_LAUNCHER\"/>\n"
+                + "            </intent-filter>\n"
+                + "        </activity>\n"
+                + "    </application>\n"
+                + "</manifest>\n"));
+        assertEquals(expected, result);
+    }
+
+    public void testBannerInApplicationTag() throws Exception {
+        mEnabled = Collections.singleton(MISSING_BANNER);
+        String expected = "No warnings.";
+        String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                + "          xmlns:tools=\"http://schemas.android.com/tools\">\n"
+                + "    <uses-feature android:name=\"android.software.leanback\"/>\n"
+                + "    <application android:banner=\"@drawable/banner\">\n"
+                + "        <activity>\n"
+                + "            <intent-filter>\n"
+                + "                <action android:name=\"android.intent.action.MAIN\"/>\n"
+                + "                <category android:name=\"android.intent.category.LEANBACK_LAUNCHER\"/>\n"
+                + "            </intent-filter>\n"
+                + "        </activity>\n"
+                + "    </application>\n"
+                + "</manifest>\n"));
+        assertEquals(expected, result);
+    }
+
+    // Implicit trigger tests
+
+    public void testLeanbackSupportTrigger() throws Exception {
+        // Expect some issue to be raised when there is the leanback support trigger.
+        mEnabled = Collections.singleton(PERMISSION_IMPLIES_UNSUPPORTED_HARDWARE);
+        String notExpected = "No warnings.";
+        String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                + "          xmlns:tools=\"http://schemas.android.com/tools\">\n"
+                + "    <uses-feature android:name=\"android.software.leanback\"/>\n"
+                + "    <uses-permission android:name=\"android.permission.CALL_PHONE\"/>\n"
+                + "</manifest>\n"));
+        assertNotSame(notExpected, result);
+
+        // Expect no warnings when there is no trigger.
+        mEnabled = Collections
+                .singleton(PERMISSION_IMPLIES_UNSUPPORTED_HARDWARE);
+        String expected = "No warnings.";
+        result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                + "          xmlns:tools=\"http://schemas.android.com/tools\">\n"
+                + "    <uses-permission android:name=\"android.permission.CALL_PHONE\"/>\n"
+                + "</manifest>\n"));
+        assertEquals(expected, result);
+    }
+
+    public void testLeanbackLauncherTrigger() throws Exception {
+        mEnabled = Collections
+                .singleton(MISSING_LEANBACK_SUPPORT);
+        String notExpected = "No warnings.";
+        String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                + "          xmlns:tools=\"http://schemas.android.com/tools\">\n"
+                + "    <application>\n"
+                + "        <activity android:name=\"com.example.android.TvActivity\">\n"
+                + "            <intent-filter>\n"
+                + "                <action android:name=\"android.intent.action.MAIN\" />\n"
+                + "                <category android:name=\"android.intent.category.LEANBACK_LAUNCHER\" />\n"
+                + "            </intent-filter>\n"
+                + "        </activity>\n"
+                + "    </application>\n"
+                + "</manifest>\n"));
+        assertNotSame(notExpected, result);
+
+        // Expect no warnings when there is no trigger.
+        mEnabled = Collections.singleton(MISSING_LEANBACK_SUPPORT);
+        String expected = "No warnings.";
+        result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                + "          xmlns:tools=\"http://schemas.android.com/tools\">\n"
+                + "    <uses-permission android:name=\"android.permission.CALL_PHONE\"/>\n"
+                + "</manifest>\n"));
+        assertEquals(expected, result);
+    }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AnnotationDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AnnotationDetectorTest.java
index 1831953..e84db85 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AnnotationDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AnnotationDetectorTest.java
@@ -16,12 +16,26 @@
 
 package com.android.tools.lint.checks;
 
+import static com.android.tools.lint.checks.AnnotationDetector.SWITCH_TYPE_DEF;
+import static com.android.tools.lint.checks.AnnotationDetector.getMissingCases;
+import static com.android.tools.lint.detector.api.TextFormat.TEXT;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+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.Severity;
+import com.android.tools.lint.detector.api.TextFormat;
 
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
-@SuppressWarnings("javadoc")
+@SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName", "UnnecessaryLocalVariable",
+        "ConstantConditionalExpression", "StatementWithEmptyBody", "RedundantCast",
+        "MethodMayBeStatic"})
 public class AnnotationDetectorTest extends AbstractCheckTest {
     public void test() throws Exception {
         assertEquals(
@@ -64,7 +78,7 @@
                         java("src/test/pkg/IntDefTest.java", ""
                                 + "package test.pkg;\n"
                                 + "import android.support.annotation.IntDef;\n"
-                                + "\n"
+                                + "import android.annotation.SuppressLint;\n"
                                 + "import java.lang.annotation.Retention;\n"
                                 + "import java.lang.annotation.RetentionPolicy;\n"
                                 + "\n"
@@ -164,6 +178,244 @@
                                 "src/android/support/annotation/IntDef.java")));
     }
 
+    public void testMissingIntDefSwitchConstants() throws Exception {
+        assertEquals(""
+                + "src/test/pkg/X.java:40: Warning: Don't use a constant here; expected one of: LENGTH_INDEFINITE, LENGTH_LONG, LENGTH_SHORT [SwitchIntDef]\n"
+                + "            case 5:\n"
+                + "                 ~\n"
+                + "src/test/pkg/X.java:47: Warning: Switch statement on an int with known associated constant missing case LENGTH_LONG [SwitchIntDef]\n"
+                + "        switch (duration) {\n"
+                + "        ~~~~~~\n"
+                + "src/test/pkg/X.java:56: Warning: Switch statement on an int with known associated constant missing case LENGTH_INDEFINITE, LENGTH_LONG, LENGTH_SHORT [SwitchIntDef]\n"
+                + "        switch (duration) {\n"
+                + "        ~~~~~~\n"
+                + "src/test/pkg/X.java:66: Warning: Switch statement on an int with known associated constant missing case LENGTH_SHORT [SwitchIntDef]\n"
+                + "        switch (duration) {\n"
+                + "        ~~~~~~\n"
+                + "src/test/pkg/X.java:75: Warning: Switch statement on an int with known associated constant missing case LENGTH_SHORT [SwitchIntDef]\n"
+                + "        switch ((int)getDuration()) {\n"
+                + "        ~~~~~~\n"
+                + "src/test/pkg/X.java:85: Warning: Switch statement on an int with known associated constant missing case LENGTH_SHORT [SwitchIntDef]\n"
+                + "        switch (true ? getDuration() : 0) {\n"
+                + "        ~~~~~~\n"
+                + "src/test/pkg/X.java:95: Warning: Switch statement on an int with known associated constant missing case X.LENGTH_SHORT [SwitchIntDef]\n"
+                + "            switch (X.getDuration()) {\n"
+                + "            ~~~~~~\n"
+                + "src/test/pkg/X.java:104: Warning: Switch statement on an int with known associated constant missing case LENGTH_INDEFINITE [SwitchIntDef]\n"
+                + "        switch (duration) {\n"
+                + "        ~~~~~~\n"
+                + "0 errors, 8 warnings\n",
+
+                lintProject(
+                        java("src/test/pkg/X.java", ""
+                                + "package test.pkg;\n"
+                                + "\n"
+                                + "import android.annotation.SuppressLint;\n"
+                                + "import android.support.annotation.IntDef;\n"
+                                + "\n"
+                                + "import java.lang.annotation.Retention;\n"
+                                + "import java.lang.annotation.RetentionPolicy;\n"
+                                + "\n"
+                                + "@SuppressWarnings({\"UnusedParameters\", \"unused\", \"SpellCheckingInspection\", \"RedundantCast\"})\n"
+                                + "public class X {\n"
+                                + "    @IntDef({LENGTH_INDEFINITE, LENGTH_SHORT, LENGTH_LONG})\n"
+                                + "    @Retention(RetentionPolicy.SOURCE)\n"
+                                + "    public @interface Duration {\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public static final int LENGTH_INDEFINITE = -2;\n"
+                                + "    public static final int LENGTH_SHORT = -1;\n"
+                                + "    public static final int LENGTH_LONG = 0;\n"
+                                + "\n"
+                                + "    public void setDuration(@Duration int duration) {\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    @Duration\n"
+                                + "    public static int getDuration() {\n"
+                                + "        return LENGTH_INDEFINITE;\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public static void testOk(@Duration int duration) {\n"
+                                + "        switch (duration) {\n"
+                                + "            case LENGTH_SHORT:\n"
+                                + "            case LENGTH_LONG:\n"
+                                + "            case LENGTH_INDEFINITE:\n"
+                                + "                break;\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public static void testLiteral(@Duration int duration) {\n"
+                                + "        switch (duration) {\n"
+                                + "            case LENGTH_SHORT:\n"
+                                + "            case 5:\n"
+                                + "            case LENGTH_INDEFINITE:\n"
+                                + "                break;\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public static void testParameter(@Duration int duration) {\n"
+                                + "        switch (duration) {\n"
+                                + "            case LENGTH_SHORT:\n"
+                                + "            case LENGTH_INDEFINITE:\n"
+                                + "                break;\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public static void testMissingAll(@Duration int duration) {\n"
+                                + "        // We don't flag these; let the IDE's normal \"empty switch\" check flag it\n"
+                                + "        switch (duration) {\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    @SuppressWarnings(\"UnnecessaryLocalVariable\")\n"
+                                + "    public static void testLocalVariableFlow() {\n"
+                                + "        int intermediate = getDuration();\n"
+                                + "        int duration = intermediate;\n"
+                                + "\n"
+                                + "        // Missing LENGTH_SHORT\n"
+                                + "        switch (duration) {\n"
+                                + "            case LENGTH_LONG:\n"
+                                + "            case LENGTH_INDEFINITE:\n"
+                                + "                break;\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public static void testMethodCall() {\n"
+                                + "        // Missing LENGTH_SHORT\n"
+                                + "        switch ((int)getDuration()) {\n"
+                                + "            case LENGTH_LONG:\n"
+                                + "            case LENGTH_INDEFINITE:\n"
+                                + "                break;\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    @SuppressWarnings(\"ConstantConditionalExpression\")\n"
+                                + "    public static void testInline() {\n"
+                                + "        // Missing LENGTH_SHORT\n"
+                                + "        switch (true ? getDuration() : 0) {\n"
+                                + "            case LENGTH_LONG:\n"
+                                + "            case LENGTH_INDEFINITE:\n"
+                                + "                break;\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    private static class SomeOtherClass {\n"
+                                + "        private void method() {\n"
+                                + "            // Missing LENGTH_SHORT\n"
+                                + "            switch (X.getDuration()) {\n"
+                                + "                case LENGTH_LONG:\n"
+                                + "                case LENGTH_INDEFINITE:\n"
+                                + "                    break;\n"
+                                + "            }\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public static void testMissingWithDefault(@Duration int duration) {\n"
+                                + "        switch (duration) {\n"
+                                + "            case LENGTH_SHORT:\n"
+                                + "            case LENGTH_LONG:\n"
+                                + "            default:\n"
+                                + "                break;\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    @SuppressLint(\"SwitchIntDef\")\n"
+                                + "    public static void testSuppressAnnotation(@Duration int duration) {\n"
+                                + "        switch (duration) {\n"
+                                + "            case LENGTH_SHORT:\n"
+                                + "            case LENGTH_INDEFINITE:\n"
+                                + "                break;\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public static void testSuppressComment(@Duration int duration) {\n"
+                                + "        //noinspection AndroidLintSwitchIntDef\n"
+                                + "        switch (duration) {\n"
+                                + "            case LENGTH_SHORT:\n"
+                                + "            case LENGTH_INDEFINITE:\n"
+                                + "                break;\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "}\n"),
+                        copy("src/android/support/annotation/IntDef.java.txt",
+                                "src/android/support/annotation/IntDef.java")
+        ));
+    }
+
+
+    public void testMissingSwitchFailingIntDef() throws Exception {
+        assertEquals(""
+                + "src/test/pkg/X.java:8: Warning: Switch statement on an int with known associated constant missing case EXACTLY, UNSPECIFIED [SwitchIntDef]\n"
+                + "        switch (val) {\n"
+                + "        ~~~~~~\n"
+                + "0 errors, 1 warnings\n",
+                lintProject(
+                        java("src/test/pkg/X.java", ""
+                                + "package test.pkg;\n"
+                                + "\n"
+                                + "import android.view.View;"
+                                + "\n"
+                                + "public class X {\n"
+                                + "\n"
+                                + "    public void measure(int mode) {\n"
+                                + "        int val = View.MeasureSpec.getMode(mode);\n"
+                                + "        switch (val) {\n"
+                                + "            case View.MeasureSpec.AT_MOST:\n"
+                                + "                break;\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "}\n"),
+                        copy("bytecode/.classpath", ".classpath")));
+    }
+
+    public void testGetEnumCases() {
+        assertEquals(
+                Arrays.asList("LENGTH_INDEFINITE", "LENGTH_SHORT", "LENGTH_LONG"),
+                getMissingCases("Don't use a constant here; expected one of: LENGTH_INDEFINITE, LENGTH_SHORT, LENGTH_LONG",
+                        TextFormat.TEXT));
+        assertEquals(
+                Collections.singletonList("LENGTH_SHORT"),
+                getMissingCases("Switch statement on an int with known associated constant missing case LENGTH_SHORT",
+                                TextFormat.TEXT));
+    }
+
+    public void testMatchEcjAndExternalFieldNames() throws Exception {
+        assertEquals("No warnings.",
+                lintProject(java("src/test/pkg/MissingEnum.java", ""
+                        + "package test.pkg;\n"
+                        + "\n"
+                        + "import android.net.wifi.WifiManager;\n"
+                        + "\n"
+                        + "public class MissingEnum {\n"
+                        + "    private WifiManager mWifiManager;\n"
+                        + "\n"
+                        + "    private void updateAccessPoints() {\n"
+                        + "        final int wifiState = mWifiManager.getWifiState();\n"
+                        + "        switch (wifiState) {\n"
+                        + "            case WifiManager.WIFI_STATE_ENABLING:\n"
+                        + "                break;\n"
+                        + "            case WifiManager.WIFI_STATE_ENABLED:\n"
+                        + "                break;\n"
+                        + "            case WifiManager.WIFI_STATE_DISABLING:\n"
+                        + "                break;\n"
+                        + "            case WifiManager.WIFI_STATE_DISABLED:\n"
+                        + "                break;\n"
+                        + "            case WifiManager.WIFI_STATE_UNKNOWN:\n"
+                        + "                break;\n"
+                        + "        }\n"
+                        + "    }\n"
+                        + "}\n")));
+    }
+
+    @Override
+    protected void checkReportedError(@NonNull Context context, @NonNull Issue issue,
+            @NonNull Severity severity, @Nullable Location location, @NonNull String message) {
+        if (issue == SWITCH_TYPE_DEF) {
+            assertNotNull("Could not extract message tokens from " + message,
+                    getMissingCases(message, TEXT));
+        }
+    }
+
     @Override
     protected Detector getDetector() {
         return new AnnotationDetector();
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ApiDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ApiDetectorTest.java
index 2cf2901..9d91d1b 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ApiDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ApiDetectorTest.java
@@ -26,7 +26,9 @@
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
 import com.android.builder.model.AndroidProject;
+import com.android.sdklib.BuildToolInfo;
 import com.android.sdklib.SdkVersionInfo;
+import com.android.sdklib.repository.FullRevision;
 import com.android.tools.lint.detector.api.Context;
 import com.android.tools.lint.detector.api.Detector;
 import com.android.tools.lint.detector.api.Issue;
@@ -35,6 +37,7 @@
 import com.android.tools.lint.detector.api.Severity;
 
 import java.io.File;
+import java.util.regex.Pattern;
 
 @SuppressWarnings("javadoc")
 public class ApiDetectorTest extends AbstractCheckTest {
@@ -43,6 +46,13 @@
         return new ApiDetector();
     }
 
+    @Override
+    protected boolean allowCompilationErrors() {
+        // Some of these unit tests are still relying on source code that references
+        // unresolved symbols etc.
+        return true;
+    }
+
     public void testXmlApi1() throws Exception {
         assertEquals(
             "res/color/colors.xml:9: Error: @android:color/holo_red_light requires API level 14 (current min is 1) [NewApi]\n" +
@@ -147,6 +157,36 @@
                 ));
     }
 
+    public void testRtlManifestAttribute() throws Exception {
+        // Treat the manifest RTL attribute in the same was as the layout start/end attributes:
+        // these are known to be benign on older platforms, so don't flag it.
+        assertEquals("No warnings.",
+                lintProject(
+                        xml("AndroidManifest.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                                + "    package=\"test.bytecode\">\n"
+                                + "\n"
+                                + "    <uses-sdk android:minSdkVersion=\"1\" />\n"
+                                + "\n"
+                                + "    <application\n"
+                                + "        android:supportsRtl='true'\n"
+
+                                // Ditto for the fullBackupContent attribute. If you're targeting
+                                // 23, you'll want to use it, but it's not an error that older
+                                // platforms aren't looking at it.
+
+                                + "        android:fullBackupContent='false'\n"
+                                + "        android:icon=\"@drawable/ic_launcher\"\n"
+                                + "        android:label=\"@string/app_name\" >\n"
+                                + "    </application>\n"
+                                + "\n"
+                                + "</manifest>\n")
+
+                )
+        );
+    }
+
     public void testXmlApi() throws Exception {
         assertEquals(""
                 + "res/layout/attribute2.xml:4: Error: ?android:switchTextAppearance requires API level 14 (current min is 11) [NewApi]\n"
@@ -827,7 +867,7 @@
             lintProject(
                     "apicheck/classpath=>.classpath",
                     "apicheck/minsdk4.xml=>AndroidManifest.xml",
-                    "project.properties1=>project.properties",
+                    "project.properties19=>project.properties",
                     "apicheck/ApiCallTest12.java.txt=>src/test/pkg/ApiCallTest12.java",
                     "apicheck/ApiCallTest12.class.data=>bin/classes/test/pkg/ApiCallTest12.class"
                 ));
@@ -840,7 +880,7 @@
             lintProject(
                     "apicheck/classpath=>.classpath",
                     "apicheck/minsdk10.xml=>AndroidManifest.xml",
-                    "project.properties1=>project.properties",
+                    "project.properties19=>project.properties",
                     "apicheck/ApiCallTest12.java.txt=>src/test/pkg/ApiCallTest12.java",
                     "apicheck/ApiCallTest12.class.data=>bin/classes/test/pkg/ApiCallTest12.class"
                 ));
@@ -887,12 +927,12 @@
                 + "src/test/pkg/ApiSourceCheck.java:90: Warning: Field requires API level 8 (current min is 1): android.R.id#custom [InlinedApi]\n"
                 + "        int custom = android.R.id.custom; // API 8\n"
                 + "                     ~~~~~~~~~~~~~~~~~~~\n"
-                + "src/test/pkg/ApiSourceCheck.java:94: Warning: Field requires API level 13 (current min is 1): android.Manifest.permission#SET_POINTER_SPEED [InlinedApi]\n"
-                + "        String setPointerSpeed = permission.SET_POINTER_SPEED;\n"
-                + "                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
-                + "src/test/pkg/ApiSourceCheck.java:95: Warning: Field requires API level 13 (current min is 1): android.Manifest.permission#SET_POINTER_SPEED [InlinedApi]\n"
-                + "        String setPointerSpeed2 = Manifest.permission.SET_POINTER_SPEED;\n"
-                + "                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ApiSourceCheck.java:94: Warning: Field requires API level 19 (current min is 1): android.Manifest.permission#BLUETOOTH_PRIVILEGED [InlinedApi]\n"
+                + "        String setPointerSpeed = permission.BLUETOOTH_PRIVILEGED;\n"
+                + "                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ApiSourceCheck.java:95: Warning: Field requires API level 19 (current min is 1): android.Manifest.permission#BLUETOOTH_PRIVILEGED [InlinedApi]\n"
+                + "        String setPointerSpeed2 = Manifest.permission.BLUETOOTH_PRIVILEGED;\n"
+                + "                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
                 + "src/test/pkg/ApiSourceCheck.java:120: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [InlinedApi]\n"
                 + "        int y = View.MEASURED_STATE_MASK; // Not OK\n"
                 + "                ~~~~~~~~~~~~~~~~~~~~~~~~\n"
@@ -907,7 +947,7 @@
                 lintProject(
                         "apicheck/classpath=>.classpath",
                         "apicheck/minsdk1.xml=>AndroidManifest.xml",
-                        "project.properties1=>project.properties",
+                        "project.properties19=>project.properties",
                         "apicheck/ApiSourceCheck.java.txt=>src/test/pkg/ApiSourceCheck.java",
                         "apicheck/ApiSourceCheck.class.data=>bin/classes/test/pkg/ApiSourceCheck.class"
                 ));
@@ -923,7 +963,7 @@
                 lintProject(
                         "apicheck/classpath=>.classpath",
                         "apicheck/minsdk10.xml=>AndroidManifest.xml",
-                        "project.properties1=>project.properties",
+                        "project.properties19=>project.properties",
                         "res/values/styles2.xml"
                 ));
     }
@@ -938,7 +978,7 @@
                 lintProject(
                         "apicheck/classpath=>.classpath",
                         "apicheck/minsdk10.xml=>AndroidManifest.xml",
-                        "project.properties1=>project.properties",
+                        "project.properties19=>project.properties",
                         "res/values/styles2.xml=>res/values-v9/styles2.xml"
                 ));
     }
@@ -950,7 +990,7 @@
                 lintProject(
                         "apicheck/classpath=>.classpath",
                         "apicheck/minsdk10.xml=>AndroidManifest.xml",
-                        "project.properties1=>project.properties",
+                        "project.properties19=>project.properties",
                         "res/values/styles2.xml=>res/values-v11/styles2.xml"
                 ));
     }
@@ -962,7 +1002,7 @@
                 lintProject(
                         "apicheck/classpath=>.classpath",
                         "apicheck/minsdk10.xml=>AndroidManifest.xml",
-                        "project.properties1=>project.properties",
+                        "project.properties19=>project.properties",
                         "res/values/styles2.xml=>res/values-v14/styles2.xml"
                 ));
     }
@@ -982,7 +1022,7 @@
                 lintProject(
                         "apicheck/classpath=>.classpath",
                         "apicheck/minsdk1.xml=>AndroidManifest.xml",
-                        "project.properties1=>project.properties",
+                        "project.properties19=>project.properties",
                         "apicheck/ApiSourceCheck2.java.txt=>src/test/pkg/ApiSourceCheck2.java",
                         "apicheck/ApiSourceCheck2.class.data=>bin/classes/test/pkg/ApiSourceCheck2.class"
                 ));
@@ -1027,7 +1067,7 @@
                 lintProject(
                         "apicheck/classpath=>.classpath",
                         "apicheck/minsdk4.xml=>AndroidManifest.xml",
-                        "project.properties1=>project.properties",
+                        "project.properties19=>project.properties",
                         "apicheck/ApiCallTest13.java.txt=>src/test/pkg/ApiCallTest13.java",
                         "apicheck/ApiCallTest13.class.data=>bin/classes/test/pkg/ApiCallTest13.class"
                 ));
@@ -1141,7 +1181,7 @@
 
     public void testVector() throws Exception {
         assertEquals(""
-                        + "res/drawable/vector.xml:1: Error: <vector> requires API level 21 (current min is 4) [NewApi]\n"
+                        + "res/drawable/vector.xml:1: Error: <vector> requires API level 21 (current min is 4) or building with Android Gradle plugin 1.4 or higher [NewApi]\n"
                         + "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n"
                         + "^\n"
                         + "1 errors, 0 warnings\n",
@@ -1160,6 +1200,14 @@
                 ));
     }
 
+    public void testVector_withGradleSupportSNAPSHOT() throws Exception {
+        assertEquals("No warnings.",
+                lintProject(
+                        "apicheck/minsdk4.xml=>AndroidManifest.xml",
+                        "apicheck/vector.xml=>res/drawable/vector.xml"
+                ));
+    }
+
     public void testAnimatedSelector() throws Exception {
         assertEquals(""
                 + "res/drawable/animated_selector.xml:1: Error: <animated-selector> requires API level 21 (current min is 14) [NewApi]\n"
@@ -1210,6 +1258,32 @@
                 ));
     }
 
+    public void testPaddingStartWithOldBuildTools() throws Exception {
+        assertEquals(""
+                        + "res/layout/padding_start.xml:14: Error: Upgrade buildToolsVersion from 22.2.1 to at least 23.0.1; if not, attribute paddingStart referenced here can result in a crash on some specific devices older than API 17 (current min is 4) [NewApi]\n"
+                        + "            android:paddingStart=\"20dp\"\n"
+                        + "            ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                        + "res/layout/padding_start.xml:21: Error: Upgrade buildToolsVersion from 22.2.1 to at least 23.0.1; if not, attribute paddingStart referenced here can result in a crash on some specific devices older than API 17 (current min is 4) [NewApi]\n"
+                        + "            android:paddingStart=\"20dp\"\n"
+                        + "            ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                        + "res/layout/padding_start.xml:28: Error: Upgrade buildToolsVersion from 22.2.1 to at least 23.0.1; if not, attribute paddingStart referenced here can result in a crash on some specific devices older than API 17 (current min is 4) [NewApi]\n"
+                        + "            android:paddingStart=\"20dp\"\n"
+                        + "            ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                        + "3 errors, 0 warnings\n",
+                lintProject(
+                        "apicheck/minsdk4.xml=>AndroidManifest.xml",
+                        "apicheck/padding_start.xml=>res/layout/padding_start.xml"
+                ));
+    }
+
+    public void testPaddingStartWithNewBuildTools() throws Exception {
+        assertEquals("No warnings.",
+                lintProject(
+                        "apicheck/minsdk4.xml=>AndroidManifest.xml",
+                        "apicheck/padding_start.xml=>res/layout/padding_start.xml"
+                ));
+    }
+
     public void testSwitch() throws Exception {
         assertEquals("No warnings.",
             lintProject(
@@ -1752,6 +1826,181 @@
                 ));
     }
 
+    public void testHigherCompileSdkVersionThanPlatformTools() throws Exception {
+        // Warn if the platform tools are too old on the system
+        assertTrue(Pattern.matches(""
+                + "ApiDetectorTest_testHigherCompileSdkVersionThanPlatformTools: Error: The SDK platform-tools version \\([^)]+\\) is too old  to check APIs compiled with API 400; please update \\[NewApi\\]\n"
+                + "1 errors, 0 warnings\n",
+
+                lintProject(
+                        copy("apicheck/minsdk14.xml", "AndroidManifest.xml"),
+                        source("project.properties", "target=android-400"), // in the future
+                        copy("apicheck/ApiCallTest12.java.txt", "src/test/pkg/ApiCallTest12.java"),
+                        copy("apicheck/ApiCallTest12.class.data", "bin/classes/test/pkg/ApiCallTest12.class")
+                )));
+    }
+
+    public void testHigherCompileSdkVersionThanPlatformToolsInEditor() throws Exception {
+        // When editing a file we place the error on the first line of the file instead
+        assertTrue(Pattern.matches(""
+                + "src/test/pkg/ApiCallTest12.java:1: Error: The SDK platform-tools version \\([^)]+\\) is too old  to check APIs compiled with API 400; please update \\[NewApi\\]\n"
+                + "package test.pkg;\n"
+                + "~~~~~~~~~~~~~~~~~\n"
+                + "1 errors, 0 warnings\n",
+
+                lintProjectIncrementally(
+                        "src/test/pkg/ApiCallTest12.java",
+                        copy("apicheck/minsdk14.xml", "AndroidManifest.xml"),
+                        source("project.properties", "target=android-400"), // in the future
+                        copy("apicheck/ApiCallTest12.java.txt", "src/test/pkg/ApiCallTest12.java"),
+                        copy("apicheck/ApiCallTest12.class.data", "bin/classes/test/pkg/ApiCallTest12.class")
+                )));
+    }
+
+    @SuppressWarnings({"MethodMayBeStatic", "ConstantConditions", "ClassNameDiffersFromFileName"})
+    public void testCastChecks() throws Exception {
+        // When editing a file we place the error on the first line of the file instead
+        assertEquals(""
+                + "src/test/pkg/CastTest.java:15: Error: Cast from Cursor to Closeable requires API level 16 (current min is 14) [NewApi]\n"
+                + "        Closeable closeable = (Closeable) cursor; // Requires 16\n"
+                + "                              ~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/CastTest.java:21: Error: Cast from KeyCharacterMap to Parcelable requires API level 16 (current min is 14) [NewApi]\n"
+                + "        Parcelable parcelable2 = (Parcelable)map; // Requires API 16\n"
+                + "                                 ~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/CastTest.java:27: Error: Cast from AnimatorListenerAdapter to Animator.AnimatorPauseListener requires API level 19 (current min is 14) [NewApi]\n"
+                + "        AnimatorPauseListener listener = (AnimatorPauseListener)adapter;\n"
+                + "                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "3 errors, 0 warnings\n",
+
+                lintProject(
+                        java("src/test/pkg/CastTest.java", ""
+                                + "import android.animation.Animator.AnimatorPauseListener;\n"
+                                + "import android.animation.AnimatorListenerAdapter;\n"
+                                + "import android.database.Cursor;\n"
+                                + "import android.database.CursorWindow;\n"
+                                + "import android.os.Parcelable;\n"
+                                + "import android.view.KeyCharacterMap;\n"
+                                + "\n"
+                                + "import java.io.Closeable;\n"
+                                + "import java.io.IOException;\n"
+                                + "\n"
+                                + "@SuppressWarnings({\"RedundantCast\", \"unused\"})\n"
+                                + "public class CastTest {\n"
+                                + "    public void test(Cursor cursor) throws IOException {\n"
+                                + "        cursor.close();\n"
+                                + "        Closeable closeable = (Closeable) cursor; // Requires 16\n"
+                                + "        closeable.close();\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public void test(CursorWindow window, KeyCharacterMap map) {\n"
+                                + "        Parcelable parcelable1 = (Parcelable)window; // OK\n"
+                                + "        Parcelable parcelable2 = (Parcelable)map; // Requires API 16\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    @SuppressWarnings(\"UnnecessaryLocalVariable\")\n"
+                                + "    public void test(AnimatorListenerAdapter adapter) {\n"
+                                + "        // Uh oh - what if the cast isn't needed anymore\n"
+                                + "        AnimatorPauseListener listener = (AnimatorPauseListener)adapter;\n"
+                                + "    }\n"
+                                + "}"),
+                        copy("apicheck/minsdk14.xml", "AndroidManifest.xml")
+                ));
+    }
+
+    @SuppressWarnings({"MethodMayBeStatic", "ConstantConditions", "ClassNameDiffersFromFileName",
+            "UnnecessaryLocalVariable"})
+    public void testImplicitCastTest() throws Exception {
+        // When editing a file we place the error on the first line of the file instead
+        assertEquals(""
+                + "src/test/pkg/ImplicitCastTest.java:14: Error: Cast from Cursor to Closeable requires API level 16 (current min is 14) [NewApi]\n"
+                + "        Closeable closeable = c;\n"
+                + "                              ~\n"
+                + "src/test/pkg/ImplicitCastTest.java:26: Error: Cast from Cursor to Closeable requires API level 16 (current min is 14) [NewApi]\n"
+                + "        closeable = c;\n"
+                + "        ~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ImplicitCastTest.java:36: Error: Cast from ParcelFileDescriptor to Closeable requires API level 16 (current min is 14) [NewApi]\n"
+                + "        safeClose(pfd);\n"
+                + "                  ~~~\n"
+                + "src/test/pkg/ImplicitCastTest.java:47: Error: Cast from AccelerateDecelerateInterpolator to BaseInterpolator requires API level 22 (current min is 14) [NewApi]\n"
+                + "        android.view.animation.BaseInterpolator base = interpolator;\n"
+                + "                                                       ~~~~~~~~~~~~\n"
+                + "4 errors, 0 warnings\n",
+
+                lintProject(
+                        java("src/test/pkg/ImplicitCastTest.java", ""
+                                + "package test.pkg;\n"
+                                + "\n"
+                                + "import android.database.Cursor;\n"
+                                + "import android.os.ParcelFileDescriptor;\n"
+                                + "\n"
+                                + "import java.io.Closeable;\n"
+                                + "import java.io.IOException;\n"
+                                + "\n"
+                                + "@SuppressWarnings(\"unused\")\n"
+                                + "public class ImplicitCastTest {\n"
+                                + "    // https://code.google.com/p/android/issues/detail?id=174535\n"
+                                + "    @SuppressWarnings(\"UnnecessaryLocalVariable\")\n"
+                                + "    public void testImplicitCast(Cursor c) {\n"
+                                + "        Closeable closeable = c;\n"
+                                + "        try {\n"
+                                + "            closeable.close();\n"
+                                + "        } catch (IOException e) {\n"
+                                + "            e.printStackTrace();\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    // Like the above, but with assignment instead of initializer\n"
+                                + "    public void testImplicitCast2(Cursor c) {\n"
+                                + "        @SuppressWarnings(\"UnnecessaryLocalVariable\")\n"
+                                + "        Closeable closeable;\n"
+                                + "        closeable = c;\n"
+                                + "        try {\n"
+                                + "            closeable.close();\n"
+                                + "        } catch (IOException e) {\n"
+                                + "            e.printStackTrace();\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    // https://code.google.com/p/android/issues/detail?id=191120\n"
+                                + "    public void testImplicitCast(ParcelFileDescriptor pfd) {\n"
+                                + "        safeClose(pfd);\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    private static void safeClose(Closeable closeable) {\n"
+                                + "        try {\n"
+                                + "            closeable.close();\n"
+                                + "        } catch (IOException ignore) {\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public void testImplicitCast(android.view.animation.AccelerateDecelerateInterpolator interpolator) {\n"
+                                + "        android.view.animation.BaseInterpolator base = interpolator;\n"
+                                + "    }\n"
+                                + "\n"
+                                + "}\n"),
+                        copy("apicheck/minsdk14.xml", "AndroidManifest.xml")
+                ));
+    }
+
+    public void testSupportLibraryCalls() throws Exception {
+        assertEquals(""
+                + "src/test/pkg/SupportLibraryApiTest.java:22: Error: Call requires API level 21 (current min is 14): android.widget.ImageButton#setBackgroundTintList [NewApi]\n"
+                + "        button.setBackgroundTintList(colors); // ERROR\n"
+                + "               ~~~~~~~~~~~~~~~~~~~~~\n"
+                + "1 errors, 0 warnings\n",
+                lintProject(
+                        copy("apicheck/minsdk14.xml", "AndroidManifest.xml"),
+                        copy("bytecode/SupportLibraryApiTest.java.txt",
+                                "src/test/pkg/SupportLibraryApiTest.java"),
+                        copy("bytecode/SupportLibraryApiTest.class.data",
+                                "bin/classes/test/pkg/SupportLibraryApiTest.class"),
+                        copy("bytecode/FloatingActionButton.java.txt",
+                                "src/android/support/design/widget/FloatingActionButton.java"),
+                        copy("bytecode/FloatingActionButton.class.data",
+                                "bin/classes/android/support/design/widget/FloatingActionButton.class")
+                ));
+    }
+
     @Override
     protected TestLintClient createClient() {
         if (getName().equals("testMissingApiDatabase")) {
@@ -1778,11 +2027,219 @@
                 }
             };
         }
+        if (getName().equals("testVector_withGradleSupportSNAPSHOT")) {
+            return new TestLintClient() {
+                @NonNull
+                @Override
+                protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+                    AndroidProject model = mock(AndroidProject.class);
+                    when(model.getModelVersion()).thenReturn("2.1.0-SNAPSHOT");
+
+                    Project fromSuper = super.createProject(dir, referenceDir);
+                    Project spy = spy(fromSuper);
+                    when(spy.getGradleProjectModel()).thenReturn(model);
+                    return spy;
+                }
+            };
+        }
+
+        if (getName().equals("testPaddingStart")) {
+            return new TestLintClient() {
+                @NonNull
+                @Override
+                protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+                    Project fromSuper = super.createProject(dir, referenceDir);
+                    Project spy = spy(fromSuper);
+                    when(spy.getBuildTools()).thenReturn(null);
+                    return spy;
+                }
+            };
+        }
+        if (getName().equals("testPaddingStartWithOldBuildTools")) {
+            return new TestLintClient() {
+                @NonNull
+                @Override
+                protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+                    FullRevision revision = new FullRevision(22, 2, 1);
+                    BuildToolInfo info = new BuildToolInfo(revision, dir);
+
+                    Project fromSuper = super.createProject(dir, referenceDir);
+                    Project spy = spy(fromSuper);
+                    when(spy.getBuildTools()).thenReturn(info);
+                    return spy;
+                }
+            };
+        }
+        if (getName().equals("testPaddingStartWithNewBuildTools")) {
+            return new TestLintClient() {
+                @NonNull
+                @Override
+                protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+                    FullRevision revision = new FullRevision(23, 0, 2);
+                    BuildToolInfo info = new BuildToolInfo(revision, dir);
+
+                    Project fromSuper = super.createProject(dir, referenceDir);
+                    Project spy = spy(fromSuper);
+                    when(spy.getBuildTools()).thenReturn(info);
+                    return spy;
+                }
+            };
+        }
         return super.createClient();
     }
 
+    // bug 198295: Add a test for a case that crashes ApiDetector due to an
+    // invalid parameterIndex causing by a varargs method invocation.
+    public void testMethodWithPrimitiveAndVarargs() throws Exception {
+        // In case of a crash, there is an assertion failure in tearDown()
+        //noinspection ClassNameDiffersFromFileName
+        assertEquals("No warnings.",
+                lintProject(
+                        copy("apicheck/minsdk14.xml", "AndroidManifest.xml"),
+                        java("src/test/pkg/LogHelper.java", "" +
+                                "package test.pkg;\n"
+                                + "\n"
+                                + "public class LogHelper {\n"
+                                + "\n"
+                                + "    public static void log(String tag, Object... args) {\n"
+                                + "    }\n"
+                                + "}"),
+                        java("src/test/pkg/Browser.java", "" +
+                                "package test.pkg;\n"
+                                + "\n"
+                                + "public class Browser {\n"
+                                + "    \n"
+                                + "    public void onCreate() {\n"
+                                + "        LogHelper.log(\"TAG\", \"arg1\", \"arg2\", 1, \"arg4\", this /*non primitive*/);\n"
+                                + "    }\n"
+                                + "}")
+                ));
+    }
+
+    public void testMethodInvocationWithGenericTypeArgs() throws Exception {
+        // Test case for https://code.google.com/p/android/issues/detail?id=198439
+        //noinspection ClassNameDiffersFromFileName
+        assertEquals("No warnings.",
+                lintProject(
+                        java("src/test/pkg/Loader.java", ""
+                                + "package test.pkg;\n"
+                                + "\n"
+                                + "public abstract class Loader<P> {\n"
+                                + "    private P mParam;\n"
+                                + "\n"
+                                + "    public abstract void loadInBackground(P val);\n"
+                                + "\n"
+                                + "    public void load() {\n"
+                                + "        // Invoke a method that takes a generic type.\n"
+                                + "        loadInBackground(mParam);\n"
+                                + "    }\n"
+                                + "}\n")
+                ));
+    }
+
+    public void testMultiCatch() throws Exception {
+        // Regression test for https://code.google.com/p/android/issues/detail?id=198854
+        // Check disjointed exception types
+
+        //noinspection ClassNameDiffersFromFileName
+        assertEquals(""
+                + "src/test/pkg/MultiCatch.java:12: Error: Class requires API level 18 (current min is 1): android.media.UnsupportedSchemeException [NewApi]\n"
+                + "        } catch (MediaDrm.MediaDrmStateException | UnsupportedSchemeException e) {\n"
+                + "                                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/MultiCatch.java:12: Error: Class requires API level 21 (current min is 1): android.media.MediaDrm.MediaDrmStateException [NewApi]\n"
+                + "        } catch (MediaDrm.MediaDrmStateException | UnsupportedSchemeException e) {\n"
+                + "                          ~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/MultiCatch.java:18: Error: Class requires API level 21 (current min is 1): android.media.MediaDrm.MediaDrmStateException [NewApi]\n"
+                + "        } catch (MediaDrm.MediaDrmStateException\n"
+                + "                          ~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/MultiCatch.java:19: Error: Class requires API level 18 (current min is 1): android.media.UnsupportedSchemeException [NewApi]\n"
+                + "                  | UnsupportedSchemeException e) {\n"
+                + "                    ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/MultiCatch.java:26: Error: Multi-catch with these reflection exceptions requires API level 19 (current min is 1) because they get compiled to the common but new super type ReflectiveOperationException. As a workaround either create individual catch statements, or catch Exception. [NewApi]\n"
+                + "            e.printStackTrace();\n"
+                + "              ~~~~~~~~~~~~~~~\n"
+                + "5 errors, 0 warnings\n",
+
+                lintProject(
+                        java("src/test/pkg/MultiCatch.java", ""
+                                + "package test.pkg;\n"
+                                + "\n"
+                                + "import android.media.MediaDrm;\n"
+                                + "import android.media.UnsupportedSchemeException;\n"
+                                + "\n"
+                                + "import java.lang.reflect.InvocationTargetException;\n"
+                                + "\n"
+                                + "public class MultiCatch {\n"
+                                + "    public void test() {\n"
+                                + "        try {\n"
+                                + "            method1();\n"
+                                + "        } catch (MediaDrm.MediaDrmStateException | UnsupportedSchemeException e) {\n"
+                                + "            e.printStackTrace();\n"
+                                + "        }\n"
+                                + "\n"
+                                + "        try {\n"
+                                + "            method2();\n"
+                                + "        } catch (MediaDrm.MediaDrmStateException\n"
+                                + "                  | UnsupportedSchemeException e) {\n"
+                                + "            e.printStackTrace();\n"
+                                + "        }\n"
+                                + "\n"
+                                + "        try {\n"
+                                + "            String.class.getMethod(\"trim\").invoke(\"\");\n"
+                                + "        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {\n"
+                                + "            e.printStackTrace();\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public void method1() throws MediaDrm.MediaDrmStateException, UnsupportedSchemeException {\n"
+                                + "    }\n"
+                                + "    public void method2() throws MediaDrm.MediaDrmStateException, UnsupportedSchemeException {\n"
+                                + "    }\n"
+                                + "}\n"),
+                        base64("bin/classes/test/pkg/MultiCatch.class", ""
+                                + "yv66vgAAADMARgoADAAmCgASACcHACkHACwKAC0ALgoAEgAvBwAwCAAxBwAy"
+                                + "CgAJADMIADQHADUKADYANwcAOAcAOQcAOgoAOwAuBwA8AQAGPGluaXQ+AQAD"
+                                + "KClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVU"
+                                + "YWJsZQEABHRoaXMBABVMdGVzdC9wa2cvTXVsdGlDYXRjaDsBAAR0ZXN0AQAB"
+                                + "ZQEAFUxqYXZhL2xhbmcvRXhjZXB0aW9uOwEAKExqYXZhL2xhbmcvUmVmbGVj"
+                                + "dGl2ZU9wZXJhdGlvbkV4Y2VwdGlvbjsBAA1TdGFja01hcFRhYmxlBwA9BwA+"
+                                + "AQAHbWV0aG9kMQEACkV4Y2VwdGlvbnMBAAdtZXRob2QyAQAKU291cmNlRmls"
+                                + "ZQEAD011bHRpQ2F0Y2guamF2YQwAEwAUDAAhABQHAD8BAC1hbmRyb2lkL21l"
+                                + "ZGlhL01lZGlhRHJtJE1lZGlhRHJtU3RhdGVFeGNlcHRpb24BABZNZWRpYURy"
+                                + "bVN0YXRlRXhjZXB0aW9uAQAMSW5uZXJDbGFzc2VzAQAoYW5kcm9pZC9tZWRp"
+                                + "YS9VbnN1cHBvcnRlZFNjaGVtZUV4Y2VwdGlvbgcAPQwAQAAUDAAjABQBABBq"
+                                + "YXZhL2xhbmcvU3RyaW5nAQAEdHJpbQEAD2phdmEvbGFuZy9DbGFzcwwAQQBC"
+                                + "AQAAAQAQamF2YS9sYW5nL09iamVjdAcAQwwARABFAQAgamF2YS9sYW5nL0ls"
+                                + "bGVnYWxBY2Nlc3NFeGNlcHRpb24BACtqYXZhL2xhbmcvcmVmbGVjdC9JbnZv"
+                                + "Y2F0aW9uVGFyZ2V0RXhjZXB0aW9uAQAfamF2YS9sYW5nL05vU3VjaE1ldGhv"
+                                + "ZEV4Y2VwdGlvbgcAPgEAE3Rlc3QvcGtnL011bHRpQ2F0Y2gBABNqYXZhL2xh"
+                                + "bmcvRXhjZXB0aW9uAQAmamF2YS9sYW5nL1JlZmxlY3RpdmVPcGVyYXRpb25F"
+                                + "eGNlcHRpb24BABZhbmRyb2lkL21lZGlhL01lZGlhRHJtAQAPcHJpbnRTdGFj"
+                                + "a1RyYWNlAQAJZ2V0TWV0aG9kAQBAKExqYXZhL2xhbmcvU3RyaW5nO1tMamF2"
+                                + "YS9sYW5nL0NsYXNzOylMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwEAGGph"
+                                + "dmEvbGFuZy9yZWZsZWN0L01ldGhvZAEABmludm9rZQEAOShMamF2YS9sYW5n"
+                                + "L09iamVjdDtbTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0"
+                                + "OwAhABIADAAAAAAABAABABMAFAABABUAAAAvAAEAAQAAAAUqtwABsQAAAAIA"
+                                + "FgAAAAYAAQAAAAgAFwAAAAwAAQAAAAUAGAAZAAAAAQAaABQAAQAVAAAA/QAD"
+                                + "AAIAAAA2KrYAAqcACEwrtgAFKrYABqcACEwrtgAFEgcSCAO9AAm2AAoSCwO9"
+                                + "AAy2AA1XpwAITCu2ABGxAAcAAAAEAAcAAwAAAAQABwAEAAwAEAATAAMADAAQ"
+                                + "ABMABAAYAC0AMAAOABgALQAwAA8AGAAtADAAEAADABYAAAA2AA0AAAALAAQA"
+                                + "DgAHAAwACAANAAwAEQAQABUAEwASABQAFAAYABgALQAbADAAGQAxABoANQAc"
+                                + "ABcAAAAqAAQACAAEABsAHAABABQABAAbABwAAQAxAAQAGwAdAAEAAAA2ABgA"
+                                + "GQAAAB4AAAARAAZHBwAfBEYHAB8EVwcAIAQAAQAhABQAAgAVAAAAKwAAAAEA"
+                                + "AAABsQAAAAIAFgAAAAYAAQAAAB8AFwAAAAwAAQAAAAEAGAAZAAAAIgAAAAYA"
+                                + "AgADAAQAAQAjABQAAgAVAAAAKwAAAAEAAAABsQAAAAIAFgAAAAYAAQAAACEA"
+                                + "FwAAAAwAAQAAAAEAGAAZAAAAIgAAAAYAAgADAAQAAgAkAAAAAgAlACsAAAAK"
+                                + "AAEAAwAoACoAGQ==")
+
+                ));
+    }
+
+
+
     @Override
     protected boolean ignoreSystemErrors() {
+        //noinspection SimplifiableIfStatement
         if (getName().equals("testMissingApiDatabase")) {
             return false;
         }
@@ -1793,8 +2250,11 @@
     protected void checkReportedError(@NonNull Context context, @NonNull Issue issue,
             @NonNull Severity severity, @Nullable Location location, @NonNull String message) {
         if (issue == UNSUPPORTED || issue == INLINED) {
+            if (message.startsWith("The SDK platform-tools version (")) {
+                return;
+            }
             int requiredVersion = ApiDetector.getRequiredVersion(issue, message, TEXT);
-            assertTrue("Could not extract message tokens from " + message,
+            assertTrue("Could not extract message tokens from \"" + message + "\"",
                     requiredVersion >= 1 && requiredVersion <= SdkVersionInfo.HIGHEST_KNOWN_API);
         }
     }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ApiLookupTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ApiLookupTest.java
index 7a7fa28..593b689 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ApiLookupTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ApiLookupTest.java
@@ -16,17 +16,59 @@
 
 package com.android.tools.lint.checks;
 
+import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_MIN_SDK_VERSION;
+import static com.android.SdkConstants.DOT_AAR;
+import static com.android.SdkConstants.DOT_CLASS;
+import static com.android.SdkConstants.DOT_JAR;
+import static com.android.SdkConstants.FN_CLASSES_JAR;
+import static com.android.SdkConstants.TAG_USES_SDK;
+import static com.android.ide.common.repository.SdkMavenRepository.ANDROID;
+import static com.android.tools.lint.detector.api.LintUtils.getChildren;
+import static com.google.common.base.Charsets.UTF_8;
+import static java.io.File.separatorChar;
+
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
+import com.android.ide.common.repository.GradleCoordinate;
+import com.android.ide.common.xml.XmlFormatPreferences;
+import com.android.ide.common.xml.XmlFormatStyle;
+import com.android.ide.common.xml.XmlPrettyPrinter;
 import com.android.tools.lint.detector.api.Detector;
 import com.android.tools.lint.detector.api.Severity;
+import com.android.utils.Pair;
+import com.android.utils.XmlUtils;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Closeables;
+import com.google.common.io.Files;
 
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.io.ByteArrayInputStream;
 import java.io.File;
+import java.io.InputStream;
 import java.io.PrintWriter;
 import java.io.RandomAccessFile;
 import java.io.StringWriter;
+import java.lang.reflect.Modifier;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.JarInputStream;
+import java.util.zip.ZipEntry;
 
-@SuppressWarnings("javadoc")
+@SuppressWarnings({"javadoc", "ConstantConditions"})
 public class ApiLookupTest extends AbstractCheckTest {
     private final ApiLookup mDb = ApiLookup.get(new TestLintClient());
 
@@ -80,6 +122,39 @@
                 "(Ljava/lang/Throwable;)V"));
     }
 
+    public void testDeprecatedFields() {
+        // Not deprecated:
+        assertEquals(-1, mDb.getFieldDeprecatedIn("android/Manifest$permission", "GET_PACKAGE_SIZE"));
+        // Field only has since > 1, no deprecation
+        assertEquals(9, mDb.getFieldVersion("android/Manifest$permission", "NFC"));
+
+        // Deprecated
+        assertEquals(21, mDb.getFieldDeprecatedIn("android/Manifest$permission", "GET_TASKS"));
+        // Field both deprecated and since > 1
+        assertEquals(21, mDb.getFieldDeprecatedIn("android/Manifest$permission", "READ_SOCIAL_STREAM"));
+        assertEquals(15, mDb.getFieldVersion("android/Manifest$permission", "READ_SOCIAL_STREAM"));
+    }
+
+    public void testDeprecatedCalls() {
+        // Not deprecated:
+        //assertEquals(12, mDb.getCallVersion("android/app/Fragment", "onInflate",
+        //        "(Landroid/app/Activity;Landroid/util/AttributeSet;Landroid/os/Bundle;)V"));
+        assertEquals(23, mDb.getCallDeprecatedIn("android/app/Fragment", "onInflate",
+                "(Landroid/app/Activity;Landroid/util/AttributeSet;Landroid/os/Bundle;)V"));
+        assertEquals(-1, mDb.getCallDeprecatedIn("android/app/Fragment", "onInflate",
+                "(Landroid/content/Context;Landroid/util/AttributeSet;Landroid/os/Bundle;)V"));
+        // Deprecated
+        assertEquals(16, mDb.getCallDeprecatedIn("android/app/Service", "onStart", "(Landroid/content/Intent;I)V"));
+        assertEquals(16, mDb.getCallDeprecatedIn("android/app/Fragment", "onInflate", "(Landroid/util/AttributeSet;Landroid/os/Bundle;)V"));
+    }
+
+    public void testDeprecatedClasses() {
+        // Not deprecated:
+        assertEquals(-1, mDb.getClassDeprecatedIn("android/app/Fragment"));
+        // Deprecated
+        assertEquals(9, mDb.getClassDeprecatedIn("org/xml/sax/Parser"));
+    }
+
     public void testInheritInterfaces() {
         // The onPreferenceStartFragment is inherited via the
         // android/preference/PreferenceFragment$OnPreferenceStartFragmentCallback
@@ -89,6 +164,24 @@
                 "(Landroid/preference/PreferenceFragment;Landroid/preference/Preference;)"));
     }
 
+    public void testInterfaceApi() {
+        assertEquals(21, mDb.getClassVersion("android/animation/StateListAnimator"));
+        assertEquals(11, mDb.getValidCastVersion("android/animation/AnimatorListenerAdapter",
+                "android/animation/Animator$AnimatorListener"));
+        assertEquals(19, mDb.getValidCastVersion("android/animation/AnimatorListenerAdapter",
+                "android/animation/Animator$AnimatorPauseListener"));
+
+        assertEquals(11, mDb.getValidCastVersion("android/animation/Animator",
+                "java/lang/Cloneable"));
+        assertEquals(22, mDb.getValidCastVersion("android/animation/StateListAnimator",
+                "java/lang/Cloneable"));
+    }
+
+    public void testSuperClassCast() {
+        assertEquals(22, mDb.getValidCastVersion("android/view/animation/AccelerateDecelerateInterpolator",
+                "android/view/animation/BaseInterpolator"));
+    }
+
     public void testIsValidPackage() {
         assertTrue(mDb.isValidJavaPackage("java/lang/Integer"));
         assertTrue(mDb.isValidJavaPackage("javax/crypto/Cipher"));
@@ -112,6 +205,11 @@
     @SuppressWarnings({"ConstantConditions", "IOResourceOpenedButNotSafelyClosed",
             "ResultOfMethodCallIgnored"})
     public void testCorruptedCacheHandling() throws Exception {
+        if (ApiLookup.DEBUG_FORCE_REGENERATE_BINARY) {
+            System.err.println("Skipping " + getName() + ": not valid while regenerating indices");
+            return;
+        }
+
         ApiLookup lookup;
 
         // Real cache:
@@ -143,7 +241,7 @@
         // Truncate file in half
         raf.setLength(100);  // Broken header
         raf.close();
-        lookup = ApiLookup.get(new LookupTestClient());
+        ApiLookup.get(new LookupTestClient());
         String message = mLogBuffer.toString();
         // NOTE: This test is incompatible with the DEBUG_FORCE_REGENERATE_BINARY and WRITE_STATS
         // flags in the ApiLookup class, so if the test fails during development and those are
@@ -184,6 +282,107 @@
         ApiLookup.dispose();
     }
 
+    private static final boolean CHECK_DEPRECATED = true;
+
+    private static void assertSameApi(String desc, int expected, int actual) {
+        // In the database we don't distinguish between 1 and -1 (to save diskspace)
+        if (expected <= 1) {
+            expected = -1;
+        }
+        if (actual <= 1) {
+            actual = -1;
+        }
+        assertEquals(desc, expected, actual);
+    }
+
+    public void testFindEverything() throws Exception {
+        // Load the API versions file and look up every single method/field/class in there
+        // (provided since != 1) and also check the deprecated calls.
+
+        File file = createClient().findResource("platform-tools/api/api-versions.xml");
+        if (file == null || !file.exists()) {
+            return;
+        }
+
+        Api info = Api.parseApi(file);
+        assertNotNull(info);
+        for (ApiClass cls : info.getClasses().values()) {
+            int classSince = cls.getSince();
+            String className = cls.getName();
+            if (className.startsWith("android/support/")) {
+                continue;
+            }
+            assertSameApi(className, classSince, mDb.getClassVersion(className));
+
+            for (String method : cls.getAllMethods(info)) {
+                int since = cls.getMethod(method, info);
+                int index = method.indexOf('(');
+                String name = method.substring(0, index);
+                String desc = method.substring(index);
+                assertSameApi(method, since, mDb.getCallVersion(className, name, desc));
+
+            }
+            for (String method : cls.getAllFields(info)) {
+                int since = cls.getField(method, info);
+                assertSameApi(method, since, mDb.getFieldVersion(className, method));
+            }
+
+            for (Pair<String, Integer> pair : cls.getInterfaces()) {
+                String interfaceName = pair.getFirst();
+                int api = pair.getSecond();
+                assertSameApi(interfaceName, api,
+                        mDb.getValidCastVersion(className, interfaceName));
+            }
+        }
+
+        if (CHECK_DEPRECATED) {
+            for (ApiClass cls : info.getClasses().values()) {
+                int classDeprecatedIn = cls.getDeprecatedIn();
+                String className = cls.getName();
+                if (className.startsWith("android/support/")) {
+                    continue;
+                }
+                if (classDeprecatedIn > 1) {
+                    assertSameApi(className, classDeprecatedIn,
+                            mDb.getClassDeprecatedIn(className));
+                } else {
+                    assertSameApi(className, -1, mDb.getClassDeprecatedIn(className));
+                }
+
+                for (String method : cls.getAllMethods(info)) {
+                    int deprecatedIn = cls.getMemberDeprecatedIn(method, info);
+                    int index = method.indexOf('(');
+                    String name = method.substring(0, index);
+                    String desc = method.substring(index);
+                    assertSameApi(method + " in " + className, deprecatedIn,
+                            mDb.getCallDeprecatedIn(className, name, desc));
+                }
+                for (String method : cls.getAllFields(info)) {
+                    int deprecatedIn = cls.getMemberDeprecatedIn(method, info);
+                    assertSameApi(method, deprecatedIn, mDb.getFieldDeprecatedIn(className,
+                            method));
+                }
+            }
+        }
+    }
+
+    public void testLookUpContractSettings() {
+        assertEquals(14, mDb.getFieldVersion("android/provider/ContactsContract$Settings", "DATA_SET"));
+    }
+
+    public void testIssue196925() {
+        if (ApiLookup.DEBUG_FORCE_REGENERATE_BINARY) {
+            // This test doesn't work when regenerating binaries: it's tied to data
+            // not included in api-versions.xml
+            return;
+        }
+        //196925: Incorrect Lint NewApi error on FloatingActionButton#setBackgroundTintList()
+        assertEquals(7, mDb.getCallVersion("android/support/design/widget/FloatingActionButton",
+                "getBackgroundTintList", "()"));
+        assertEquals(7, mDb.getCallVersion("android/support/design/widget/FloatingActionButton",
+                "setBackgroundTintList", "(Landroid/content/res/ColorStateList;)"));
+    }
+
     private final class LookupTestClient extends TestLintClient {
         @SuppressWarnings("ResultOfMethodCallIgnored")
         @Override
@@ -218,4 +417,396 @@
             log(Severity.WARNING, exception, format, args);
         }
     }
+
+    /**
+     * Finds the most recent version of the support/appcompat library, and for any
+     * classes that extend framework classes, creates a list of APIs that should
+     * <b>not</b> be flagged when called via the support library (since the support
+     * library provides a backport of the APIs).
+     * <p>
+     * Example: {@code FloatingActionButton#setBackgroundTintList()}
+     * This method is available on any version, yet it extends a method
+     * ({@code ImageButton#setBackgroundTintList} which has min api 21) so lint
+     * flags it.
+     */
+    public void testSupportLibraryMap() throws Exception {
+        if (ApiLookup.DEBUG_FORCE_REGENERATE_BINARY) {
+            generateSupportLibraryFile();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private void generateSupportLibraryFile() throws Exception {
+        //noinspection PointlessBooleanExpression
+        if (!ApiLookup.DEBUG_FORCE_REGENERATE_BINARY) {
+            System.out.println("Ignoring " + getName() + " since"
+                    + " ApiLookup.DEBUG_FORCE_REGENERATE_BINARY is not set to true");
+            return;
+        }
+        File sdkHome = createClient().getSdkHome();
+        if (sdkHome == null) {
+            System.err.println("Ignoring " + getName() + ": no SDK home found");
+            return;
+        }
+
+        File root = ANDROID.getRepositoryLocation(sdkHome, true);
+        if (root == null) {
+            System.out.println("No android support repository installed in the SDK home");
+            return;
+        }
+
+        @SuppressWarnings("SpellCheckingInspection")
+        String[] artifacts = new String[] {
+                "appcompat-v7",
+                "cardview-v7",
+                "customtabs",
+                "design",
+                "gridlayout-v7",
+                "leanback-v17",
+                "mediarouter-v7",
+                "multidex",
+                "multidex-instrumentation",
+                "palette-v7",
+                "percent",
+                "preference-leanback-v17",
+                "preference-v14",
+                "preference-v7",
+                "recommendation",
+                "recyclerview-v7",
+                "support-annotations",
+                "support-v13",
+                "support-v4",
+//                "test"
+        };
+        String groupId = "com.android.support";
+
+        Map<String, ClassNode> classes = Maps.newHashMapWithExpectedSize(1000);
+        Map<String, Integer> minSdkMap = Maps.newHashMapWithExpectedSize(1000);
+
+        for (String artifact : artifacts) {
+            GradleCoordinate version = ANDROID.getHighestInstalledVersion(sdkHome, groupId,
+                    artifact, null, true);
+            String revision = version.getFullRevision();
+            File file = new File(root, groupId.replace('.', separatorChar) + separatorChar
+                    + artifact + separatorChar + revision
+                    + separatorChar + artifact + "-" + revision + DOT_AAR);
+            if (!file.exists()) {
+                String path = file.getPath();
+                path = path.substring(0, path.length() - DOT_AAR.length()) + DOT_JAR;
+                file = new File(path);
+                if (!file.exists()) {
+                    System.err.println(
+                            "Ignoring artifact " + artifact + ": couldn't find .aar/.jar file");
+                    continue;
+                }
+            }
+
+            System.out.println("Analyzing file " + file);
+
+            byte[] bytes = Files.toByteArray(file);
+            String path = file.getPath();
+            if (path.endsWith(DOT_AAR)) {
+                analyzeAar(bytes, classes, minSdkMap);
+            } else {
+                assertTrue(path, path.endsWith(DOT_JAR));
+                analyzeJar(bytes, classes, minSdkMap, -1);
+            }
+        }
+
+        System.out.println("Found " + classes.size() + " classes (including innerclasses)");
+        File file = createClient().findResource("platform-tools/api/api-versions.xml");
+        if (file == null || !file.exists()) {
+            System.out.println("No API versions xml file found.");
+            return;
+        }
+
+        Api api = Api.parseApi(file);
+
+        Document document = XmlUtils.parseDocument(""
+                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                + "<!--\n"
+                + "  ~ Copyright (C) 2015 The Android Open Source Project\n"
+                + "  ~\n"
+                + "  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n"
+                + "  ~ you may not use this file except in compliance with the License.\n"
+                + "  ~ You may obtain a copy of the License at\n"
+                + "  ~\n"
+                + "  ~      http://www.apache.org/licenses/LICENSE-2.0\n"
+                + "  ~\n"
+                + "  ~ Unless required by applicable law or agreed to in writing, software\n"
+                + "  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n"
+                + "  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"
+                + "  ~ See the License for the specific language governing permissions and\n"
+                + "  ~ limitations under the License.\n"
+                + "  -->\n"
+                + "<!-- This file is generated by ApiLookupTest#generateSupportLibraryFile() -->\n"
+                + "<api version=\"2\"/>", false);
+        Element rootElement = document.getDocumentElement();
+
+        Set<ClassNode> referencedClasses = Sets.newHashSetWithExpectedSize(100);
+        Set<ClassNode> referencedSuperClasses = Sets.newHashSetWithExpectedSize(100);
+
+        // Walk through the various support classes, and walk up the inheritance chain
+        // to see if it extends a class from the support library, and if so, mark
+        // any methods as deliberately having a lower API level
+        for (ClassNode node : sorted(classes.values())) {
+            String name = node.name;
+            if (name.indexOf('$') != -1) {
+                // Ignore inner classes
+                continue;
+            }
+            if ((node.access & Modifier.PUBLIC) == 0) {
+                continue;
+            }
+            ApiClass apiClass = extendsKnownApi(api, node, classes);
+            if (apiClass != null && !apiClass.getName().equals("java/lang/Object")) {
+                @SuppressWarnings("unchecked") // ASM API
+                List<MethodNode> methodList = sorted((List<MethodNode>) node.methods);
+                if (methodList.isEmpty()) {
+                    return;
+                }
+
+                int supportMin = getMinSdk(node.name, minSdkMap);
+                Element classNode = null;
+
+                for (MethodNode method : methodList) {
+                    String signature = method.name + method.desc;
+                    int end = signature.indexOf(')');
+                    if (end != -1) {
+                        signature = signature.substring(0, end + 1);
+                    }
+                    int methodSince = apiClass.getMethod(signature, api);
+                    if (methodSince < Integer.MAX_VALUE) {
+                        if (supportMin < methodSince) {
+                            referencedClasses.add(node);
+                            if (classNode == null) {
+                                classNode = document.createElement("class");
+                                rootElement.appendChild(classNode);
+                                classNode.setAttribute("name", node.name);
+                                classNode.setAttribute("since", Integer.toString(supportMin));
+                                if (node.superName != null) {
+                                    Element extendsNode = document.createElement("extends");
+                                    classNode.appendChild(extendsNode);
+                                    extendsNode.setAttribute("name", node.superName);
+
+                                    ClassNode superClassNode = classes.get(node.superName);
+                                    while (superClassNode != null) {
+                                        referencedSuperClasses.add(superClassNode);
+                                        superClassNode = classes.get(superClassNode.superName);
+                                    }
+                                }
+                            }
+                            Element methodNode = document.createElement("method");
+                            classNode.appendChild(methodNode);
+                            methodNode.setAttribute("name", method.name + method.desc);
+                            methodNode.setAttribute("since", Integer.toString(supportMin));
+                        }
+                    }
+                }
+            }
+        }
+
+        // Also list any super classes referenced such that we ensure we have super-class
+        // references to them in the ApiClass info (such that it can correctly pull in
+        // methods from the framework to check their since-versions relative to the class'
+        // own since value)
+        referencedSuperClasses.removeAll(referencedClasses);
+        if (!referencedSuperClasses.isEmpty()) {
+            rootElement.appendChild(document.createTextNode("\n"));
+            rootElement.appendChild(document.createComment("Referenced Super Classes"));
+            for (ClassNode node : sorted(referencedSuperClasses)) {
+                int supportMin = getMinSdk(node.name, minSdkMap);
+                Element classNode = document.createElement("class");
+                rootElement.appendChild(classNode);
+                classNode.setAttribute("name", node.name);
+                classNode.setAttribute("since", Integer.toString(supportMin));
+                if (node.superName != null) {
+                    Element extendsNode = document.createElement("extends");
+                    classNode.appendChild(extendsNode);
+                    extendsNode.setAttribute("name", node.superName);
+                }
+            }
+        }
+
+        String xml = XmlPrettyPrinter.prettyPrint(document, XmlFormatPreferences.defaults(),
+                XmlFormatStyle.RESOURCE, "\n", false);
+        xml = xml.replace("\n\n", "\n");
+
+        File xmlFile = findSrcDir();
+        if (xmlFile == null) {
+            System.out.println("Ignoring " + getName() + ": Should set $ANDROID_SRC to point "
+                    + "to source dir to run this test");
+            return;
+        }
+        xmlFile = new File(xmlFile, ("tools/base/lint/libs/lint-checks/src/main/java/com/android/"
+                + "tools/lint/checks/api-versions-support-library.xml").replace('/', separatorChar));
+        assertTrue(xmlFile.getPath(), xmlFile.exists());
+        String prev = Files.toString(xmlFile, UTF_8);
+        assertEquals(prev, xml);
+    }
+
+    @NonNull
+    private static List<MethodNode> sorted(List<MethodNode> methods) {
+        List<MethodNode> sorted = Lists.newArrayList(methods);
+        Collections.sort(sorted, new Comparator<MethodNode>() {
+            @Override
+            public int compare(MethodNode node1, MethodNode node2) {
+                int delta = node1.name.compareTo(node2.name);
+                if (delta != 0) {
+                    return delta;
+                }
+                return node1.desc.compareTo(node2.desc);
+            }
+        });
+        return sorted;
+    }
+
+    @NonNull
+    private static List<ClassNode> sorted(Collection<ClassNode> classes) {
+        List<ClassNode> sorted = Lists.newArrayList(classes);
+        Collections.sort(sorted, new Comparator<ClassNode>() {
+            @Override
+            public int compare(ClassNode node1, ClassNode node2) {
+                return node1.name.compareTo(node2.name);
+            }
+        });
+        return sorted;
+    }
+
+    private static int getMinSdk(@NonNull String name, @NonNull Map<String, Integer> minSdkMap) {
+        Integer min = minSdkMap.get(name);
+        if (min != null) {
+            return min;
+        }
+        String prefix = "android/support/v";
+        if (name.startsWith(prefix)) {
+            int endIndex = name.indexOf('/', prefix.length());
+            if (endIndex != -1) {
+                return Integer.parseInt(name.substring(prefix.length(), endIndex));
+            }
+        }
+
+        return 7;
+    }
+
+    @Nullable
+    private static ClassNode getSuperClass(@NonNull ClassNode node,
+            @NonNull Map<String, ClassNode> classes) {
+        if (node.superName != null) {
+            return classes.get(node.superName);
+        }
+
+        return null;
+    }
+
+    @Nullable
+    private static ApiClass extendsKnownApi(@NonNull Api api, @Nullable ClassNode node,
+            @NonNull Map<String, ClassNode> classes) {
+        while (node != null) {
+            ApiClass cls = api.getClass(node.name);
+            if (cls != null) {
+                return cls;
+            }
+
+            ClassNode superClass = getSuperClass(node, classes);
+            if (superClass == null && node.superName != null) {
+                // Pointing up into android.jar, not in our class map?
+                return api.getClass(node.superName);
+            } else {
+                node = superClass;
+            }
+        }
+
+        return null;
+    }
+
+    private static void analyzeAar(@NonNull byte[] bytes, @NonNull Map<String, ClassNode> classes,
+            @NonNull Map<String, Integer> minSdkMap) throws Exception {
+        JarInputStream zis = null;
+        try {
+            InputStream fis = new ByteArrayInputStream(bytes);
+            try {
+                zis = new JarInputStream(fis);
+                ZipEntry entry = zis.getNextEntry();
+                int minSdk = -1;
+                while (entry != null) {
+                    String name = entry.getName();
+                    if (name.equals(ANDROID_MANIFEST_XML)) {
+                        byte[] b = ByteStreams.toByteArray(zis);
+                        assertNotNull(b);
+                        String xml = new String(b, UTF_8);
+                        Document document = XmlUtils.parseDocumentSilently(xml, true);
+                        assertNotNull(document);
+                        assertNotNull(document.getDocumentElement());
+                        for (Element element : getChildren(document.getDocumentElement())) {
+                            if (element.getTagName().equals(TAG_USES_SDK)) {
+                                String min = element.getAttributeNS(ANDROID_URI,
+                                        ATTR_MIN_SDK_VERSION);
+                                if (!min.isEmpty()) {
+                                    try {
+                                        minSdk = Integer.parseInt(min);
+                                    } catch (NumberFormatException e) {
+                                        fail(e.toString());
+                                    }
+                                }
+                            }
+                        }
+                    } else if (name.equals(FN_CLASSES_JAR)) {
+                        // Bingo!
+                        byte[] b = ByteStreams.toByteArray(zis);
+                        assertNotNull(b);
+                        analyzeJar(b, classes, minSdkMap, minSdk);
+                        break;
+                    }
+                    entry = zis.getNextEntry();
+                }
+            } finally {
+                Closeables.close(fis, true);
+            }
+        } finally {
+            Closeables.close(zis, false);
+        }
+    }
+
+    private static void analyzeJar(@NonNull byte[] bytes, @NonNull Map<String, ClassNode> classes,
+            @NonNull Map<String, Integer> minSdkMap, int manifestMinSdk) throws Exception {
+        JarInputStream zis = null;
+        try {
+            InputStream fis = new ByteArrayInputStream(bytes);
+            try {
+                zis = new JarInputStream(fis);
+                ZipEntry entry = zis.getNextEntry();
+                while (entry != null) {
+                    String name = entry.getName();
+                    if (name.endsWith(DOT_CLASS)) {
+                        // Bingo!
+                        byte[] b = ByteStreams.toByteArray(zis);
+                        if (b != null) {
+                            analyzeClass(b, classes, minSdkMap, manifestMinSdk);
+                        }
+                    }
+                    entry = zis.getNextEntry();
+                }
+            } finally {
+                Closeables.close(fis, true);
+            }
+        } finally {
+            Closeables.close(zis, false);
+        }
+    }
+
+    private static void analyzeClass(@NonNull byte[] bytes,
+            @NonNull Map<String, ClassNode> classes, @NonNull Map<String, Integer> minSdkMap,
+            int manifestMinSdk) {
+
+        ClassReader reader = new ClassReader(bytes);
+        ClassNode classNode = new ClassNode();
+        reader.accept(classNode, 0 /* flags */);
+
+        assertNull(classes.get(classNode.name));
+        classes.put(classNode.name, classNode);
+
+        int minSdk = manifestMinSdk != -1 ? manifestMinSdk : getMinSdk(classNode.name, minSdkMap);
+        minSdkMap.put(classNode.name, minSdk);
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AppIndexingApiDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AppIndexingApiDetectorTest.java
index 4c0a56a..0fa1b47 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AppIndexingApiDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AppIndexingApiDetectorTest.java
@@ -26,6 +26,11 @@
         return new AppIndexingApiDetector();
     }
 
+    @Override
+    protected boolean allowCompilationErrors() {
+        return true;
+    }
+
     public void testOk() throws Exception {
         assertEquals("No warnings.",
                 lintProject(xml("AndroidManifest.xml", ""
@@ -52,17 +57,22 @@
                         + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
                         + "            </intent-filter>\n"
                         + "        </activity>\n"
+                        + "        <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
                         + "    </application>\n"
                         + "\n"
                         + "</manifest>\n")));
     }
 
     public void testDataMissing() throws Exception {
+        assertEquals(AppIndexingApiDetector.IssueType.DATA_MISSING, AppIndexingApiDetector.IssueType.parse("Missing data element"));
         assertEquals(""
-                        + "AndroidManifest.xml:15: Error: Missing data node? [AppIndexingError]\n"
+                        + "AndroidManifest.xml:15: Error: Missing data element [GoogleAppIndexingUrlError]\n"
                         + "            <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
                         + "            ^\n"
-                        + "1 errors, 0 warnings\n",
+                        + "AndroidManifest.xml:15: Warning: Missing URL [GoogleAppIndexingWarning]\n"
+                        + "            <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
+                        + "            ^\n"
+                        + "1 errors, 1 warnings\n",
                 lintProject(xml("AndroidManifest.xml", ""
                         + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                         + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
@@ -84,17 +94,22 @@
                         + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
                         + "            </intent-filter>\n"
                         + "        </activity>\n"
+                        + "        <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
                         + "    </application>\n"
                         + "\n"
                         + "</manifest>\n")));
     }
 
     public void testNoUrl() throws Exception {
+        assertEquals(AppIndexingApiDetector.IssueType.URL_MISSING, AppIndexingApiDetector.IssueType.parse("Missing URL for the intent filter"));
         assertEquals(""
-                        + "AndroidManifest.xml:17: Error: Missing URL for the intent filter? [AppIndexingError]\n"
+                        + "AndroidManifest.xml:17: Error: Missing URL for the intent filter [GoogleAppIndexingUrlError]\n"
                         + "                <data />\n"
                         + "                ~~~~~~~~\n"
-                        + "1 errors, 0 warnings\n",
+                        + "AndroidManifest.xml:15: Warning: Missing URL [GoogleAppIndexingWarning]\n"
+                        + "            <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
+                        + "            ^\n"
+                        + "1 errors, 1 warnings\n",
                 lintProject(xml("AndroidManifest.xml", ""
                         + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                         + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
@@ -117,13 +132,17 @@
                         + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
                         + "            </intent-filter>\n"
                         + "        </activity>\n"
+                        + "        <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
                         + "    </application>\n"
                         + "\n"
                         + "</manifest>\n")));
     }
 
     public void testMimeType() throws Exception {
-        assertEquals("No warnings.",
+        assertEquals("AndroidManifest.xml:15: Warning: Missing URL [GoogleAppIndexingWarning]\n"
+                        + "            <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
+                        + "            ^\n"
+                        + "0 errors, 1 warnings\n",
                 lintProject(xml("AndroidManifest.xml", ""
                         + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                         + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
@@ -146,15 +165,19 @@
                         + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
                         + "            </intent-filter>\n"
                         + "        </activity>\n"
+                        + "        <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
                         + "    </application>\n"
                         + "\n"
                         + "</manifest>\n")));
     }
 
-
     public void testNoActivity() throws Exception {
         assertEquals(
-                "No warnings.",
+                ""
+                        + "AndroidManifest.xml:5: Warning: App is not indexable by Google Search; consider adding at least one Activity with an ACTION-VIEW intent-filler. See issue explanation for more details. [GoogleAppIndexingWarning]\n"
+                        + "    <application\n"
+                        + "    ^\n"
+                        + "0 errors, 1 warnings\n",
                 lintProject(xml("AndroidManifest.xml", ""
                         + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                         + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
@@ -165,14 +188,71 @@
                         + "        android:icon=\"@mipmap/ic_launcher\"\n"
                         + "        android:label=\"@string/app_name\"\n"
                         + "        android:theme=\"@style/AppTheme\" >\n"
+                        + "        <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
+                        + "    </application>\n"
+                        + "\n"
+                        + "</manifest>\n")));
+    }
+
+    public void testNoWarningInLibraries() throws Exception {
+        // Regression test for https://code.google.com/p/android/issues/detail?id=194937
+        // 194937: App indexing lint check shouldn't apply to library projects
+        assertEquals(
+                "No warnings.",
+                lintProject(
+                        xml("AndroidManifest.xml", ""
+                        + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                        + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                        + "    package=\"com.example.helloworld\" >\n"
+                        + "\n"
+                        + "    <application\n"
+                        + "        android:allowBackup=\"true\"\n"
+                        + "        android:icon=\"@mipmap/ic_launcher\"\n"
+                        + "        android:label=\"@string/app_name\"\n"
+                        + "        android:theme=\"@style/AppTheme\" >\n"
+                        + "        <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
+                        + "    </application>\n"
+                        + "\n"
+                        + "</manifest>\n"),
+                        // Mark project as library
+                        source("project.properties", "android.library=true\n")));
+    }
+
+    public void testNoActionView() throws Exception {
+        assertEquals(
+                ""
+                        + "AndroidManifest.xml:5: Warning: App is not indexable by Google Search; consider adding at least one Activity with an ACTION-VIEW intent-filler. See issue explanation for more details. [GoogleAppIndexingWarning]\n"
+                        + "    <application\n"
+                        + "    ^\n"
+                        + "0 errors, 1 warnings\n",
+                lintProject(xml("AndroidManifest.xml", ""
+                        + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                        + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                        + "    package=\"com.example.helloworld\" >\n"
+                        + "\n"
+                        + "    <application\n"
+                        + "        android:allowBackup=\"true\"\n"
+                        + "        android:icon=\"@mipmap/ic_launcher\"\n"
+                        + "        android:label=\"@string/app_name\"\n"
+                        + "        android:theme=\"@style/AppTheme\" >\n"
+                        + "        <activity\n"
+                        + "            android:name=\".MainActivity\"\n"
+                        + "            android:label=\"@string/app_name\" >\n"
+                        + "            <intent-filter>\n"
+                        + "                <action android:name=\"android.intent.action.MAIN\" />\n"
+                        + "                <category android:name=\"android.intent.category.LAUNCHER\" />\n"
+                        + "            </intent-filter>\n"
+                        + "        </activity>\n"
+                        + "        <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
                         + "    </application>\n"
                         + "\n"
                         + "</manifest>\n")));
     }
 
     public void testNotBrowsable() throws Exception {
+        assertEquals(AppIndexingApiDetector.IssueType.NOT_BROWSABLE, AppIndexingApiDetector.IssueType.parse("Activity supporting ACTION_VIEW is not set as BROWSABLE"));
         assertEquals(""
-                        + "AndroidManifest.xml:25: Warning: Activity supporting ACTION_VIEW is not set as BROWSABLE [AppIndexingWarning]\n"
+                        + "AndroidManifest.xml:25: Warning: Activity supporting ACTION_VIEW is not set as BROWSABLE [GoogleAppIndexingWarning]\n"
                         + "            <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
                         + "            ^\n"
                         + "0 errors, 1 warnings\n",
@@ -209,14 +289,16 @@
                         + "                <category android:name=\"android.intent.category.DEFAULT\" />\n"
                         + "            </intent-filter>\n"
                         + "        </activity>\n"
+                        + "        <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
                         + "    </application>\n"
                         + "\n"
                         + "</manifest>\n")));
     }
 
     public void testWrongPathPrefix() throws Exception {
+        assertEquals(AppIndexingApiDetector.IssueType.MISSING_SLASH, AppIndexingApiDetector.IssueType.parse("android:pathPrefix attribute should start with '/', but it is : gizmos"));
         assertEquals(""
-                        + "AndroidManifest.xml:19: Error: android:pathPrefix attribute should start with '/', but it is : gizmos [AppIndexingError]\n"
+                        + "AndroidManifest.xml:19: Error: android:pathPrefix attribute should start with '/', but it is : gizmos [GoogleAppIndexingUrlError]\n"
                         + "                    android:pathPrefix=\"gizmos\" />\n"
                         + "                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
                         + "1 errors, 0 warnings\n",
@@ -244,14 +326,16 @@
                         + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
                         + "            </intent-filter>\n"
                         + "        </activity>\n"
+                        + "        <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
                         + "    </application>\n"
                         + "\n"
                         + "</manifest>\n")));
     }
 
     public void testWrongPort() throws Exception {
+        assertEquals(AppIndexingApiDetector.IssueType.ILLEGAL_NUMBER, AppIndexingApiDetector.IssueType.parse("android:port is not a legal number"));
         assertEquals(""
-                        + "AndroidManifest.xml:19: Error: android:port is not a legal number [AppIndexingError]\n"
+                        + "AndroidManifest.xml:19: Error: android:port is not a legal number [GoogleAppIndexingUrlError]\n"
                         + "                    android:port=\"ABCD\"\n"
                         + "                    ~~~~~~~~~~~~~~~~~~~\n"
                         + "1 errors, 0 warnings\n",
@@ -280,23 +364,29 @@
                         + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
                         + "            </intent-filter>\n"
                         + "        </activity>\n"
+                        + "        <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
                         + "    </application>\n"
                         + "\n"
                         + "</manifest>\n")));
     }
 
     public void testSchemeAndHostMissing() throws Exception {
+        assertEquals(AppIndexingApiDetector.IssueType.SCHEME_MISSING, AppIndexingApiDetector.IssueType.parse("android:scheme is missing"));
+        assertEquals(AppIndexingApiDetector.IssueType.HOST_MISSING, AppIndexingApiDetector.IssueType.parse("android:host is missing"));
         assertEquals(""
-                        + "AndroidManifest.xml:17: Error: Missing URL for the intent filter? [AppIndexingError]\n"
+                        + "AndroidManifest.xml:17: Error: Missing URL for the intent filter [GoogleAppIndexingUrlError]\n"
                         + "                <data android:pathPrefix=\"/gizmos\" />\n"
                         + "                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
-                        + "AndroidManifest.xml:17: Error: android:host missing [AppIndexingError]\n"
+                        + "AndroidManifest.xml:17: Error: android:host is missing [GoogleAppIndexingUrlError]\n"
                         + "                <data android:pathPrefix=\"/gizmos\" />\n"
                         + "                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
-                        + "AndroidManifest.xml:17: Error: android:scheme missing [AppIndexingError]\n"
+                        + "AndroidManifest.xml:17: Error: android:scheme is missing [GoogleAppIndexingUrlError]\n"
                         + "                <data android:pathPrefix=\"/gizmos\" />\n"
                         + "                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
-                        + "3 errors, 0 warnings\n",
+                        + "AndroidManifest.xml:15: Warning: Missing URL [GoogleAppIndexingWarning]\n"
+                        + "            <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
+                        + "            ^\n"
+                        + "3 errors, 1 warnings\n",
                 lintProject(xml("AndroidManifest.xml", ""
                         + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                         + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
@@ -319,6 +409,7 @@
                         + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
                         + "            </intent-filter>\n"
                         + "        </activity>\n"
+                        + "        <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
                         + "    </application>\n"
                         + "\n"
                         + "</manifest>\n")));
@@ -350,6 +441,7 @@
                         + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
                         + "            </intent-filter>\n"
                         + "        </activity>\n"
+                        + "        <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
                         + "    </application>\n"
                         + "\n"
                         + "</manifest>\n")));
@@ -385,6 +477,7 @@
                         + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
                         + "            </intent-filter>\n"
                         + "        </activity>\n"
+                        + "        <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
                         + "    </application>\n"
                         + "\n"
                         + "</manifest>\n")));
@@ -392,7 +485,7 @@
 
     public void testMultiIntentWithError() throws Exception {
         assertEquals(""
-                        + "AndroidManifest.xml:20: Error: android:host missing [AppIndexingError]\n"
+                        + "AndroidManifest.xml:20: Error: android:host is missing [GoogleAppIndexingUrlError]\n"
                         + "                <data android:scheme=\"http\"\n"
                         + "                ^\n"
                         + "1 errors, 0 warnings\n",
@@ -423,6 +516,42 @@
                         + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
                         + "            </intent-filter>\n"
                         + "        </activity>\n"
+                        + "        <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
+                        + "    </application>\n"
+                        + "\n"
+                        + "</manifest>\n")));
+    }
+
+    public void testNotExported() throws Exception {
+        assertEquals("AndroidManifest.xml:10: Error: Activity supporting ACTION_VIEW is not exported [GoogleAppIndexingUrlError]\n"
+                        + "        <activity android:exported=\"false\"\n"
+                        + "        ^\n"
+                        + "1 errors, 0 warnings\n",
+                lintProject(xml("AndroidManifest.xml", ""
+                        + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                        + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                        + "    package=\"com.example.helloworld\" >\n"
+                        + "\n"
+                        + "    <application\n"
+                        + "        android:allowBackup=\"true\"\n"
+                        + "        android:icon=\"@mipmap/ic_launcher\"\n"
+                        + "        android:label=\"@string/app_name\"\n"
+                        + "        android:theme=\"@style/AppTheme\" >\n"
+                        + "        <activity android:exported=\"false\"\n"
+                        + "            android:name=\".FullscreenActivity\"\n"
+                        + "            android:configChanges=\"orientation|keyboardHidden|screenSize\"\n"
+                        + "            android:label=\"@string/title_activity_fullscreen\"\n"
+                        + "            android:theme=\"@style/FullscreenTheme\" >\n"
+                        + "            <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
+                        + "                <action android:name=\"android.intent.action.VIEW\" />\n"
+                        + "                <data android:scheme=\"http\"\n"
+                        + "                    android:host=\"example.com\"\n"
+                        + "                    android:pathPrefix=\"/gizmos\" />\n"
+                        + "                <category android:name=\"android.intent.category.DEFAULT\" />\n"
+                        + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+                        + "            </intent-filter>\n"
+                        + "        </activity>\n"
+                        + "        <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
                         + "    </application>\n"
                         + "\n"
                         + "</manifest>\n")));
@@ -437,10 +566,10 @@
     }
 
     public void testWrongWithResource() throws Exception {
-        assertEquals("" + "AndroidManifest.xml:18: Error: android:pathPrefix attribute should start with '/', but it is : pathprefix [AppIndexingError]\n"
+        assertEquals("" + "AndroidManifest.xml:18: Error: android:pathPrefix attribute should start with '/', but it is : pathprefix [GoogleAppIndexingUrlError]\n"
                         + "                      android:pathPrefix=\"@string/path_prefix\"\n"
                         + "                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
-                        + "AndroidManifest.xml:19: Error: android:port is not a legal number [AppIndexingError]\n"
+                        + "AndroidManifest.xml:19: Error: android:port is not a legal number [GoogleAppIndexingUrlError]\n"
                         + "                      android:port=\"@string/port\"/>\n"
                         + "                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
                         + "2 errors, 0 warnings\n",
@@ -449,4 +578,152 @@
                         "appindexing_manifest.xml=>AndroidManifest.xml",
                         "res/values/appindexing_wrong_strings.xml"));
     }
+
+    public void testJavaOk() throws Exception {
+        assertEquals("No warnings.",
+                lintProject(
+                        "src/com/example/helloworld/AppIndexingApiTestOk.java.txt=>src/com/example/helloworld/AppIndexingApiTest.java",
+                        "app_indexing_api_test.xml=>AndroidManifest.xml",
+                        "src/com/appindexing/AppIndex.java.txt=>src/com/google/android/gms/appindexing/AppIndex.java",
+                        "src/com/appindexing/AppIndexApi.java.txt=>src/com/google/android/gms/appindexing/AppIndexApi.java",
+                        "src/com/appindexing/GoogleApiClient.java.txt=>src/com/google/android/gms/common/api/GoogleApiClient.java",
+                        "src/com/appindexing/Activity.java.txt=>src/com/google/android/app/Activity.java",
+                        "src/com/appindexing/Api.java.txt=>src/com/google/android/gms/common/api/Api.java"));
+    }
+
+    public void testNoManifest() throws Exception {
+        assertEquals("" + "src/com/example/helloworld/AppIndexingApiTest.java:28: Warning: Missing support for Google App Indexing in the manifest [GoogleAppIndexingApiWarning]\n"
+                        + "    AppIndex.AppIndexApi.start(mClient, action);\n"
+                        + "                         ~~~~~\n"
+                        + "src/com/example/helloworld/AppIndexingApiTest.java:36: Warning: Missing support for Google App Indexing in the manifest [GoogleAppIndexingApiWarning]\n"
+                        + "    AppIndex.AppIndexApi.end(mClient, action);\n"
+                        + "                         ~~~\n"
+                        + "0 errors, 2 warnings\n",
+                lintProject(
+                        "src/com/example/helloworld/AppIndexingApiTestOk.java.txt=>src/com/example/helloworld/AppIndexingApiTest.java",
+                        "src/com/appindexing/AppIndex.java.txt=>src/com/google/android/gms/appindexing/AppIndex.java",
+                        "src/com/appindexing/AppIndexApi.java.txt=>src/com/google/android/gms/appindexing/AppIndexApi.java",
+                        "src/com/appindexing/GoogleApiClient.java.txt=>src/com/google/android/gms/common/api/GoogleApiClient.java",
+                        "src/com/appindexing/Activity.java.txt=>src/com/google/android/app/Activity.java",
+                        "src/com/appindexing/Api.java.txt=>src/com/google/android/gms/common/api/Api.java"));
+    }
+
+    public void testNoStartEnd() throws Exception {
+        assertEquals(""
+                        + "src/com/example/helloworld/AppIndexingApiTest.java:11: Warning: Missing support for Google App Indexing API [GoogleAppIndexingApiWarning]\n"
+                        + "public class AppIndexingApiTest extends Activity {\n"
+                        + "             ~~~~~~~~~~~~~~~~~~\n"
+                        + "0 errors, 1 warnings\n",
+                lintProject(
+                        "src/com/example/helloworld/AppIndexingApiTestNoStartEnd.java.txt=>src/com/example/helloworld/AppIndexingApiTest.java",
+                        "app_indexing_api_test.xml=>AndroidManifest.xml",
+                        "src/com/appindexing/AppIndex.java.txt=>src/com/google/android/gms/appindexing/AppIndex.java",
+                        "src/com/appindexing/AppIndexApi.java.txt=>src/com/google/android/gms/appindexing/AppIndexApi.java",
+                        "src/com/appindexing/GoogleApiClient.java.txt=>src/com/google/android/gms/common/api/GoogleApiClient.java",
+                        "src/com/appindexing/Activity.java.txt=>src/com/google/android/app/Activity.java",
+                        "src/com/appindexing/Api.java.txt=>src/com/google/android/gms/common/api/Api.java"));
+    }
+
+    public void testStartMatch() throws Exception {
+        assertEquals("" + "src/com/example/helloworld/AppIndexingApiTest.java:27: Warning: GoogleApiClient mClient is not connected [GoogleAppIndexingApiWarning]\n"
+                        + "    AppIndex.AppIndexApi.start(mClient, action);\n"
+                        + "                               ~~~~~~~\n"
+                        + "src/com/example/helloworld/AppIndexingApiTest.java:27: Warning: Missing corresponding AppIndex.AppIndexApi.end method [GoogleAppIndexingApiWarning]\n"
+                        + "    AppIndex.AppIndexApi.start(mClient, action);\n"
+                        + "                         ~~~~~\n"
+                        + "0 errors, 2 warnings\n",
+                lintProject(
+                        "src/com/example/helloworld/AppIndexingApiTestStartMatch.java.txt=>src/com/example/helloworld/AppIndexingApiTest.java",
+                        "app_indexing_api_test.xml=>AndroidManifest.xml",
+                        "src/com/appindexing/AppIndex.java.txt=>src/com/google/android/gms/appindexing/AppIndex.java",
+                        "src/com/appindexing/AppIndexApi.java.txt=>src/com/google/android/gms/appindexing/AppIndexApi.java",
+                        "src/com/appindexing/GoogleApiClient.java.txt=>src/com/google/android/gms/common/api/GoogleApiClient.java",
+                        "src/com/appindexing/Activity.java.txt=>src/com/google/android/app/Activity.java",
+                        "src/com/appindexing/Api.java.txt=>src/com/google/android/gms/common/api/Api.java"));
+    }
+
+    public void testEndMatch() throws Exception {
+        assertEquals("" + "src/com/example/helloworld/AppIndexingApiTest.java:33: Warning: GoogleApiClient mClient is not disconnected [GoogleAppIndexingApiWarning]\n"
+                        + "    AppIndex.AppIndexApi.end(mClient, action);\n"
+                        + "                             ~~~~~~~\n"
+                        + "src/com/example/helloworld/AppIndexingApiTest.java:33: Warning: Missing corresponding AppIndex.AppIndexApi.start method [GoogleAppIndexingApiWarning]\n"
+                        + "    AppIndex.AppIndexApi.end(mClient, action);\n"
+                        + "                         ~~~\n"
+                        + "0 errors, 2 warnings\n",
+                lintProject(
+                        "src/com/example/helloworld/AppIndexingApiTestEndMatch.java.txt=>src/com/example/helloworld/AppIndexingApiTest.java",
+                        "app_indexing_api_test.xml=>AndroidManifest.xml",
+                        "src/com/appindexing/AppIndex.java.txt=>src/com/google/android/gms/appindexing/AppIndex.java",
+                        "src/com/appindexing/AppIndexApi.java.txt=>src/com/google/android/gms/appindexing/AppIndexApi.java",
+                        "src/com/appindexing/GoogleApiClient.java.txt=>src/com/google/android/gms/common/api/GoogleApiClient.java",
+                        "src/com/appindexing/Activity.java.txt=>src/com/google/android/app/Activity.java",
+                        "src/com/appindexing/Api.java.txt=>src/com/google/android/gms/common/api/Api.java"));
+    }
+
+    public void testViewMatch() throws Exception {
+        assertEquals("" + "src/com/example/helloworld/AppIndexingApiTest.java:26: Warning: GoogleApiClient mClient is not connected [GoogleAppIndexingApiWarning]\n"
+                        + "    AppIndex.AppIndexApi.view(mClient, this, APP_URI, title, WEB_URL, null);\n"
+                        + "                              ~~~~~~~\n"
+                        + "src/com/example/helloworld/AppIndexingApiTest.java:26: Warning: Missing corresponding AppIndex.AppIndexApi.end method [GoogleAppIndexingApiWarning]\n"
+                        + "    AppIndex.AppIndexApi.view(mClient, this, APP_URI, title, WEB_URL, null);\n"
+                        + "                         ~~~~\n"
+                        + "0 errors, 2 warnings\n",
+                lintProject(
+                        "src/com/example/helloworld/AppIndexingApiTestViewMatch.java.txt=>src/com/example/helloworld/AppIndexingApiTest.java",
+                        "app_indexing_api_test.xml=>AndroidManifest.xml",
+                        "src/com/appindexing/AppIndex.java.txt=>src/com/google/android/gms/appindexing/AppIndex.java",
+                        "src/com/appindexing/AppIndexApi.java.txt=>src/com/google/android/gms/appindexing/AppIndexApi.java",
+                        "src/com/appindexing/GoogleApiClient.java.txt=>src/com/google/android/gms/common/api/GoogleApiClient.java",
+                        "src/com/appindexing/Activity.java.txt=>src/com/google/android/app/Activity.java",
+                        "src/com/appindexing/Api.java.txt=>src/com/google/android/gms/common/api/Api.java"));
+    }
+
+    public void testViewEndMatch() throws Exception {
+        assertEquals("" + "src/com/example/helloworld/AppIndexingApiTest.java:29: Warning: GoogleApiClient mClient is not disconnected [GoogleAppIndexingApiWarning]\n"
+                        + "    AppIndex.AppIndexApi.viewEnd(mClient, this, APP_URI);\n"
+                        + "                                 ~~~~~~~\n"
+                        + "src/com/example/helloworld/AppIndexingApiTest.java:29: Warning: Missing corresponding AppIndex.AppIndexApi.start method [GoogleAppIndexingApiWarning]\n"
+                        + "    AppIndex.AppIndexApi.viewEnd(mClient, this, APP_URI);\n"
+                        + "                         ~~~~~~~\n"
+                        + "0 errors, 2 warnings\n",
+                lintProject(
+                        "src/com/example/helloworld/AppIndexingApiTestViewEndMatch.java.txt=>src/com/example/helloworld/AppIndexingApiTest.java",
+                        "app_indexing_api_test.xml=>AndroidManifest.xml",
+                        "src/com/appindexing/AppIndex.java.txt=>src/com/google/android/gms/appindexing/AppIndex.java",
+                        "src/com/appindexing/AppIndexApi.java.txt=>src/com/google/android/gms/appindexing/AppIndexApi.java",
+                        "src/com/appindexing/GoogleApiClient.java.txt=>src/com/google/android/gms/common/api/GoogleApiClient.java",
+                        "src/com/appindexing/Activity.java.txt=>src/com/google/android/app/Activity.java",
+                        "src/com/appindexing/Api.java.txt=>src/com/google/android/gms/common/api/Api.java"));
+    }
+
+    public void testWrongOrder() throws Exception {
+        assertEquals("No warnings.",
+                lintProject(
+                        "src/com/example/helloworld/AppIndexingApiTestWrongOrder.java.txt=>src/com/example/helloworld/AppIndexingApiTest.java",
+                        "app_indexing_api_test.xml=>AndroidManifest.xml",
+                        "src/com/appindexing/AppIndex.java.txt=>src/com/google/android/gms/appindexing/AppIndex.java",
+                        "src/com/appindexing/AppIndexApi.java.txt=>src/com/google/android/gms/appindexing/AppIndexApi.java",
+                        "src/com/appindexing/GoogleApiClient.java.txt=>src/com/google/android/gms/common/api/GoogleApiClient.java",
+                        "src/com/appindexing/Activity.java.txt=>src/com/google/android/app/Activity.java",
+                        "src/com/appindexing/Api.java.txt=>src/com/google/android/gms/common/api/Api.java"));
+    }
+
+    public void testGoogleApiClientAddApi() throws Exception {
+        assertEquals("" + "src/com/example/helloworld/AppIndexingApiTest.java:28: Warning: GoogleApiClient mClient has not added support for App Indexing API [GoogleAppIndexingApiWarning]\n"
+                        + "    AppIndex.AppIndexApi.start(mClient, action);\n"
+                        + "                               ~~~~~~~\n"
+                        + "src/com/example/helloworld/AppIndexingApiTest.java:36: Warning: GoogleApiClient mClient has not added support for App Indexing API [GoogleAppIndexingApiWarning]\n"
+                        + "    AppIndex.AppIndexApi.end(mClient, action);\n"
+                        + "                             ~~~~~~~\n"
+                        + "0 errors, 2 warnings\n",
+                lintProject(
+                        "src/com/example/helloworld/AppIndexingApiTestGoogleApiClientAddApi.java.txt=>src/com/example/helloworld/AppIndexingApiTest.java",
+                        "app_indexing_api_test.xml=>AndroidManifest.xml",
+                        "src/com/appindexing/AppIndex.java.txt=>src/com/google/android/gms/appindexing/AppIndex.java",
+                        "src/com/appindexing/AppIndexApi.java.txt=>src/com/google/android/gms/appindexing/AppIndexApi.java",
+                        "src/com/appindexing/GoogleApiClient.java.txt=>src/com/google/android/gms/common/api/GoogleApiClient.java",
+                        "src/com/appindexing/Activity.java.txt=>src/com/google/android/app/Activity.java",
+                        "src/com/appindexing/Api.java.txt=>src/com/google/android/gms/common/api/Api.java"));
+    }
+
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AppLinksAutoVerifyDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AppLinksAutoVerifyDetectorTest.java
new file mode 100644
index 0000000..674a6af
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AppLinksAutoVerifyDetectorTest.java
@@ -0,0 +1,607 @@
+/*
+ * Copyright (C) 2015 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 com.google.common.collect.Maps;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+
+import java.util.Map;
+
+@SuppressWarnings("javadoc")
+public class AppLinksAutoVerifyDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new AppLinksAutoVerifyDetector();
+    }
+
+    public void testOk() throws Exception {
+        try {
+            Map<String, AppLinksAutoVerifyDetector.HttpResult> data = Maps.newHashMap();
+            AppLinksAutoVerifyDetector.sMockData = data;
+            JsonArray statementList = new JsonArray();
+            JsonObject statement = new JsonObject();
+            JsonArray relation = new JsonArray();
+            relation.add(new JsonPrimitive("delegate_permission/common.handle_all_urls"));
+            statement.add("relation", relation);
+            JsonObject target = new JsonObject();
+            target.addProperty("namespace", "android_app");
+            target.addProperty("package_name", "com.example.helloworld");
+            statement.add("target", target);
+            statementList.add(statement);
+            data.put("http://example.com", new AppLinksAutoVerifyDetector.HttpResult(
+                    AppLinksAutoVerifyDetector.STATUS_HTTP_OK, statementList));
+
+            assertEquals("No warnings.",
+                    lintProject(xml("AndroidManifest.xml", ""
+                            + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                            + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                            + "    package=\"com.example.helloworld\" >\n"
+                            + "\n"
+                            + "    <application\n"
+                            + "        android:allowBackup=\"true\"\n"
+                            + "        android:icon=\"@mipmap/ic_launcher\" >\n"
+                            + "        <activity android:name=\".MainActivity\" >\n"
+                            + "            <intent-filter android:autoVerify=\"true\">\n"
+                            + "                <action android:name=\"android.intent.action.VIEW\" />\n"
+                            + "                <data android:scheme=\"http\"\n"
+                            + "                    android:host=\"example.com\"\n"
+                            + "                    android:pathPrefix=\"/gizmos\" />\n"
+                            + "                <category android:name=\"android.intent.category.DEFAULT\" />\n"
+                            + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+                            + "            </intent-filter>\n"
+                            + "        </activity>\n"
+                            + "    </application>\n"
+                            + "\n"
+                            + "</manifest>\n")));
+        } finally {
+            AppLinksAutoVerifyDetector.sMockData = null;
+        }
+    }
+
+    public void testInvalidPackage() throws Exception {
+        try {
+            Map<String, AppLinksAutoVerifyDetector.HttpResult> data = Maps.newHashMap();
+            AppLinksAutoVerifyDetector.sMockData = data;
+            JsonArray statementList = new JsonArray();
+            JsonObject statement = new JsonObject();
+            JsonArray relation = new JsonArray();
+            relation.add(new JsonPrimitive("delegate_permission/common.handle_all_urls"));
+            statement.add("relation", relation);
+            JsonObject target = new JsonObject();
+            target.addProperty("namespace", "android_app");
+            target.addProperty("package_name", "com.example");
+            statement.add("target", target);
+            statementList.add(statement);
+            data.put("http://example.com", new AppLinksAutoVerifyDetector.HttpResult(
+                    AppLinksAutoVerifyDetector.STATUS_HTTP_OK, statementList));
+
+            assertEquals(
+                    "AndroidManifest.xml:12: Error: This host does not support app links to your app. Checks the Digital Asset Links JSON file: http://example.com/.well-known/assetlinks.json [AppLinksAutoVerifyError]\n"
+                            + "                    android:host=\"example.com\"\n"
+                            + "                    ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                            + "1 errors, 0 warnings\n",
+                    lintProject(xml("AndroidManifest.xml", ""
+                            + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                            + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                            + "    package=\"com.example.helloworld\" >\n"
+                            + "\n"
+                            + "    <application\n"
+                            + "        android:allowBackup=\"true\"\n"
+                            + "        android:icon=\"@mipmap/ic_launcher\" >\n"
+                            + "        <activity android:name=\".MainActivity\" >\n"
+                            + "            <intent-filter android:autoVerify=\"true\">\n"
+                            + "                <action android:name=\"android.intent.action.VIEW\" />\n"
+                            + "                <data android:scheme=\"http\"\n"
+                            + "                    android:host=\"example.com\"\n"
+                            + "                    android:pathPrefix=\"/gizmos\" />\n"
+                            + "                <category android:name=\"android.intent.category.DEFAULT\" />\n"
+                            + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+                            + "            </intent-filter>\n"
+                            + "        </activity>\n"
+                            + "    </application>\n"
+                            + "\n"
+                            + "</manifest>\n")));
+        } finally {
+            AppLinksAutoVerifyDetector.sMockData = null;
+        }
+    }
+
+    public void testNotAppTarget() throws Exception {
+        try {
+            Map<String, AppLinksAutoVerifyDetector.HttpResult> data = Maps.newHashMap();
+            AppLinksAutoVerifyDetector.sMockData = data;
+            JsonArray statementList = new JsonArray();
+            JsonObject statement = new JsonObject();
+            JsonArray relation = new JsonArray();
+            relation.add(new JsonPrimitive("delegate_permission/common.handle_all_urls"));
+            statement.add("relation", relation);
+            JsonObject target = new JsonObject();
+            target.addProperty("namespace", "web");
+            target.addProperty("package_name", "com.example.helloworld");
+            statement.add("target", target);
+            statementList.add(statement);
+            data.put("http://example.com", new AppLinksAutoVerifyDetector.HttpResult(
+                    AppLinksAutoVerifyDetector.STATUS_HTTP_OK, statementList));
+
+            assertEquals(
+                    "AndroidManifest.xml:12: Error: This host does not support app links to your app. Checks the Digital Asset Links JSON file: http://example.com/.well-known/assetlinks.json [AppLinksAutoVerifyError]\n"
+                            + "                    android:host=\"example.com\"\n"
+                            + "                    ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                            + "1 errors, 0 warnings\n",
+                    lintProject(xml("AndroidManifest.xml", ""
+                            + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                            + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                            + "    package=\"com.example.helloworld\" >\n"
+                            + "\n"
+                            + "    <application\n"
+                            + "        android:allowBackup=\"true\"\n"
+                            + "        android:icon=\"@mipmap/ic_launcher\" >\n"
+                            + "        <activity android:name=\".MainActivity\" >\n"
+                            + "            <intent-filter android:autoVerify=\"true\">\n"
+                            + "                <action android:name=\"android.intent.action.VIEW\" />\n"
+                            + "                <data android:scheme=\"http\"\n"
+                            + "                    android:host=\"example.com\"\n"
+                            + "                    android:pathPrefix=\"/gizmos\" />\n"
+                            + "                <category android:name=\"android.intent.category.DEFAULT\" />\n"
+                            + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+                            + "            </intent-filter>\n"
+                            + "        </activity>\n"
+                            + "    </application>\n"
+                            + "\n"
+                            + "</manifest>\n")));
+        } finally {
+            AppLinksAutoVerifyDetector.sMockData = null;
+        }
+    }
+
+    public void testHttpResponseError() throws Exception {
+        try {
+            Map<String, AppLinksAutoVerifyDetector.HttpResult> data = Maps.newHashMap();
+            AppLinksAutoVerifyDetector.sMockData = data;
+            data.put("http://example.com", new AppLinksAutoVerifyDetector.HttpResult(404, null));
+
+            assertEquals(
+                    "AndroidManifest.xml:12: Warning: HTTP request for Digital Asset Links JSON file http://example.com/.well-known/assetlinks.json fails. HTTP response code: 404 [AppLinksAutoVerifyWarning]\n"
+                            + "                    android:host=\"example.com\"\n"
+                            + "                    ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                            + "0 errors, 1 warnings\n",
+                    lintProject(xml("AndroidManifest.xml", ""
+                            + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                            + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                            + "    package=\"com.example.helloworld\" >\n"
+                            + "\n"
+                            + "    <application\n"
+                            + "        android:allowBackup=\"true\"\n"
+                            + "        android:icon=\"@mipmap/ic_launcher\" >\n"
+                            + "        <activity android:name=\".MainActivity\" >\n"
+                            + "            <intent-filter android:autoVerify=\"true\">\n"
+                            + "                <action android:name=\"android.intent.action.VIEW\" />\n"
+                            + "                <data android:scheme=\"http\"\n"
+                            + "                    android:host=\"example.com\"\n"
+                            + "                    android:pathPrefix=\"/gizmos\" />\n"
+                            + "                <category android:name=\"android.intent.category.DEFAULT\" />\n"
+                            + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+                            + "            </intent-filter>\n"
+                            + "        </activity>\n"
+                            + "    </application>\n"
+                            + "\n"
+                            + "</manifest>\n")));
+        } finally {
+            AppLinksAutoVerifyDetector.sMockData = null;
+        }
+    }
+
+    public void testFailedHttpConnection() throws Exception {
+        try {
+            Map<String, AppLinksAutoVerifyDetector.HttpResult> data = Maps.newHashMap();
+            AppLinksAutoVerifyDetector.sMockData = data;
+            data.put("http://example.com", new AppLinksAutoVerifyDetector.HttpResult(
+                    AppLinksAutoVerifyDetector.STATUS_HTTP_CONNECT_FAIL, null));
+
+            assertEquals(
+                    "AndroidManifest.xml:12: Warning: Connection to Digital Asset Links JSON file http://example.com/.well-known/assetlinks.json fails [AppLinksAutoVerifyWarning]\n"
+                            + "                    android:host=\"example.com\"\n"
+                            + "                    ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                            + "0 errors, 1 warnings\n",
+                    lintProject(xml("AndroidManifest.xml", ""
+                            + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                            + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                            + "    package=\"com.example.helloworld\" >\n"
+                            + "\n"
+                            + "    <application\n"
+                            + "        android:allowBackup=\"true\"\n"
+                            + "        android:icon=\"@mipmap/ic_launcher\" >\n"
+                            + "        <activity android:name=\".MainActivity\" >\n"
+                            + "            <intent-filter android:autoVerify=\"true\">\n"
+                            + "                <action android:name=\"android.intent.action.VIEW\" />\n"
+                            + "                <data android:scheme=\"http\"\n"
+                            + "                    android:host=\"example.com\"\n"
+                            + "                    android:pathPrefix=\"/gizmos\" />\n"
+                            + "                <category android:name=\"android.intent.category.DEFAULT\" />\n"
+                            + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+                            + "            </intent-filter>\n"
+                            + "        </activity>\n"
+                            + "    </application>\n"
+                            + "\n"
+                            + "</manifest>\n")));
+        } finally {
+            AppLinksAutoVerifyDetector.sMockData = null;
+        }
+    }
+
+    public void testMalformedUrl() throws Exception {
+        try {
+            Map<String, AppLinksAutoVerifyDetector.HttpResult> data = Maps.newHashMap();
+            AppLinksAutoVerifyDetector.sMockData = data;
+            data.put("http://example.com", new AppLinksAutoVerifyDetector.HttpResult(
+                    AppLinksAutoVerifyDetector.STATUS_MALFORMED_URL, null));
+
+            assertEquals(
+                    "AndroidManifest.xml:12: Error: Malformed URL of Digital Asset Links JSON file: http://example.com/.well-known/assetlinks.json. An unknown protocol is specified [AppLinksAutoVerifyError]\n"
+                            + "                    android:host=\"example.com\"\n"
+                            + "                    ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                            + "1 errors, 0 warnings\n",
+                    lintProject(xml("AndroidManifest.xml", ""
+                            + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                            + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                            + "    package=\"com.example.helloworld\" >\n"
+                            + "\n"
+                            + "    <application\n"
+                            + "        android:allowBackup=\"true\"\n"
+                            + "        android:icon=\"@mipmap/ic_launcher\" >\n"
+                            + "        <activity android:name=\".MainActivity\" >\n"
+                            + "            <intent-filter android:autoVerify=\"true\">\n"
+                            + "                <action android:name=\"android.intent.action.VIEW\" />\n"
+                            + "                <data android:scheme=\"http\"\n"
+                            + "                    android:host=\"example.com\"\n"
+                            + "                    android:pathPrefix=\"/gizmos\" />\n"
+                            + "                <category android:name=\"android.intent.category.DEFAULT\" />\n"
+                            + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+                            + "            </intent-filter>\n"
+                            + "        </activity>\n"
+                            + "    </application>\n"
+                            + "\n"
+                            + "</manifest>\n")));
+        } finally {
+            AppLinksAutoVerifyDetector.sMockData = null;
+        }
+    }
+
+    public void testUnknownHost() throws Exception {
+        try {
+            Map<String, AppLinksAutoVerifyDetector.HttpResult> data = Maps.newHashMap();
+            AppLinksAutoVerifyDetector.sMockData = data;
+            data.put("http://example.com", new AppLinksAutoVerifyDetector.HttpResult(
+                    AppLinksAutoVerifyDetector.STATUS_UNKNOWN_HOST, null));
+
+            assertEquals(
+                    "AndroidManifest.xml:12: Warning: Unknown host: http://example.com. Check if the host exists, and check your network connection [AppLinksAutoVerifyWarning]\n"
+                            + "                    android:host=\"example.com\"\n"
+                            + "                    ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                            + "0 errors, 1 warnings\n",
+                    lintProject(xml("AndroidManifest.xml", ""
+                            + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                            + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                            + "    package=\"com.example.helloworld\" >\n"
+                            + "\n"
+                            + "    <application\n"
+                            + "        android:allowBackup=\"true\"\n"
+                            + "        android:icon=\"@mipmap/ic_launcher\" >\n"
+                            + "        <activity android:name=\".MainActivity\" >\n"
+                            + "            <intent-filter android:autoVerify=\"true\">\n"
+                            + "                <action android:name=\"android.intent.action.VIEW\" />\n"
+                            + "                <data android:scheme=\"http\"\n"
+                            + "                    android:host=\"example.com\"\n"
+                            + "                    android:pathPrefix=\"/gizmos\" />\n"
+                            + "                <category android:name=\"android.intent.category.DEFAULT\" />\n"
+                            + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+                            + "            </intent-filter>\n"
+                            + "        </activity>\n"
+                            + "    </application>\n"
+                            + "\n"
+                            + "</manifest>\n")));
+        } finally {
+            AppLinksAutoVerifyDetector.sMockData = null;
+        }
+    }
+
+    public void testNotFound() throws Exception {
+        try {
+            Map<String, AppLinksAutoVerifyDetector.HttpResult> data = Maps.newHashMap();
+            AppLinksAutoVerifyDetector.sMockData = data;
+            data.put("http://example.com", new AppLinksAutoVerifyDetector.HttpResult(
+                    AppLinksAutoVerifyDetector.STATUS_NOT_FOUND, null));
+
+            assertEquals(
+                    "AndroidManifest.xml:12: Error: Digital Asset Links JSON file http://example.com/.well-known/assetlinks.json is not found on the host [AppLinksAutoVerifyError]\n"
+                            + "                    android:host=\"example.com\"\n"
+                            + "                    ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                            + "1 errors, 0 warnings\n",
+                    lintProject(xml("AndroidManifest.xml", ""
+                            + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                            + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                            + "    package=\"com.example.helloworld\" >\n"
+                            + "\n"
+                            + "    <application\n"
+                            + "        android:allowBackup=\"true\"\n"
+                            + "        android:icon=\"@mipmap/ic_launcher\" >\n"
+                            + "        <activity android:name=\".MainActivity\" >\n"
+                            + "            <intent-filter android:autoVerify=\"true\">\n"
+                            + "                <action android:name=\"android.intent.action.VIEW\" />\n"
+                            + "                <data android:scheme=\"http\"\n"
+                            + "                    android:host=\"example.com\"\n"
+                            + "                    android:pathPrefix=\"/gizmos\" />\n"
+                            + "                <category android:name=\"android.intent.category.DEFAULT\" />\n"
+                            + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+                            + "            </intent-filter>\n"
+                            + "        </activity>\n"
+                            + "    </application>\n"
+                            + "\n"
+                            + "</manifest>\n")));
+        } finally {
+            AppLinksAutoVerifyDetector.sMockData = null;
+        }
+    }
+
+    public void testWrongJsonSyntax() throws Exception {
+        try {
+            Map<String, AppLinksAutoVerifyDetector.HttpResult> data = Maps.newHashMap();
+            AppLinksAutoVerifyDetector.sMockData = data;
+            data.put("http://example.com", new AppLinksAutoVerifyDetector.HttpResult(
+                    AppLinksAutoVerifyDetector.STATUS_WRONG_JSON_SYNTAX, null));
+
+            assertEquals(
+                    "AndroidManifest.xml:12: Error: http://example.com/.well-known/assetlinks.json has incorrect JSON syntax [AppLinksAutoVerifyError]\n"
+                            + "                    android:host=\"example.com\"\n"
+                            + "                    ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                            + "1 errors, 0 warnings\n",
+                    lintProject(xml("AndroidManifest.xml", ""
+                            + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                            + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                            + "    package=\"com.example.helloworld\" >\n"
+                            + "\n"
+                            + "    <application\n"
+                            + "        android:allowBackup=\"true\"\n"
+                            + "        android:icon=\"@mipmap/ic_launcher\" >\n"
+                            + "        <activity android:name=\".MainActivity\" >\n"
+                            + "            <intent-filter android:autoVerify=\"true\">\n"
+                            + "                <action android:name=\"android.intent.action.VIEW\" />\n"
+                            + "                <data android:scheme=\"http\"\n"
+                            + "                    android:host=\"example.com\"\n"
+                            + "                    android:pathPrefix=\"/gizmos\" />\n"
+                            + "                <category android:name=\"android.intent.category.DEFAULT\" />\n"
+                            + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+                            + "            </intent-filter>\n"
+                            + "        </activity>\n"
+                            + "    </application>\n"
+                            + "\n"
+                            + "</manifest>\n")));
+        } finally {
+            AppLinksAutoVerifyDetector.sMockData = null;
+        }
+    }
+
+    public void testFailedJsonParsing() throws Exception {
+        try {
+            Map<String, AppLinksAutoVerifyDetector.HttpResult> data = Maps.newHashMap();
+            AppLinksAutoVerifyDetector.sMockData = data;
+            data.put("http://example.com", new AppLinksAutoVerifyDetector.HttpResult(
+                    AppLinksAutoVerifyDetector.STATUS_JSON_PARSE_FAIL, null));
+
+            assertEquals(
+                    "AndroidManifest.xml:12: Error: Parsing JSON file http://example.com/.well-known/assetlinks.json fails [AppLinksAutoVerifyError]\n"
+                            + "                    android:host=\"example.com\"\n"
+                            + "                    ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                            + "1 errors, 0 warnings\n",
+                    lintProject(xml("AndroidManifest.xml", ""
+                            + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                            + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                            + "    package=\"com.example.helloworld\" >\n"
+                            + "\n"
+                            + "    <application\n"
+                            + "        android:allowBackup=\"true\"\n"
+                            + "        android:icon=\"@mipmap/ic_launcher\" >\n"
+                            + "        <activity android:name=\".MainActivity\" >\n"
+                            + "            <intent-filter android:autoVerify=\"true\">\n"
+                            + "                <action android:name=\"android.intent.action.VIEW\" />\n"
+                            + "                <data android:scheme=\"http\"\n"
+                            + "                    android:host=\"example.com\"\n"
+                            + "                    android:pathPrefix=\"/gizmos\" />\n"
+                            + "                <category android:name=\"android.intent.category.DEFAULT\" />\n"
+                            + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+                            + "            </intent-filter>\n"
+                            + "        </activity>\n"
+                            + "    </application>\n"
+                            + "\n"
+                            + "</manifest>\n")));
+        } finally {
+            AppLinksAutoVerifyDetector.sMockData = null;
+        }
+    }
+
+    public void testNoAutoVerify() throws Exception {
+        assertEquals(
+                "No warnings.",
+                lintProject(xml("AndroidManifest.xml", ""
+                        + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                        + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                        + "    package=\"com.example.helloworld\" >\n"
+                        + "\n"
+                        + "    <application\n"
+                        + "        android:allowBackup=\"true\"\n"
+                        + "        android:icon=\"@mipmap/ic_launcher\" >\n"
+                        + "        <activity android:name=\".MainActivity\" >\n"
+                        + "            <intent-filter>\n"
+                        + "                <action android:name=\"android.intent.action.VIEW\" />\n"
+                        + "                <data android:scheme=\"http\"\n"
+                        + "                    android:host=\"example.com\"\n"
+                        + "                    android:pathPrefix=\"/gizmos\" />\n"
+                        + "                <category android:name=\"android.intent.category.DEFAULT\" />\n"
+                        + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+                        + "            </intent-filter>\n"
+                        + "        </activity>\n"
+                        + "    </application>\n"
+                        + "\n"
+                        + "</manifest>\n")));
+    }
+
+    public void testNotAppLinkInIntents() throws Exception {
+        assertEquals(
+                "No warnings.",
+                lintProject(xml("AndroidManifest.xml", ""
+                        + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                        + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                        + "    package=\"com.example.helloworld\" >\n"
+                        + "\n"
+                        + "    <application\n"
+                        + "        android:allowBackup=\"true\"\n"
+                        + "        android:icon=\"@mipmap/ic_launcher\" >\n"
+                        + "        <activity android:name=\".MainActivity\" >\n"
+                        + "            <intent-filter android:autoVerify=\"true\">\n"
+                        + "                <data android:scheme=\"http\"\n"
+                        + "                    android:host=\"example.com\"\n"
+                        + "                    android:pathPrefix=\"/gizmos\" />\n"
+                        + "                <category android:name=\"android.intent.category.DEFAULT\" />\n"
+                        + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+                        + "            </intent-filter>\n"
+                        + "            <intent-filter android:autoVerify=\"true\">\n"
+                        + "                <action android:name=\"android.intent.action.VIEW\" />\n"
+                        + "                <data android:scheme=\"http\"\n"
+                        + "                    android:host=\"example.com\"\n"
+                        + "                    android:pathPrefix=\"/gizmos\" />\n"
+                        + "                <category android:name=\"android.intent.category.DEFAULT\" />\n"
+                        + "            </intent-filter>\n"
+                        + "            <intent-filter android:autoVerify=\"true\">\n"
+                        + "                <action android:name=\"android.intent.action.VIEW\" />\n"
+                        + "                <category android:name=\"android.intent.category.DEFAULT\" />\n"
+                        + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+                        + "            </intent-filter>\n"
+                        + "        </activity>\n"
+                        + "    </application>\n"
+                        + "\n"
+                        + "</manifest>\n")));
+    }
+
+    public void testMultipleLinks() throws Exception {
+        try {
+            Map<String, AppLinksAutoVerifyDetector.HttpResult> data = Maps.newHashMap();
+            AppLinksAutoVerifyDetector.sMockData = data;
+            data.put("http://example.com", new AppLinksAutoVerifyDetector.HttpResult(
+                    AppLinksAutoVerifyDetector.STATUS_HTTP_CONNECT_FAIL, null));
+            data.put("https://example.com", new AppLinksAutoVerifyDetector.HttpResult(
+                    AppLinksAutoVerifyDetector.STATUS_NOT_FOUND, null));
+            data.put("http://www.example.com", new AppLinksAutoVerifyDetector.HttpResult(
+                    AppLinksAutoVerifyDetector.STATUS_UNKNOWN_HOST, null));
+            data.put("https://www.example.com", new AppLinksAutoVerifyDetector.HttpResult(
+                    AppLinksAutoVerifyDetector.STATUS_WRONG_JSON_SYNTAX, null));
+
+            assertEquals(
+                    "AndroidManifest.xml:12: Error: Digital Asset Links JSON file https://example.com/.well-known/assetlinks.json is not found on the host [AppLinksAutoVerifyError]\n"
+                            + "                    android:host=\"example.com\"\n"
+                            + "                    ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                            + "AndroidManifest.xml:15: Error: https://www.example.com/.well-known/assetlinks.json has incorrect JSON syntax [AppLinksAutoVerifyError]\n"
+                            + "                <data android:host=\"www.example.com\" />\n"
+                            + "                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                            + "AndroidManifest.xml:12: Warning: Connection to Digital Asset Links JSON file http://example.com/.well-known/assetlinks.json fails [AppLinksAutoVerifyWarning]\n"
+                            + "                    android:host=\"example.com\"\n"
+                            + "                    ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                            + "AndroidManifest.xml:15: Warning: Unknown host: http://www.example.com. Check if the host exists, and check your network connection [AppLinksAutoVerifyWarning]\n"
+                            + "                <data android:host=\"www.example.com\" />\n"
+                            + "                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                            + "2 errors, 2 warnings\n",
+                    lintProject(xml("AndroidManifest.xml", ""
+                            + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                            + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                            + "    package=\"com.example.helloworld\" >\n"
+                            + "\n"
+                            + "    <application\n"
+                            + "        android:allowBackup=\"true\"\n"
+                            + "        android:icon=\"@mipmap/ic_launcher\" >\n"
+                            + "        <activity android:name=\".MainActivity\" >\n"
+                            + "            <intent-filter android:autoVerify=\"true\">\n"
+                            + "                <action android:name=\"android.intent.action.VIEW\" />\n"
+                            + "                <data android:scheme=\"http\"\n"
+                            + "                    android:host=\"example.com\"\n"
+                            + "                    android:pathPrefix=\"/gizmos\" />\n"
+                            + "                <data android:scheme=\"https\" />\n"
+                            + "                <data android:host=\"www.example.com\" />\n"
+                            + "                <category android:name=\"android.intent.category.DEFAULT\" />\n"
+                            + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+                            + "            </intent-filter>\n"
+                            + "        </activity>\n"
+                            + "    </application>\n"
+                            + "\n"
+                            + "</manifest>\n")));
+        } finally {
+            AppLinksAutoVerifyDetector.sMockData = null;
+        }
+    }
+
+    public void testMultipleIntents() throws Exception {
+        try {
+            Map<String, AppLinksAutoVerifyDetector.HttpResult> data = Maps.newHashMap();
+            AppLinksAutoVerifyDetector.sMockData = data;
+            data.put("http://example.com", new AppLinksAutoVerifyDetector.HttpResult(
+                    AppLinksAutoVerifyDetector.STATUS_HTTP_CONNECT_FAIL, null));
+            data.put("http://www.example.com", new AppLinksAutoVerifyDetector.HttpResult(
+                    AppLinksAutoVerifyDetector.STATUS_UNKNOWN_HOST, null));
+
+            assertEquals(
+                    "AndroidManifest.xml:12: Warning: Unknown host: http://www.example.com. Check if the host exists, and check your network connection [AppLinksAutoVerifyWarning]\n"
+                            + "                    android:host=\"www.example.com\"\n"
+                            + "                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                            + "AndroidManifest.xml:20: Warning: Connection to Digital Asset Links JSON file http://example.com/.well-known/assetlinks.json fails [AppLinksAutoVerifyWarning]\n"
+                            + "                    android:host=\"example.com\"\n"
+                            + "                    ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                            + "0 errors, 2 warnings\n",
+                    lintProject(xml("AndroidManifest.xml", ""
+                            + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                            + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                            + "    package=\"com.example.helloworld\" >\n"
+                            + "\n"
+                            + "    <application\n"
+                            + "        android:allowBackup=\"true\"\n"
+                            + "        android:icon=\"@mipmap/ic_launcher\" >\n"
+                            + "        <activity android:name=\".MainActivity\" >\n"
+                            + "            <intent-filter>\n"
+                            + "                <action android:name=\"android.intent.action.VIEW\" />\n"
+                            + "                <data android:scheme=\"http\"\n"
+                            + "                    android:host=\"www.example.com\"\n"
+                            + "                    android:pathPrefix=\"/gizmos\" />\n"
+                            + "                <category android:name=\"android.intent.category.DEFAULT\" />\n"
+                            + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+                            + "            </intent-filter>\n"
+                            + "            <intent-filter android:autoVerify=\"true\">\n"
+                            + "                <action android:name=\"android.intent.action.VIEW\" />\n"
+                            + "                <data android:scheme=\"http\"\n"
+                            + "                    android:host=\"example.com\"\n"
+                            + "                    android:pathPrefix=\"/gizmos\" />\n"
+                            + "                <category android:name=\"android.intent.category.DEFAULT\" />\n"
+                            + "                <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+                            + "            </intent-filter>\n"
+                            + "        </activity>\n"
+                            + "    </application>\n"
+                            + "\n"
+                            + "</manifest>\n")));
+        } finally {
+            AppLinksAutoVerifyDetector.sMockData = null;
+        }
+    }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/BadHostnameVerifierDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/BadHostnameVerifierDetectorTest.java
new file mode 100644
index 0000000..1dd48bc
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/BadHostnameVerifierDetectorTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2015 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", "ClassNameDiffersFromFileName", "ImplicitArrayToString",
+        "ConstantConditions", "ConstantIfStatement"})
+public class BadHostnameVerifierDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new BadHostnameVerifierDetector();
+    }
+
+    public void testBroken() throws Exception {
+        assertEquals(""
+                + "src/test/pkg/InsecureHostnameVerifier.java:9: Warning: verify always returns true, which could cause insecure network traffic due to trusting TLS/SSL server certificates for wrong hostnames [BadHostnameVerifier]\n"
+                + "        public boolean verify(String hostname, SSLSession session) {\n"
+                + "                       ~~~~~~\n"
+                + "0 errors, 1 warnings\n",
+                lintProject(
+                        xml("AndroidManifest.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                                + "    package=\"test.pkg\"\n"
+                                + "    android:versionCode=\"1\"\n"
+                                + "    android:versionName=\"1.0\" >\n"
+                                + "\n"
+                                + "    <uses-sdk android:minSdkVersion=\"14\" />\n"
+                                + "\n"
+                                + "    <application\n"
+                                + "        android:icon=\"@drawable/ic_launcher\"\n"
+                                + "        android:label=\"@string/app_name\" >\n"
+                                + "        <service\n"
+                                + "            android:name=\".InsecureHostnameVerifier\" >\n"
+                                + "        </service>\n"
+                                + "    </application>\n"
+                                + "\n"
+                                + "</manifest>\n"),
+                        copy("res/values/strings.xml"),
+                        java("src/test/pkg/InsecureHostnameVerifier.java", ""
+                                + "package test.pkg;\n"
+                                + "\n"
+                                + "import javax.net.ssl.HostnameVerifier;\n"
+                                + "import javax.net.ssl.SSLSession;\n"
+                                + "\n"
+                                + "public abstract class InsecureHostnameVerifier  {\n"
+                                + "    HostnameVerifier allowAll = new HostnameVerifier() {\n"
+                                + "        @Override\n"
+                                + "        public boolean verify(String hostname, SSLSession session) {\n"
+                                + "            return true;\n"
+                                + "        }\n"
+                                + "    };\n"
+                                + "\n"
+                                + "    HostnameVerifier allowAll2 = new HostnameVerifier() {\n"
+                                + "        @Override\n"
+                                + "        public boolean verify(String hostname, SSLSession session) {\n"
+                                + "            boolean returnValue = true;\n"
+                                + "            if (true) {\n"
+                                + "                int irrelevant = 5;\n"
+                                + "                if (irrelevant > 6) {\n"
+                                + "                    return returnValue;\n"
+                                + "                }\n"
+                                + "            }\n"
+                                + "            return returnValue;\n"
+                                + "        }\n"
+                                + "    };\n"
+                                + "\n"
+                                + "    HostnameVerifier unknown = new HostnameVerifier() {\n"
+                                + "        @Override\n"
+                                + "        public boolean verify(String hostname, SSLSession session) {\n"
+                                + "            boolean returnValue = true;\n"
+                                + "            if (hostname.contains(\"something\")) {\n"
+                                + "                returnValue = false;\n"
+                                + "            }\n"
+                                + "            return returnValue;\n"
+                                + "        }\n"
+                                + "    };\n"
+                                + "}\n"
+                                + "\n")
+                ));
+    }
+
+    public void testCorrect() throws Exception {
+        assertEquals(
+                "No warnings.",
+                lintProject(
+                        xml("AndroidManifest.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                        + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                                        + "    package=\"test.pkg\"\n"
+                                        + "    android:versionCode=\"1\"\n"
+                                        + "    android:versionName=\"1.0\" >\n"
+                                        + "\n"
+                                        + "    <uses-sdk android:minSdkVersion=\"14\" />\n"
+                                        + "\n"
+                                        + "    <application\n"
+                                        + "        android:icon=\"@drawable/ic_launcher\"\n"
+                                        + "        android:label=\"@string/app_name\" >\n"
+                                        + "        <service\n"
+                                        + "            android:name=\".ExampleHostnameVerifier\" >\n"
+                                        + "        </service>\n"
+                                        + "    </application>\n"
+                                        + "\n"
+                                        + "</manifest>\n"),
+                        copy("res/values/strings.xml"),
+                        java("src/test/pkg/ExampleHostnameVerifier.java", ""
+                                + "package test.pkg;\n"
+                                        + "\n"
+                                        + "import android.content.Intent;\n"
+                                        + "import android.app.IntentService;\n"
+                                        + "\n"
+                                        + "import java.io.IOException;\n"
+                                        + "import java.net.URL;\n"
+                                        + "import javax.net.ssl.HostnameVerifier;\n"
+                                        + "import javax.net.ssl.HttpsURLConnection;\n"
+                                        + "import javax.net.ssl.SSLContext;\n"
+                                        + "import javax.net.ssl.SSLSession;\n"
+                                        + "import javax.net.ssl.TrustManager;\n"
+                                        + "import javax.net.ssl.X509TrustManager;\n"
+                                        + "\n"
+                                        + "import org.apache.http.conn.ssl.SSLSocketFactory;\n"
+                                        + "import org.apache.http.conn.ssl.StrictHostnameVerifier;\n"
+                                        + "\n"
+                                        + "public class ExampleHostnameVerifier extends IntentService {\n"
+                                        + "    HostnameVerifier denyAll = new HostnameVerifier() {\n"
+                                        + "        @Override\n"
+                                        + "        public boolean verify(String hostname, SSLSession session) {\n"
+                                        + "            return false;\n"
+                                        + "        }\n"
+                                        + "    };\n"
+                                        + "\n"
+                                        + "    public ExampleHostnameVerifier() {\n"
+                                        + "        super(\"ExampleHostnameVerifier\");\n"
+                                        + "    }\n"
+                                        + "\n"
+                                        + "    @Override\n"
+                                        + "    protected void onHandleIntent(Intent intent) {\n"
+                                        + "        try {\n"
+                                        + "            URL url = new URL(\"https://www.google.com\");\n"
+                                        + "            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();\n"
+                                        + "            connection.setHostnameVerifier(denyAll);\n"
+                                        + "        } catch (IOException e) {\n"
+                                        + "            System.out.println(e.getStackTrace());\n"
+                                        + "        }\n"
+                                        + "    }\n"
+                                        + "}\n")
+                ));
+    }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/BuiltinIssueRegistryTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/BuiltinIssueRegistryTest.java
index aba38f4..a7922be 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/BuiltinIssueRegistryTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/BuiltinIssueRegistryTest.java
@@ -22,11 +22,19 @@
 
 import junit.framework.TestCase;
 
+import org.jetbrains.annotations.NotNull;
+
 import java.lang.reflect.Field;
+import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 public class BuiltinIssueRegistryTest extends TestCase {
     public void testNoListResize() {
@@ -74,6 +82,99 @@
         }
     }
 
+    public void testSimultaneousIssueMapInitialization() throws Exception {
+        // Regression test for b/27563821: bogus
+        //     Error: Unknown issue id "UseCompoundDrawables"
+
+        final BuiltinIssueRegistry registry1 = new BuiltinIssueRegistry();
+        assertNotNull(registry1.getIssue(UseCompoundDrawableDetector.ISSUE.getId()));
+
+        // Step 1: Somebody resets the issue registry (usually the JarFileIssueRegistry
+        // because new custom rules have been loaded for some new library dependency
+        BuiltinIssueRegistry.reset();
+
+        // Step 2: Thread 1 calls issue registry getIssue(String id)
+        // and since the id to issue map is null (because of the above reset)
+        // it's computed in double checked locking code. It ends up calling into
+        // getIssues(). We'll make the code stall there with a barrier:
+
+        final CyclicBarrier barrier1 = new CyclicBarrier(2);
+        final CyclicBarrier barrier2 = new CyclicBarrier(2);
+        final BuiltinIssueRegistry registry2 = new BuiltinIssueRegistry() {
+            @NonNull
+            @Override
+            public List<Issue> getIssues() {
+                final List<Issue> superList = super.getIssues();
+                // Special list constructed such that *iterating* through the list
+                // can be interrupted at the beginning. This lets us sequence timings
+                // in getIssue(String) such that we can pause the implementation
+                // right in the for loop in the middle (between the map construction
+                // and assigning the field. Prior to this bug fix, at this point
+                // the field would have been initialized and other threads could
+                // skip the whole locked region.
+                return new ArrayList<Issue>() {
+                    @NotNull
+                    @Override
+                    public Iterator<Issue> iterator() {
+                        try {
+                            barrier1.await();
+
+                            // With the bug, the second thread would immediately
+                            // see the field (pointing to the empty map) and proceed.
+                            // In that case the test fails immediately. However, when
+                            // the code is working correctly, the second registry can't
+                            // access the map while we're in the synchronized block - and
+                            // if we wait forever on this barrier, the code will deadlock.
+                            // Therefore, we only wait 3 seconds to simulate contention,
+                            // and when the await finally times out it will exit the
+                            // critical section, finish the map and let other registries
+                            // access it.
+                            barrier2.await(3, TimeUnit.SECONDS);
+                            fail("Incorrect synchronization: other thread should have "
+                                    + "blocked in synchronized block and never reached barrier "
+                                    + "until timeout");
+                        } catch (InterruptedException e) {
+                            fail(e.getMessage());
+                        } catch (BrokenBarrierException ignore) {
+                            // This is expected; see above
+                        } catch (TimeoutException ignore) {
+                            // This is expected; see above
+                        }
+                        return superList.listIterator();
+                    }
+                };
+            }
+        };
+
+        Thread thread = new Thread() {
+            @Override
+            public void run() {
+                // Trigger computation of the issue map (which will enter the
+                // synchronized section and blocking on barrier1 in getIssues()
+                registry2.getIssue(UseCompoundDrawableDetector.ISSUE.getId());
+            }
+        };
+        thread.start();
+
+        // Sync this thread with the issue thread such that we know it's inside the
+        // getIssue(String) method:
+        barrier1.await();
+
+        // Now thread 2 (the UI test thread) comes along and asks for the issue id's
+        // (while the other thread is busy computing the map - it's between barrier1 and
+        // barrier2 in getIssues() above)
+        assertNotNull(registry1.getIssue(UseCompoundDrawableDetector.ISSUE.getId()));
+
+        // All done
+        try {
+            barrier2.await();
+            fail("Incorrect synchronization: getIssue should have blocked until thread1 is done");
+        } catch (BrokenBarrierException ignore) {
+            // This is expected; see comment in iterator() above
+        }
+        thread.join();
+    }
+
     private static class TestIssueRegistry extends BuiltinIssueRegistry {
         // Override to make method accessible outside package
         @NonNull
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CallSuperDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CallSuperDetectorTest.java
index 1b885f3..2c0a17b 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CallSuperDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CallSuperDetectorTest.java
@@ -27,22 +27,22 @@
         assertEquals(""
                 + "src/test/pkg/CallSuperTest.java:11: Error: Overriding method should call super.test1 [MissingSuperCall]\n"
                 + "        protected void test1() { // ERROR\n"
-                + "                       ~~~~~~~\n"
+                + "                       ~~~~~\n"
                 + "src/test/pkg/CallSuperTest.java:14: Error: Overriding method should call super.test2 [MissingSuperCall]\n"
                 + "        protected void test2() { // ERROR\n"
-                + "                       ~~~~~~~\n"
+                + "                       ~~~~~\n"
                 + "src/test/pkg/CallSuperTest.java:17: Error: Overriding method should call super.test3 [MissingSuperCall]\n"
                 + "        protected void test3() { // ERROR\n"
-                + "                       ~~~~~~~\n"
+                + "                       ~~~~~\n"
                 + "src/test/pkg/CallSuperTest.java:20: Error: Overriding method should call super.test4 [MissingSuperCall]\n"
                 + "        protected void test4(int arg) { // ERROR\n"
-                + "                       ~~~~~~~~~~~~~~\n"
+                + "                       ~~~~~\n"
                 + "src/test/pkg/CallSuperTest.java:26: Error: Overriding method should call super.test5 [MissingSuperCall]\n"
                 + "        protected void test5(int arg1, boolean arg2, Map<List<String>,?> arg3,  // ERROR\n"
-                + "                       ^\n"
+                + "                       ~~~~~\n"
                 + "src/test/pkg/CallSuperTest.java:30: Error: Overriding method should call super.test5 [MissingSuperCall]\n"
                 + "        protected void test5() { // ERROR\n"
-                + "                       ~~~~~~~\n"
+                + "                       ~~~~~\n"
                 + "6 errors, 0 warnings\n",
 
                 lintProject("src/test/pkg/CallSuperTest.java.txt=>src/test/pkg/CallSuperTest.java",
@@ -100,10 +100,10 @@
         assertEquals(""
                 + "src/test/pkg/DetachedFromWindow.java:7: Error: Overriding method should call super.onDetachedFromWindow [MissingSuperCall]\n"
                 + "        protected void onDetachedFromWindow() {\n"
-                + "                       ~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "                       ~~~~~~~~~~~~~~~~~~~~\n"
                 + "src/test/pkg/DetachedFromWindow.java:26: Error: Overriding method should call super.onDetachedFromWindow [MissingSuperCall]\n"
                 + "        protected void onDetachedFromWindow() {\n"
-                + "                       ~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "                       ~~~~~~~~~~~~~~~~~~~~\n"
                 + "2 errors, 0 warnings\n",
 
                 lintProject("src/test/pkg/DetachedFromWindow.java.txt=>" +
@@ -114,7 +114,7 @@
         assertEquals(""
                 + "src/test/pkg/WatchFaceTest.java:9: Error: Overriding method should call super.onVisibilityChanged [MissingSuperCall]\n"
                 + "        public void onVisibilityChanged(boolean visible) { // ERROR: Missing super call\n"
-                + "                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "                    ~~~~~~~~~~~~~~~~~~~\n"
                 + "1 errors, 0 warnings\n",
 
                 lintProject(
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CleanupDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CleanupDetectorTest.java
index 1fd8e61..5d56a76 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CleanupDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CleanupDetectorTest.java
@@ -74,20 +74,20 @@
     }
 
     public void testCommit() throws Exception {
-        assertEquals("" +
-            "src/test/pkg/CommitTest.java:25: Warning: This transaction should be completed with a commit() call [CommitTransaction]\n" +
-            "        getFragmentManager().beginTransaction(); // Missing commit\n" +
-            "                             ~~~~~~~~~~~~~~~~\n" +
-            "src/test/pkg/CommitTest.java:30: Warning: This transaction should be completed with a commit() call [CommitTransaction]\n" +
-            "        FragmentTransaction transaction2 = getFragmentManager().beginTransaction(); // Missing commit\n" +
-            "                                                                ~~~~~~~~~~~~~~~~\n" +
-            "src/test/pkg/CommitTest.java:39: Warning: This transaction should be completed with a commit() call [CommitTransaction]\n" +
-            "        getFragmentManager().beginTransaction(); // Missing commit\n" +
-            "                             ~~~~~~~~~~~~~~~~\n" +
-            "src/test/pkg/CommitTest.java:65: Warning: This transaction should be completed with a commit() call [CommitTransaction]\n" +
-            "        getSupportFragmentManager().beginTransaction();\n" +
-            "                                    ~~~~~~~~~~~~~~~~\n" +
-            "0 errors, 4 warnings\n",
+        assertEquals(""
+                + "src/test/pkg/CommitTest.java:25: Warning: This transaction should be completed with a commit() call [CommitTransaction]\n"
+                + "        getFragmentManager().beginTransaction(); // Missing commit\n"
+                + "                             ~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/CommitTest.java:30: Warning: This transaction should be completed with a commit() call [CommitTransaction]\n"
+                + "        FragmentTransaction transaction2 = getFragmentManager().beginTransaction(); // Missing commit\n"
+                + "                                                                ~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/CommitTest.java:39: Warning: This transaction should be completed with a commit() call [CommitTransaction]\n"
+                + "        getFragmentManager().beginTransaction(); // Missing commit\n"
+                + "                             ~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/CommitTest.java:65: Warning: This transaction should be completed with a commit() call [CommitTransaction]\n"
+                + "        getSupportFragmentManager().beginTransaction();\n"
+                + "                                    ~~~~~~~~~~~~~~~~\n"
+                + "0 errors, 4 warnings\n",
 
             lintProject(
                     "apicheck/classpath=>.classpath",
@@ -245,4 +245,32 @@
                         "src/test/pkg/CursorTest.java.txt=>src/test/pkg/CursorTest.java"
                 ));
     }
+
+    public void testDatabaseCursorReassignment() throws Exception {
+        //noinspection ClassNameDiffersFromFileName,SpellCheckingInspection
+        assertEquals("No warnings.",
+                lintProject(java("src/test/pkg/CursorTest.java", ""
+                        + "package test.pkg;\n"
+                        + "\n"
+                        + "import android.app.Activity;\n"
+                        + "import android.database.Cursor;\n"
+                        + "import android.database.sqlite.SQLiteException;\n"
+                        + "import android.net.Uri;\n"
+                        + "\n"
+                        + "public class CursorTest extends Activity {\n"
+                        + "    public void testSimple() {\n"
+                        + "        Cursor cursor;\n"
+                        + "        try {\n"
+                        + "            cursor = getContentResolver().query(Uri.parse(\"blahblah\"),\n"
+                        + "                    new String[]{\"_id\", \"display_name\"}, null, null, null);\n"
+                        + "        } catch (SQLiteException e) {\n"
+                        + "            // Fallback\n"
+                        + "            cursor = getContentResolver().query(Uri.parse(\"blahblah\"),\n"
+                        + "                    new String[]{\"_id2\", \"display_name\"}, null, null, null);\n"
+                        + "        }\n"
+                        + "        assert cursor != null;\n"
+                        + "        cursor.close();\n"
+                        + "    }\n"
+                        + "}\n")));
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CutPasteDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CutPasteDetectorTest.java
index df02a41..314f146 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CutPasteDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CutPasteDetectorTest.java
@@ -25,6 +25,13 @@
         return new CutPasteDetector();
     }
 
+    @Override
+    protected boolean allowCompilationErrors() {
+        // Some of these unit tests are still relying on source code that references
+        // unresolved symbols etc.
+        return true;
+    }
+
     public void test() throws Exception {
         assertEquals(
             "src/test/pkg/PasteError.java:15: Warning: The id R.id.textView1 has already been looked up in this method; possible cut & paste error? [CutPasteId]\n" +
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DeprecationDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DeprecationDetectorTest.java
index f399014..76ddf03 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DeprecationDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DeprecationDetectorTest.java
@@ -84,4 +84,40 @@
                     "apicheck/minsdk4.xml=>AndroidManifest.xml",
                     "res/layout/deprecation.xml"));
     }
+
+    public void testUsesSdkM() throws Exception {
+        assertEquals(""
+                + "AndroidManifest.xml:8: Warning: uses-permission-sdk-m is deprecated: Use `uses-permission-sdk-23 instead [Deprecated]\n"
+                + "    <uses-permission-sdk-m android:name=\"foo.bar.BAZ\" />\n"
+                + "    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "0 errors, 1 warnings\n",
+                lintProject(
+                        xml("AndroidManifest.xml", ""
+                                        + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                        + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                                        + "    package=\"test.pkg\">\n"
+                                        + "\n"
+                                        + "    <uses-sdk android:minSdkVersion=\"4\" />\n"
+                                        + "    <uses-permission android:name=\"foo.bar.BAZ\" />\n"
+                                        + "    <uses-permission-sdk-23 android:name=\"foo.bar.BAZ\" />\n"
+                                        + "    <uses-permission-sdk-m android:name=\"foo.bar.BAZ\" />\n"
+                                        + "\n"
+                                        + "    <application\n"
+                                        + "        android:icon=\"@drawable/ic_launcher\"\n"
+                                        + "        android:label=\"@string/app_name\" >\n"
+                                        + "        <activity\n"
+                                        + "            android:name=\".BytecodeTestsActivity\"\n"
+                                        + "            android:label=\"@string/app_name\" >\n"
+                                        + "            <intent-filter>\n"
+                                        + "                <action android:name=\"android.intent.action.MAIN\" />\n"
+                                        + "\n"
+                                        + "                <category android:name=\"android.intent.category.LAUNCHER\" />\n"
+                                        + "            </intent-filter>\n"
+                                        + "        </activity>\n"
+                                        + "    </application>\n"
+                                        + "\n"
+                                        + "</manifest>\n"
+                        )
+                ));
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DetectMissingPrefixTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DetectMissingPrefixTest.java
index ed10742..4682eb2 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DetectMissingPrefixTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DetectMissingPrefixTest.java
@@ -157,4 +157,24 @@
                         + "    </LinearLayout>\n"
                         + "</layout>")));
     }
+
+    public void testAppCompat() throws Exception {
+        // Regression test for https://code.google.com/p/android/issues/detail?id=201790
+        assertEquals("No warnings.",
+                lintProject(xml("res/layout/app_compat.xml", ""
+                        + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                        + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                        + "    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n"
+                        + "    android:layout_width=\"match_parent\"\n"
+                        + "    android:layout_height=\"match_parent\"\n"
+                        + "    android:orientation=\"vertical\">\n"
+                        + "\n"
+                        + "    <ImageButton\n"
+                        + "        android:id=\"@+id/vote_button\"\n"
+                        + "        android:layout_width=\"wrap_content\"\n"
+                        + "        android:layout_height=\"wrap_content\"\n"
+                        + "        app:srcCompat=\"@mipmap/ic_launcher\" />\n"
+                        + "\n"
+                        + "</LinearLayout>")));
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DuplicateResourceDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DuplicateResourceDetectorTest.java
index fc6b042..b51a436 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DuplicateResourceDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DuplicateResourceDetectorTest.java
@@ -129,6 +129,23 @@
             lintProject("res/values/refs.xml"));
     }
 
+    public void testPublic() throws Exception {
+        assertEquals(""
+                + "No warnings.",
+
+                lintProject(
+                        xml("res/values/refs.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<resources>\n"
+                                + "    <item type='dimen' name='largePadding'>20dp</item>\n"
+                                + "    <item type='dimen' name='smallPadding'>15dp</item>\n"
+                                + "    <public type='dimen' name='largePadding' />"
+                                + "    <public type='string' name='largePadding' />"
+                                + "    <public type='dimen' name='smallPadding' />"
+                                + "    <public type='dimen' name='smallPadding' />"
+                                + "</resources>\n")));
+    }
+
     public void testGetExpectedType() {
         assertEquals("string", DuplicateResourceDetector.getExpectedType(
                 "Unexpected resource reference type; expected value of type `@string/`", RAW));
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/FragmentDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/FragmentDetectorTest.java
index 150b772..42e8e7a 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/FragmentDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/FragmentDetectorTest.java
@@ -18,7 +18,7 @@
 
 import com.android.tools.lint.detector.api.Detector;
 
-@SuppressWarnings("javadoc")
+@SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName", "MethodMayBeStatic"})
 public class FragmentDetectorTest extends AbstractCheckTest {
     @Override
     protected Detector getDetector() {
@@ -57,4 +57,24 @@
                 "bytecode/FragmentTest$NotAFragment.class.data=>bin/classes/test/pkg/FragmentTest$NotAFragment.class",
                 "bytecode/FragmentTest.java.txt=>src/test/pkg/FragmentTest.java"));
     }
+
+    public void testAnonymousInnerClass() throws Exception {
+        assertEquals(""
+                + "src/test/pkg/Parent.java:7: Error: Fragments should be static such that they can be re-instantiated by the system, and anonymous classes are not static [ValidFragment]\n"
+                + "        return new Fragment() {\n"
+                + "                   ~~~~~~~~\n"
+                + "1 errors, 0 warnings\n",
+
+                lintProject(java("src/test/pkg/Parent.java", ""
+                        + "package test.pkg;\n"
+                        + "\n"
+                        + "import android.app.Fragment;\n"
+                        + "\n"
+                        + "public class Parent {\n"
+                        + "    public Fragment method() {\n"
+                        + "        return new Fragment() {\n"
+                        + "        };\n"
+                        + "    }\n"
+                        + "}\n")));
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GetSignaturesDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GetSignaturesDetectorTest.java
index 12acffc..8b3ffad 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GetSignaturesDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GetSignaturesDetectorTest.java
@@ -25,6 +25,13 @@
         return new GetSignaturesDetector();
     }
 
+    @Override
+    protected boolean allowCompilationErrors() {
+        // Some of these unit tests are still relying on source code that references
+        // unresolved symbols etc.
+        return true;
+    }
+
     public void testLintWarningOnSingleGetSignaturesFlag() throws Exception {
         assertEquals(
                 "src/test/pkg/GetSignaturesSingleFlagTest.java:9: Information: Reading app signatures from getPackageInfo: The app signatures could be exploited if not validated properly; see issue explanation for details. [PackageManagerGetSignatures]\n"
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GradleDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GradleDetectorTest.java
index 4ac43b7..4343b3d 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GradleDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GradleDetectorTest.java
@@ -24,6 +24,7 @@
 import static com.android.tools.lint.checks.GradleDetector.DEPRECATED;
 import static com.android.tools.lint.checks.GradleDetector.GRADLE_GETTER;
 import static com.android.tools.lint.checks.GradleDetector.GRADLE_PLUGIN_COMPATIBILITY;
+import static com.android.tools.lint.checks.GradleDetector.NOT_INTERPOLATED;
 import static com.android.tools.lint.checks.GradleDetector.PATH;
 import static com.android.tools.lint.checks.GradleDetector.PLUS;
 import static com.android.tools.lint.checks.GradleDetector.REMOTE_VERSION;
@@ -222,7 +223,7 @@
     public void test() throws Exception {
         mEnabled = Sets.newHashSet(COMPATIBILITY, DEPRECATED, DEPENDENCY, PLUS);
         assertEquals(""
-            + "build.gradle:25: Error: This support library should not use a lower version (13) than the targetSdkVersion (17) [GradleCompatible]\n"
+            + "build.gradle:25: Error: This support library should not use a different version (13) than the compileSdkVersion (19) [GradleCompatible]\n"
             + "    compile 'com.android.support:appcompat-v7:13.0.0'\n"
             + "    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
             + "build.gradle:1: Warning: 'android' is deprecated; use 'com.android.application' instead [GradleDeprecated]\n"
@@ -259,7 +260,7 @@
     public void testIncompatiblePlugin() throws Exception {
         mEnabled = Collections.singleton(GRADLE_PLUGIN_COMPATIBILITY);
         assertEquals(""
-                + "build.gradle:6: Error: You must use a newer version of the Android Gradle plugin. The minimum supported version is " + GRADLE_PLUGIN_MINIMUM_VERSION + " and the recommended version is " + GRADLE_PLUGIN_RECOMMENDED_VERSION + " [AndroidGradlePluginVersion]\n"
+                + "build.gradle:6: Error: You must use a newer version of the Android Gradle plugin. The minimum supported version is " + GRADLE_PLUGIN_MINIMUM_VERSION + " and the recommended version is " + GRADLE_PLUGIN_RECOMMENDED_VERSION + " [GradlePluginVersion]\n"
                 + "    classpath 'com.android.tools.build:gradle:0.1.0'\n"
                 + "    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
                 + "1 errors, 0 warnings\n",
@@ -330,7 +331,7 @@
         // Regression test for https://code.google.com/p/android/issues/detail?id=77594
         mEnabled = Collections.singleton(DEPENDENCY);
         assertEquals(""
-                + "build.gradle:13: Warning: A newer version of com.google.code.gson:gson than 2.2 is available: 2.3 [GradleDependency]\n"
+                + "build.gradle:13: Warning: A newer version of com.google.code.gson:gson than 2.2 is available: 2.4 [GradleDependency]\n"
                 + "    compile 'com.google.code.gson:gson:2.2'\n"
                 + "    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
                 + "0 errors, 1 warnings\n",
@@ -386,7 +387,11 @@
                 + "build.gradle:10: Warning: Avoid using + in version numbers; can lead to unpredictable and unrepeatable builds (com.android.support:support-v4:21.0.+) [GradleDynamicVersion]\n"
                 + "    compile group: 'com.android.support', name: 'support-v4', version: '21.0.+'\n"
                 + "    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
-                + "0 errors, 2 warnings\n",
+                + "build.gradle:11: Warning: Avoid using + in version numbers; can lead to unpredictable and unrepeatable builds (com.android.support:appcompat-v7:+@aar) [GradleDynamicVersion]\n"
+                + "    compile 'com.android.support:appcompat-v7:+@aar'\n"
+                + "    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "0 errors, 3 warnings\n",
+
 
                 lintProject("gradle/Plus.gradle=>build.gradle"));
     }
@@ -570,6 +575,24 @@
                 lintProject("gradle/PlayServices2.gradle=>build.gradle"));
     }
 
+    public void testWrongQuotes() throws Exception {
+        mEnabled = Collections.singleton(NOT_INTERPOLATED);
+        assertEquals(""
+                        + "build.gradle:5: Error: It looks like you are trying to substitute a version variable, but using single quotes ('). For Groovy string interpolation you must use double quotes (\"). [NotInterpolated]\n"
+                        + "    compile 'com.android.support:design:${supportLibVersion}'\n"
+                        + "    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                        + "1 errors, 0 warnings\n",
+
+                lintProject(source("build.gradle", ""
+                        + "ext {\n"
+                        + "    supportLibVersion = \"23.1.1\"\n"
+                        + "}\n"
+                        + "dependencies {\n"
+                        + "    compile 'com.android.support:design:${supportLibVersion}'\n"
+                        + "    compile \"com.android.support:appcompat-v7:${supportLibVersion}\"\n"
+                        + "}\n")));
+    }
+
     @Override
     protected void checkReportedError(@NonNull Context context, @NonNull Issue issue,
             @NonNull Severity severity, @Nullable Location location, @NonNull String message) {
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/HandlerDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/HandlerDetectorTest.java
index aa52ba2..3211e5b 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/HandlerDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/HandlerDetectorTest.java
@@ -26,23 +26,59 @@
     }
 
     public void testRegistered() throws Exception {
-        assertEquals(
-            "src/test/pkg/HandlerTest.java:12: Warning: This Handler class should be static or leaks might occur (test.pkg.HandlerTest.Inner) [HandlerLeak]\n" +
-            "    public class Inner extends Handler { // ERROR\n" +
-            "                 ~~~~~\n" +
-            "src/test/pkg/HandlerTest.java:18: Warning: This Handler class should be static or leaks might occur (new android.os.Handler(){}) [HandlerLeak]\n" +
-            "        Handler anonymous = new Handler() { // ERROR\n" +
-            "                                          ^\n" +
-            "0 errors, 2 warnings\n",
+        assertEquals(""
+                + "src/test/pkg/HandlerTest.java:12: Warning: This Handler class should be static or leaks might occur (test.pkg.HandlerTest.Inner) [HandlerLeak]\n"
+                + "    public class Inner extends Handler { // ERROR\n"
+                + "                 ~~~~~\n"
+                + "src/test/pkg/HandlerTest.java:18: Warning: This Handler class should be static or leaks might occur (anonymous android.os.Handler) [HandlerLeak]\n"
+                + "        Handler anonymous = new Handler() { // ERROR\n"
+                + "                            ~~~~~~~~~~~\n"
+                + "0 errors, 2 warnings\n",
 
             lintProject(
-                "bytecode/HandlerTest.java.txt=>src/test/pkg/HandlerTest.java",
-                "bytecode/HandlerTest.class.data=>bin/classes/test/pkg/HandlerTest.class",
-                "bytecode/HandlerTest$Inner.class.data=>bin/classes/test/pkg/HandlerTest$Inner.class",
-                "bytecode/HandlerTest$StaticInner.class.data=>bin/classes/test/pkg/HandlerTest$StaticInner.class",
-                "bytecode/HandlerTest$WithArbitraryLooper.class.data=>bin/classes/test/pkg/HandlerTest$WithArbitraryLooper.class",
-                "bytecode/HandlerTest$1.class.data=>bin/classes/test/pkg/HandlerTest$1.class",
-                "bytecode/HandlerTest$2.class.data=>bin/classes/test/pkg/HandlerTest$2.class"));
+                    java("src/test/pkg/HandlerTest.java", ""
+                            + "package test.pkg;\n"
+                            + "import android.os.Looper;\n"
+                            + "import android.os.Handler;\n"
+                            + "import android.os.Message;\n"
+                            + "\n"
+                            + "public class HandlerTest extends Handler { // OK\n"
+                            + "    public static class StaticInner extends Handler { // OK\n"
+                            + "        public void dispatchMessage(Message msg) {\n"
+                            + "            super.dispatchMessage(msg);\n"
+                            + "        };\n"
+                            + "    }\n"
+                            + "    public class Inner extends Handler { // ERROR\n"
+                            + "        public void dispatchMessage(Message msg) {\n"
+                            + "            super.dispatchMessage(msg);\n"
+                            + "        };\n"
+                            + "    }\n"
+                            + "    void method() {\n"
+                            + "        Handler anonymous = new Handler() { // ERROR\n"
+                            + "            public void dispatchMessage(Message msg) {\n"
+                            + "                super.dispatchMessage(msg);\n"
+                            + "            };\n"
+                            + "        };\n"
+                            + "\n"
+                            + "        Looper looper = null;\n"
+                            + "        Handler anonymous2 = new Handler(looper) { // OK\n"
+                            + "            public void dispatchMessage(Message msg) {\n"
+                            + "                super.dispatchMessage(msg);\n"
+                            + "            };\n"
+                            + "        };\n"
+                            + "    }\n"
+                            + "\n"
+                            + "    public class WithArbitraryLooper extends Handler {\n"
+                            + "        public WithArbitraryLooper(String unused, Looper looper) { // OK\n"
+                            + "            super(looper, null);\n"
+                            + "        }\n"
+                            + "\n"
+                            + "        public void dispatchMessage(Message msg) {\n"
+                            + "            super.dispatchMessage(msg);\n"
+                            + "        };\n"
+                            + "    }\n"
+                            + "}\n")
+            ));
     }
 
     public void testSuppress() throws Exception {
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/HardcodedValuesDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/HardcodedValuesDetectorTest.java
index 8b9163a..e1d5be4 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/HardcodedValuesDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/HardcodedValuesDetectorTest.java
@@ -19,7 +19,7 @@
 import com.android.tools.lint.detector.api.Detector;
 
 @SuppressWarnings("javadoc")
-public class HardcodedValuesDetectorTest  extends AbstractCheckTest {
+public class HardcodedValuesDetectorTest extends AbstractCheckTest {
     @Override
     protected Detector getDetector() {
         return new HardcodedValuesDetector();
@@ -79,4 +79,95 @@
 
                 lintFiles("res/layout/ignores2.xml"));
     }
+
+    public void testSkippingPlaceHolders() throws Exception {
+        assertEquals("No warnings.",
+                lintProject(
+                        xml("res/layout/test.xml", ""
+                        + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                        + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                        + "    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n"
+                        + "    xmlns:tools=\"http://schemas.android.com/tools\"\n"
+                        + "    android:layout_width=\"match_parent\"\n"
+                        + "    android:layout_height=\"match_parent\">\n"
+                        + "\n"
+                        + "    <TextView\n"
+                        + "        android:id=\"@+id/textView\"\n"
+                        + "        android:layout_width=\"wrap_content\"\n"
+                        + "        android:layout_height=\"wrap_content\"\n"
+                        + "        android:text=\"Hello World!\" />\n"
+                        + "\n"
+                        + "    <Button\n"
+                        + "        android:id=\"@+id/button\"\n"
+                        + "        android:layout_width=\"wrap_content\"\n"
+                        + "        android:layout_height=\"wrap_content\"\n"
+                        + "        android:text=\"New Button\" />\n"
+                        + "\n"
+                        + "    <TextView\n"
+                        + "        android:id=\"@+id/textView2\"\n"
+                        + "        android:layout_width=\"wrap_content\"\n"
+                        + "        android:layout_height=\"wrap_content\"\n"
+                        + "        android:text=\"Large Text\"\n"
+                        + "        android:textAppearance=\"?android:attr/textAppearanceLarge\" />\n"
+                        + "\n"
+                        + "    <Button\n"
+                        + "        android:id=\"@+id/button2\"\n"
+                        + "        style=\"?android:attr/buttonStyleSmall\"\n"
+                        + "        android:layout_width=\"wrap_content\"\n"
+                        + "        android:layout_height=\"wrap_content\"\n"
+                        + "        android:text=\"New Button\" />\n"
+                        + "\n"
+                        + "    <CheckBox\n"
+                        + "        android:id=\"@+id/checkBox\"\n"
+                        + "        android:layout_width=\"wrap_content\"\n"
+                        + "        android:layout_height=\"wrap_content\"\n"
+                        + "        android:text=\"New CheckBox\" />\n"
+                        + "\n"
+                        + "    <TextView\n"
+                        + "        android:id=\"@+id/textView3\"\n"
+                        + "        android:layout_width=\"wrap_content\"\n"
+                        + "        android:layout_height=\"wrap_content\"\n"
+                        + "        android:text=\"New Text\" />\n"
+                        + "</LinearLayout>\n")));
+    }
+
+    public void testAppRestrictions() throws Exception {
+        // Sample from https://developer.android.com/samples/AppRestrictionSchema/index.html
+        assertEquals(""
+                        + "res/xml/app_restrictions.xml:12: Warning: [I18N] Hardcoded string \"Hardcoded description\", should use @string resource [HardcodedText]\n"
+                        + "        android:description=\"Hardcoded description\"\n"
+                        + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                        + "res/xml/app_restrictions.xml:15: Warning: [I18N] Hardcoded string \"Hardcoded title\", should use @string resource [HardcodedText]\n"
+                        + "        android:title=\"Hardcoded title\"/>\n"
+                        + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                        + "0 errors, 2 warnings\n",
+                lintProject(
+                        xml("res/xml/app_restrictions.xml", ""
+                                + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+                                + " \n"
+                                + "    <restriction\n"
+                                + "        android:defaultValue=\"@bool/default_can_say_hello\"\n"
+                                + "        android:description=\"@string/description_can_say_hello\"\n"
+                                + "        android:key=\"can_say_hello\"\n"
+                                + "        android:restrictionType=\"bool\"\n"
+                                + "        android:title=\"@string/title_can_say_hello\"/>\n"
+                                + " \n"
+                                + "    <restriction\n"
+                                + "        android:defaultValue=\"Hardcoded default value\"\n"
+                                + "        android:description=\"Hardcoded description\"\n"
+                                + "        android:key=\"message\"\n"
+                                + "        android:restrictionType=\"string\"\n"
+                                + "        android:title=\"Hardcoded title\"/>\n"
+                                + " \n"
+                                + "</restrictions>"),
+                        xml("res/xml/random_file.xml", ""
+                                + "<myRoot xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+                                + " \n"
+                                + "    <myElement\n"
+                                + "        android:description=\"Hardcoded description\"\n"
+                                + "        android:title=\"Hardcoded title\"/>\n"
+                                + " \n"
+                                + "</myRoot>")
+                ));
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/IconDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/IconDetectorTest.java
index 17703af..dcad992 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/IconDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/IconDetectorTest.java
@@ -86,6 +86,13 @@
     }
 
     @Override
+    protected boolean allowCompilationErrors() {
+        // Some of these unit tests are still relying on source code that references
+        // unresolved symbols etc.
+        return true;
+    }
+
+    @Override
     protected TestConfiguration getConfiguration(LintClient client, Project project) {
         return new TestConfiguration(client, project, null) {
             @Override
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/InvalidPackageDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/InvalidPackageDetectorTest.java
index 02cddae..b3daf88 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/InvalidPackageDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/InvalidPackageDetectorTest.java
@@ -16,7 +16,13 @@
 
 package com.android.tools.lint.checks;
 
+import com.android.annotations.NonNull;
 import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Project;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
 
 @SuppressWarnings("javadoc")
 public class InvalidPackageDetectorTest extends AbstractCheckTest {
@@ -94,4 +100,43 @@
                 "bytecode/dagger-compiler-1.2.1-subset.jar.data=>libs/dagger-compiler-1.2.1.jar"
             ));
     }
+
+    public void testSkipProvidedLibraries() throws Exception {
+        // Regression test for https://code.google.com/p/android/issues/detail?id=187191
+        assertEquals("No warnings.",
+
+                lintProject(
+                        "apicheck/minsdk14.xml=>AndroidManifest.xml",
+                        "apicheck/layout.xml=>res/layout/layout.xml",
+                        "apicheck/themes.xml=>res/values/themes.xml",
+                        "apicheck/themes.xml=>res/color/colors.xml",
+                        "apicheck/unsupported.jar.data=>libs/unsupported.jar"
+                ));
+    }
+
+    @Override
+    protected TestLintClient createClient() {
+        if ("testSkipProvidedLibraries".equals(getName())) {
+            // Set up a mock project model for the resource configuration test(s)
+            // where we provide a subset of densities to be included
+            return new TestLintClient() {
+                @NonNull
+                @Override
+                protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+                    return new Project(this, dir, referenceDir) {
+                        @NonNull
+                        @Override
+                        public List<File> getJavaLibraries(boolean includeProvided) {
+                            if (!includeProvided) {
+                                return Collections.emptyList();
+                            }
+                            return super.getJavaLibraries(true);
+                        }
+                    };
+                }
+            };
+        }
+
+        return super.createClient();
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/JavaPerformanceDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/JavaPerformanceDetectorTest.java
index 60ca2a0..e7ba0ca 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/JavaPerformanceDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/JavaPerformanceDetectorTest.java
@@ -25,6 +25,13 @@
         return new JavaPerformanceDetector();
     }
 
+    @Override
+    protected boolean allowCompilationErrors() {
+        // Some of these unit tests are still relying on source code that references
+        // unresolved symbols etc.
+        return true;
+    }
+
     public void test() throws Exception {
         assertEquals(
             "src/test/pkg/JavaPerformanceTest.java:28: Warning: Avoid object allocations during draw/layout operations (preallocate and reuse instead) [DrawAllocation]\n" +
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LayoutConsistencyDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LayoutConsistencyDetectorTest.java
index 211af9d..b42022e 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LayoutConsistencyDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LayoutConsistencyDetectorTest.java
@@ -25,6 +25,13 @@
         return new LayoutConsistencyDetector();
     }
 
+    @Override
+    protected boolean allowCompilationErrors() {
+        // Some of these unit tests are still relying on source code that references
+        // unresolved symbols etc.
+        return true;
+    }
+
     public void test() throws Exception {
         assertEquals(""
                 + "res/layout/layout1.xml:11: Warning: The id \"button1\" in layout \"layout1\" is missing from the following layout configurations: layout-xlarge (present in layout) [InconsistentLayout]\n"
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LayoutInflationDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LayoutInflationDetectorTest.java
index ca1f290..606c945 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LayoutInflationDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LayoutInflationDetectorTest.java
@@ -29,6 +29,13 @@
         return new LayoutInflationDetector();
     }
 
+    @Override
+    protected boolean allowCompilationErrors() {
+        // Some of these unit tests are still relying on source code that references
+        // unresolved symbols etc.
+        return true;
+    }
+
     public void test() throws Exception {
         assertEquals(""
                 + "src/test/pkg/LayoutInflationTest.java:13: Warning: Avoid passing null as the view root (needed to resolve layout parameters on the inflated layout's root element) [InflateParams]\n"
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LocaleDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LocaleDetectorTest.java
index d34ce36..dd5231a 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LocaleDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LocaleDetectorTest.java
@@ -18,7 +18,7 @@
 
 import com.android.tools.lint.detector.api.Detector;
 
-@SuppressWarnings("javadoc")
+@SuppressWarnings("ALL")
 public class LocaleDetectorTest extends AbstractCheckTest {
     @Override
     protected Detector getDetector() {
@@ -26,42 +26,98 @@
     }
 
     public void test() throws Exception {
-        assertEquals(
-            "src/test/pkg/LocaleTest.java:11: Warning: Implicitly using the default locale is a common source of bugs: Use toUpperCase(Locale) instead [DefaultLocale]\n" +
-            "        System.out.println(\"WRONG\".toUpperCase());\n" +
-            "                                   ~~~~~~~~~~~\n" +
-            "src/test/pkg/LocaleTest.java:16: Warning: Implicitly using the default locale is a common source of bugs: Use toLowerCase(Locale) instead [DefaultLocale]\n" +
-            "        System.out.println(\"WRONG\".toLowerCase());\n" +
-            "                                   ~~~~~~~~~~~\n" +
-            "src/test/pkg/LocaleTest.java:20: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n" +
-            "        String.format(\"WRONG: %f\", 1.0f); // Implies locale\n" +
-            "               ~~~~~~\n" +
-            "src/test/pkg/LocaleTest.java:21: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n" +
-            "        String.format(\"WRONG: %1$f\", 1.0f);\n" +
-            "               ~~~~~~\n" +
-            "src/test/pkg/LocaleTest.java:22: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n" +
-            "        String.format(\"WRONG: %e\", 1.0f);\n" +
-            "               ~~~~~~\n" +
-            "src/test/pkg/LocaleTest.java:23: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n" +
-            "        String.format(\"WRONG: %d\", 1.0f);\n" +
-            "               ~~~~~~\n" +
-            "src/test/pkg/LocaleTest.java:24: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n" +
-            "        String.format(\"WRONG: %g\", 1.0f);\n" +
-            "               ~~~~~~\n" +
-            "src/test/pkg/LocaleTest.java:25: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n" +
-            "        String.format(\"WRONG: %g\", 1.0f);\n" +
-            "               ~~~~~~\n" +
-            "src/test/pkg/LocaleTest.java:26: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n" +
-            "        String.format(\"WRONG: %1$tm %1$te,%1$tY\",\n" +
-            "               ~~~~~~\n" +
-            "0 errors, 9 warnings\n",
+        assertEquals(""
+                + "src/test/pkg/LocaleTest.java:11: Warning: Implicitly using the default locale is a common source of bugs: Use toUpperCase(Locale) instead [DefaultLocale]\n"
+                + "        System.out.println(\"WRONG\".toUpperCase());\n"
+                + "                                   ~~~~~~~~~~~\n"
+                + "src/test/pkg/LocaleTest.java:16: Warning: Implicitly using the default locale is a common source of bugs: Use toLowerCase(Locale) instead [DefaultLocale]\n"
+                + "        System.out.println(\"WRONG\".toLowerCase());\n"
+                + "                                   ~~~~~~~~~~~\n"
+                + "src/test/pkg/LocaleTest.java:20: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n"
+                + "        String.format(\"WRONG: %f\", 1.0f); // Implies locale\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/LocaleTest.java:21: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n"
+                + "        String.format(\"WRONG: %1$f\", 1.0f);\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/LocaleTest.java:22: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n"
+                + "        String.format(\"WRONG: %e\", 1.0f);\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/LocaleTest.java:23: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n"
+                + "        String.format(\"WRONG: %d\", 1.0f);\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/LocaleTest.java:24: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n"
+                + "        String.format(\"WRONG: %g\", 1.0f);\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/LocaleTest.java:25: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n"
+                + "        String.format(\"WRONG: %g\", 1.0f);\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/LocaleTest.java:26: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n"
+                + "        String.format(\"WRONG: %1$tm %1$te,%1$tY\",\n"
+                + "        ^\n"
+                + "0 errors, 9 warnings\n"
+,
 
             lintProject(
-                    "bytecode/.classpath=>.classpath",
-                    "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
-                    "res/layout/onclick.xml=>res/layout/onclick.xml",
-                    "bytecode/LocaleTest.java.txt=>src/test/pkg/LocaleTest.java",
-                    "bytecode/LocaleTest.class.data=>bin/classes/test/pkg/LocaleTest.class"
+                    java("src/test/pkg/LocaleTest.java", ""
+                            + "package test.pkg;\n"
+                            + "\n"
+                            + "import java.text.*;\n"
+                            + "import java.util.*;\n"
+                            + "\n"
+                            + "public class LocaleTest {\n"
+                            + "    public void testStrings() {\n"
+                            + "        System.out.println(\"OK\".toUpperCase(Locale.getDefault()));\n"
+                            + "        System.out.println(\"OK\".toUpperCase(Locale.US));\n"
+                            + "        System.out.println(\"OK\".toUpperCase(Locale.CHINA));\n"
+                            + "        System.out.println(\"WRONG\".toUpperCase());\n"
+                            + "\n"
+                            + "        System.out.println(\"OK\".toLowerCase(Locale.getDefault()));\n"
+                            + "        System.out.println(\"OK\".toLowerCase(Locale.US));\n"
+                            + "        System.out.println(\"OK\".toLowerCase(Locale.CHINA));\n"
+                            + "        System.out.println(\"WRONG\".toLowerCase());\n"
+                            + "\n"
+                            + "        String.format(Locale.getDefault(), \"OK: %f\", 1.0f);\n"
+                            + "        String.format(\"OK: %x %A %c %b %B %h %n %%\", 1, 2, 'c', true, false, 5);\n"
+                            + "        String.format(\"WRONG: %f\", 1.0f); // Implies locale\n"
+                            + "        String.format(\"WRONG: %1$f\", 1.0f);\n"
+                            + "        String.format(\"WRONG: %e\", 1.0f);\n"
+                            + "        String.format(\"WRONG: %d\", 1.0f);\n"
+                            + "        String.format(\"WRONG: %g\", 1.0f);\n"
+                            + "        String.format(\"WRONG: %g\", 1.0f);\n"
+                            + "        String.format(\"WRONG: %1$tm %1$te,%1$tY\",\n"
+                            + "                new GregorianCalendar(2012, GregorianCalendar.AUGUST, 27));\n"
+                            + "    }\n"
+                            + "\n"
+                            + "    @android.annotation.SuppressLint(\"NewApi\") // DateFormatSymbols requires API 9\n"
+                            + "    public void testSimpleDateFormat() {\n"
+                            + "        new SimpleDateFormat(); // WRONG\n"
+                            + "        new SimpleDateFormat(\"yyyy-MM-dd\"); // WRONG\n"
+                            + "        new SimpleDateFormat(\"yyyy-MM-dd\", DateFormatSymbols.getInstance()); // WRONG\n"
+                            + "        new SimpleDateFormat(\"yyyy-MM-dd\", Locale.US); // OK\n"
+                            + "    }\n"
+                            + "}\n")
                     ));
     }
+
+    public void testIgnoreLoggingWithoutLocale() throws Exception {
+        assertEquals("No warnings.",
+                lintProject(
+                        java("src/test/pkg/LogTest.java", ""
+                                + "package test.pkg;\n"
+                                + "\n"
+                                + "import android.util.Log;\n"
+                                + "\n"
+                                + "public class LogTest {\n"
+                                + "    private static final String TAG = \"mytag\";\n"
+                                + "\n"
+                                + "    // Don't flag String.format inside logging calls\n"
+                                + "    public void test(String dataItemName, int eventStatus) {\n"
+                                + "        if (Log.isLoggable(TAG, Log.DEBUG)) {\n"
+                                + "            Log.d(TAG, String.format(\"CQS:Event=%s, keeping status=%d\", dataItemName,\n"
+                                + "                    eventStatus));\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "}\n")
+                ));
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LogDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LogDetectorTest.java
index 7187b6c..48531fb 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LogDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LogDetectorTest.java
@@ -19,7 +19,7 @@
 import com.android.tools.lint.detector.api.Detector;
 
 @SuppressWarnings("javadoc")
-public class LogDetectorTest  extends AbstractCheckTest {
+public class LogDetectorTest extends AbstractCheckTest {
     @Override
     protected Detector getDetector() {
         return new LogDetector();
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ManifestDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ManifestDetectorTest.java
index db62652..ddd9050 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ManifestDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ManifestDetectorTest.java
@@ -503,6 +503,24 @@
                         "multiproject/library.properties=>build.gradle")); // dummy; only name counts
     }
 
+    public void testGradleOverrideManifestMergerOverride() throws Exception {
+        // Regression test for https://code.google.com/p/android/issues/detail?id=186762
+        mEnabled = Collections.singleton(ManifestDetector.GRADLE_OVERRIDES);
+        assertEquals("No warnings.",
+                lintProject(
+                        xml("AndroidManifest.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                                + "    xmlns:tools=\"http://schemas.android.com/tools\"\n"
+                                + "    package=\"test.pkg\">\n"
+                                + "\n"
+                                + "    <uses-sdk android:minSdkVersion=\"14\" tools:overrideLibrary=\"lib.pkg\" />\n"
+                                + "\n"
+                                + "</manifest>\n"),
+                        copy("multiproject/library.properties", "build.gradle") // dummy; only name counts));
+        ));
+    }
+
     public void testManifestPackagePlaceholder() throws Exception {
         mEnabled = Collections.singleton(ManifestDetector.GRADLE_OVERRIDES);
         assertEquals(""
@@ -585,6 +603,28 @@
                         + "</manifest>\n")));
     }
 
+    public void testFullBackupContentMissingInLibrary() throws Exception {
+        mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
+        assertEquals("No warnings.",
+
+                lintProjectIncrementally(
+                        "AndroidManifest.xml",
+                        xml("AndroidManifest.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                                + "    package=\"com.example.helloworld\" >\n"
+                                + "\n"
+                                + "    <application\n"
+                                + "        android:allowBackup=\"true\"\n"
+                                + "        android:fullBackupContent=\"@xml/backup\"\n"
+                                + "        android:label=\"@string/app_name\"\n"
+                                + "        android:theme=\"@style/AppTheme\" >\n"
+                                + "    </application>\n"
+                                + "\n"
+                                + "</manifest>\n"),
+                        copy("multiproject/library.properties", "project.properties")));
+    }
+
     public void testFullBackupContentOk() throws Exception {
         mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
         assertEquals("No warnings.",
@@ -1020,14 +1060,14 @@
                         public Variant getCurrentVariant() {
                             ProductFlavor flavor = mock(ProductFlavor.class);
                             if (getName().equals("ManifestDetectorTest_testGradleOverridesOk") ||
-                                    getName().equals(
-                                        "ManifestDetectorTest_testManifestPackagePlaceholder")) {
+                                    getName().equals("ManifestDetectorTest_testManifestPackagePlaceholder")) {
                                 when(flavor.getMinSdkVersion()).thenReturn(null);
                                 when(flavor.getTargetSdkVersion()).thenReturn(null);
                                 when(flavor.getVersionCode()).thenReturn(null);
                                 when(flavor.getVersionName()).thenReturn(null);
                             } else {
-                                assertEquals(getName(), "ManifestDetectorTest_testGradleOverrides");
+                                assertTrue(getName(), getName().equals("ManifestDetectorTest_testGradleOverrides") ||
+                                    getName().equals("ManifestDetectorTest_testGradleOverrideManifestMergerOverride"));
 
                                 ApiVersion apiMock = mock(ApiVersion.class);
                                 when(apiMock.getApiLevel()).thenReturn(5);
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ManifestResourceDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ManifestResourceDetectorTest.java
new file mode 100644
index 0000000..6e6ac31
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ManifestResourceDetectorTest.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2015 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;
+
+public class ManifestResourceDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new ManifestResourceDetector();
+    }
+
+    public void test() throws Exception {
+        assertEquals("No warnings.",
+                lintProjectIncrementally(
+                        "AndroidManifest.xml",
+                        xml("AndroidManifest.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                                + "    package=\"foo.bar2\"\n"
+                                + "    android:versionCode=\"1\"\n"
+                                + "    android:versionName=\"1.0\" >\n"
+                                + "\n"
+                                + "    <uses-sdk android:minSdkVersion=\"14\" />\n"
+                                + "\n"
+                                + "    <application\n"
+                                + "        android:icon=\"@drawable/ic_launcher\"\n"
+                                + "        android:label=\"@string/app_name\" >\n"
+                                + "        <receiver android:enabled=\"@bool/has_honeycomb\" android:name=\"com.google.android.apps.iosched.appwidget.ScheduleWidgetProvider\">\n"
+                                + "            <intent-filter>\n"
+                                + "                <action android:name=\"android.appwidget.action.APPWIDGET_UPDATE\"/>\n"
+                                + "            </intent-filter>\n"
+                                + "            <!-- This specifies the widget provider info -->\n"
+                                + "            <meta-data android:name=\"android.appwidget.provider\" android:resource=\"@xml/widgetinfo\"/>\n"
+                                + "        </receiver>\n"
+                                + "    </application>\n"
+                                + "\n"
+                                + "</manifest>"),
+                        xml("res/values/values.xml", ""
+                                + "<resources>\n"
+                                + "    <string name=\"app_name\">App Name (Default)</string>\n"
+                                + "    <bool name=\"has_honeycomb\">false</bool>"
+                                + "</resources>"),
+                        xml("res/values-v11/values.xml", ""
+                                + "<resources>\n"
+                                + "    <bool name=\"has_honeycomb\">true</bool>\n"
+                                + "</resources>"),
+                        xml("res/values-en-rUS/values.xml", ""
+                                + "<resources>\n"
+                                + "    <string name=\"app_name\">App Name (English)</string>\n"
+                                + "</resources>"),
+                        xml("res/values-xlarge/values.xml", ""
+                                + "<resources>\n"
+                                + "    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n"
+                                + "</resources>")
+                        ));
+    }
+
+    public void testInvalidManifestReference() throws Exception {
+        assertEquals(""
+                + "AndroidManifest.xml:6: Error: Resources referenced from the manifest cannot vary by configuration (except for version qualifiers, e.g. -v21.) Found variation in mcc [ManifestResource]\n"
+                + "    <application android:fullBackupContent=\"@xml/backup\">\n"
+                + "                                            ~~~~~~~~~~~\n"
+                + "AndroidManifest.xml:8: Error: Resources referenced from the manifest cannot vary by configuration (except for version qualifiers, e.g. -v21.) Found variation in en-rUS [ManifestResource]\n"
+                + "            android:process=\"@string/location_process\"\n"
+                + "                             ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "AndroidManifest.xml:9: Error: Resources referenced from the manifest cannot vary by configuration (except for version qualifiers, e.g. -v21.) Found variation in watch [ManifestResource]\n"
+                + "            android:enabled=\"@bool/enable_wearable_location_service\">\n"
+                + "                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "3 errors, 0 warnings\n",
+                lintProjectIncrementally(
+                        "AndroidManifest.xml",
+                        xml("AndroidManifest.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                                + "    package=\"test.pkg\">\n"
+                                + "    <uses-sdk android:minSdkVersion=\"14\" />\n"
+                                + "\n"
+                                + "    <application android:fullBackupContent=\"@xml/backup\">\n"
+                                + "        <service\n" // missing stuff here, not important for test
+                                + "            android:process=\"@string/location_process\"\n"
+                                + "            android:enabled=\"@bool/enable_wearable_location_service\">\n"
+                                + "        </service>"
+                                + "    </application>\n"
+                                + "\n"
+                                + "</manifest>"),
+                        xml("res/values/values.xml", ""
+                                + "<resources>\n"
+                                + "    <string name=\"location_process\">Location Process</string>\n"
+                                + "</resources>"),
+                        xml("res/values/bools.xml", ""
+                                + "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
+                                + "    <bool name=\"enable_wearable_location_service\">true</bool>\n"
+                                + "</resources>"),
+                        xml("res/values-en-rUS/values.xml", ""
+                                + "<resources>\n"
+                                + "    <string name=\"location_process\">Location Process (English)</string>\n"
+                                + "</resources>"),
+                        xml("res/values-watch/bools.xml", ""
+                                + "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
+                                + "    <bool name=\"enable_wearable_location_service\">false</bool>\n"
+                                + "</resources>"),
+                        xml("res/xml/backup.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<full-backup-content>\n"
+                                + "     <include domain=\"file\" path=\"dd\"/>\n"
+                                + "     <exclude domain=\"file\" path=\"dd/fo3o.txt\"/>\n"
+                                + "     <exclude domain=\"file\" path=\"dd/ss/foo.txt\"/>\n"
+                                + "</full-backup-content>"),
+                        xml("res/xml-mcc/backup.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<full-backup-content>\n"
+                                + "     <include domain=\"file\" path=\"mcc\"/>\n"
+                                + "</full-backup-content>")
+                ));
+    }
+
+    public void testBatchAnalysis() throws Exception {
+        assertEquals(""
+                + "AndroidManifest.xml:11: Error: Resources referenced from the manifest cannot vary by configuration (except for version qualifiers, e.g. -v21.) Found variation in mcc [ManifestResource]\n"
+                + "        android:fullBackupContent=\"@xml/backup\"\n"
+                + "                                   ~~~~~~~~~~~\n"
+                + "    res/xml-mcc/backup.xml:2: This value will not be used\n"
+                + "AndroidManifest.xml:21: Error: Resources referenced from the manifest cannot vary by configuration (except for version qualifiers, e.g. -v21.) Found variation in en-rUS [ManifestResource]\n"
+                + "            android:process=\"@string/location_process\"\n"
+                + "                             ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "    res/values-en-rUS/values.xml:2: This value will not be used\n"
+                + "AndroidManifest.xml:22: Error: Resources referenced from the manifest cannot vary by configuration (except for version qualifiers, e.g. -v21.) Found variation in watch [ManifestResource]\n"
+                + "            android:enabled=\"@bool/enable_wearable_location_service\">\n"
+                + "                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "    res/values-watch/bools.xml:2: This value will not be used\n"
+                + "3 errors, 0 warnings\n",
+                lintProject(
+                        xml("AndroidManifest.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                                + "    package=\"foo.bar2\"\n"
+                                + "    android:versionCode=\"1\"\n"
+                                + "    android:versionName=\"1.0\" >\n"
+                                + "\n"
+                                + "    <uses-sdk android:minSdkVersion=\"14\" />\n"
+                                + "\n"
+                                + "    <application\n"
+                                + "        android:icon=\"@drawable/ic_launcher\"\n"
+                                + "        android:fullBackupContent=\"@xml/backup\"\n"
+                                + "        android:label=\"@string/app_name\" >\n"
+                                + "        <receiver android:enabled=\"@bool/has_honeycomb\" android:name=\"com.google.android.apps.iosched.appwidget.ScheduleWidgetProvider\">\n"
+                                + "            <intent-filter>\n"
+                                + "                <action android:name=\"android.appwidget.action.APPWIDGET_UPDATE\"/>\n"
+                                + "            </intent-filter>\n"
+                                + "            <!-- This specifies the widget provider info -->\n"
+                                + "            <meta-data android:name=\"android.appwidget.provider\" android:resource=\"@xml/widgetinfo\"/>\n"
+                                + "        </receiver>\n"
+                                + "        <service\n"
+                                + "            android:process=\"@string/location_process\"\n"
+                                + "            android:enabled=\"@bool/enable_wearable_location_service\">\n"
+                                + "        </service>"
+                                + "    </application>\n"
+                                + "\n"
+                                + "</manifest>"),
+                        xml("res/values/values.xml", ""
+                                + "<resources>\n"
+                                + "    <string name=\"location_process\">Location Process</string>\n"
+                                + "    <string name=\"app_name\">App Name (Default)</string>\n"
+                                + "    <bool name=\"has_honeycomb\">false</bool>"
+                                + "</resources>"),
+                        xml("res/values/bools.xml", ""
+                                + "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
+                                + "    <bool name=\"enable_wearable_location_service\">true</bool>\n"
+                                + "</resources>"),
+                        xml("res/values-en-rUS/values.xml", ""
+                                + "<resources>\n"
+                                + "    <string name=\"location_process\">Location Process (English)</string>\n"
+                                + "    <string name=\"app_name\">App Name (English)</string>\n"
+                                + "</resources>"),
+                        xml("res/values-watch/bools.xml", ""
+                                + "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
+                                + "    <bool name=\"enable_wearable_location_service\">false</bool>\n"
+                                + "</resources>"),
+                        xml("res/values-v11/values.xml", ""
+                                + "<resources>\n"
+                                + "    <bool name=\"has_honeycomb\">true</bool>\n"
+                                + "</resources>"),
+                        xml("res/values-xlarge/values.xml", ""
+                                + "<resources>\n"
+                                + "    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n"
+                                + "</resources>"),
+                        xml("res/xml/backup.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<full-backup-content>\n"
+                                + "     <include domain=\"file\" path=\"dd\"/>\n"
+                                + "     <exclude domain=\"file\" path=\"dd/fo3o.txt\"/>\n"
+                                + "     <exclude domain=\"file\" path=\"dd/ss/foo.txt\"/>\n"
+                                + "</full-backup-content>"),
+                        xml("res/xml-mcc/backup.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<full-backup-content>\n"
+                                + "     <include domain=\"file\" path=\"mcc\"/>\n"
+                                + "</full-backup-content>")
+                ));
+    }
+
+    public void testAllowPermissionNameLocalizations() throws Exception {
+        assertEquals("No warnings.",
+                lintProjectIncrementally(
+                        "AndroidManifest.xml",
+                        xml("AndroidManifest.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                                + "    package=\"foo.bar2\"\n"
+                                + "    android:versionCode=\"1\"\n"
+                                + "    android:versionName=\"1.0\" >\n"
+                                + "\n"
+                                + "    <permission-group android:name=\"android.permission-group.CONTACTS\"\n"
+                                + "        android:icon=\"@drawable/perm_group_contacts\"\n"
+                                + "        android:label=\"@string/permgrouplab_contacts\"\n"
+                                + "        android:description=\"@string/permgroupdesc_contacts\"\n"
+                                + "        android:priority=\"100\" />\n"
+                                + "\n"
+                                + "    <permission android:name=\"android.permission.READ_CONTACTS\"\n"
+                                + "        android:permissionGroup=\"android.permission-group.CONTACTS\"\n"
+                                + "        android:label=\"@string/permlab_readContacts\"\n"
+                                + "        android:description=\"@string/permdesc_readContacts\"\n"
+                                + "        android:protectionLevel=\"dangerous\" />"
+                                + "</manifest>"),
+                        xml("res/values/values.xml", ""
+                                + "<resources>\n"
+                                + "    <string name=\"permgrouplab_contacts\">Contacts</string>\n"
+                                + "    <string name=\"permgroupdesc_contacts\">access your contacts</string>\n"
+                                + "    <string name=\"permlab_readContacts\">read your contacts</string>\n"
+                                + "    <string name=\"permdesc_readContacts\">Allows the app to...</string>"
+                                + "</resources>"),
+                        xml("res/values-nb/values.xml", ""
+                                + "<resources>\n"
+                                + "    <string name=\"permgrouplab_contacts\">\"Kontakter\"</string>\n"
+                                + "    <string name=\"permgroupdesc_contacts\">\"se kontaktene dine\"</string>\n"
+                                + "    <string name=\"permlab_readContacts\">\"lese kontaktene dine\"</string>\n"
+                                + "    <string name=\"permdesc_readContacts\">\"Lar appen lese...</string>"
+                                + "</resources>")
+                ));
+
+    }
+}
\ No newline at end of file
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MathDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MathDetectorTest.java
index da12bda..f4453c7 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MathDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MathDetectorTest.java
@@ -18,50 +18,82 @@
 
 import com.android.tools.lint.detector.api.Detector;
 
-@SuppressWarnings("javadoc")
+@SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName"})
 public class MathDetectorTest extends AbstractCheckTest {
+
+    @Override
+    protected boolean allowCompilationErrors() {
+        // FloatMath methods were removed in API 23; the test below attempts to
+        // use API 3, but if not available in test environment ensure test doesn't fail.
+        return true;
+    }
+
+    private TestFile mTestFile = java("src/test/bytecode/MathTest.java", ""
+            + "package test.bytecode;\n"
+            + "\n"
+            + "import android.util.FloatMath;\n"
+            + "import static android.util.FloatMath.sin;\n"
+            + "\n"
+            + "//Test data for the MathDetector\n"
+            + "public class MathTest {\n"
+            + "    public float floatResult;\n"
+            + "    public double doubleResult;\n"
+            + "\n"
+            + "    public void floatToFloatTest(float x, double y, int z) {\n"
+            + "        floatResult = FloatMath.cos(x);\n"
+            + "        floatResult = FloatMath.sin((float) y);\n"
+            + "        floatResult = android.util.FloatMath.ceil((float) y);\n"
+            + "        System.out.println(FloatMath.floor(x));\n"
+            + "        System.out.println(FloatMath.sqrt(z));\n"
+            + "        floatResult = sin((float) y);\n"
+            + "\n"
+            + "        // No warnings for plain math\n"
+            + "        floatResult = (float) Math.cos(x);\n"
+            + "        floatResult = (float) java.lang.Math.sin(x);\n"
+            + "    }\n"
+            + "}\n");
+
+
     @Override
     protected Detector getDetector() {
         return new MathDetector();
     }
 
     public void test() throws Exception {
-        assertEquals(
-            "src/test/bytecode/MathTest.java:11: Warning: Use java.lang.Math#cos instead of android.util.FloatMath#cos() since it is faster as of API 8 [FloatMath]\n" +
-            "        floatResult = FloatMath.cos(x);\n" +
-            "                                ~~~\n" +
-            "src/test/bytecode/MathTest.java:12: Warning: Use java.lang.Math#sin instead of android.util.FloatMath#sin() since it is faster as of API 8 [FloatMath]\n" +
-            "        floatResult = FloatMath.sin((float) y);\n" +
-            "                                ~~~\n" +
-            "src/test/bytecode/MathTest.java:13: Warning: Use java.lang.Math#ceil instead of android.util.FloatMath#ceil() since it is faster as of API 8 [FloatMath]\n" +
-            "        floatResult = android.util.FloatMath.ceil((float) y);\n" +
-            "                                             ~~~~\n" +
-            "src/test/bytecode/MathTest.java:14: Warning: Use java.lang.Math#floor instead of android.util.FloatMath#floor() since it is faster as of API 8 [FloatMath]\n" +
-            "        System.out.println(FloatMath.floor(x));\n" +
-            "                                     ~~~~~\n" +
-            "src/test/bytecode/MathTest.java:15: Warning: Use java.lang.Math#sqrt instead of android.util.FloatMath#sqrt() since it is faster as of API 8 [FloatMath]\n" +
-            "        System.out.println(FloatMath.sqrt(z));\n" +
-            "                                     ~~~~\n" +
-            "0 errors, 5 warnings\n",
+        assertEquals(""
+                + "src/test/bytecode/MathTest.java:12: Warning: Use java.lang.Math#cos instead of android.util.FloatMath#cos() since it is faster as of API 8 [FloatMath]\n"
+                + "        floatResult = FloatMath.cos(x);\n"
+                + "                      ~~~~~~~~~~~~~\n"
+                + "src/test/bytecode/MathTest.java:13: Warning: Use java.lang.Math#sin instead of android.util.FloatMath#sin() since it is faster as of API 8 [FloatMath]\n"
+                + "        floatResult = FloatMath.sin((float) y);\n"
+                + "                      ~~~~~~~~~~~~~\n"
+                + "src/test/bytecode/MathTest.java:14: Warning: Use java.lang.Math#ceil instead of android.util.FloatMath#ceil() since it is faster as of API 8 [FloatMath]\n"
+                + "        floatResult = android.util.FloatMath.ceil((float) y);\n"
+                + "                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/bytecode/MathTest.java:15: Warning: Use java.lang.Math#floor instead of android.util.FloatMath#floor() since it is faster as of API 8 [FloatMath]\n"
+                + "        System.out.println(FloatMath.floor(x));\n"
+                + "                           ~~~~~~~~~~~~~~~\n"
+                + "src/test/bytecode/MathTest.java:16: Warning: Use java.lang.Math#sqrt instead of android.util.FloatMath#sqrt() since it is faster as of API 8 [FloatMath]\n"
+                + "        System.out.println(FloatMath.sqrt(z));\n"
+                + "                           ~~~~~~~~~~~~~~\n"
+                + "src/test/bytecode/MathTest.java:17: Warning: Use java.lang.Math#sin instead of android.util.FloatMath#sin() since it is faster as of API 8 [FloatMath]\n"
+                + "        floatResult = sin((float) y);\n"
+                + "                      ~~~~~~~~~~~~~~\n"
+                + "0 errors, 6 warnings\n",
 
             lintProject(
-                    "bytecode/.classpath=>.classpath",
-                    "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
-                    "bytecode/MathTest.java.txt=>src/test/bytecode/MathTest.java",
-                    "bytecode/MathTest.class.data=>bin/classes/test/bytecode/MathTest.class"
-                    ));
+                    mTestFile,
+                    source("project.properties", "target=android-19"),
+                    copy("apicheck/minsdk14.xml", "AndroidManifest.xml")));
     }
 
     public void testNoWarningsPreFroyo() throws Exception {
         assertEquals(
             "No warnings.",
 
-            lintProject(
-                    "bytecode/.classpath=>.classpath",
-                    "apicheck/minsdk2.xml=>AndroidManifest.xml",
-                    "bytecode/MathTest.java.txt=>src/test/bytecode/MathTest.java",
-                    "bytecode/MathTest.class.data=>bin/classes/test/bytecode/MathTest.class"
-                    ));
+            lintProject(mTestFile,
+                    source("project.properties", "target=android-3"),
+                    copy("apicheck/minsdk2.xml", "AndroidManifest.xml")));
     }
 
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MergeRootFrameLayoutDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MergeRootFrameLayoutDetectorTest.java
index 22a001f..391ae59 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MergeRootFrameLayoutDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MergeRootFrameLayoutDetectorTest.java
@@ -25,6 +25,12 @@
         return new MergeRootFrameLayoutDetector();
     }
 
+    @Override
+    protected boolean allowCompilationErrors() {
+        // Some of these unit tests are still relying on source code that references
+        // unresolved symbols etc.
+        return true;
+    }
 
     public void testMergeRefFromJava() throws Exception {
         assertEquals(
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MissingClassDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MissingClassDetectorTest.java
index 86b0494..dcc5a95 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MissingClassDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MissingClassDetectorTest.java
@@ -221,6 +221,86 @@
                 checkLint(Arrays.asList(master, library2, library)));
     }
 
+    public void testLibraryWithMissingClass() throws Exception {
+        mScopes = null;
+        mEnabled = Sets.newHashSet(MISSING);
+        assertEquals("No warnings.",
+                lintProject(
+                        xml("AndroidManifest.xml", ""
+                                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                                + "    package=\"test.pkg\"\n"
+                                + "    android:versionCode=\"1\"\n"
+                                + "    android:versionName=\"1.0\" >\n"
+                                + "\n"
+                                + "    <uses-sdk android:minSdkVersion=\"14\" />\n"
+                                + "\n"
+                                + "    <application\n"
+                                + "        android:icon=\"@drawable/ic_launcher\"\n"
+                                + "        android:label=\"@string/app_name\" >\n"
+                                + "        <service android:name=\".TestService\" />\n"
+                                + "\n"
+                                + "    </application>\n"
+                                + "\n"
+                                + "</manifest>"),
+                        // This is not the actual class that is present in AndroidManifest.xml
+                        copy("bytecode/TestProvider2.class.data",
+                                "bin/classes/test/pkg/TestProvider2.class"),
+                        copy("bytecode/.classpath", ".classpath"),
+                        // Note that the manifestmerger.enabled property is necessary for
+                        // the manifest scoped lint detectors to run.
+                        source("project.properties", ""
+                                + "target=android-14\n"
+                                + "android.library=true\n"
+                                + "manifestmerger.enabled=true\n"))
+        );
+    }
+
+    public void testLibraryWithMissingClassInApp() throws Exception {
+        mScopes = null;
+        mEnabled = Sets.newHashSet(MISSING);
+
+        File master = getProjectDir("MasterProject",
+                // Master project
+                xml("AndroidManifest.xml", ""
+                        + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                        + "    package=\"test.pkg.app\">\n"
+                        + "</manifest>"),
+                copy("bytecode/TestProvider2.class.data",
+                        "bin/classes/test/pkg/TestProvider2.class"),
+                projectProperties().dependsOn("../LibraryProject").manifestMerger(true).compileSdk(14)
+        );
+        File library = getProjectDir("LibraryProject",
+                // Library project
+                xml("AndroidManifest.xml", ""
+                        + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                        + "    package=\"test.pkg\"\n"
+                        + "    android:versionCode=\"1\"\n"
+                        + "    android:versionName=\"1.0\" >\n"
+                        + "\n"
+                        + "    <uses-sdk android:minSdkVersion=\"14\" />\n"
+                        + "\n"
+                        + "    <application\n"
+                        + "        android:icon=\"@drawable/ic_launcher\"\n"
+                        + "        android:label=\"@string/app_name\" >\n"
+                        + "        <service android:name=\".TestService\" />\n"
+                        + "\n"
+                        + "    </application>\n"
+                        + "\n"
+                        + "</manifest>"),
+                projectProperties().library(true).compileSdk(14),
+                copy("bytecode/TestProvider2.class.data",
+                        "bin/classes/test/pkg/TestProvider2.class"),
+                copy("bytecode/.classpath", ".classpath")
+        );
+
+        assertEquals(""
+                + "LibraryProject/AndroidManifest.xml:11: Error: Class referenced in the manifest, test.pkg.app.TestService, was not found in the project or the libraries [MissingRegistered]\n"
+                + "        <service android:name=\".TestService\" />\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "1 errors, 0 warnings\n",
+                checkLint(Arrays.asList(master, library)).replace("/TESTROOT/",""));
+    }
+
     public void testInnerClassStatic() throws Exception {
         mScopes = null;
         mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
@@ -448,7 +528,6 @@
                 ));
     }
 
-
     public void testMissingClass() throws Exception {
         mScopes = null;
         mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ParcelDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ParcelDetectorTest.java
index 49eb702..c2be37d 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ParcelDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ParcelDetectorTest.java
@@ -17,7 +17,7 @@
 
 import com.android.tools.lint.detector.api.Detector;
 
-@SuppressWarnings("javadoc")
+@SuppressWarnings({"javadoc", "override", "MethodMayBeStatic"})
 public class ParcelDetectorTest extends AbstractCheckTest {
     @Override
     protected Detector getDetector() {
@@ -47,4 +47,79 @@
                 "bytecode/MyParcelable5.class.data=>bin/classes/test/bytecode/MyParcelable5.class"
                 ));
     }
+
+    @SuppressWarnings("ClassNameDiffersFromFileName")
+    public void testInterfaceOnSuperClass() throws Exception {
+        // Regression test for https://code.google.com/p/android/issues/detail?id=171522
+        assertEquals(""
+                + "src/test/pkg/ParcelableDemo.java:14: Error: This class implements Parcelable but does not provide a CREATOR field [ParcelCreator]\n"
+                + "    private static class JustParcelable implements Parcelable {\n"
+                + "                         ~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ParcelableDemo.java:19: Error: This class implements Parcelable but does not provide a CREATOR field [ParcelCreator]\n"
+                + "    private static class JustParcelableSubclass extends JustParcelable {\n"
+                + "                         ~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ParcelableDemo.java:22: Error: This class implements Parcelable but does not provide a CREATOR field [ParcelCreator]\n"
+                + "    private static class ParcelableThroughAbstractSuper extends AbstractParcelable {\n"
+                + "                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ParcelableDemo.java:27: Error: This class implements Parcelable but does not provide a CREATOR field [ParcelCreator]\n"
+                + "    private static class ParcelableThroughInterface implements MoreThanParcelable {\n"
+                + "                         ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "4 errors, 0 warnings\n",
+
+                lintProject(
+                        java("src/test/pkg/ParcelableDemo.java", ""
+                                + "package test.pkg;\n"
+                                + "\n"
+                                + "import android.os.Parcel;\n"
+                                + "import android.os.Parcelable;\n"
+                                + "\n"
+                                + "public class ParcelableDemo {\n"
+                                + "    private interface MoreThanParcelable extends Parcelable {\n"
+                                + "        void somethingMore();\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    private abstract static class AbstractParcelable implements Parcelable {\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    private static class JustParcelable implements Parcelable {\n"
+                                + "        public int describeContents() {return 0;}\n"
+                                + "        public void writeToParcel(Parcel dest, int flags) {}\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    private static class JustParcelableSubclass extends JustParcelable {\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    private static class ParcelableThroughAbstractSuper extends AbstractParcelable {\n"
+                                + "        public int describeContents() {return 0;}\n"
+                                + "        public void writeToParcel(Parcel dest, int flags) {}\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    private static class ParcelableThroughInterface implements MoreThanParcelable {\n"
+                                + "        public int describeContents() {return 0;}\n"
+                                + "        public void writeToParcel(Parcel dest, int flags) {}\n"
+                                + "        public void somethingMore() {}\n"
+                                + "    }\n"
+                                + "}")
+                ));
+    }
+
+    @SuppressWarnings("ClassNameDiffersFromFileName")
+    public void testSpans() throws Exception {
+        // Regression test for https://code.google.com/p/android/issues/detail?id=192841
+        assertEquals("No warnings.",
+
+                lintProject(
+                        java("src/test/pkg/TestSpan.java", ""
+                                + "package test.pkg;\n"
+                                + "\n"
+                                + "import android.text.TextPaint;\n"
+                                + "import android.text.style.URLSpan;\n"
+                                + "\n"
+                                + "public class TestSpan extends URLSpan {\n"
+                                + "    public TestSpan(String url) {\n"
+                                + "        super(url);\n"
+                                + "    }\n"
+                                + "}")
+                ));
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PermissionRequirementTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PermissionRequirementTest.java
index 7992903..4ce0d07 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PermissionRequirementTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PermissionRequirementTest.java
@@ -150,6 +150,14 @@
         assertSame(BinaryOperator.LOGICAL_AND, req.getOperator());
     }
 
+    public void testSingleAsArray() {
+        // Annotations let you supply a single string to an array method
+        ResolvedAnnotation.Value values = new ResolvedAnnotation.Value("allOf",
+                "android.permission.ACCESS_FINE_LOCATION");
+        ResolvedAnnotation annotation = createAnnotation(PERMISSION_ANNOTATION, values);
+        assertTrue(PermissionRequirement.create(null, annotation).isSingle());
+    }
+
     public void testRevocable() {
         assertTrue(isRevocableSystemPermission("android.permission.ACCESS_FINE_LOCATION"));
         assertTrue(isRevocableSystemPermission("android.permission.ACCESS_COARSE_LOCATION"));
@@ -219,7 +227,7 @@
         }
         List<String> actual = Arrays.asList(REVOCABLE_PERMISSION_NAMES);
         if (!expected.equals(actual)) {
-            System.out.println("Correct list of exceptions:");
+            System.out.println("Correct list of permissions:");
             for (String name : expected) {
                 System.out.println("            \"" + name + "\",");
             }
@@ -242,7 +250,7 @@
         // TODO: We should ship this file with the SDK!
         File file = new File(top, "frameworks/base/core/res/AndroidManifest.xml");
         if (!file.exists()) {
-            System.out.println("Set $ANDROID_BUILD_TOP to point to the git repository to check permissions");
+            System.out.println("Set $ANDROID_BUILD_TOP to point to the git repository");
             return null;
         }
         boolean passedRuntimeHeader = false;
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PluralsDatabaseTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PluralsDatabaseTest.java
index 1ec507a..e63e6f2 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PluralsDatabaseTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PluralsDatabaseTest.java
@@ -43,6 +43,9 @@
 
 import junit.framework.TestCase;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
@@ -121,11 +124,11 @@
                 continue;
             }
             for (Quantity q : Quantity.values()) {
-                boolean mv1 = pdb.hasMultipleValuesForQuantity(language, q);
-                boolean mv2 = db.hasMultipleValuesForQuantity(language, q);
+                boolean mv1 = pdb.hasMultipleValuesForQuantity(language, q); // binary database
+                boolean mv2 = db.hasMultipleValuesForQuantity(language, q); // text database
                 if (mv1 != mv2) {
                     dumpDatabaseTables();
-                    assertEquals(language, mv1, mv2);
+                    assertEquals(language + " with quantity " + q, mv1, mv2);
                 }
                 if (mv2) {
                     String e1 = pdb.findIntegerExamples(language, q);
@@ -245,6 +248,7 @@
             assertEquals((int)languageIndices.get(language), index);
             index++;
         }
+        stripLastComma(sb);
         sb.append("\n};\n");
         System.out.println(sb);
 
@@ -304,6 +308,7 @@
 
             index++;
         }
+        stripLastComma(sb);
         sb.append("\n};\n");
         System.out.println(sb);
 
@@ -314,6 +319,24 @@
 
     }
 
+    private static String stripLastComma(String s) {
+        StringBuilder stringBuilder = new StringBuilder(s);
+        stripLastComma(stringBuilder);
+        return stringBuilder.toString();
+    }
+
+    private static void stripLastComma(@NonNull StringBuilder sb) {
+        for (int i = sb.length() - 1; i >= 1; i--) {
+            char c = sb.charAt(i);
+            if (!Character.isWhitespace(c)) {
+                if (c == ',') {
+                    sb.setLength(i);
+                }
+                break;
+            }
+        }
+    }
+
     private static Map<String, String> computeExamples(PluralsTextDatabase db, Quantity quantity,
             Set<String> sets, Map<String, String> languageMap) {
 
@@ -541,21 +564,23 @@
             //        one{"i = 0,1 and n != 0 @integer 1 @decimal 0.1~1.6"}
             //    }
             // since it looks to me like this only differs from 1 in the fractional part.
-            //
-            // This is encoded by looking at the rules; this is done by the unit test
-            // testDeriveMultiValueSetNames() (which ensures that the set is correct and if
-            // not computes the correct set of set names to use for the current plurals.txt
-            // database.
-
-            mMultiValueSetNames = Maps.newEnumMap(Quantity.class);
-            mMultiValueSetNames.put(Quantity.two, Sets.newHashSet("set21", "set22", "set30", "set32"));
-            mMultiValueSetNames.put(Quantity.one, Sets.newHashSet(
-                    "set1", "set11", "set12", "set13", "set14", "set2", "set20",
-                    "set21", "set22", "set26", "set27", "set29", "set30", "set32", "set5",
-                    "set6"));
-            mMultiValueSetNames.put(Quantity.zero, Sets.newHashSet("set14"));
-
             mSetNamePerLanguage = Maps.newHashMapWithExpectedSize(20);
+            mMultiValueSetNames = Maps.newEnumMap(Quantity.class);
+            Quantity[] quantities = new Quantity[] { Quantity.zero, Quantity.one, Quantity.two };
+            for (Quantity quantity : quantities) {
+                mMultiValueSetNames.put(quantity, Sets.<String>newHashSet());
+                for (String language : LocaleManager.getLanguageCodes()) {
+                    String examples = findIntegerExamples(language, quantity);
+                    if (examples != null && examples.indexOf(',') != -1) {
+                        String setName = getSetName(language);
+                        assertNotNull(setName);
+                        Set<String> set = mMultiValueSetNames.get(quantity);
+                        assertNotNull(set);
+                        set.add(setName);
+                    }
+                }
+            }
+
             mPlurals = Maps.newHashMapWithExpectedSize(20);
         }
 
@@ -578,7 +603,6 @@
             return null;
         }
 
-
         @NonNull
         private String getPluralsDescriptions() {
             if (mDescriptions == null) {
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PrivateResourceDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PrivateResourceDetectorTest.java
index 4df2fd2..0726b56 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PrivateResourceDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PrivateResourceDetectorTest.java
@@ -27,7 +27,9 @@
 import com.android.builder.model.AndroidLibrary;
 import com.android.builder.model.AndroidProject;
 import com.android.builder.model.Dependencies;
+import com.android.builder.model.MavenCoordinates;
 import com.android.builder.model.Variant;
+import com.android.ide.common.repository.GradleCoordinate;
 import com.android.testutils.TestUtils;
 import com.android.tools.lint.detector.api.Detector;
 import com.android.tools.lint.detector.api.Project;
@@ -48,9 +50,16 @@
         return new PrivateResourceDetector();
     }
 
+    @Override
+    protected boolean allowCompilationErrors() {
+        // Some of these unit tests are still relying on source code that references
+        // unresolved symbols etc.
+        return true;
+    }
+
     public void testPrivateInXml() throws Exception {
         assertEquals(""
-                + "res/layout/private.xml:11: Warning: The resource @string/my_private_string is marked as private in the library [PrivateResource]\n"
+                + "res/layout/private.xml:11: Warning: The resource @string/my_private_string is marked as private in com.android.tools:test-library [PrivateResource]\n"
                 + "            android:text=\"@string/my_private_string\" />\n"
                 + "                          ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
                 + "0 errors, 1 warnings\n",
@@ -74,15 +83,15 @@
                         + "</LinearLayout>\n")));
     }
 
+    @SuppressWarnings("ClassNameDiffersFromFileName")
     public void testPrivateInJava() throws Exception {
         assertEquals(""
-                + ""
-                + "src/test/pkg/Private.java:3: Warning: The resource @string/my_private_string is marked as private in the library [PrivateResource]\n"
+                + "src/test/pkg/Private.java:3: Warning: The resource @string/my_private_string is marked as private in com.android.tools:test-library [PrivateResource]\n"
                 + "        int x = R.string.my_private_string; // ERROR\n"
                 + "                ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
                 + "0 errors, 1 warnings\n",
                 lintProject(java("src/test/pkg/Private.java", ""
-                        + "public class PrivateResourceDetectorTest {\n"
+                        + "public class Private {\n"
                         + "    void test() {\n"
                         + "        int x = R.string.my_private_string; // ERROR\n"
                         + "        int y = R.string.my_public_string; // OK\n"
@@ -93,14 +102,14 @@
 
     public void testOverride() throws Exception {
         assertEquals(""
-                + "res/layout/my_private_layout.xml: Warning: Overriding @layout/my_private_layout which is marked as private in the library. If deliberate, use tools:override=\"true\", otherwise pick a different name. [PrivateResource]\n"
-                + "res/values/strings.xml:5: Warning: Overriding @string/my_private_string which is marked as private in the library. If deliberate, use tools:override=\"true\", otherwise pick a different name. [PrivateResource]\n"
+                + "res/layout/my_private_layout.xml: Warning: Overriding @layout/my_private_layout which is marked as private in com.android.tools:test-library. If deliberate, use tools:override=\"true\", otherwise pick a different name. [PrivateResource]\n"
+                + "res/values/strings.xml:5: Warning: Overriding @string/my_private_string which is marked as private in com.android.tools:test-library. If deliberate, use tools:override=\"true\", otherwise pick a different name. [PrivateResource]\n"
                 + "    <string name=\"my_private_string\">String 1</string>\n"
                 + "                  ~~~~~~~~~~~~~~~~~\n"
-                + "res/values/strings.xml:9: Warning: Overriding @string/my_private_string which is marked as private in the library. If deliberate, use tools:override=\"true\", otherwise pick a different name. [PrivateResource]\n"
+                + "res/values/strings.xml:9: Warning: Overriding @string/my_private_string which is marked as private in com.android.tools:test-library. If deliberate, use tools:override=\"true\", otherwise pick a different name. [PrivateResource]\n"
                 + "    <item type=\"string\" name=\"my_private_string\">String 1</item>\n"
                 + "                              ~~~~~~~~~~~~~~~~~\n"
-                + "res/values/strings.xml:12: Warning: Overriding @string/my_private_string which is marked as private in the library. If deliberate, use tools:override=\"true\", otherwise pick a different name. [PrivateResource]\n"
+                + "res/values/strings.xml:12: Warning: Overriding @string/my_private_string which is marked as private in com.android.tools:test-library. If deliberate, use tools:override=\"true\", otherwise pick a different name. [PrivateResource]\n"
                 + "    <string tools:override=\"false\" name=\"my_private_string\">String 2</string>\n"
                 + "                                         ~~~~~~~~~~~~~~~~~\n"
                 + "0 errors, 4 warnings\n",
@@ -124,6 +133,26 @@
                         xml("res/layout/my_public_layout.xml", "<LinearLayout/>")));
     }
 
+    @SuppressWarnings("ClassNameDiffersFromFileName")
+    public void testIds() throws Exception {
+        // Regression test for https://code.google.com/p/android/issues/detail?id=183851
+        assertEquals("No warnings.",
+                lintProject(
+                        xml("res/layout/private.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                                + "              android:id=\"@+id/title\"\n"
+                                + "              android:orientation=\"vertical\"\n"
+                                + "              android:layout_width=\"match_parent\"\n"
+                                + "              android:layout_height=\"match_parent\"/>\n"),
+                        java("src/test/pkg/Private.java", ""
+                                + "public class Private {\n"
+                                + "    void test() {\n"
+                                + "        int x = R.id.title; // ERROR\n"
+                                + "    }\n"
+                                + "}\n")));
+    }
+
     @Override
     protected TestLintClient createClient() {
         return new TestLintClient() {
@@ -149,10 +178,12 @@
                     public Variant getCurrentVariant() {
                         try {
                             AndroidLibrary library = createMockLibrary(
+                                    "com.android.tools:test-library:1.0.0",
                                     ""
                                             + "int string my_private_string 0x7f040000\n"
                                             + "int string my_public_string 0x7f040001\n"
-                                            + "int layout my_private_layout 0x7f040002\n",
+                                            + "int layout my_private_layout 0x7f040002\n"
+                                            + "int id title 0x7f040003\n",
                                     ""
                                             + ""
                                             + "string my_public_string\n",
@@ -192,7 +223,8 @@
         return artifact;
     }
 
-    public static AndroidLibrary createMockLibrary(String allResources, String publicResources,
+    public static AndroidLibrary createMockLibrary(String name,
+            String allResources, String publicResources,
             List<AndroidLibrary> dependencies)
             throws IOException {
         final File tempDir = TestUtils.createTempDirDeletedOnExit();
@@ -204,9 +236,18 @@
         }
         AndroidLibrary library = mock(AndroidLibrary.class);
         when(library.getPublicResources()).thenReturn(publicTxtFile);
+        GradleCoordinate c = GradleCoordinate.parseCoordinateString(name);
+        assertNotNull(c);
+        MavenCoordinates coordinates = mock(MavenCoordinates.class);
+        when(coordinates.getGroupId()).thenReturn(c.getGroupId());
+        when(coordinates.getArtifactId()).thenReturn(c.getArtifactId());
+        when(coordinates.getVersion()).thenReturn(c.getFullRevision());
+        when(library.getResolvedCoordinates()).thenReturn(coordinates);
+        when(library.getBundle()).thenReturn(new File("intermediates" + File.separator +
+                "exploded-aar" + File.separator + name));
 
         // Work around wildcard capture
-        //when(mock.getLibraryDependencies()).thenReturn(dependencies);
+        //when(library.getLibraryDependencies()).thenReturn(dependencies);
         List libraryDependencies = library.getLibraryDependencies();
         OngoingStubbing<List> setter = when(libraryDependencies);
         setter.thenReturn(dependencies);
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ReadParcelableDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ReadParcelableDetectorTest.java
new file mode 100644
index 0000000..611d505
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ReadParcelableDetectorTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2015 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", "override", "MethodMayBeStatic"})
+public class ReadParcelableDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new ReadParcelableDetector();
+    }
+
+    @SuppressWarnings("ClassNameDiffersFromFileName")
+    public void test() throws Exception {
+        // Regression test for https://code.google.com/p/android/issues/detail?id=196457
+        assertEquals(""
+                + "src/test/pkg/ParcelableDemo.java:10: Warning: Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example getClass().getClassLoader() instead. [ParcelClassLoader]\n"
+                + "        Parcelable error1   = in.readParcelable(null);\n"
+                + "                                 ~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ParcelableDemo.java:11: Warning: Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example getClass().getClassLoader() instead. [ParcelClassLoader]\n"
+                + "        Parcelable[] error2 = in.readParcelableArray(null);\n"
+                + "                                 ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ParcelableDemo.java:12: Warning: Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example getClass().getClassLoader() instead. [ParcelClassLoader]\n"
+                + "        Bundle error3       = in.readBundle(null);\n"
+                + "                                 ~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ParcelableDemo.java:13: Warning: Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example getClass().getClassLoader() instead. [ParcelClassLoader]\n"
+                + "        Object[] error4     = in.readArray(null);\n"
+                + "                                 ~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ParcelableDemo.java:14: Warning: Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example getClass().getClassLoader() instead. [ParcelClassLoader]\n"
+                + "        SparseArray error5  = in.readSparseArray(null);\n"
+                + "                                 ~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ParcelableDemo.java:15: Warning: Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example getClass().getClassLoader() instead. [ParcelClassLoader]\n"
+                + "        Object error6       = in.readValue(null);\n"
+                + "                                 ~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ParcelableDemo.java:16: Warning: Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example getClass().getClassLoader() instead. [ParcelClassLoader]\n"
+                + "        Parcelable error7   = in.readPersistableBundle(null);\n"
+                + "                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ParcelableDemo.java:17: Warning: Using the default class loader will not work if you are restoring your own classes. Consider using for example readBundle(getClass().getClassLoader()) instead. [ParcelClassLoader]\n"
+                + "        Bundle error8       = in.readBundle();\n"
+                + "                                 ~~~~~~~~~~~~\n"
+                + "src/test/pkg/ParcelableDemo.java:18: Warning: Using the default class loader will not work if you are restoring your own classes. Consider using for example readPersistableBundle(getClass().getClassLoader()) instead. [ParcelClassLoader]\n"
+                + "        Parcelable error9   = in.readPersistableBundle();\n"
+                + "                                 ~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "0 errors, 9 warnings\n",
+
+                lintProject(
+                        java("src/test/pkg/ParcelableDemo.java", ""
+                                + "package test.pkg;\n"
+                                + "\n"
+                                + "import android.os.Bundle;\n"
+                                + "import android.os.Parcel;\n"
+                                + "import android.os.Parcelable;\n"
+                                + "import android.util.SparseArray;\n"
+                                + "\n"
+                                + "public class ParcelableDemo {\n"
+                                + "    private void testParcelable(Parcel in) {\n"
+                                + "        Parcelable error1   = in.readParcelable(null);\n"
+                                + "        Parcelable[] error2 = in.readParcelableArray(null);\n"
+                                + "        Bundle error3       = in.readBundle(null);\n"
+                                + "        Object[] error4     = in.readArray(null);\n"
+                                + "        SparseArray error5  = in.readSparseArray(null);\n"
+                                + "        Object error6       = in.readValue(null);\n"
+                                + "        Parcelable error7   = in.readPersistableBundle(null);\n"
+                                + "        Bundle error8       = in.readBundle();\n"
+                                + "        Parcelable error9   = in.readPersistableBundle();\n"
+                                + "\n"
+                                + "        Parcelable ok      = in.readParcelable(getClass().getClassLoader());\n"
+                                + "    }\n"
+                                + "}")
+                ));
+    }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RecyclerViewDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RecyclerViewDetectorTest.java
new file mode 100644
index 0000000..aead7dc
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RecyclerViewDetectorTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2015 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({"ClassNameDiffersFromFileName", "MethodMayBeStatic", "SpellCheckingInspection"})
+public class RecyclerViewDetectorTest extends AbstractCheckTest {
+    public void test() throws Exception {
+        assertEquals(""
+                + "src/test/pkg/RecyclerViewTest.java:69: Warning: Do not treat position as fixed; only use immediately and call holder.getAdapterPosition() to look it up later [RecyclerView]\n"
+                + "        public void onBindViewHolder(ViewHolder holder, int position) {\n"
+                + "                                                        ~~~~~~~~~~~~\n"
+                + "src/test/pkg/RecyclerViewTest.java:82: Warning: Do not treat position as fixed; only use immediately and call holder.getAdapterPosition() to look it up later [RecyclerView]\n"
+                + "        public void onBindViewHolder(ViewHolder holder, final int position) {\n"
+                + "                                                        ~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/RecyclerViewTest.java:102: Warning: Do not treat position as fixed; only use immediately and call holder.getAdapterPosition() to look it up later [RecyclerView]\n"
+                + "        public void onBindViewHolder(ViewHolder holder, final int position) {\n"
+                + "                                                        ~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/RecyclerViewTest.java:111: Warning: Do not treat position as fixed; only use immediately and call holder.getAdapterPosition() to look it up later [RecyclerView]\n"
+                + "        public void onBindViewHolder(ViewHolder holder, final int position, List<Object> payloads) {\n"
+                + "                                                        ~~~~~~~~~~~~~~~~~~\n"
+                + "0 errors, 4 warnings\n",
+
+                lintProject(
+                    java("src/test/pkg/RecyclerViewTest.java", ""
+                            + "package test.pkg;\n"
+                            + "\n"
+                            + "import android.support.v7.widget.RecyclerView;\n"
+                            + "import android.view.View;\n"
+                            + "import android.widget.TextView;\n"
+                            + "\n"
+                            + "import java.util.List;\n"
+                            + "\n"
+                            + "@SuppressWarnings({\"ClassNameDiffersFromFileName\", \"unused\"})\n"
+                            + "public class RecyclerViewTest {\n"
+                            + "    // From https://developer.android.com/training/material/lists-cards.html\n"
+                            + "    public static class Test1 extends RecyclerView.Adapter<Test1.ViewHolder> {\n"
+                            + "        private String[] mDataset;\n"
+                            + "        public static class ViewHolder extends RecyclerView.ViewHolder {\n"
+                            + "            public TextView mTextView;\n"
+                            + "            public ViewHolder(TextView v) {\n"
+                            + "                super(v);\n"
+                            + "                mTextView = v;\n"
+                            + "            }\n"
+                            + "        }\n"
+                            + "\n"
+                            + "        public Test1(String[] myDataset) {\n"
+                            + "            mDataset = myDataset;\n"
+                            + "        }\n"
+                            + "\n"
+                            + "        @Override\n"
+                            + "        public void onBindViewHolder(ViewHolder holder, int position) {\n"
+                            + "            holder.mTextView.setText(mDataset[position]); // OK\n"
+                            + "        }\n"
+                            + "    }\n"
+                            + "\n"
+                            + "    public static class Test2 extends RecyclerView.Adapter<Test2.ViewHolder> {\n"
+                            + "        public static class ViewHolder extends RecyclerView.ViewHolder {\n"
+                            + "            public ViewHolder(View v) {\n"
+                            + "                super(v);\n"
+                            + "            }\n"
+                            + "        }\n"
+                            + "\n"
+                            + "        @Override\n"
+                            + "        public void onBindViewHolder(ViewHolder holder, int position) {\n"
+                            + "            // OK\n"
+                            + "        }\n"
+                            + "    }\n"
+                            + "\n"
+                            + "    public static class Test3 extends RecyclerView.Adapter<Test3.ViewHolder> {\n"
+                            + "        public static class ViewHolder extends RecyclerView.ViewHolder {\n"
+                            + "            public ViewHolder(View v) {\n"
+                            + "                super(v);\n"
+                            + "            }\n"
+                            + "        }\n"
+                            + "\n"
+                            + "        @Override\n"
+                            + "        public void onBindViewHolder(ViewHolder holder, final int position) {\n"
+                            + "            // OK - final, but not referenced\n\n"
+                            + "        }\n"
+                            + "    }\n"
+                            + "\n"
+                            + "    public static class Test4 extends RecyclerView.Adapter<Test4.ViewHolder> {\n"
+                            + "        private int myCachedPosition;\n"
+                            + "\n"
+                            + "        public static class ViewHolder extends RecyclerView.ViewHolder {\n"
+                            + "            public ViewHolder(View v) {\n"
+                            + "                super(v);\n"
+                            + "            }\n"
+                            + "        }\n"
+                            + "\n"
+                            + "        @Override\n"
+                            + "        public void onBindViewHolder(ViewHolder holder, int position) {\n"
+                            + "            myCachedPosition = position; // ERROR: escapes\n"
+                            + "        }\n"
+                            + "    }\n"
+                            + "\n"
+                            + "    public static class Test5 extends RecyclerView.Adapter<Test5.ViewHolder> {\n"
+                            + "        public static class ViewHolder extends RecyclerView.ViewHolder {\n"
+                            + "            public ViewHolder(View v) {\n"
+                            + "                super(v);\n"
+                            + "            }\n"
+                            + "        }\n"
+                            + "\n"
+                            + "        @Override\n"
+                            + "        public void onBindViewHolder(ViewHolder holder, final int position) {\n"
+                            + "            new Runnable() {\n"
+                            + "                @Override public void run() {\n"
+                            + "                    System.out.println(position); // ERROR: escapes\n"
+                            + "                }\n"
+                            + "            }.run();\n"
+                            + "        }\n"
+                            + "    }\n"
+                            + "\n"
+                            + "    // https://code.google.com/p/android/issues/detail?id=172335\n"
+                            + "    public static class Test6 extends RecyclerView.Adapter<Test6.ViewHolder> {\n"
+                            + "        List<String> myData;\n"
+                            + "        public static class ViewHolder extends RecyclerView.ViewHolder {\n"
+                            + "            private View itemView;\n"
+                            + "            public ViewHolder(View v) {\n"
+                            + "                super(v);\n"
+                            + "            }\n"
+                            + "        }\n"
+                            + "\n"
+                            + "        @Override\n"
+                            + "        public void onBindViewHolder(ViewHolder holder, final int position) {\n"
+                            + "            holder.itemView.setOnClickListener(new View.OnClickListener() {\n"
+                            + "                public void onClick(View view) {\n"
+                            + "                    myData.get(position); // ERROR\n"
+                            + "                }\n"
+                            + "            });\n"
+                            + "        }\n"
+                            + "\n"
+                            + "        @Override\n"
+                            + "        public void onBindViewHolder(ViewHolder holder, final int position, List<Object> payloads) {\n"
+                            + "            holder.itemView.setOnClickListener(new View.OnClickListener() {\n"
+                            + "                public void onClick(View view) {\n"
+                            + "                    myData.get(position); // ERROR\n"
+                            + "                }\n"
+                            + "            });\n"
+                            + "        }\n"
+                            + "    }\n"
+                            + "}\n"),
+                        java("src/android/support/v7/widget/RecyclerView.java", ""
+                                + "package android.support.v7.widget;\n"
+                                + "\n"
+                                + "import android.content.Context;\n"
+                                + "import android.util.AttributeSet;\n"
+                                + "import android.view.View;\n"
+                                + "import java.util.List;\n"
+                                + "\n"
+                                + "// Just a stub for lint unit tests\n"
+                                + "public class RecyclerView extends View {\n"
+                                + "    public RecyclerView(Context context, AttributeSet attrs) {\n"
+                                + "        super(context, attrs);\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public abstract static class ViewHolder {\n"
+                                + "        public ViewHolder(View itemView) {\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public abstract static class Adapter<VH extends ViewHolder> {\n"
+                                + "        public abstract void onBindViewHolder(VH holder, int position);\n"
+                                + "        public void onBindViewHolder(VH holder, int position, List<Object> payloads) {\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "}\n"))
+            );
+    }
+
+    @Override
+    protected Detector getDetector() {
+        return new RecyclerViewDetector();
+    }
+}
\ No newline at end of file
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RegistrationDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RegistrationDetectorTest.java
index 23a5ac3..bc4a07e 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RegistrationDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RegistrationDetectorTest.java
@@ -18,114 +18,274 @@
 
 import com.android.tools.lint.detector.api.Detector;
 
-@SuppressWarnings("javadoc")
+@SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName", "MethodMayBeStatic"})
 public class RegistrationDetectorTest extends AbstractCheckTest {
-    @Override
-    protected Detector getDetector() {
-        return new RegistrationDetector();
-    }
 
     public void testRegistered() throws Exception {
-        assertEquals(
-            "src/test/pkg/OnClickActivity.java:8: Warning: The <activity> test.pkg.OnClickActivity is not registered in the manifest [Registered]\n" +
-            "public class OnClickActivity extends Activity {\n" +
-            "             ~~~~~~~~~~~~~~~\n" +
-            "src/test/pkg/TestProvider.java:8: Warning: The <provider> test.pkg.TestProvider is not registered in the manifest [Registered]\n" +
-            "public class TestProvider extends ContentProvider {\n" +
-            "             ~~~~~~~~~~~~\n" +
-            "src/test/pkg/TestProvider2.java:3: Warning: The <provider> test.pkg.TestProvider2 is not registered in the manifest [Registered]\n" +
-            "public class TestProvider2 extends TestProvider {\n" +
-            "^\n" +
-            "src/test/pkg/TestService.java:7: Warning: The <service> test.pkg.TestService is not registered in the manifest [Registered]\n" +
-            "public class TestService extends Service {\n" +
-            "             ~~~~~~~~~~~\n" +
-            "0 errors, 4 warnings\n" +
-            "",
+        assertEquals("No warnings.",
+                lintProject(
+                        xml("AndroidManifest.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                                + "    package=\"test.pkg\">\n"
+                                + "    <application\n"
+                                + "        android:name=\".MyApplication\">\n"
+                                + "        <activity android:name=\".TestActivity\" />\n"
+                                + "        <service android:name=\".TestService\" />\n"
+                                + "        <provider android:name=\".TestProvider\" />\n"
+                                + "        <provider android:name=\".TestProvider2\" />\n"
+                                + "        <receiver android:name=\".TestReceiver\" />\n"
+                                + "    </application>\n"
+                                + "</manifest>\n"),
+                        mApplication,
+                        mTestActivity,
+                        mTestService,
+                        mTestProvider,
+                        mTestProvider2,
+                        mTestReceiver));
+    }
 
-            lintProject(
-                "bytecode/.classpath=>.classpath",
-                "bytecode/OnClickActivity.java.txt=>src/test/pkg/OnClickActivity.java",
-                "bytecode/OnClickActivity.class.data=>bin/classes/test/pkg/OnClickActivity.class",
-                "bytecode/TestService.java.txt=>src/test/pkg/TestService.java",
-                "bytecode/TestService.class.data=>bin/classes/test/pkg/TestService.class",
-                "bytecode/TestProvider.java.txt=>src/test/pkg/TestProvider.java",
-                "bytecode/TestProvider.class.data=>bin/classes/test/pkg/TestProvider.class",
-                "bytecode/TestProvider2.java.txt=>src/test/pkg/TestProvider2.java",
-                "bytecode/TestProvider2.class.data=>bin/classes/test/pkg/TestProvider2.class",
-                "bytecode/TestReceiver.java.txt=>src/test/pkg/TestReceiver.java",
-                "bytecode/TestReceiver.class.data=>bin/classes/test/pkg/TestReceiver.class"
-                ));
+    public void testNotRegistered() throws Exception {
+        assertEquals(""
+                + "src/test/pkg/MyApplication.java:5: Warning: The <application> test.pkg.MyApplication is not registered in the manifest [Registered]\n"
+                + "public class MyApplication extends Application {\n"
+                + "             ~~~~~~~~~~~~~\n"
+                + "src/test/pkg/TestActivity.java:3: Warning: The <activity> test.pkg.TestActivity is not registered in the manifest [Registered]\n"
+                + "public class TestActivity extends Activity {\n"
+                + "             ~~~~~~~~~~~~\n"
+                + "src/test/pkg/TestProvider.java:8: Warning: The <provider> test.pkg.TestProvider is not registered in the manifest [Registered]\n"
+                + "public class TestProvider extends ContentProvider {\n"
+                + "             ~~~~~~~~~~~~\n"
+                + "src/test/pkg/TestProvider2.java:3: Warning: The <provider> test.pkg.TestProvider2 is not registered in the manifest [Registered]\n"
+                + "public class TestProvider2 extends TestProvider {\n"
+                + "             ~~~~~~~~~~~~~\n"
+                + "src/test/pkg/TestService.java:7: Warning: The <service> test.pkg.TestService is not registered in the manifest [Registered]\n"
+                + "public class TestService extends Service {\n"
+                + "             ~~~~~~~~~~~\n"
+                + "0 errors, 5 warnings\n",
+
+                lintProject(
+                        // no manifest
+                        mApplication,
+                        mTestActivity,
+                        mTestService,
+                        mTestProvider,
+                        mTestProvider2,
+                        mTestReceiver,
+                        mSuppressedApplication));
     }
 
     public void testNoDot() throws Exception {
-        assertEquals(
-            "No warnings.",
-
-            lintProject(
-                "bytecode/AndroidManifestReg.xml=>AndroidManifest.xml",
-                "bytecode/.classpath=>.classpath",
-                "bytecode/CommentsActivity.java.txt=>src/test/pkg/Foo/CommentsActivity.java",
-                "bytecode/CommentsActivity.class.data=>bin/classes/test/pkg/Foo/CommentsActivity.class"
-                ));
+        assertEquals("No warnings.",
+                lintProject(
+                        xml("AndroidManifest.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                                + "    package=\"test.pkg\">\n"
+                                + "    <application>\n"
+                                + "        <activity android:name=\"TestActivity\" />\n"
+                                + "    </application>\n"
+                                + "</manifest>\n"),
+                        mTestActivity));
     }
 
     public void testWrongRegistrations() throws Exception {
-        assertEquals(
-            "src/test/pkg/OnClickActivity.java:8: Warning: test.pkg.OnClickActivity is a <activity> but is registered in the manifest as a <receiver> [Registered]\n" +
-            "public class OnClickActivity extends Activity {\n" +
-            "             ~~~~~~~~~~~~~~~\n" +
-            "src/test/pkg/TestProvider.java:8: Warning: test.pkg.TestProvider is a <provider> but is registered in the manifest as a <activity> [Registered]\n" +
-            "public class TestProvider extends ContentProvider {\n" +
-            "             ~~~~~~~~~~~~\n" +
-            "src/test/pkg/TestProvider2.java:3: Warning: test.pkg.TestProvider2 is a <provider> but is registered in the manifest as a <service> [Registered]\n" +
-            "public class TestProvider2 extends TestProvider {\n" +
-            "^\n" +
-            "src/test/pkg/TestReceiver.java:7: Warning: test.pkg.TestReceiver is a <receiver> but is registered in the manifest as a <service> [Registered]\n" +
-            "public class TestReceiver extends BroadcastReceiver {\n" +
-            "             ~~~~~~~~~~~~\n" +
-            "src/test/pkg/TestService.java:7: Warning: test.pkg.TestService is a <service> but is registered in the manifest as a <provider> [Registered]\n" +
-            "public class TestService extends Service {\n" +
-            "             ~~~~~~~~~~~\n" +
-            "0 errors, 5 warnings\n" +
-            "",
+        assertEquals(""
+                + "src/test/pkg/MyApplication.java:5: Warning: test.pkg.MyApplication is an <application> but is registered in the manifest as a <service> [Registered]\n"
+                + "public class MyApplication extends Application {\n"
+                + "             ~~~~~~~~~~~~~\n"
+                + "src/test/pkg/TestActivity.java:3: Warning: test.pkg.TestActivity is an <activity> but is registered in the manifest as a <receiver> [Registered]\n"
+                + "public class TestActivity extends Activity {\n"
+                + "             ~~~~~~~~~~~~\n"
+                + "src/test/pkg/TestProvider.java:8: Warning: test.pkg.TestProvider is a <provider> but is registered in the manifest as an <activity> [Registered]\n"
+                + "public class TestProvider extends ContentProvider {\n"
+                + "             ~~~~~~~~~~~~\n"
+                + "src/test/pkg/TestProvider2.java:3: Warning: test.pkg.TestProvider2 is a <provider> but is registered in the manifest as a <service> [Registered]\n"
+                + "public class TestProvider2 extends TestProvider {\n"
+                + "             ~~~~~~~~~~~~~\n"
+                + "src/test/pkg/TestReceiver.java:7: Warning: test.pkg.TestReceiver is a <receiver> but is registered in the manifest as a <service> [Registered]\n"
+                + "public class TestReceiver extends BroadcastReceiver {\n"
+                + "             ~~~~~~~~~~~~\n"
+                + "src/test/pkg/TestService.java:7: Warning: test.pkg.TestService is a <service> but is registered in the manifest as a <provider> [Registered]\n"
+                + "public class TestService extends Service {\n"
+                + "             ~~~~~~~~~~~\n"
+                + "0 errors, 6 warnings\n",
 
-            lintProject(
-                "bytecode/.classpath=>.classpath",
-                "bytecode/AndroidManifestWrongRegs.xml=>AndroidManifest.xml",
-                "bytecode/OnClickActivity.java.txt=>src/test/pkg/OnClickActivity.java",
-                "bytecode/OnClickActivity.class.data=>bin/classes/test/pkg/OnClickActivity.class",
-                "bytecode/AbstractActivity.java.txt=>src/test/pkg/AbstractActivity.java",
-                "bytecode/AbstractActivity.class.data=>bin/classes/test/pkg/AbstractActivity.class",
-                "bytecode/TestService.java.txt=>src/test/pkg/TestService.java",
-                "bytecode/TestService.class.data=>bin/classes/test/pkg/TestService.class",
-                "bytecode/TestProvider.java.txt=>src/test/pkg/TestProvider.java",
-                "bytecode/TestProvider.class.data=>bin/classes/test/pkg/TestProvider.class",
-                "bytecode/TestProvider2.java.txt=>src/test/pkg/TestProvider2.java",
-                "bytecode/TestProvider2.class.data=>bin/classes/test/pkg/TestProvider2.class",
-                "bytecode/TestReceiver.java.txt=>src/test/pkg/TestReceiver.java",
-                "bytecode/TestReceiver.class.data=>bin/classes/test/pkg/TestReceiver.class",
-                "bytecode/TestReceiver$1.class.data=>bin/classes/test/pkg/TestReceiver$1.class"
-                ));
+                lintProject(
+                        xml("AndroidManifest.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                                + "    package=\"test.pkg\">\n"
+                                + "    <application\n"
+                                + "        android:name=\".TestActivity\">\n"
+                                + "        <!-- These registrations are bogus (wrong type) -->\n"
+                                + "        <activity android:name=\".TestProvider\" />\n"
+                                + "        <service android:name=\"test.pkg.TestProvider2\" />\n"
+                                + "        <provider android:name=\".TestService\" />\n"
+                                + "        <receiver android:name=\".TestActivity\" />\n"
+                                + "        <service android:name=\".TestReceiver\" />\n"
+                                + "        <service android:name=\".MyApplication\" />\n"
+                                + "    </application>\n"
+                                + "</manifest>\n"),
+                        mApplication,
+                        mTestActivity,
+                        mTestService,
+                        mTestProvider,
+                        mTestProvider2,
+                        mTestReceiver));
     }
 
     public void testLibraryProjects() throws Exception {
         // If a library project provides additional activities, it is not an error to
         // not register all of those here
         assertEquals(
-            "No warnings.",
+                "No warnings.",
+                lintProject(
+                        // Master project
+                        source("project.properties", "android.library.reference.1=../LibraryProject2"),
+                        // Library project
+                        source("../LibraryProject2/project.properties", "android.library=true"),
 
-            lintProject(
-                // Master project
-                "multiproject/main-manifest.xml=>AndroidManifest.xml",
-                "multiproject/main.properties=>project.properties",
-
-                // Library project
-                "multiproject/library-manifest.xml=>../LibraryProject/AndroidManifest.xml",
-                "multiproject/library.properties=>../LibraryProject/project.properties",
-
-                "bytecode/.classpath=>../LibraryProject/.classpath",
-                "bytecode/OnClickActivity.java.txt=>../LibraryProject/src/test/pkg/OnClickActivity.java",
-                "bytecode/OnClickActivity.class.data=>../LibraryProject/bin/classes/test/pkg/OnClickActivity.class"
+                        java("../LibraryProject2/src/test/pkg/TestActivity.java", ""
+                                + "package test.pkg;\n"
+                                + "import android.app.Activity;\n"
+                                + "public class TestActivity extends Activity {\n"
+                                + "}\n")
                 ));
     }
-}
+
+    public void testSkipReceivers() throws Exception {
+        assertEquals("No warnings.",
+                lintProject(java("src/test/pkg/MyReceiver.java", ""
+                        + "package test.pkg;\n"
+                        + "\n"
+                        + "import android.app.Activity;\n"
+                        + "import android.content.BroadcastReceiver;\n"
+                        + "import android.content.Context;\n"
+                        + "import android.content.Intent;\n"
+                        + "\n"
+                        + "public class MyReceiver extends BroadcastReceiver {\n"
+                        + "    @Override\n"
+                        + "    public void onReceive(Context context, Intent intent) {\n"
+                        + "    }\n"
+                        + "\n"
+                        + "    private static class MyActivity extends Activity {\n"
+                        + "    }\n"
+                        + "}\n")));
+    }
+
+    @Override
+    protected Detector getDetector() {
+        return new RegistrationDetector();
+    }
+
+    private TestFile mTestActivity = java("src/test/pkg/TestActivity.java", ""
+            + "package test.pkg;\n"
+            + "import android.app.Activity;\n"
+            + "public class TestActivity extends Activity {\n"
+            + "}\n");
+
+    private TestFile mTestService = java("src/test/pkg/TestService.java", ""
+            + "package test.pkg;\n"
+            + "\n"
+            + "import android.app.Service;\n"
+            + "import android.content.Intent;\n"
+            + "import android.os.IBinder;\n"
+            + "\n"
+            + "public class TestService extends Service {\n"
+            + "\n"
+            + "    @Override\n"
+            + "    public IBinder onBind(Intent intent) {\n"
+            + "        return null;\n"
+            + "    }\n"
+            + "\n"
+            + "}\n");
+
+    private TestFile mTestProvider = java("src/test/pkg/TestProvider.java", "package test.pkg;\n"
+            + "\n"
+            + "import android.content.ContentProvider;\n"
+            + "import android.content.ContentValues;\n"
+            + "import android.database.Cursor;\n"
+            + "import android.net.Uri;\n"
+            + "\n"
+            + "public class TestProvider extends ContentProvider {\n"
+            + "    @Override\n"
+            + "    public int delete(Uri uri, String selection, String[] selectionArgs) {\n"
+            + "        return 0;\n"
+            + "    }\n"
+            + "\n"
+            + "    @Override\n"
+            + "    public String getType(Uri uri) {\n"
+            + "        return null;\n"
+            + "    }\n"
+            + "\n"
+            + "    @Override\n"
+            + "    public Uri insert(Uri uri, ContentValues values) {\n"
+            + "        return null;\n"
+            + "    }\n"
+            + "\n"
+            + "    @Override\n"
+            + "    public boolean onCreate() {\n"
+            + "        return false;\n"
+            + "    }\n"
+            + "\n"
+            + "    @Override\n"
+            + "    public Cursor query(Uri uri, String[] projection, String selection,\n"
+            + "            String[] selectionArgs, String sortOrder) {\n"
+            + "        return null;\n"
+            + "    }\n"
+            + "\n"
+            + "    @Override\n"
+            + "    public int update(Uri uri, ContentValues values, String selection,\n"
+            + "            String[] selectionArgs) {\n"
+            + "        return 0;\n"
+            + "    }\n"
+            + "}\n");
+
+    private TestFile mTestProvider2 = java("src/test/pkg/TestProvider2.java", ""
+            + "package test.pkg;\n"
+            + "\n"
+            + "public class TestProvider2 extends TestProvider {\n"
+            + "}\n");
+
+    private TestFile mTestReceiver = java("src/test/pkg/TestReceiver.java", ""
+            + "package test.pkg;\n"
+            + "\n"
+            + "import android.content.BroadcastReceiver;\n"
+            + "import android.content.Context;\n"
+            + "import android.content.Intent;\n"
+            + "\n"
+            + "public class TestReceiver extends BroadcastReceiver {\n"
+            + "\n"
+            + "    @Override\n"
+            + "    public void onReceive(Context context, Intent intent) {\n"
+            + "    }\n"
+            + "\n"
+            + "    // Anonymous classes should NOT be counted as a must-register\n"
+            + "    private BroadcastReceiver dummy() {\n"
+            + "        return new BroadcastReceiver() {\n"
+            + "            @Override\n"
+            + "            public void onReceive(Context context, Intent intent) {\n"
+            + "            }\n"
+            + "        };\n"
+            + "    }\n"
+            + "}\n");
+
+    private TestFile mApplication = java("src/test/pkg/MyApplication.java", ""
+            + "package test.pkg;\n"
+            + "\n"
+            + "import android.app.Application;\n"
+            + "\n"
+            + "public class MyApplication extends Application {\n"
+            + "}\n");
+
+    private TestFile mSuppressedApplication = java("src/test/pkg/MySuppressedApplication.java", ""
+            + "package test.pkg;\n"
+            + "\n"
+            + "import android.app.Application;\n"
+            +  "import android.annotation.SuppressLint;\n"
+            + "\n"
+            + "@SuppressLint(\"Registered\")\n"
+            + "public class MySuppressedApplication extends Application {\n"
+            + "}\n");
+}
\ No newline at end of file
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RelativeOverlapDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RelativeOverlapDetectorTest.java
index 0e78d22..b1cb92c 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RelativeOverlapDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RelativeOverlapDetectorTest.java
@@ -33,4 +33,62 @@
             "0 errors, 1 warnings\n",
             lintFiles("res/layout/relative_overlap.xml"));
     }
+
+    public void testOneOverlapPercent() throws Exception {
+        assertEquals(""
+                + "res/layout/relative_percent_overlap.xml:17: Warning: @id/label2 can overlap @id/label1 if @string/label1_text, @string/label2_text grow due to localized text expansion [RelativeOverlap]\n"
+                + "        <TextView\n"
+                + "        ^\n"
+                + "0 errors, 1 warnings\n",
+                lintProject(xml("res/layout/relative_percent_overlap.xml", ""
+                        + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                        + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                        + "    android:id=\"@+id/container\"\n"
+                        + "    android:layout_width=\"match_parent\"\n"
+                        + "    android:layout_height=\"wrap_content\"\n"
+                        + "    android:orientation=\"vertical\">\n"
+                        + "    <android.support.percent.PercentRelativeLayout\n"
+                        + "        android:layout_width=\"match_parent\"\n"
+                        + "        android:layout_height=\"wrap_content\">\n"
+                        + "        <TextView\n"
+                        + "            android:id=\"@+id/label1\"\n"
+                        + "            android:layout_alignParentLeft=\"true\"\n"
+                        + "            android:layout_width=\"wrap_content\"\n"
+                        + "            android:layout_height=\"wrap_content\"\n"
+                        + "            android:text=\"@string/label1_text\"\n"
+                        + "            android:ellipsize=\"end\" />\n"
+                        + "        <TextView\n"
+                        + "            android:id=\"@+id/label2\"\n"
+                        + "            android:layout_alignParentRight=\"true\"\n"
+                        + "            android:layout_width=\"wrap_content\"\n"
+                        + "            android:layout_height=\"wrap_content\"\n"
+                        + "            android:text=\"@string/label2_text\"\n"
+                        + "            android:ellipsize=\"end\" />\n"
+                        + "        <TextView\n"
+                        + "            android:id=\"@+id/circular1\"\n"
+                        + "            android:layout_alignParentBottom=\"true\"\n"
+                        + "            android:layout_toRightOf=\"@+id/circular2\"\n"
+                        + "            android:layout_width=\"wrap_content\"\n"
+                        + "            android:layout_height=\"wrap_content\"\n"
+                        + "            android:text=\"@string/label1_text\"\n"
+                        + "            android:ellipsize=\"end\" />\n"
+                        + "        <TextView\n"
+                        + "            android:id=\"@id/circular2\"\n"
+                        + "            android:layout_alignParentBottom=\"true\"\n"
+                        + "            android:layout_toRightOf=\"@id/circular1\"\n"
+                        + "            android:layout_width=\"wrap_content\"\n"
+                        + "            android:layout_height=\"wrap_content\"\n"
+                        + "            android:text=\"@string/label2_text\"\n"
+                        + "            android:ellipsize=\"end\" />\n"
+                        + "        <TextView\n"
+                        + "            android:id=\"@id/circular3\"\n"
+                        + "            android:layout_alignParentBottom=\"true\"\n"
+                        + "            android:layout_toRightOf=\"@id/circular1\"\n"
+                        + "            android:layout_width=\"wrap_content\"\n"
+                        + "            android:layout_height=\"wrap_content\"\n"
+                        + "            android:text=\"@string/label2_text\"\n"
+                        + "            android:ellipsize=\"end\" />\n"
+                        + "    </android.support.percent.PercentRelativeLayout>\n"
+                        + "</LinearLayout>\n")));
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RequiredAttributeDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RequiredAttributeDetectorTest.java
index da36270..0d8f9d5 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RequiredAttributeDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RequiredAttributeDetectorTest.java
@@ -27,6 +27,13 @@
         return new RequiredAttributeDetector();
     }
 
+    @Override
+    protected boolean allowCompilationErrors() {
+        // Some of these unit tests are still relying on source code that references
+        // unresolved symbols etc.
+        return true;
+    }
+
     public void test() throws Exception {
         // Simple: Only consider missing attributes in the layout xml file
         // (though skip warnings on <merge> tags and under <GridLayout>
@@ -63,6 +70,65 @@
                     ));
     }
 
+    public void testPercent() throws Exception {
+        // Regression test for https://code.google.com/p/android/issues/detail?id=198432
+        // Don't flag missing layout_width in PercentFrameLayout or PercentRelativeLayout
+        assertEquals(""
+                + "res/layout/test.xml:28: Error: The required layout_width or layout_widthPercent and layout_height or layout_heightPercent attributes are missing [RequiredSize]\n"
+                + "        <View />\n"
+                + "        ~~~~~~~~\n"
+                + "res/layout/test.xml:30: Error: The required layout_width or layout_widthPercent attribute is missing [RequiredSize]\n"
+                + "        <View\n"
+                + "        ^\n"
+                + "res/layout/test.xml:34: Error: The required layout_height or layout_heightPercent attribute is missing [RequiredSize]\n"
+                + "        <View\n"
+                + "        ^\n"
+                + "3 errors, 0 warnings\n",
+                lintProject(xml("res/layout/test.xml", ""
+                        + "<merge xmlns:android=\"http://schemas.android.com/apk/res/android\""
+                        + "     xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n"
+                        + "  <android.support.percent.PercentFrameLayout\n"
+                        + "        android:layout_width=\"match_parent\"\n"
+                        + "        android:layout_height=\"match_parent\"\n"
+                        + "        >\n"
+                        + "        <View\n"
+                        + "            app:layout_widthPercent=\"50%\"\n"
+                        + "            app:layout_heightPercent=\"50%\"/>\n"
+                        + "        <View\n"
+                        + "            android:layout_width=\"wrap_content\"\n"
+                        + "            android:layout_height=\"wrap_content\"\n"
+                        + "            app:layout_marginStartPercent=\"25%\"\n"
+                        + "            app:layout_marginEndPercent=\"25%\"/>\n"
+                        + "        <View\n"
+                        + "            android:id=\"@+id/textview2\"\n"
+                        + "            android:layout_height=\"wrap_content\"\n"
+                        + "            app:layout_widthPercent=\"60%\"/>\n"
+                        + "    </android.support.percent.PercentFrameLayout>"
+                        + "\n"
+                        + "    <android.support.percent.PercentRelativeLayout\n"
+                        + "        android:layout_width=\"match_parent\"\n"
+                        + "        android:layout_height=\"match_parent\">\n"
+                        + "        <View\n"
+                        + "            android:layout_gravity=\"center\"\n"
+                        + "            app:layout_widthPercent=\"50%\"\n"
+                        + "            app:layout_heightPercent=\"50%\"/>\n"
+                        + "        <!-- Errors -->\n"
+                        + "        <!-- Missing both -->\n"
+                        + "        <View />\n"
+                        + "        <!-- Missing width -->\n"
+                        + "        <View\n"
+                        + "            android:layout_gravity=\"center\"\n"
+                        + "            app:layout_heightPercent=\"50%\"/>\n"
+                        + "        <!-- Missing height -->\n"
+                        + "        <View\n"
+                        + "            android:layout_gravity=\"center\"\n"
+                        + "            app:layout_widthPercent=\"50%\"/>\n"
+                        + "\n"
+                        + "    </android.support.percent.PercentRelativeLayout>\n"
+                        + "\n"
+                        + "</merge>")));
+    }
+
     public void testInflaters() throws Exception {
         // Consider java inflation
         assertEquals(
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ResourcePrefixDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ResourcePrefixDetectorTest.java
index 7c3aced..e34c012 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ResourcePrefixDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ResourcePrefixDetectorTest.java
@@ -110,6 +110,14 @@
             checkLint(Arrays.asList(master, library)).replace("/TESTROOT/",""));
     }
 
+    public void testSuppressGeneratedRs() throws Exception {
+        assertEquals("No warnings.",
+                lintProject(
+                        copy("res/layout/layout1.xml", "res/raw/blend.bc")
+                ));
+
+    }
+
     // TODO: Test suppressing root level tag
 
     @Override
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RestrictionsDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RestrictionsDetectorTest.java
new file mode 100644
index 0000000..16a05c0
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RestrictionsDetectorTest.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.tools.lint.checks.RestrictionsDetector.MAX_NESTING_DEPTH;
+import static com.android.tools.lint.checks.RestrictionsDetector.MAX_NUMBER_OF_NESTED_RESTRICTIONS;
+
+import com.android.tools.lint.detector.api.Detector;
+
+public class RestrictionsDetectorTest  extends AbstractCheckTest {
+
+    @Override
+    protected Detector getDetector() {
+        return new RestrictionsDetector();
+    }
+
+    public void testSample() throws Exception {
+        // Sample from https://developer.android.com/samples/AppRestrictionSchema/index.html
+        // We expect no warnings.
+        assertEquals("No warnings.",
+                lintProject(
+                        xml("AndroidManifest.xml", ""
+                                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                                + "    package=\"com.example.android.apprestrictionschema\"\n"
+                                + "    android:versionCode=\"1\"\n"
+                                + "    android:versionName=\"1.0\">\n"
+                                + " \n"
+                                + "    <!-- uses-sdk android:minSdkVersion=\"21\" android:targetSdkVersion=\"21\" /-->\n"
+                                + " \n"
+                                + "    <application\n"
+                                + "        android:allowBackup=\"true\"\n"
+                                + "        android:icon=\"@drawable/ic_launcher\"\n"
+                                + "        android:label=\"@string/app_name\"\n"
+                                + "        android:theme=\"@style/AppTheme\">\n"
+                                + " \n"
+                                + "        <meta-data\n"
+                                + "            android:name=\"android.content.APP_RESTRICTIONS\"\n"
+                                + "            android:resource=\"@xml/app_restrictions\" />\n"
+                                + " \n"
+                                + "        <activity\n"
+                                + "            android:name=\".MainActivity\"\n"
+                                + "            android:label=\"@string/app_name\">\n"
+                                + "            <intent-filter>\n"
+                                + "                <action android:name=\"android.intent.action.MAIN\" />\n"
+                                + "                <category android:name=\"android.intent.category.LAUNCHER\" />\n"
+                                + "            </intent-filter>\n"
+                                + "        </activity>\n"
+                                + "    </application>\n"
+                                + " \n"
+                                + " \n"
+                                + "</manifest>"),
+                        xml("res/xml/app_restrictions.xml", ""
+                                + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+                                + " \n"
+                                + "    <!--\n"
+                                + "    Refer to the javadoc of RestrictionsManager for detail of this file.\n"
+                                + "    https://developer.android.com/reference/android/content/RestrictionsManager.html\n"
+                                + "    -->\n"
+                                + " \n"
+                                + "    <restriction\n"
+                                + "        android:defaultValue=\"@bool/default_can_say_hello\"\n"
+                                + "        android:description=\"@string/description_can_say_hello\"\n"
+                                + "        android:key=\"can_say_hello\"\n"
+                                + "        android:restrictionType=\"bool\"\n"
+                                + "        android:title=\"@string/title_can_say_hello\"/>\n"
+                                + " \n"
+                                + "    <restriction\n"
+                                + "        android:defaultValue=\"@string/default_message\"\n"
+                                + "        android:description=\"@string/description_message\"\n"
+                                + "        android:key=\"message\"\n"
+                                + "        android:restrictionType=\"string\"\n"
+                                + "        android:title=\"@string/title_message\"/>\n"
+                                + " \n"
+                                + "    <restriction\n"
+                                + "        android:defaultValue=\"@integer/default_number\"\n"
+                                + "        android:description=\"@string/description_number\"\n"
+                                + "        android:key=\"number\"\n"
+                                + "        android:restrictionType=\"integer\"\n"
+                                + "        android:title=\"@string/title_number\"/>\n"
+                                + " \n"
+                                + "    <restriction\n"
+                                + "        android:defaultValue=\"@string/default_rank\"\n"
+                                + "        android:description=\"@string/description_rank\"\n"
+                                + "        android:entries=\"@array/entries_rank\"\n"
+                                + "        android:entryValues=\"@array/entry_values_rank\"\n"
+                                + "        android:key=\"rank\"\n"
+                                + "        android:restrictionType=\"choice\"\n"
+                                + "        android:title=\"@string/title_rank\"/>\n"
+                                + " \n"
+                                + "    <restriction\n"
+                                + "        android:defaultValue=\"@array/default_approvals\"\n"
+                                + "        android:description=\"@string/description_approvals\"\n"
+                                + "        android:entries=\"@array/entries_approvals\"\n"
+                                + "        android:entryValues=\"@array/entry_values_approvals\"\n"
+                                + "        android:key=\"approvals\"\n"
+                                + "        android:restrictionType=\"multi-select\"\n"
+                                + "        android:title=\"@string/title_approvals\"/>\n"
+                                + " \n"
+                                + "    <restriction\n"
+                                + "        android:defaultValue=\"@string/default_secret_code\"\n"
+                                + "        android:description=\"@string/description_secret_code\"\n"
+                                + "        android:key=\"secret_code\"\n"
+                                + "        android:restrictionType=\"hidden\"\n"
+                                + "        android:title=\"@string/title_secret_code\"/>\n"
+                                + " \n"
+                                + "</restrictions>")
+
+                ));
+    }
+
+    public void testMissingRequiredAttributes() throws Exception {
+        assertEquals(""
+                        + "res/xml/app_restrictions.xml:2: Error: Missing required attribute android:key [ValidRestrictions]\n"
+                        + "    <restriction />\n"
+                        + "    ~~~~~~~~~~~~~~~\n"
+                        + "res/xml/app_restrictions.xml:2: Error: Missing required attribute android:restrictionType [ValidRestrictions]\n"
+                        + "    <restriction />\n"
+                        + "    ~~~~~~~~~~~~~~~\n"
+                        + "res/xml/app_restrictions.xml:2: Error: Missing required attribute android:title [ValidRestrictions]\n"
+                        + "    <restriction />\n"
+                        + "    ~~~~~~~~~~~~~~~\n"
+                        + "3 errors, 0 warnings\n",
+                lintProject(
+                        xml("res/xml/app_restrictions.xml", ""
+                                + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+                                + "    <restriction />\n"
+                                + "</restrictions>")
+                ));
+    }
+
+    public void testNewSample() throws Exception {
+        assertEquals("No warnings.",
+                lintProject(
+                        xml("res/xml/app_restrictions.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+                                + "    <restriction android:key=\"key_bool\"\n"
+                                + "            android:restrictionType=\"bool\"\n"
+                                + "            android:title=\"@string/title_bool\"\n"
+                                + "            android:description=\"@string/desc_bool\"\n"
+                                + "            android:defaultValue=\"true\"\n"
+                                + "            />\n"
+                                + "    <restriction android:key=\"key_int\"\n"
+                                + "            android:restrictionType=\"integer\"\n"
+                                + "            android:title=\"@string/title_int\"\n"
+                                + "            android:defaultValue=\"15\"\n"
+                                + "            />\n"
+                                + "    <restriction android:key=\"key_string\"\n"
+                                + "            android:restrictionType=\"string\"\n"
+                                + "            android:defaultValue=\"@string/string_value\"\n"
+                                + "            android:title=\"@string/missing_title\"\n" // MISSING IN SAMPLE!
+                                + "            />\n"
+                                + "    <restriction android:key=\"components\"\n"
+                                + "                 android:restrictionType=\"bundle_array\"\n"
+                                + "                 android:title=\"@string/title_bundle_array\"\n"
+                                + "                 android:description=\"@string/desc_bundle_array\">\n"
+                                + "        <restriction android:restrictionType=\"bundle\"\n"
+                                + "                     android:key=\"someKey\"\n"
+                                + "                     android:title=\"@string/title_bundle_comp\"\n"
+                                + "                     android:description=\"@string/desc_bundle_comp\">\n"
+                                + "            <restriction android:key=\"enabled\"\n"
+                                + "                         android:restrictionType=\"bool\"\n"
+                                + "                         android:defaultValue=\"true\"\n"
+                                + "                         android:title=\"@string/missing_title\"\n" // MISSING IN SAMPLE!
+                                + "                         />\n"
+                                + "            <restriction android:key=\"name\"\n"
+                                + "                         android:restrictionType=\"string\"\n"
+                                + "                         android:title=\"@string/missing_title\"\n" // MISSING IN SAMPLE!
+                                + "                         />\n"
+                                + "        </restriction>\n"
+                                + "\n"
+                                + "    </restriction>\n"
+                                + "    <restriction android:key=\"connection_settings\"\n"
+                                + "                 android:restrictionType=\"bundle\"\n"
+                                + "                 android:title=\"@string/title_bundle\"\n"
+                                + "                 android:description=\"@string/desc_bundle\">\n"
+                                + "        <restriction android:key=\"max_wait_time_ms\"\n"
+                                + "                     android:restrictionType=\"integer\"\n"
+                                + "                     android:title=\"@string/title_int\"\n"
+                                + "                     android:defaultValue=\"1000\"\n"
+                                + "                     />\n"
+                                + "        <restriction android:key=\"host\"\n"
+                                + "                     android:restrictionType=\"string\"\n"
+                                + "                     android:title=\"@string/missing_title\"\n" // MISSING IN SAMPLE!
+                                + "                     />\n"
+                                + "    </restriction>\n"
+                                + "</restrictions>\n")
+                ));
+    }
+
+    public void testMissingRequiredAttributesForChoice() throws Exception {
+        assertEquals(""
+                        + "res/xml/app_restrictions.xml:2: Error: Missing required attribute android:entries [ValidRestrictions]\n"
+                        + "    <restriction\n"
+                        + "    ^\n"
+                        + "res/xml/app_restrictions.xml:2: Error: Missing required attribute android:entryValues [ValidRestrictions]\n"
+                        + "    <restriction\n"
+                        + "    ^\n"
+                        + "2 errors, 0 warnings\n",
+                lintProject(
+                        xml("res/xml/app_restrictions.xml", ""
+                                + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+                                + "    <restriction\n"
+                                + "        android:description=\"@string/description_number\"\n"
+                                + "        android:key=\"number\"\n"
+                                + "        android:restrictionType=\"choice\"\n"
+                                + "        android:title=\"@string/title_number\"/>\n"
+                                + "</restrictions>")
+                ));
+    }
+
+    public void testMissingRequiredAttributesForHidden() throws Exception {
+        assertEquals(""
+                        + "res/xml/app_restrictions.xml:2: Error: Missing required attribute android:defaultValue [ValidRestrictions]\n"
+                        + "    <restriction\n"
+                        + "    ^\n"
+                        + "1 errors, 0 warnings\n",
+                lintProject(
+                        xml("res/xml/app_restrictions.xml", ""
+                                + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+                                + "    <restriction\n"
+                                + "        android:description=\"@string/description_number\"\n"
+                                + "        android:key=\"number\"\n"
+                                + "        android:restrictionType=\"hidden\"\n"
+                                + "        android:title=\"@string/title_number\"/>\n"
+                                + "</restrictions>")
+                ));
+    }
+
+    public void testValidNumber() throws Exception {
+        assertEquals(""
+                        + "res/xml/app_restrictions.xml:3: Error: Invalid number [ValidRestrictions]\n"
+                        + "        android:defaultValue=\"abc\"\n"
+                        + "                              ~~~\n"
+                        + "1 errors, 0 warnings\n",
+                lintProject(
+                        xml("res/xml/app_restrictions.xml", ""
+                                + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+                                + "    <restriction\n"
+                                + "        android:defaultValue=\"abc\"\n" // ERROR
+                                + "        android:description=\"@string/description_number\"\n"
+                                + "        android:key=\"message1\"\n"
+                                + "        android:restrictionType=\"integer\"\n"
+                                + "        android:title=\"@string/title_number\"/>\n"
+                                + "    <restriction\n"
+                                + "        android:defaultValue=\"@integer/default_number\"\n" // OK
+                                + "        android:description=\"@string/description_message\"\n"
+                                + "        android:key=\"message2\"\n"
+                                + "        android:restrictionType=\"integer\"\n"
+                                + "        android:title=\"@string/title_number2\"/>\n"
+                                + "    <restriction\n"
+                                + "        android:defaultValue=\"123\"\n" // OK
+                                + "        android:description=\"@string/description_message2\"\n"
+                                + "        android:key=\"message3\"\n"
+                                + "        android:restrictionType=\"integer\"\n"
+                                + "        android:title=\"@string/title_number3\"/>\n"
+                                + "</restrictions>")
+                ));
+    }
+
+    public void testUnexpectedTag() throws Exception {
+        assertEquals(""
+                        + "res/xml/app_restrictions.xml:3: Error: Unexpected tag <wrongtag>, expected <restriction> [ValidRestrictions]\n"
+                        + "    <wrongtag />\n"
+                        + "     ~~~~~~~~\n"
+                        + "1 errors, 0 warnings\n",
+                lintProject(
+                        xml("res/xml/app_restrictions.xml", ""
+                                + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+                                + "    <!-- Comments are okay -->\n"
+                                + "    <wrongtag />\n"
+                                + "</restrictions>")
+
+                ));
+    }
+
+    public void testLocalizedKey() throws Exception {
+        assertEquals(""
+                        + "res/xml/app_restrictions.xml:5: Error: Keys cannot be localized, they should be specified with a string literal [ValidRestrictions]\n"
+                        + "        android:key=\"@string/can_say_hello\"\n"
+                        + "                     ~~~~~~~~~~~~~~~~~~~~~\n"
+                        + "1 errors, 0 warnings\n",
+                lintProject(
+                        xml("res/xml/app_restrictions.xml", ""
+                                + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+                                + "    <restriction\n"
+                                + "        android:defaultValue=\"@bool/default_can_say_hello\"\n"
+                                + "        android:description=\"@string/description_can_say_hello\"\n"
+                                + "        android:key=\"@string/can_say_hello\"\n"
+                                + "        android:restrictionType=\"bool\"\n"
+                                + "        android:title=\"@string/title_can_say_hello\"/>\n"
+                                + "</restrictions>")
+
+                ));
+    }
+
+    public void testDuplicateKeys() throws Exception {
+        assertEquals(""
+                        + "res/xml/app_restrictions.xml:19: Error: Duplicate key can_say_hello [ValidRestrictions]\n"
+                        + "        android:key=\"can_say_hello\"\n"
+                        + "                     ~~~~~~~~~~~~~\n"
+                        + "    res/xml/app_restrictions.xml:5: Previous use of key here\n"
+                        + "1 errors, 0 warnings\n",
+                lintProject(
+                        xml("res/xml/app_restrictions.xml", ""
+                                + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+                                + "    <restriction\n"
+                                + "        android:defaultValue=\"@bool/default_can_say_hello\"\n"
+                                + "        android:description=\"@string/description_can_say_hello\"\n"
+                                + "        android:key=\"can_say_hello\"\n"
+                                + "        android:restrictionType=\"bool\"\n"
+                                + "        android:title=\"@string/title_can_say_hello\"/>\n"
+                                + " \n"
+                                + "    <restriction\n"
+                                + "        android:defaultValue=\"@string/default_message\"\n"
+                                + "        android:description=\"@string/description_message\"\n"
+                                + "        android:key=\"message\"\n"
+                                + "        android:restrictionType=\"string\"\n"
+                                + "        android:title=\"@string/title_message\"/>\n"
+                                + " \n"
+                                + "    <restriction\n"
+                                + "        android:defaultValue=\"@integer/default_number\"\n"
+                                + "        android:description=\"@string/description_number\"\n"
+                                + "        android:key=\"can_say_hello\"\n" // ERROR: Duplicate
+                                + "        android:restrictionType=\"integer\"\n"
+                                + "        android:title=\"@string/title_number\"/>\n"
+                                + "</restrictions>")
+
+                ));
+    }
+
+    public void testNoDefaultValueForBundles() throws Exception {
+        assertEquals(""
+                        + "res/xml/app_restrictions.xml:3: Error: Restriction type bundle_array should not have a default value [ValidRestrictions]\n"
+                        + "        android:defaultValue=\"@string/default_message\"\n"
+                        + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                        + "1 errors, 0 warnings\n",
+                lintProject(
+                        xml("res/xml/app_restrictions.xml", ""
+                                + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+                                + "    <restriction\n"
+                                + "        android:defaultValue=\"@string/default_message\"\n"
+                                + "        android:description=\"@string/description_message\"\n"
+                                + "        android:key=\"message\"\n"
+                                + "        android:restrictionType=\"bundle_array\"\n"
+                                + "        android:title=\"@string/title_message\">\n"
+                                + "      <restriction\n"
+                                + "          android:defaultValue=\"@bool/default_can_say_hello\"\n"
+                                + "          android:description=\"@string/description_can_say_hello\"\n"
+                                + "          android:key=\"can_say_hello\"\n"
+                                + "          android:restrictionType=\"string\"\n"
+                                + "          android:title=\"@string/title_can_say_hello\"/>\n"
+                                + "    </restriction>"
+                                + "</restrictions>")
+
+                ));
+    }
+
+    public void testNoChildrenForBundle() throws Exception {
+        assertEquals(""
+                        + "res/xml/app_restrictions.xml:2: Error: Restriction type bundle should have at least one nested restriction [ValidRestrictions]\n"
+                        + "    <restriction\n"
+                        + "    ^\n"
+                        + "1 errors, 0 warnings\n",
+                lintProject(
+                        xml("res/xml/app_restrictions.xml", ""
+                                + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+                                + "    <restriction\n"
+                                + "        android:description=\"@string/description_message\"\n"
+                                + "        android:key=\"message\"\n"
+                                + "        android:restrictionType=\"bundle\"\n"
+                                + "        android:title=\"@string/title_message\"/>\n"
+                                + "</restrictions>")
+
+                ));
+    }
+
+    public void testNoChildrenForBundleArray() throws Exception {
+        assertEquals(""
+                        + "res/xml/app_restrictions.xml:2: Error: Expected exactly one child for restriction of type bundle_array [ValidRestrictions]\n"
+                        + "    <restriction\n"
+                        + "    ^\n"
+                        + "1 errors, 0 warnings\n",
+                lintProject(
+                        xml("res/xml/app_restrictions.xml", ""
+                                + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+                                + "    <restriction\n"
+                                + "        android:description=\"@string/description_message\"\n"
+                                + "        android:key=\"message\"\n"
+                                + "        android:restrictionType=\"bundle_array\"\n"
+                                + "        android:title=\"@string/title_message\"/>\n"
+                                + "</restrictions>")
+
+                ));
+    }
+
+    public void testTooManyChildren() throws Exception {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < MAX_NUMBER_OF_NESTED_RESTRICTIONS + 2; i++) {
+            //noinspection StringConcatenationInsideStringBufferAppend
+            sb.append(""
+                    + "    <restriction\n"
+                    + "        android:defaultValue=\"@bool/default_can_say_hello" + i + "\"\n"
+                    + "        android:description=\"@string/description_can_say_hello" + i + "\"\n"
+                    + "        android:key=\"can_say_hello" + i + "\"\n"
+                    + "        android:restrictionType=\"bool\"\n"
+                    + "        android:title=\"@string/title_can_say_hello" + i + "\"/>\n"
+                    + " \n");
+
+        }
+
+        assertEquals(""
+                        + "res/xml/app_restrictions.xml:1: Error: Invalid nested restriction: too many nested restrictions (was 1002, max 1000) [ValidRestrictions]\n"
+                        + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+                        + "^\n"
+                        + "1 errors, 0 warnings\n",
+                lintProject(
+                        xml("res/xml/app_restrictions.xml", ""
+                                + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+                                + sb.toString()
+                                + "</restrictions>")
+
+                ));
+    }
+
+    public void testNestingTooDeep() throws Exception {
+        StringBuilder sb = new StringBuilder();
+        int maxDepth = MAX_NESTING_DEPTH + 1;
+        for (int i = 0; i < maxDepth; i++) {
+            //noinspection StringConcatenationInsideStringBufferAppend
+            sb.append(""
+                    + "    <restriction\n"
+                    + "        android:description=\"@string/description_can_say_hello" + i + "\"\n"
+                    + "        android:key=\"can_say_hello" + i + "\"\n"
+                    + "        android:restrictionType=\"bundle\"\n"
+                    + "        android:title=\"@string/title_can_say_hello" + i + "\">\n"
+                    + " \n");
+        }
+        sb.append(""
+                + "    <restriction\n"
+                + "        android:defaultValue=\"@string/default_message\"\n"
+                + "        android:description=\"@string/description_message\"\n"
+                + "        android:key=\"message\"\n"
+                + "        android:restrictionType=\"string\"\n"
+                + "        android:title=\"@string/title_message\"/>\n"
+                + " \n");
+        for (int i = 0; i < maxDepth; i++) {
+            sb.append("    </restriction>\n");
+        }
+
+        assertEquals(""
+                        + "res/xml/app_restrictions.xml:122: Error: Invalid nested restriction: nesting depth 21 too large (max 20 [ValidRestrictions]\n"
+                        + "    <restriction\n"
+                        + "    ^\n"
+                        + "1 errors, 0 warnings\n",
+                lintProject(
+                        xml("res/xml/app_restrictions.xml", ""
+                                + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+                                + sb.toString()
+                                + "</restrictions>")
+
+                ));
+    }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RtlDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RtlDetectorTest.java
index 5e3e42e..8d70bbb 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RtlDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RtlDetectorTest.java
@@ -46,6 +46,13 @@
         return new RtlDetector();
     }
 
+    @Override
+    protected boolean allowCompilationErrors() {
+        // Some of these unit tests are still relying on source code that references
+        // unresolved symbols etc.
+        return true;
+    }
+
     private Set<Issue> mEnabled = new HashSet<Issue>();
     private static final Set<Issue> ALL = new HashSet<Issue>();
     static {
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SQLiteDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SQLiteDetectorTest.java
index d98ed6a..c500e1e 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SQLiteDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SQLiteDetectorTest.java
@@ -35,7 +35,7 @@
                 + "0 errors, 2 warnings\n",
 
                 lintProject(
-                        "src/test/pkg/MyTracksProvider.java.txt=>src/test/pkg/MyTracksProvider.java",
+                        "src/test/pkg/MyTracksProvider.java.txt=>src/test/pkg/TracksColumns.java",
                         "src/test/pkg/SQLiteTest.java.txt=>src/test/pkg/SQLiteTest.java",
                         // stub for type resolution
                         "src/test/pkg/SQLiteDatabase.java.txt=>src/android/database/sqlite/SQLiteDatabase.java"
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SdCardDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SdCardDetectorTest.java
index 5ad46b9..e34f509 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SdCardDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SdCardDetectorTest.java
@@ -18,7 +18,7 @@
 
 import com.android.tools.lint.detector.api.Detector;
 
-@SuppressWarnings("javadoc")
+@SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName"})
 public class SdCardDetectorTest extends AbstractCheckTest {
     @Override
     protected Detector getDetector() {
@@ -95,4 +95,15 @@
 
             lintProject("src/test/pkg/Utf8BomTest.java.data=>src/test/pkg/Utf8BomTest.java"));
     }
+
+    public void testSuppressInAnnotation() throws Exception {
+        assertEquals("No warnings.",
+                lintProject(java("src/test/pkg/MyInterface.java", ""
+                        + "package test.pkg;\n"
+                        + "import android.annotation.SuppressLint;\n"
+                        + "public @interface MyInterface {\n"
+                        + "    @SuppressLint(\"SdCardPath\")\n"
+                        + "    String engineer() default \"/sdcard/this/is/wrong\";\n"
+                        + "}\n")));
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SecureRandomDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SecureRandomDetectorTest.java
index 0ecbe02..22395bb 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SecureRandomDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SecureRandomDetectorTest.java
@@ -18,47 +18,106 @@
 
 import com.android.tools.lint.detector.api.Detector;
 
-@SuppressWarnings("javadoc")
+@SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName", "MethodMayBeStatic", "RedundantCast"})
 public class SecureRandomDetectorTest extends AbstractCheckTest {
     @Override
     protected Detector getDetector() {
         return new SecureRandomDetector();
     }
 
-    public void test1() throws Exception {
-        assertEquals(
-            "src/test/pkg/SecureRandomTest.java:12: Warning: It is dangerous to seed SecureRandom with the current time because that value is more predictable to an attacker than the default seed. [SecureRandom]\n" +
-            "        random1.setSeed(System.currentTimeMillis()); // OK\n" +
-            "                ~~~~~~~\n" +
-            "src/test/pkg/SecureRandomTest.java:14: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n" +
-            "        random1.setSeed(0); // Wrong\n" +
-            "                ~~~~~~~\n" +
-            "src/test/pkg/SecureRandomTest.java:15: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n" +
-            "        random1.setSeed(1); // Wrong\n" +
-            "                ~~~~~~~\n" +
-            "src/test/pkg/SecureRandomTest.java:16: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n" +
-            "        random1.setSeed((int)1023); // Wrong\n" +
-            "                ~~~~~~~\n" +
-            "src/test/pkg/SecureRandomTest.java:17: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n" +
-            "        random1.setSeed(1023L); // Wrong\n" +
-            "                ~~~~~~~\n" +
-            "src/test/pkg/SecureRandomTest.java:18: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n" +
-            "        random1.setSeed(FIXED_SEED); // Wrong\n" +
-            "                ~~~~~~~\n" +
-            "src/test/pkg/SecureRandomTest.java:28: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n" +
-            "        random3.setSeed(0); // Wrong: owner is java/util/Random, but applied to SecureRandom object\n" +
-            "                ~~~~~~~\n" +
-            "0 errors, 7 warnings\n" +
-            "",
-            // Missing error on line 40, using flow analysis to determine that the seed byte
-            // array passed into the SecureRandom constructor is static.
+    public void test0() throws Exception {
+        assertEquals(""
+                + "src/test/pkg/SecureRandomTest.java:12: Warning: It is dangerous to seed SecureRandom with the current time because that value is more predictable to an attacker than the default seed. [SecureRandom]\n"
+                + "        random1.setSeed(System.currentTimeMillis()); // Wrong\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/SecureRandomTest.java:13: Warning: It is dangerous to seed SecureRandom with the current time because that value is more predictable to an attacker than the default seed. [SecureRandom]\n"
+                + "        random1.setSeed(System.nanoTime()); // Wrong\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/SecureRandomTest.java:15: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n"
+                + "        random1.setSeed(0); // Wrong\n"
+                + "        ~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/SecureRandomTest.java:16: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n"
+                + "        random1.setSeed(1); // Wrong\n"
+                + "        ~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/SecureRandomTest.java:17: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n"
+                + "        random1.setSeed((int)1023); // Wrong\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/SecureRandomTest.java:18: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n"
+                + "        random1.setSeed(1023L); // Wrong\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/SecureRandomTest.java:19: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n"
+                + "        random1.setSeed(FIXED_SEED); // Wrong\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/SecureRandomTest.java:29: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n"
+                + "        random3.setSeed(0); // Wrong: owner is java/util/Random, but applied to SecureRandom object\n"
+                + "        ~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/SecureRandomTest.java:41: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n"
+                + "        random2.setSeed(seed); // Wrong\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/SecureRandomTest.java:47: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n"
+                + "        random2.setSeed(seedBytes); // Wrong\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "0 errors, 10 warnings\n",
+                // Error on line 55 is only found in the IDE where we can map from ResolvedNodes to AST nodes
 
-            lintProject(
-                "bytecode/.classpath=>.classpath",
-                "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
-                "res/layout/onclick.xml=>res/layout/onclick.xml",
-                "bytecode/SecureRandomTest.java.txt=>src/test/pkg/SecureRandomTest.java",
-                "bytecode/SecureRandomTest.class.data=>bin/classes/test/pkg/SecureRandomTest.class"
-                ));
+                lintProject(java("src/test/pkg/SecureRandomTest.java", ""
+                        + "package test.pkg;\n"
+                        + "\n"
+                        + "import java.security.SecureRandom;\n"
+                        + "import java.util.Random;\n"
+                        + "\n"
+                        + "public class SecureRandomTest {\n"
+                        + "    private static final long FIXED_SEED = 1000L;\n"
+                        + "    protected int getDynamicSeed() {  return 1; }\n"
+                        + "\n"
+                        + "    public void testLiterals() {\n"
+                        + "        SecureRandom random1 = new SecureRandom();\n"
+                        + "        random1.setSeed(System.currentTimeMillis()); // Wrong\n"
+                        + "        random1.setSeed(System.nanoTime()); // Wrong\n"
+                        + "        random1.setSeed(getDynamicSeed()); // OK\n"
+                        + "        random1.setSeed(0); // Wrong\n"
+                        + "        random1.setSeed(1); // Wrong\n"
+                        + "        random1.setSeed((int)1023); // Wrong\n"
+                        + "        random1.setSeed(1023L); // Wrong\n"
+                        + "        random1.setSeed(FIXED_SEED); // Wrong\n"
+                        + "    }\n"
+                        + "\n"
+                        + "    public static void testRandomTypeOk() {\n"
+                        + "        Random random2 = new Random();\n"
+                        + "        random2.setSeed(0); // OK\n"
+                        + "    }\n"
+                        + "\n"
+                        + "    public static void testRandomTypeWrong() {\n"
+                        + "        Random random3 = new SecureRandom();\n"
+                        + "        random3.setSeed(0); // Wrong: owner is java/util/Random, but applied to SecureRandom object\n"
+                        + "    }\n"
+                        + "\n"
+                        + "    public static void testBytesOk() {\n"
+                        + "        SecureRandom random1 = new SecureRandom();\n"
+                        + "        byte[] seed = random1.generateSeed(4);\n"
+                        + "        random1.setSeed(seed); // OK\n"
+                        + "    }\n"
+                        + "\n"
+                        + "    public static void testBytesWrong() {\n"
+                        + "        SecureRandom random2 = new SecureRandom();\n"
+                        + "        byte[] seed = new byte[3];\n"
+                        + "        random2.setSeed(seed); // Wrong\n"
+                        + "    }\n"
+                        + "\n"
+                        + "    public static void testFixedSeedBytes(byte something) {\n"
+                        + "        SecureRandom random2 = new SecureRandom();\n"
+                        + "        byte[] seedBytes = new byte[] { 1, 2, 3 };\n"
+                        + "        random2.setSeed(seedBytes); // Wrong\n"
+                        + "        byte[] seedBytes2 = new byte[] { 1, something, 3 };\n"
+                        + "        random2.setSeed(seedBytes2); // OK\n"
+                        + "    }\n"
+                        + "\n"
+                        + "    private static final byte[] fixedSeed = new byte[] { 1, 2, 3 };\n"
+                        + "    public void testFixedSeedBytesField() {\n"
+                        + "        SecureRandom random2 = new SecureRandom();\n"
+                        + "        random2.setSeed(fixedSeed); // Wrong\n"
+                        + "    }\n"
+                        + "\n"
+                        + "}\n")));
     }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SecurityDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SecurityDetectorTest.java
index a2ac0f8..30a58b3 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SecurityDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SecurityDetectorTest.java
@@ -25,6 +25,13 @@
         return new SecurityDetector();
     }
 
+    @Override
+    protected boolean allowCompilationErrors() {
+        // Some of these unit tests are still relying on source code that references
+        // unresolved symbols etc.
+        return true;
+    }
+
     public void testBroken() throws Exception {
         assertEquals(
             "AndroidManifest.xml:12: Warning: Exported service does not require permission [ExportedService]\n" +
@@ -123,19 +130,31 @@
 
     public void testWorldWriteable() throws Exception {
         assertEquals(
-            "src/test/pkg/WorldWriteableFile.java:26: Warning: Using MODE_WORLD_READABLE when creating files can be risky, review carefully [WorldReadableFiles]\n" +
+            "src/test/pkg/WorldWriteableFile.java:41: Warning: Setting file permissions to world-readable can be risky, review carefully [SetWorldReadable]\n" +
+            "            mFile.setReadable(true, false);\n" +
+            "            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+            "src/test/pkg/WorldWriteableFile.java:48: Warning: Setting file permissions to world-writable can be risky, review carefully [SetWorldWritable]\n" +
+            "            mFile.setWritable(true, false);\n" +
+            "            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+            "src/test/pkg/WorldWriteableFile.java:27: Warning: Using MODE_WORLD_READABLE when creating files can be risky, review carefully [WorldReadableFiles]\n" +
             "            out = openFileOutput(mFile.getName(), MODE_WORLD_READABLE);\n" +
             "                                                  ~~~~~~~~~~~~~~~~~~~\n" +
-            "src/test/pkg/WorldWriteableFile.java:31: Warning: Using MODE_WORLD_READABLE when creating files can be risky, review carefully [WorldReadableFiles]\n" +
+            "src/test/pkg/WorldWriteableFile.java:32: Warning: Using MODE_WORLD_READABLE when creating files can be risky, review carefully [WorldReadableFiles]\n" +
             "            prefs = getSharedPreferences(mContext, MODE_WORLD_READABLE);\n" +
             "                                                   ~~~~~~~~~~~~~~~~~~~\n" +
-            "src/test/pkg/WorldWriteableFile.java:25: Warning: Using MODE_WORLD_WRITEABLE when creating files can be risky, review carefully [WorldWriteableFiles]\n" +
+            "src/test/pkg/WorldWriteableFile.java:36: Warning: Using MODE_WORLD_READABLE when creating files can be risky, review carefully [WorldReadableFiles]\n" +
+            "            dir = getDir(mFile.getName(), MODE_WORLD_READABLE);\n" +
+            "                                          ~~~~~~~~~~~~~~~~~~~\n" +
+            "src/test/pkg/WorldWriteableFile.java:26: Warning: Using MODE_WORLD_WRITEABLE when creating files can be risky, review carefully [WorldWriteableFiles]\n" +
             "            out = openFileOutput(mFile.getName(), MODE_WORLD_WRITEABLE);\n" +
             "                                                  ~~~~~~~~~~~~~~~~~~~~\n" +
-            "src/test/pkg/WorldWriteableFile.java:30: Warning: Using MODE_WORLD_WRITEABLE when creating files can be risky, review carefully [WorldWriteableFiles]\n" +
+            "src/test/pkg/WorldWriteableFile.java:31: Warning: Using MODE_WORLD_WRITEABLE when creating files can be risky, review carefully [WorldWriteableFiles]\n" +
             "            prefs = getSharedPreferences(mContext, MODE_WORLD_WRITEABLE);\n" +
             "                                                   ~~~~~~~~~~~~~~~~~~~~\n" +
-            "0 errors, 4 warnings\n" +
+            "src/test/pkg/WorldWriteableFile.java:35: Warning: Using MODE_WORLD_WRITEABLE when creating files can be risky, review carefully [WorldWriteableFiles]\n" +
+            "            dir = getDir(mFile.getName(), MODE_WORLD_WRITEABLE);\n" +
+            "                                          ~~~~~~~~~~~~~~~~~~~~\n" +
+            "0 errors, 8 warnings\n" +
             "",
 
             lintProject(
@@ -227,4 +246,4 @@
                 lintProject("exportreceiver8.xml=>AndroidManifest.xml"));
 
     }
-}
\ No newline at end of file
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SetTextDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SetTextDetectorTest.java
new file mode 100644
index 0000000..0ab937f
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SetTextDetectorTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+@SuppressWarnings("javadoc")
+public class SetTextDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new SetTextDetector();
+    }
+
+    @SuppressWarnings("ClassNameDiffersFromFileName")
+    public void test() throws Exception {
+        assertEquals(
+            "src/test/pkg/CustomScreen.java:13: Warning: String literal in setText can not be translated. Use Android resources instead. [SetTextI18n]\n" +
+            "    view.setText(\"Hardcoded\");\n" +
+            "                 ~~~~~~~~~~~\n" +
+            "src/test/pkg/CustomScreen.java:17: Warning: Do not concatenate text displayed with setText. Use resource string with placeholders. [SetTextI18n]\n" +
+            "    view.setText(Integer.toString(50) + \"%\");\n" +
+            "                 ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+            "src/test/pkg/CustomScreen.java:17: Warning: Number formatting does not take into account locale settings. Consider using String.format instead. [SetTextI18n]\n" +
+            "    view.setText(Integer.toString(50) + \"%\");\n" +
+            "                 ~~~~~~~~~~~~~~~~~~~~\n" +
+            "src/test/pkg/CustomScreen.java:18: Warning: Do not concatenate text displayed with setText. Use resource string with placeholders. [SetTextI18n]\n" +
+            "    view.setText(Double.toString(12.5) + \" miles\");\n" +
+            "                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+            "src/test/pkg/CustomScreen.java:18: Warning: Number formatting does not take into account locale settings. Consider using String.format instead. [SetTextI18n]\n" +
+            "    view.setText(Double.toString(12.5) + \" miles\");\n" +
+            "                 ~~~~~~~~~~~~~~~~~~~~~\n" +
+            "src/test/pkg/CustomScreen.java:18: Warning: String literal in setText can not be translated. Use Android resources instead. [SetTextI18n]\n" +
+            "    view.setText(Double.toString(12.5) + \" miles\");\n" +
+            "                                         ~~~~~~~~\n" +
+            "src/test/pkg/CustomScreen.java:21: Warning: Do not concatenate text displayed with setText. Use resource string with placeholders. [SetTextI18n]\n" +
+            "    btn.setText(\"User \" + getUserName());\n" +
+            "                ~~~~~~~~~~~~~~~~~~~~~~~\n" +
+            "src/test/pkg/CustomScreen.java:21: Warning: String literal in setText can not be translated. Use Android resources instead. [SetTextI18n]\n" +
+            "    btn.setText(\"User \" + getUserName());\n" +
+            "                ~~~~~~~\n" +
+            "0 errors, 8 warnings\n",
+
+            lintProject(
+                java("src/test/pkg/CustomScreen.java", ""
+                + "package test.pkg;\n"
+                + "\n"
+                + "import android.content.Context;\n"
+                + "import android.widget.Button;\n"
+                + "import android.widget.TextView;\n"
+                + "\n"
+                + "class CustomScreen {\n"
+                + "\n"
+                + "  public CustomScreen(Context context) {\n"
+                + "    TextView view = new TextView(context);\n"
+                + "\n"
+                + "    // Should fail - hardcoded string\n"
+                + "    view.setText(\"Hardcoded\");\n"
+                + "    // Should pass - no letters\n"
+                + "    view.setText(\"-\");\n"
+                + "    // Should fail - concatenation and toString for numbers.\n"
+                + "    view.setText(Integer.toString(50) + \"%\");\n"
+                + "    view.setText(Double.toString(12.5) + \" miles\");\n"
+                + "\n"
+                + "    Button btn = new Button(context);\n"
+                + "    btn.setText(\"User \" + getUserName());\n"
+                + "    btn.setText(String.format(\"%s of %s users\", Integer.toString(5), Integer.toString(10)));\n"
+                + "  }\n"
+                + "\n"
+                + "  private static String getUserName() {\n"
+                + "    return \"stub\";\n"
+                + "  }\n"
+                + "}\n")));
+    }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SharedPrefsDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SharedPrefsDetectorTest.java
index 4fb95fa..ad778eb 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SharedPrefsDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SharedPrefsDetectorTest.java
@@ -18,13 +18,20 @@
 
 import com.android.tools.lint.detector.api.Detector;
 
-@SuppressWarnings("javadoc")
+@SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName"})
 public class SharedPrefsDetectorTest extends AbstractCheckTest {
     @Override
     protected Detector getDetector() {
         return new SharedPrefsDetector();
     }
 
+    @Override
+    protected boolean allowCompilationErrors() {
+        // Some of these unit tests are still relying on source code that references
+        // unresolved symbols etc.
+        return true;
+    }
+
     public void test() throws Exception {
         assertEquals(
             "src/test/pkg/SharedPrefsTest.java:54: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n" +
@@ -139,4 +146,42 @@
                     "apicheck/minsdk11.xml=>AndroidManifest.xml",
                     "src/test/pkg/SharedPrefsTest8.java.txt=>src/test/pkg/SharedPrefsTest8.java"));
     }
+
+    public void testChainedCalls() throws Exception {
+        assertEquals(""
+                + "src/test/pkg/Chained.java:24: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n"
+                + "        PreferenceManager\n"
+                + "        ^\n"
+                + "0 errors, 1 warnings\n",
+                lintProject(java("src/test/pkg/Chained.java", ""
+                        + "package test.pkg;\n"
+                        + "\n"
+                        + "import android.content.Context;\n"
+                        + "import android.preference.PreferenceManager;\n"
+                        + "\n"
+                        + "public class Chained {\n"
+                        + "    private static void falsePositive(Context context) {\n"
+                        + "        PreferenceManager\n"
+                        + "                .getDefaultSharedPreferences(context)\n"
+                        + "                .edit()\n"
+                        + "                .putString(\"wat\", \"wat\")\n"
+                        + "                .commit();\n"
+                        + "    }\n"
+                        + "\n"
+                        + "    private static void falsePositive2(Context context) {\n"
+                        + "        boolean var = PreferenceManager\n"
+                        + "                .getDefaultSharedPreferences(context)\n"
+                        + "                .edit()\n"
+                        + "                .putString(\"wat\", \"wat\")\n"
+                        + "                .commit();\n"
+                        + "    }\n"
+                        + "\n"
+                        + "    private static void truePositive(Context context) {\n"
+                        + "        PreferenceManager\n"
+                        + "                .getDefaultSharedPreferences(context)\n"
+                        + "                .edit()\n"
+                        + "                .putString(\"wat\", \"wat\");\n"
+                        + "    }\n"
+                        + "}\n")));
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SslCertificateSocketFactoryDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SslCertificateSocketFactoryDetectorTest.java
new file mode 100644
index 0000000..9d85889
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SslCertificateSocketFactoryDetectorTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2015 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 SslCertificateSocketFactoryDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new SslCertificateSocketFactoryDetector();
+    }
+
+    @Override
+    protected boolean allowCompilationErrors() {
+        // Some of these unit tests are still relying on source code that references
+        // unresolved symbols etc.
+        return true;
+    }
+
+    public void test() throws Exception {
+        assertEquals(
+                "src/test/pkg/SSLCertificateSocketFactoryTest.java:21: Warning: Use of SSLCertificateSocketFactory.createSocket() with an InetAddress parameter can cause insecure network traffic due to trusting arbitrary hostnames in TLS/SSL certificates presented by peers [SSLCertificateSocketFactoryCreateSocket]\n" +
+                "        sf.createSocket(inet, 80);\n" +
+                "        ~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+                "src/test/pkg/SSLCertificateSocketFactoryTest.java:22: Warning: Use of SSLCertificateSocketFactory.createSocket() with an InetAddress parameter can cause insecure network traffic due to trusting arbitrary hostnames in TLS/SSL certificates presented by peers [SSLCertificateSocketFactoryCreateSocket]\n" +
+                "        sf.createSocket(inet4, 80);\n" +
+                "        ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+                "src/test/pkg/SSLCertificateSocketFactoryTest.java:23: Warning: Use of SSLCertificateSocketFactory.createSocket() with an InetAddress parameter can cause insecure network traffic due to trusting arbitrary hostnames in TLS/SSL certificates presented by peers [SSLCertificateSocketFactoryCreateSocket]\n" +
+                "        sf.createSocket(inet6, 80);\n" +
+                "        ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+                "src/test/pkg/SSLCertificateSocketFactoryTest.java:24: Warning: Use of SSLCertificateSocketFactory.createSocket() with an InetAddress parameter can cause insecure network traffic due to trusting arbitrary hostnames in TLS/SSL certificates presented by peers [SSLCertificateSocketFactoryCreateSocket]\n" +
+                "        sf.createSocket(inet, 80, inet, 2000);\n" +
+                "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+                "src/test/pkg/SSLCertificateSocketFactoryTest.java:25: Warning: Use of SSLCertificateSocketFactory.createSocket() with an InetAddress parameter can cause insecure network traffic due to trusting arbitrary hostnames in TLS/SSL certificates presented by peers [SSLCertificateSocketFactoryCreateSocket]\n" +
+                "        sf.createSocket(inet4, 80, inet, 2000);\n" +
+                "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+                "src/test/pkg/SSLCertificateSocketFactoryTest.java:26: Warning: Use of SSLCertificateSocketFactory.createSocket() with an InetAddress parameter can cause insecure network traffic due to trusting arbitrary hostnames in TLS/SSL certificates presented by peers [SSLCertificateSocketFactoryCreateSocket]\n" +
+                "        sf.createSocket(inet6, 80, inet, 2000);\n" +
+                "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+                "src/test/pkg/SSLCertificateSocketFactoryTest.java:29: Warning: Use of SSLCertificateSocketFactory.getInsecure() can cause insecure network traffic due to trusting arbitrary TLS/SSL certificates presented by peers [SSLCertificateSocketFactoryGetInsecure]\n" +
+                "                SSLCertificateSocketFactory.getInsecure(-1,null));\n" +
+                "                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+                "0 errors, 7 warnings\n",
+                lintProject(java("src/test/pkg/SSLCertificateSocketFactoryTest.java", ""
+                        + "package test.pkg;\n"
+                        + "\n"
+                        + "import android.net.SSLCertificateSocketFactory;\n"
+                        + "import java.net.InetAddress;\n"
+                        + "import java.net.Inet4Address;\n"
+                        + "import java.net.Inet6Address;\n"
+                        + "import javax.net.ssl.HttpsURLConnection;\n"
+                        + "\n"
+                        + "public class SSLCertificateSocketFactoryTest {\n"
+                        + "    public void foo() {\n"
+                        + "        byte[] ipv4 = new byte[4];\n"
+                        + "        byte[] ipv6 = new byte[16];\n"
+                        + "        InetAddress inet = Inet4Address.getByAddress(ipv4);\n"
+                        + "        Inet4Address inet4 = (Inet4Address) Inet4Address.getByAddress(ipv4);\n"
+                        + "        Inet6Address inet6 = Inet6Address.getByAddress(null, ipv6, 0);\n"
+                        + "\n"
+                        + "        SSLCertificateSocketFactory sf = (SSLCertificateSocketFactory)\n"
+                        + "        SSLCertificateSocketFactory.getDefault(0);\n"
+                        + "        sf.createSocket(\"www.google.com\", 80); // ok\n"
+                        + "        sf.createSocket(\"www.google.com\", 80, inet, 2000); // ok\n"
+                        + "        sf.createSocket(inet, 80);\n"
+                        + "        sf.createSocket(inet4, 80);\n"
+                        + "        sf.createSocket(inet6, 80);\n"
+                        + "        sf.createSocket(inet, 80, inet, 2000);\n"
+                        + "        sf.createSocket(inet4, 80, inet, 2000);\n"
+                        + "        sf.createSocket(inet6, 80, inet, 2000);\n"
+                        + "\n"
+                        + "        HttpsURLConnection.setDefaultSSLSocketFactory(\n"
+                        + "                SSLCertificateSocketFactory.getInsecure(-1,null));\n"
+                        + "    }\n"
+                        + "}\n")));
+    }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/StringFormatDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/StringFormatDetectorTest.java
index 862d0ec..49ea0ce 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/StringFormatDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/StringFormatDetectorTest.java
@@ -23,8 +23,8 @@
 import java.util.HashSet;
 import java.util.Set;
 
-@SuppressWarnings("javadoc")
-public class StringFormatDetectorTest  extends AbstractCheckTest {
+@SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName"})
+public class StringFormatDetectorTest extends AbstractCheckTest {
     @Override
     protected Detector getDetector() {
         return new StringFormatDetector();
@@ -45,17 +45,13 @@
             "                                              ~~~~\n" +
             "    res/values/formatstrings.xml:6: Conflicting argument declaration here\n" +
             "src/test/pkg/StringFormatActivity.java:22: Error: Wrong argument type for formatting argument '#1' in score: conversion is 'd', received boolean (argument #2 in method call) [StringFormatMatches]\n" +
-            "        String output4 = String.format(score, won);   // wrong\n" +
+            "        String output  = String.format(score, won);   // wrong\n" +
             "                                              ~~~\n" +
             "    res/values/formatstrings.xml:6: Conflicting argument declaration here\n" +
             "src/test/pkg/StringFormatActivity.java:24: Error: Wrong argument count, format string hello2 requires 3 but format call supplies 2 [StringFormatMatches]\n" +
             "        String.format(getResources().getString(R.string.hello2), target, \"How are you\");\n" +
             "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
             "    res/values-es/formatstrings.xml:4: This definition requires 3 arguments\n" +
-            "src/test/pkg/StringFormatActivity.java:25: Error: Wrong argument count, format string hello2 requires 3 but format call supplies 2 [StringFormatMatches]\n" +
-            "        getResources().getString(hello2, target, \"How are you\");\n" +
-            "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
-            "    res/values-es/formatstrings.xml:4: This definition requires 3 arguments\n" +
             "src/test/pkg/StringFormatActivity.java:26: Error: Wrong argument count, format string hello2 requires 3 but format call supplies 2 [StringFormatMatches]\n" +
             "        getResources().getString(R.string.hello2, target, \"How are you\");\n" +
             "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
@@ -75,7 +71,7 @@
             "res/values/formatstrings.xml:5: Warning: Formatting string 'missing' is not referencing numbered arguments [1, 2] [StringFormatCount]\n" +
             "    <string name=\"missing\">Hello %3$s World</string>\n" +
             "    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
-            "9 errors, 2 warnings\n",
+            "8 errors, 2 warnings\n",
 
             lintProject(
                     "res/values/formatstrings.xml",
@@ -150,13 +146,52 @@
     }
 
     public void testSuppressed() throws Exception {
+        //noinspection ClassNameDiffersFromFileName,ConstantConditions
         assertEquals(
             "No warnings.",
 
             lintProject(
-                    "res/values/formatstrings_ignore.xml=>res/values/formatstrings.xml",
-                    "res/values-es/formatstrings_ignore.xml=>res/values-es/formatstrings.xml",
-                    "src/test/pkg/StringFormatActivity_ignore.java.txt=>src/test/pkg/StringFormatActivity.java"
+                    copy("res/values/formatstrings_ignore.xml", "res/values/formatstrings.xml"),
+                    copy("res/values-es/formatstrings_ignore.xml", "res/values-es/formatstrings.xml"),
+                    java("src/test/pkg/StringFormatActivity_ignore.java.txt", ""
+                            + "package test.pkg;\n"
+                            + "\n"
+                            + "import android.annotation.SuppressLint;\n"
+                            + "import android.app.Activity;\n"
+                            + "import android.os.Bundle;\n"
+                            + "\n"
+                            + "public class StringFormatActivity extends Activity {\n"
+                            + "    /** Called when the activity is first created. */\n"
+                            + "    @SuppressLint(\"all\")\n"
+                            + "    @Override\n"
+                            + "    public void onCreate(Bundle savedInstanceState) {\n"
+                            + "        super.onCreate(savedInstanceState);\n"
+                            + "        String target = \"World\";\n"
+                            + "        String hello = getResources().getString(R.string.hello);\n"
+                            + "        String output1 = String.format(hello, target);\n"
+                            + "        String hello2 = getResources().getString(R.string.hello2);\n"
+                            + "        String output2 = String.format(hello2, target, \"How are you\");\n"
+                            + "        setContentView(R.layout.main);\n"
+                            + "        String score = getResources().getString(R.string.score);\n"
+                            + "        int points = 50;\n"
+                            + "        boolean won = true;\n"
+                            + "        String output3 = String.format(score, points);\n"
+                            + "        String output4 = String.format(score, true);  // wrong\n"
+                            + "        String output  = String.format(score, won);   // wrong\n"
+                            + "        String output5 = String.format(score, 75);\n"
+                            + "    }\n"
+                            + "\n"
+                            + "    private static class R {\n"
+                            + "        private static class string {\n"
+                            + "            public static final int hello = 1;\n"
+                            + "            public static final int hello2 = 2;\n"
+                            + "            public static final int score = 3;\n"
+                            + "        }\n"
+                            + "        private static class layout {\n"
+                            + "            public static final int main = 4;\n"
+                            + "        }\n"
+                            + "    }\n"
+                            + "}\n")
                 ));
     }
 
@@ -222,11 +257,19 @@
 
     public void testGetStringAsParameter() throws Exception {
         assertEquals(""
-                + "No warnings.",
+                + "src/test/pkg/StringFormat4.java:11: Error: Wrong argument count, format string error_and_source requires 2 but format call supplies 1 [StringFormatMatches]\n"
+                + "        getString(R.string.error_and_source, getString(R.string.data_source)); // ERROR\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "    res/values/formatstrings6.xml:24: This definition requires 2 arguments\n"
+                + "src/test/pkg/StringFormat4.java:13: Error: Wrong argument count, format string error_and_source requires 2 but format call supplies 1 [StringFormatMatches]\n"
+                + "        getString(R.string.error_and_source, \"data source\"); // ERROR\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "    res/values/formatstrings6.xml:24: This definition requires 2 arguments\n"
+                + "2 errors, 0 warnings\n",
 
                 lintProject(
                         "res/values/formatstrings6.xml",
-                        "src/test/pkg/StringFormat4.java.txt=>src/test/pkg/StringFormat3.java"));
+                        "src/test/pkg/StringFormat4.java.txt=>src/test/pkg/StringFormat4.java"));
     }
 
     public void testNotLocaleMethod() throws Exception {
@@ -272,17 +315,13 @@
                 "                                              ~~~~\n" +
                 "    res/values/formatstrings.xml: Conflicting argument declaration here\n" +
                 "src/test/pkg/StringFormatActivity.java:22: Error: Wrong argument type for formatting argument '#1' in score: conversion is 'd', received boolean (argument #2 in method call) [StringFormatMatches]\n" +
-                "        String output4 = String.format(score, won);   // wrong\n" +
+                "        String output  = String.format(score, won);   // wrong\n" +
                 "                                              ~~~\n" +
                 "    res/values/formatstrings.xml: Conflicting argument declaration here\n" +
                 "src/test/pkg/StringFormatActivity.java:24: Error: Wrong argument count, format string hello2 requires 3 but format call supplies 2 [StringFormatMatches]\n" +
                 "        String.format(getResources().getString(R.string.hello2), target, \"How are you\");\n" +
                 "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
                 "    res/values-es/formatstrings.xml: This definition requires 3 arguments\n" +
-                "src/test/pkg/StringFormatActivity.java:25: Error: Wrong argument count, format string hello2 requires 3 but format call supplies 2 [StringFormatMatches]\n" +
-                "        getResources().getString(hello2, target, \"How are you\");\n" +
-                "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
-                "    res/values-es/formatstrings.xml: This definition requires 3 arguments\n" +
                 "src/test/pkg/StringFormatActivity.java:26: Error: Wrong argument count, format string hello2 requires 3 but format call supplies 2 [StringFormatMatches]\n" +
                 "        getResources().getString(R.string.hello2, target, \"How are you\");\n" +
                 "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
@@ -295,7 +334,7 @@
                 "    res/values-es/formatstrings.xml: Conflicting argument type here\n" +
                 "res/values/formatstrings.xml: Warning: Inconsistent number of arguments in formatting string hello2; found both 3 and 2 [StringFormatCount]\n" +
                 "    res/values-es/formatstrings.xml: Conflicting number of arguments here\n" +
-                "9 errors, 1 warnings\n",
+                "8 errors, 1 warnings\n",
 
         lintProjectIncrementally(
                 "src/test/pkg/StringFormatActivity.java",
@@ -313,7 +352,7 @@
                 lintProject(
                         "res/values/formatstrings3.xml",//"res/values/formatstrings.xml",
                         "res/values/shared_prefs_keys.xml",
-                        "src/test/pkg/SharedPrefsTest6.java.txt=>src/test/pkg/SharedPrefsTest6.java"));
+                        "src/test/pkg/SharedPrefsTest6.java.txt=>src/test/pkg/SharedPrefsFormat.java"));
     }
 
     public void testNotStringFormatIncrementally() throws Exception {
@@ -321,11 +360,11 @@
         assertEquals("No warnings.",
 
                 lintProjectIncrementally(
-                        "src/test/pkg/SharedPrefsTest6.java",
+                        "src/test/pkg/SharedPrefsFormat.java",
 
                         "res/values/formatstrings3.xml",//"res/values/formatstrings.xml",
                         "res/values/shared_prefs_keys.xml",
-                        "src/test/pkg/SharedPrefsTest6.java.txt=>src/test/pkg/SharedPrefsTest6.java"));
+                        "src/test/pkg/SharedPrefsTest6.java.txt=>src/test/pkg/SharedPrefsFormat.java"));
     }
 
     public void testIncrementalNonMatch() throws Exception {
@@ -417,4 +456,261 @@
 
                         ));
     }
+
+    @SuppressWarnings("ClassNameDiffersFromFileName")
+    public void testAdditionalGetStringMethods() throws Exception {
+        // Regression test for
+        //   https://code.google.com/p/android/issues/detail?id=183643
+        //   183643: Lint format detector should apply to Context#getString
+        // It also checks that we handle Object[] properly
+        assertEquals(""
+                + "src/test/pkg/FormatCheck.java:11: Error: Format string 'zero_args' is not a valid format string so it should not be passed to String.format [StringFormatInvalid]\n"
+                + "        context.getString(R.string.zero_args, \"first\"); // ERROR\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "    res/values/strings.xml:4: This definition does not require arguments\n"
+                + "src/test/pkg/FormatCheck.java:13: Error: Format string 'zero_args' is not a valid format string so it should not be passed to String.format [StringFormatInvalid]\n"
+                + "        context.getString(R.string.zero_args, new Object[] { \"first\" }); // ERROR\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "    res/values/strings.xml:4: This definition does not require arguments\n"
+                + "src/test/pkg/FormatCheck.java:17: Error: Wrong argument count, format string one_arg requires 1 but format call supplies 2 [StringFormatMatches]\n"
+                + "        context.getString(R.string.one_arg, \"too\", \"many\"); // ERROR: too many arguments\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "    res/values/strings.xml:5: This definition requires 1 arguments\n"
+                + "src/test/pkg/FormatCheck.java:18: Error: Wrong argument count, format string one_arg requires 1 but format call supplies 0 [StringFormatMatches]\n"
+                + "        context.getString(R.string.one_arg, new Object[0]); // ERROR: not enough arguments\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "    res/values/strings.xml:5: This definition requires 1 arguments\n"
+                + "src/test/pkg/FormatCheck.java:20: Error: Wrong argument count, format string one_arg requires 1 but format call supplies 2 [StringFormatMatches]\n"
+                + "        context.getString(R.string.one_arg, new Object[] { \"first\", \"second\" }); // ERROR\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "    res/values/strings.xml:5: This definition requires 1 arguments\n"
+                + "src/test/pkg/FormatCheck.java:22: Error: Wrong argument count, format string two_args requires 2 but format call supplies 1 [StringFormatMatches]\n"
+                + "        context.getString(R.string.two_args, \"first\"); // ERROR: too few\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "    res/values/strings.xml:6: This definition requires 2 arguments\n"
+                + "src/test/pkg/FormatCheck.java:24: Error: Wrong argument count, format string two_args requires 2 but format call supplies 0 [StringFormatMatches]\n"
+                + "        context.getString(R.string.two_args, new Object[0]); // ERROR: not enough arguments\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "    res/values/strings.xml:6: This definition requires 2 arguments\n"
+                + "src/test/pkg/FormatCheck.java:26: Error: Wrong argument count, format string two_args requires 2 but format call supplies 3 [StringFormatMatches]\n"
+                + "        context.getString(R.string.two_args, new Object[] { \"first\", \"second\", \"third\" }); // ERROR\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "    res/values/strings.xml:6: This definition requires 2 arguments\n"
+                + "src/test/pkg/FormatCheck.java:36: Error: Wrong argument count, format string one_arg requires 1 but format call supplies 3 [StringFormatMatches]\n"
+                + "        fragment.getString(R.string.one_arg, \"too\", \"many\", \"args\"); // ERROR: not enough arguments\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "    res/values/strings.xml:5: This definition requires 1 arguments\n"
+                + "9 errors, 0 warnings\n",
+
+                lintProject(
+                        java("src/test/pkg/FormatCheck.java", ""
+                                + "package test.pkg;\n"
+                                + "\n"
+                                + "import android.app.Fragment;\n"
+                                + "import android.content.Context;\n"
+                                + "\n"
+                                + "\n"
+                                + "\n"
+                                + "public class FormatCheck {\n"
+                                + "    public static void testContext(Context context) {\n"
+                                + "        context.getString(R.string.zero_args); // OK: Just looking up the string (includes %1$s)\n"
+                                + "        context.getString(R.string.zero_args, \"first\"); // ERROR\n"
+                                + "        context.getString(R.string.zero_args, new Object[0]); // OK\n"
+                                + "        context.getString(R.string.zero_args, new Object[] { \"first\" }); // ERROR\n"
+                                + "\n"
+                                + "        context.getString(R.string.one_arg); // OK: Just looking up the string (includes %1$s)\n"
+                                + "        context.getString(R.string.one_arg, \"first\"); // OK\n"
+                                + "        context.getString(R.string.one_arg, \"too\", \"many\"); // ERROR: too many arguments\n"
+                                + "        context.getString(R.string.one_arg, new Object[0]); // ERROR: not enough arguments\n"
+                                + "        context.getString(R.string.one_arg, new Object[] { \"first\" }); // OK\n"
+                                + "        context.getString(R.string.one_arg, new Object[] { \"first\", \"second\" }); // ERROR\n"
+                                + "        \n"
+                                + "        context.getString(R.string.two_args, \"first\"); // ERROR: too few\n"
+                                + "        context.getString(R.string.two_args, \"first\", \"second\"); // OK\n"
+                                + "        context.getString(R.string.two_args, new Object[0]); // ERROR: not enough arguments\n"
+                                + "        context.getString(R.string.two_args, new Object[] { \"first\", \"second\" }); // OK\n"
+                                + "        context.getString(R.string.two_args, new Object[] { \"first\", \"second\", \"third\" }); // ERROR\n"
+                                + "        String[] args2 = new String[] { \"first\", \"second\" };\n"
+                                + "        context.getString(R.string.two_args, args2); // OK\n"
+                                + "        String[] args3 = new String[] { \"first\", \"second\", \"third\" };\n"
+                                + "        context.getString(R.string.two_args, args3); // ERROR\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public static void testFragment(Fragment fragment) {\n"
+                                + "        fragment.getString(R.string.one_arg); // OK: Just looking up the string\n"
+                                + "        fragment.getString(R.string.one_arg, \"\"); // OK: Not checking non-varargs version\n"
+                                + "        fragment.getString(R.string.one_arg, \"too\", \"many\", \"args\"); // ERROR: not enough arguments\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public static void testArrayTypeConversions(Context context) {\n"
+                                + "        context.getString(R.string.one_arg, new Object[] { 5 }); // ERROR: Wrong type\n"
+                                + "        context.getString(R.string.two_args, new Object[] { 5, 5.0f }); // ERROR: Wrong type\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public static final class R {\n"
+                                + "        public static final class string {\n"
+                                + "            public static final int hello = 0x7f0a0000;\n"
+                                + "            public static final int zero_args = 0x7f0a0001;\n"
+                                + "            public static final int one_arg = 0x7f0a0002;\n"
+                                + "            public static final int two_args = 0x7f0a0003;\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "}\n"),
+                        xml("res/values/strings.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<resources>\n"
+                                + "    <string name=\"hello\">Hello %1$s</string>\n"
+                                + "    <string name=\"zero_args\">Hello</string>\n"
+                                + "    <string name=\"one_arg\">Hello %1$s</string>\n"
+                                + "    <string name=\"two_args\">Hello %1$s %2$s</string>\n"
+                                + "</resources>\n"
+                                + "\n")
+                ));
+    }
+
+    /**
+     * This test checks the same behaviour as {@link #testAdditionalGetStringMethods()} when lint
+     * is running incrementally.
+     */
+    @SuppressWarnings("ClassNameDiffersFromFileName")
+    public void testAdditionalGetStringMethodsIncrementally() throws Exception {
+        assertEquals(""
+                + "src/test/pkg/FormatCheck.java:11: Error: Format string 'zero_args' is not a valid format string so it should not be passed to String.format [StringFormatInvalid]\n"
+                + "        context.getString(R.string.zero_args, \"first\"); // ERROR\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "    res/values/strings.xml: This definition does not require arguments\n"
+                + "src/test/pkg/FormatCheck.java:13: Error: Format string 'zero_args' is not a valid format string so it should not be passed to String.format [StringFormatInvalid]\n"
+                + "        context.getString(R.string.zero_args, new Object[] { \"first\" }); // ERROR\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "    res/values/strings.xml: This definition does not require arguments\n"
+                + "src/test/pkg/FormatCheck.java:17: Error: Wrong argument count, format string one_arg requires 1 but format call supplies 2 [StringFormatMatches]\n"
+                + "        context.getString(R.string.one_arg, \"too\", \"many\"); // ERROR: too many arguments\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "    res/values/strings.xml: This definition requires 1 arguments\n"
+                + "src/test/pkg/FormatCheck.java:18: Error: Wrong argument count, format string one_arg requires 1 but format call supplies 0 [StringFormatMatches]\n"
+                + "        context.getString(R.string.one_arg, new Object[0]); // ERROR: not enough arguments\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "    res/values/strings.xml: This definition requires 1 arguments\n"
+                + "src/test/pkg/FormatCheck.java:20: Error: Wrong argument count, format string one_arg requires 1 but format call supplies 2 [StringFormatMatches]\n"
+                + "        context.getString(R.string.one_arg, new Object[] { \"first\", \"second\" }); // ERROR\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "    res/values/strings.xml: This definition requires 1 arguments\n"
+                + "src/test/pkg/FormatCheck.java:22: Error: Wrong argument count, format string two_args requires 2 but format call supplies 1 [StringFormatMatches]\n"
+                + "        context.getString(R.string.two_args, \"first\"); // ERROR: too few\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "    res/values/strings.xml: This definition requires 2 arguments\n"
+                + "src/test/pkg/FormatCheck.java:24: Error: Wrong argument count, format string two_args requires 2 but format call supplies 0 [StringFormatMatches]\n"
+                + "        context.getString(R.string.two_args, new Object[0]); // ERROR: not enough arguments\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "    res/values/strings.xml: This definition requires 2 arguments\n"
+                + "src/test/pkg/FormatCheck.java:26: Error: Wrong argument count, format string two_args requires 2 but format call supplies 3 [StringFormatMatches]\n"
+                + "        context.getString(R.string.two_args, new Object[] { \"first\", \"second\", \"third\" }); // ERROR\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "    res/values/strings.xml: This definition requires 2 arguments\n"
+                + "src/test/pkg/FormatCheck.java:36: Error: Wrong argument count, format string one_arg requires 1 but format call supplies 3 [StringFormatMatches]\n"
+                + "        fragment.getString(R.string.one_arg, \"too\", \"many\", \"args\"); // ERROR: not enough arguments\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "    res/values/strings.xml: This definition requires 1 arguments\n"
+                + "9 errors, 0 warnings\n",
+
+                lintProjectIncrementally(
+                        "src/test/pkg/FormatCheck.java",
+                        java("src/test/pkg/FormatCheck.java", ""
+                                + "package test.pkg;\n"
+                                + "\n"
+                                + "import android.app.Fragment;\n"
+                                + "import android.content.Context;\n"
+                                + "\n"
+                                + "\n"
+                                + "\n"
+                                + "public class FormatCheck {\n"
+                                + "    public static void testContext(Context context) {\n"
+                                + "        context.getString(R.string.zero_args); // OK: Just looking up the string (includes %1$s)\n"
+                                + "        context.getString(R.string.zero_args, \"first\"); // ERROR\n"
+                                + "        context.getString(R.string.zero_args, new Object[0]); // OK\n"
+                                + "        context.getString(R.string.zero_args, new Object[] { \"first\" }); // ERROR\n"
+                                + "\n"
+                                + "        context.getString(R.string.one_arg); // OK: Just looking up the string (includes %1$s)\n"
+                                + "        context.getString(R.string.one_arg, \"first\"); // OK\n"
+                                + "        context.getString(R.string.one_arg, \"too\", \"many\"); // ERROR: too many arguments\n"
+                                + "        context.getString(R.string.one_arg, new Object[0]); // ERROR: not enough arguments\n"
+                                + "        context.getString(R.string.one_arg, new Object[] { \"first\" }); // OK\n"
+                                + "        context.getString(R.string.one_arg, new Object[] { \"first\", \"second\" }); // ERROR\n"
+                                + "        \n"
+                                + "        context.getString(R.string.two_args, \"first\"); // ERROR: too few\n"
+                                + "        context.getString(R.string.two_args, \"first\", \"second\"); // OK\n"
+                                + "        context.getString(R.string.two_args, new Object[0]); // ERROR: not enough arguments\n"
+                                + "        context.getString(R.string.two_args, new Object[] { \"first\", \"second\" }); // OK\n"
+                                + "        context.getString(R.string.two_args, new Object[] { \"first\", \"second\", \"third\" }); // ERROR\n"
+                                + "        String[] args2 = new String[] { \"first\", \"second\" };\n"
+                                + "        context.getString(R.string.two_args, args2); // OK\n"
+                                + "        String[] args3 = new String[] { \"first\", \"second\", \"third\" };\n"
+                                + "        context.getString(R.string.two_args, args3); // ERROR\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public static void testFragment(Fragment fragment) {\n"
+                                + "        fragment.getString(R.string.one_arg); // OK: Just looking up the string\n"
+                                + "        fragment.getString(R.string.one_arg, \"\"); // OK: Not checking non-varargs version\n"
+                                + "        fragment.getString(R.string.one_arg, \"too\", \"many\", \"args\"); // ERROR: not enough arguments\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public static void testArrayTypeConversions(Context context) {\n"
+                                + "        context.getString(R.string.one_arg, new Object[] { 5 }); // ERROR: Wrong type\n"
+                                + "        context.getString(R.string.two_args, new Object[] { 5, 5.0f }); // ERROR: Wrong type\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public static final class R {\n"
+                                + "        public static final class string {\n"
+                                + "            public static final int hello = 0x7f0a0000;\n"
+                                + "            public static final int zero_args = 0x7f0a0001;\n"
+                                + "            public static final int one_arg = 0x7f0a0002;\n"
+                                + "            public static final int two_args = 0x7f0a0003;\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "}\n"),
+                        xml("res/values/strings.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<resources>\n"
+                                + "    <string name=\"hello\">Hello %1$s</string>\n"
+                                + "    <string name=\"zero_args\">Hello</string>\n"
+                                + "    <string name=\"one_arg\">Hello %1$s</string>\n"
+                                + "    <string name=\"two_args\">Hello %1$s %2$s</string>\n"
+                                + "</resources>\n"
+                                + "\n")
+                ));
+    }
+
+    public void testIssue197940() throws Exception {
+        // Regression test for
+        //   https://code.google.com/p/android/issues/detail?id=197940
+        //    197940: The linter alerts about a wrong String.format format, but it's ok
+        assertEquals("No warnings.",
+
+                lintProject(
+                        java("src/test/pkg/FormatCheck.java", ""
+                                + "package test.pkg;\n"
+                                + "\n"
+                                + "import android.content.res.Resources;\n"
+                                + "\n"
+                                + "public class FormatCheck {\n"
+
+                                + "    private static String test(Resources resources) {\n"
+                                + "        return String.format(\"%s\", resources.getString(R.string.a_b_c));\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public static final class R {\n"
+                                + "        public static final class string {\n"
+                                + "            public static final int a_b_c = 0x7f0a0000;\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "}\n"),
+                        xml("res/values/strings.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<resources>\n"
+                                + "    <string name=\"a_b_c\">A b c </string>\n\n"
+                                + "    <string name=\"a_b_c_2\">A %1$s c </string>\n\n"
+                                + "</resources>\n"
+                                + "\n")
+                ));
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SupportAnnotationDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SupportAnnotationDetectorTest.java
index 518566a..6e61d6e 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SupportAnnotationDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SupportAnnotationDetectorTest.java
@@ -16,6 +16,9 @@
 
 package com.android.tools.lint.checks;
 
+import static com.android.SdkConstants.TAG_USES_PERMISSION;
+import static com.android.SdkConstants.TAG_USES_PERMISSION_SDK_23;
+import static com.android.SdkConstants.TAG_USES_PERMISSION_SDK_M;
 import static com.android.tools.lint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION;
 
 import com.android.tools.lint.ExternalAnnotationRepository;
@@ -417,6 +420,26 @@
                                 "src/android/support/annotation/DrawableRes.java")));
     }
 
+    // Temporarily disabled; TypedArray.getResourceId has now been annotated with @StyleRes
+    //public void testResourceTypesIssue182433() throws Exception {
+    //    // Regression test for https://code.google.com/p/android/issues/detail?id=182433
+    //    assertEquals("No warnings.",
+    //            lintProject(
+    //                    java("src/test/pkg/ResourceTypeTest.java", ""
+    //                            + "package test.pkg;\n"
+    //                            + "import android.app.Activity;\n"
+    //                            + "import android.content.res.TypedArray;\n"
+    //                            + "\n"
+    //                            + "@SuppressWarnings(\"unused\")\n"
+    //                            + "public class ResourceTypeTest extends Activity {\n"
+    //                            + "    public static void test(TypedArray typedArray) {\n"
+    //                            + "       typedArray.getResourceId(2 /* index */, 0 /* invalid drawableRes */);\n"
+    //                            + "    }\n"
+    //                            + "}\n"),
+    //                    mAnyResAnnotation
+    //            ));
+    //}
+
     @SuppressWarnings({"MethodMayBeStatic", "ResultOfObjectAllocationIgnored"})
     public void testConstructor() throws Exception {
         assertEquals(""
@@ -676,7 +699,7 @@
                 + "    android:versionCode=\"1\"\n"
                 + "    android:versionName=\"1.0\" >\n"
                 + "\n"
-                + "    <uses-sdk android:minSdkVersion=\"14\" android:targetSdkVersion=\""
+                + "    <uses-sdk android:minSdkVersion=\"" + minSdk + "\" android:targetSdkVersion=\""
                 + targetSdk + "\" />\n"
                 + "\n"
                 + permissionBlock.toString()
@@ -695,7 +718,7 @@
             + "import android.content.Context;\n"
             + "import android.content.pm.PackageManager;\n"
             + "import android.location.LocationManager;\n"
-            + "\n"
+            + "import java.io.IOException;\n"
             + "import java.security.AccessControlException;\n"
             + "\n"
             + "public class RevokeTest {\n"
@@ -708,8 +731,8 @@
             + "        }\n"
             + "\n"
             + "        try {\n"
-            + "            // Ok: Security exception super class caught in one of the branches\n"
-            + "            locationManager.myMethod(provider); // OK\n"
+            + "            // You have to catch SecurityException explicitly, not parent\n"
+            + "            locationManager.myMethod(provider); // ERROR\n"
             + "        } catch (RuntimeException e) { // includes Security Exception\n"
             + "        }\n"
             + "\n"
@@ -724,8 +747,8 @@
             + "        }\n"
             + "\n"
             + "        try {\n"
-            + "            // Ok: Security exception super class caught in one of the branches\n"
-            + "            locationManager.myMethod(provider); // OK\n"
+            + "            // You have to catch SecurityException explicitly, not parent\n"
+            + "            locationManager.myMethod(provider); // ERROR\n"
             + "        } catch (Exception e) { // includes Security Exception\n"
             + "        }\n"
             + "\n"
@@ -759,7 +782,8 @@
             + "\n"
             + "    public static void test6(LocationManager locationManager, String provider)\n"
             + "            throws Exception { // includes Security Exception\n"
-            + "        locationManager.myMethod(provider); // OK\n"
+            + "        // You have to throw SecurityException explicitly, not parent\n"
+            + "        locationManager.myMethod(provider); // ERROR\n"
             + "    }\n"
             + "\n"
             + "    public static void test7(LocationManager locationManager, String provider, Context context)\n"
@@ -770,6 +794,23 @@
             + "        locationManager.myMethod(provider); // OK: permission checked\n"
             + "    }\n"
             + "\n"
+            + "    public void test8(LocationManager locationManager, String provider) {\n"
+            + "          // Regression test for http://b.android.com/187204\n"
+            + "        try {\n"
+            + "            locationManager.myMethod(provider); // ERROR\n"
+            + "            mightThrow();\n"
+            + "        } catch (SecurityException | IOException se) { // OK: Checked in multi catch\n"
+            + "        }\n"
+            + "        try {\n"
+            + "            locationManager.myMethod(provider); // ERROR\n"
+            + "            mightThrow();\n"
+            + "        } catch (IOException | SecurityException se) { // OK: Checked in multi catch\n"
+            + "        }\n"
+            + "    }\n"
+            + "\n"
+            + "    public void mightThrow() throws IOException {\n"
+            + "    }\n"
+            + "\n"
             + "}\n");
 
     public void testMissingPermissions() throws Exception {
@@ -796,19 +837,28 @@
 
     public void testRevokePermissions() throws Exception {
         assertEquals(""
-                + "src/test/pkg/RevokeTest.java:44: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or handle a potential SecurityException [MissingPermission]\n"
+                + "src/test/pkg/RevokeTest.java:20: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or explicitly handle a potential SecurityException [MissingPermission]\n"
                 + "            locationManager.myMethod(provider); // ERROR\n"
                 + "            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
-                + "src/test/pkg/RevokeTest.java:50: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or handle a potential SecurityException [MissingPermission]\n"
+                + "src/test/pkg/RevokeTest.java:36: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or explicitly handle a potential SecurityException [MissingPermission]\n"
+                + "            locationManager.myMethod(provider); // ERROR\n"
+                + "            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/RevokeTest.java:44: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or explicitly handle a potential SecurityException [MissingPermission]\n"
+                + "            locationManager.myMethod(provider); // ERROR\n"
+                + "            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/RevokeTest.java:50: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or explicitly handle a potential SecurityException [MissingPermission]\n"
                 + "        locationManager.myMethod(provider); // ERROR: not caught\n"
                 + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
-                + "src/test/pkg/RevokeTest.java:55: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or handle a potential SecurityException [MissingPermission]\n"
+                + "src/test/pkg/RevokeTest.java:55: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or explicitly handle a potential SecurityException [MissingPermission]\n"
                 + "        locationManager.myMethod(provider); // ERROR: not caught by right type\n"
                 + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
-                + "src/test/pkg/RevokeTest.java:60: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or handle a potential SecurityException [MissingPermission]\n"
+                + "src/test/pkg/RevokeTest.java:60: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or explicitly handle a potential SecurityException [MissingPermission]\n"
                 + "        locationManager.myMethod(provider); // ERROR\n"
                 + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
-                + "4 errors, 0 warnings\n",
+                + "src/test/pkg/RevokeTest.java:71: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or explicitly handle a potential SecurityException [MissingPermission]\n"
+                + "        locationManager.myMethod(provider); // ERROR\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "7 errors, 0 warnings\n",
                 lintProject(
                         getManifestWithPermissions(23, "android.permission.ACCESS_FINE_LOCATION"),
                         mLocationManagerStub,
@@ -904,6 +954,38 @@
                         mRequirePermissionAnnotation));
     }
 
+    public void testUsesPermissionSdk23() throws Exception {
+        TestFile manifest = getManifestWithPermissions(14,
+                "android.permission.ACCESS_FINE_LOCATION",
+                "android.permission.BLUETOOTH");
+        String contents = manifest.getContents();
+        assertNotNull(contents);
+        String s = contents.replace(TAG_USES_PERMISSION, TAG_USES_PERMISSION_SDK_23);
+        manifest.withSource(s);
+        assertEquals("No warnings.",
+                lintProject(
+                        manifest,
+                        mPermissionTest,
+                        mComplexLocationManagerStub,
+                        mRequirePermissionAnnotation));
+    }
+
+    public void testUsesPermissionSdkM() throws Exception {
+        TestFile manifest = getManifestWithPermissions(14,
+                "android.permission.ACCESS_FINE_LOCATION",
+                "android.permission.BLUETOOTH");
+        String contents = manifest.getContents();
+        assertNotNull(contents);
+        String s = contents.replace(TAG_USES_PERMISSION, TAG_USES_PERMISSION_SDK_M);
+        manifest.withSource(s);
+        assertEquals("No warnings.",
+                lintProject(
+                        manifest,
+                        mPermissionTest,
+                        mComplexLocationManagerStub,
+                        mRequirePermissionAnnotation));
+    }
+
     public void testPermissionAnnotation() throws Exception {
         assertEquals(""
                 + "src/test/pkg/LocationManager.java:24: Error: Missing permissions required by LocationManager.getLastKnownLocation: android.permission.ACCESS_FINE_LOCATION or android.permission.ACCESS_COARSE_LOCATION [MissingPermission]\n"
@@ -979,6 +1061,15 @@
                         + "            protected void onPreExecute() {\n"
                         + "                publishProgress(); // ERROR\n"
                         + "                onProgressUpdate(); // OK\n"
+                        + "                // Suppressed via older Android Studio inspection id:\n"
+                        + "                //noinspection ResourceType\n"
+                        + "                publishProgress(); // SUPPRESSED\n"
+                        + "                // Suppressed via new lint id:\n"
+                        + "                //noinspection WrongThread\n"
+                        + "                publishProgress(); // SUPPRESSED\n"
+                        + "                // Suppressed via Studio inspection id:\n"
+                        + "                //noinspection AndroidLintWrongThread\n"
+                        + "                publishProgress(); // SUPPRESSED\n"
                         + "            }\n"
                         + "        };\n"
                         + "    }\n"
@@ -1139,7 +1230,8 @@
                                 + "        Intent intent = new Intent(Intent.ACTION_CALL);\n"
                                 + "        intent.setData(Uri.parse(\"tel:1234567890\"));\n"
                                 + "        // This one will only be flagged if we have framework metadata on Intent.ACTION_CALL\n"
-                                + "        activity.startActivity(intent);\n"
+                                // Too flaky
+                                + "        //activity.startActivity(intent);\n"
                                 + "    }\n"
                                 + "\n"
                                 + "    public static void activities2(Activity activity) {\n"
@@ -1259,5 +1351,351 @@
                 ));
     }
 
+    public void testMultipleProjects() throws Exception {
+        // Regression test for https://code.google.com/p/android/issues/detail?id=182179
+        // 182179: Lint gives erroneous @StringDef errors in androidTests
+        assertEquals(""
+                        + "src/test/zpkg/SomeClassTest.java:10: Error: Must be one of: SomeClass.MY_CONSTANT [WrongConstant]\n"
+                        + "        SomeClass.doSomething(\"error\");\n"
+                        + "                              ~~~~~~~\n"
+                        + "1 errors, 0 warnings\n",
 
+                lintProject(
+                        getManifestWithPermissions(14, 23),
+                        java("src/test/pkg/SomeClass.java", ""
+                                + "package test.pkg;\n"
+                                + "\n"
+                                + "import android.support.annotation.StringDef;\n"
+                                + "import android.util.Log;\n"
+                                + "\n"
+                                + "import java.lang.annotation.Documented;\n"
+                                + "import java.lang.annotation.Retention;\n"
+                                + "import java.lang.annotation.RetentionPolicy;\n"
+                                + "\n"
+                                + "public class SomeClass {\n"
+                                + "\n"
+                                + "    public static final String MY_CONSTANT = \"foo\";\n"
+                                + "\n"
+                                + "    public static void doSomething(@MyTypeDef final String myString) {\n"
+                                + "        Log.v(\"tag\", myString);\n"
+                                + "    }\n"
+                                + "\n"
+                                + "\n"
+                                + "    /**\n"
+                                + "     * Defines the possible values for state type.\n"
+                                + "     */\n"
+                                + "    @StringDef({MY_CONSTANT})\n"
+                                + "    @Documented\n"
+                                + "    @Retention(RetentionPolicy.SOURCE)\n"
+                                + "    public @interface MyTypeDef {\n"
+                                + "\n"
+                                + "    }\n"
+                                + "}"),
+                        // test.zpkg: alphabetically after test.pkg: We want to make sure
+                        // that the SomeClass source unit is disposed before we try to
+                        // process SomeClassTest and try to resolve its SomeClass.MY_CONSTANT
+                        // @IntDef reference
+                        java("src/test/zpkg/SomeClassTest.java", ""
+                                + "package test.zpkg;\n"
+                                + "\n"
+                                + "import test.pkg.SomeClass;\n"
+                                + "import junit.framework.TestCase;\n"
+                                + "\n"
+                                + "public class SomeClassTest extends TestCase {\n"
+                                + "\n"
+                                + "    public void testDoSomething() {\n"
+                                + "        SomeClass.doSomething(SomeClass.MY_CONSTANT);\n"
+                                + "        SomeClass.doSomething(\"error\");\n"
+                                + "    }\n"
+                                + "}"),
+                        copy("src/android/support/annotation/StringDef.java.txt",
+                                "src/android/support/annotation/StringDef.java")
+                ));
+    }
+
+    @SuppressWarnings({"InstantiationOfUtilityClass", "ResultOfObjectAllocationIgnored"})
+    public void testMultipleResourceTypes() throws Exception {
+        // Regression test for
+        //   https://code.google.com/p/android/issues/detail?id=187181
+        // Make sure that parameters which specify multiple resource types are handled
+        // correctly.
+        assertEquals(""
+                + "src/test/pkg/ResourceTypeTest.java:14: Error: Expected resource of type drawable or string [ResourceType]\n"
+                + "        new ResourceTypeTest(res, R.raw.my_raw_file); // ERROR\n"
+                + "                                  ~~~~~~~~~~~~~~~~~\n"
+                + "1 errors, 0 warnings\n",
+
+                lintProject(
+                        java("src/test/pkg/ResourceTypeTest.java", ""
+                                + "package test.pkg;\n"
+                                + "\n"
+                                + "import android.content.res.Resources;\n"
+                                + "import android.support.annotation.DrawableRes;\n"
+                                + "import android.support.annotation.StringRes;\n"
+                                + "\n"
+                                + "public class ResourceTypeTest {\n"
+                                + "    public ResourceTypeTest(Resources res, @DrawableRes @StringRes int id) {\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public static void test(Resources res) {\n"
+                                + "        new ResourceTypeTest(res, R.drawable.ic_announcement_24dp); // OK\n"
+                                + "        new ResourceTypeTest(res, R.string.action_settings); // OK\n"
+                                + "        new ResourceTypeTest(res, R.raw.my_raw_file); // ERROR\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public static final class R {\n"
+                                + "        public static final class drawable {\n"
+                                + "            public static final int ic_announcement_24dp = 0x7f0a0000;\n"
+                                + "        }\n"
+                                + "        public static final class string {\n"
+                                + "            public static final int action_settings = 0x7f0a0001;\n"
+                                + "        }\n"
+                                + "        public static final class raw {\n"
+                                + "            public static final int my_raw_file = 0x7f0a0002;\n"
+                                + "        }\n"
+                                + "    }"
+                                + "}"),
+                        copy("src/android/support/annotation/DrawableRes.java.txt", "src/android/support/annotation/DrawableRes.java"),
+                        mStringResAnnotation
+                ));
+    }
+
+    @SuppressWarnings({"InstantiationOfUtilityClass", "ResultOfObjectAllocationIgnored"})
+    public void testAnyRes() throws Exception {
+        // Make sure error messages for @AnyRes are handled right since it's now an
+        // enum set containing all possible resource types
+        assertEquals(""
+                + "src/test/pkg/AnyResTest.java:14: Error: Expected resource identifier (R.type.name) [ResourceType]\n"
+                + "        new AnyResTest(res, 52); // ERROR\n"
+                + "                            ~~\n"
+                + "1 errors, 0 warnings\n",
+
+                lintProject(
+                        java("src/test/pkg/AnyResTest.java", ""
+                                + "package test.pkg;\n"
+                                + "\n"
+                                + "import android.content.res.Resources;\n"
+                                + "import android.support.annotation.AnyRes;\n"
+                                + "\n"
+                                + "public class AnyResTest {\n"
+                                + "    public AnyResTest(Resources res, @AnyRes int id) {\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public static void test(Resources res) {\n"
+                                + "        new AnyResTest(res, R.drawable.ic_announcement_24dp); // OK\n"
+                                + "        new AnyResTest(res, R.string.action_settings); // OK\n"
+                                + "        new AnyResTest(res, R.raw.my_raw_file); // OK\n"
+                                + "        new AnyResTest(res, 52); // ERROR\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public static final class R {\n"
+                                + "        public static final class drawable {\n"
+                                + "            public static final int ic_announcement_24dp = 0x7f0a0000;\n"
+                                + "        }\n"
+                                + "        public static final class string {\n"
+                                + "            public static final int action_settings = 0x7f0a0001;\n"
+                                + "        }\n"
+                                + "        public static final class raw {\n"
+                                + "            public static final int my_raw_file = 0x7f0a0002;\n"
+                                + "        }\n"
+                                + "    }"
+                                + "}"),
+                        copy("src/android/support/annotation/AnyRes.java.txt", "src/android/support/annotation/AnyRes.java")
+                ));
+    }
+
+    /**
+     * Test @IntDef when applied to multiple elements like arrays or varargs.
+     */
+    public void testIntDefMultiple() throws Exception {
+        assertEquals(""
+                + "src/test/pkg/IntDefMultiple.java:24: Error: Must be one of: IntDefMultiple.VALUE_A, IntDefMultiple.VALUE_B [WrongConstant]\n"
+                + "        restrictedArray(/*Must be one of: X.VALUE_A, X.VALUE_B*/new int[]{VALUE_A, 0, VALUE_B}/**/); // ERROR;\n"
+                + "                                                                                   ~\n"
+                + "src/test/pkg/IntDefMultiple.java:31: Error: Must be one of: IntDefMultiple.VALUE_A, IntDefMultiple.VALUE_B [WrongConstant]\n"
+                + "        restrictedEllipsis(VALUE_A, /*Must be one of: X.VALUE_A, X.VALUE_B*/0/**/, VALUE_B); // ERROR\n"
+                + "                                                                            ~\n"
+                + "src/test/pkg/IntDefMultiple.java:32: Error: Must be one of: IntDefMultiple.VALUE_A, IntDefMultiple.VALUE_B [WrongConstant]\n"
+                + "        restrictedEllipsis(/*Must be one of: X.VALUE_A, X.VALUE_B*/0/**/); // ERROR\n"
+                + "                                                                   ~\n"
+                + "3 errors, 0 warnings\n",
+                lintProject(
+                        java("src/test/pkg/IntDefMultiple.java", ""
+                                + "package test.pkg;\n"
+                                + "import android.support.annotation.IntDef;\n"
+                                + "\n"
+                                + "public class IntDefMultiple {\n"
+                                + "    private static final int VALUE_A = 0;\n"
+                                + "    private static final int VALUE_B = 1;\n"
+                                + "\n"
+                                + "    private static final int[] VALID_ARRAY = {VALUE_A, VALUE_B};\n"
+                                + "    private static final int[] INVALID_ARRAY = {VALUE_A, 0, VALUE_B};\n"
+                                + "    private static final int[] INVALID_ARRAY2 = {10};\n"
+                                + "\n"
+                                + "    @IntDef({VALUE_A, VALUE_B})\n"
+                                + "    public @interface MyIntDef {}\n"
+                                + "\n"
+                                + "    @MyIntDef\n"
+                                + "    public int a = 0;\n"
+                                + "\n"
+                                + "    @MyIntDef\n"
+                                + "    public int[] b;\n"
+                                + "\n"
+                                + "    public void testCall() {\n"
+                                + "        restrictedArray(new int[]{VALUE_A}); // OK\n"
+                                + "        restrictedArray(new int[]{VALUE_A, VALUE_B}); // OK\n"
+                                + "        restrictedArray(/*Must be one of: X.VALUE_A, X.VALUE_B*/new int[]{VALUE_A, 0, VALUE_B}/**/); // ERROR;\n"
+                                + "        restrictedArray(VALID_ARRAY); // OK\n"
+                                + "        restrictedArray(/*Must be one of: X.VALUE_A, X.VALUE_B*/INVALID_ARRAY/**/); // ERROR\n"
+                                + "        restrictedArray(/*Must be one of: X.VALUE_A, X.VALUE_B*/INVALID_ARRAY2/**/); // ERROR\n"
+                                + "\n"
+                                + "        restrictedEllipsis(VALUE_A); // OK\n"
+                                + "        restrictedEllipsis(VALUE_A, VALUE_B); // OK\n"
+                                + "        restrictedEllipsis(VALUE_A, /*Must be one of: X.VALUE_A, X.VALUE_B*/0/**/, VALUE_B); // ERROR\n"
+                                + "        restrictedEllipsis(/*Must be one of: X.VALUE_A, X.VALUE_B*/0/**/); // ERROR\n"
+                                + "        // Suppressed via older Android Studio inspection id:\n"
+                                + "        //noinspection ResourceType\n"
+                                + "        restrictedEllipsis(0); // SUPPRESSED\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    private void restrictedEllipsis(@MyIntDef int... test) {}\n"
+                                + "\n"
+                                + "    private void restrictedArray(@MyIntDef int[] test) {}\n"
+                                + "}"),
+                        copy("src/android/support/annotation/IntDef.java.txt",
+                                "src/android/support/annotation/IntDef.java")));
+    }
+
+    /**
+     * Test @IntRange and @FloatRange support annotation applied to arrays and vargs.
+     */
+    public void testRangesMultiple() throws Exception {
+        assertEquals(""
+                + "src/test/pkg/RangesMultiple.java:22: Error: Value must be ≥ 10.0 (was 5.0) [Range]\n"
+                + "        varargsFloat(15.0f, 10.0f, /*Value must be ≥ 10.0 and ≤ 15.0 (was 5.0f)*/5.0f/**/); // ERROR\n"
+                + "                                                                                 ~~~~\n"
+                + "src/test/pkg/RangesMultiple.java:32: Error: Value must be ≤ 500 (was 510) [Range]\n"
+                + "        varargsInt(15, 10, /*Value must be ≥ 10 and ≤ 500 (was 510)*/510/**/); // ERROR\n"
+                + "                                                                     ~~~\n"
+                + "src/test/pkg/RangesMultiple.java:36: Error: Value must be ≥ 10 (was 0) [Range]\n"
+                + "        restrictedIntArray(/*Value must be ≥ 10 and ≤ 500*/new int[]{0, 500}/**/); // ERROR\n"
+                + "                                                           ~~~~~~~~~~~~~~~~~\n"
+                + "3 errors, 0 warnings\n",
+                lintProject(
+                        java("src/test/pkg/RangesMultiple.java", ""
+                                + "package test.pkg;\n"
+                                + "import android.support.annotation.FloatRange;\n"
+                                + "import android.support.annotation.IntRange;\n"
+                                + "\n"
+                                + "public class RangesMultiple {\n"
+                                + "    private static final float[] VALID_FLOAT_ARRAY = new float[] {10.0f, 12.0f, 15.0f};\n"
+                                + "    private static final float[] INVALID_FLOAT_ARRAY = new float[] {10.0f, 12.0f, 5.0f};\n"
+                                + "\n"
+                                + "    private static final int[] VALID_INT_ARRAY = new int[] {15, 120, 500};\n"
+                                + "    private static final int[] INVALID_INT_ARRAY = new int[] {15, 120, 5};\n"
+                                + "\n"
+                                + "    @FloatRange(from = 10.0, to = 15.0)\n"
+                                + "    public float[] a;\n"
+                                + "\n"
+                                + "    @IntRange(from = 10, to = 500)\n"
+                                + "    public int[] b;\n"
+                                + "\n"
+                                + "    public void testCall() {\n"
+                                + "        a = new float[2];\n"
+                                + "        a[0] = /*Value must be ≥ 10.0 and ≤ 15.0 (was 5f)*/5f/**/; // ERROR\n"
+                                + "        a[1] = 14f; // OK\n"
+                                + "        varargsFloat(15.0f, 10.0f, /*Value must be ≥ 10.0 and ≤ 15.0 (was 5.0f)*/5.0f/**/); // ERROR\n"
+                                + "        restrictedFloatArray(VALID_FLOAT_ARRAY); // OK\n"
+                                + "        restrictedFloatArray(/*Value must be ≥ 10.0 and ≤ 15.0*/INVALID_FLOAT_ARRAY/**/); // ERROR\n"
+                                + "        restrictedFloatArray(new float[]{10.5f, 14.5f}); // OK\n"
+                                + "        restrictedFloatArray(/*Value must be ≥ 10.0 and ≤ 15.0*/new float[]{12.0f, 500.0f}/**/); // ERROR\n"
+                                + "\n"
+                                + "\n"
+                                + "        b = new int[2];\n"
+                                + "        b[0] = /*Value must be ≥ 10 and ≤ 500 (was 5)*/5/**/; // ERROR\n"
+                                + "        b[1] = 100; // OK\n"
+                                + "        varargsInt(15, 10, /*Value must be ≥ 10 and ≤ 500 (was 510)*/510/**/); // ERROR\n"
+                                + "        restrictedIntArray(VALID_INT_ARRAY); // OK\n"
+                                + "        restrictedIntArray(/*Value must be ≥ 10 and ≤ 500*/INVALID_INT_ARRAY/**/); // ERROR\n"
+                                + "        restrictedIntArray(new int[]{50, 500}); // OK\n"
+                                + "        restrictedIntArray(/*Value must be ≥ 10 and ≤ 500*/new int[]{0, 500}/**/); // ERROR\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public void restrictedIntArray(@IntRange(from = 10, to = 500) int[] a) {\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public void varargsInt(@IntRange(from = 10, to = 500) int... a) {\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public void varargsFloat(@FloatRange(from = 10.0, to = 15.0) float... a) {\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public void restrictedFloatArray(@FloatRange(from = 10.0, to = 15.0) float[] a) {\n"
+                                + "    }\n"
+                                + "}\n"
+                                + "\n"),
+                        copy("src/android/support/annotation/IntRange.java.txt", "src/android/support/annotation/IntRange.java"),
+                        copy("src/android/support/annotation/FloatRange.java.txt", "src/android/support/annotation/FloatRange.java")));
+    }
+
+    public void testIntDefInBuilder() throws Exception {
+        // Ensure that we only check constants, not instance fields, when passing
+        // fields as arguments to typedef parameters.
+        assertEquals("No warnings.",
+                lintProject(
+                        java("src/test/pkg/Product.java", ""
+                                + "package test.pkg;\n"
+                                + "\n"
+                                + "import android.support.annotation.IntDef;\n"
+                                + "\n"
+                                + "import java.lang.annotation.Retention;\n"
+                                + "import java.lang.annotation.RetentionPolicy;\n"
+                                + "\n"
+                                + "public class Product {\n"
+                                + "    @IntDef({\n"
+                                + "         STATUS_AVAILABLE, STATUS_BACK_ORDER, STATUS_UNAVAILABLE\n"
+                                + "    })\n"
+                                + "    @Retention(RetentionPolicy.SOURCE)\n"
+                                + "    public @interface Status {\n"
+                                + "    }\n"
+                                + "    public static final int STATUS_AVAILABLE = 1;\n"
+                                + "    public static final int STATUS_BACK_ORDER = 2;\n"
+                                + "    public static final int STATUS_UNAVAILABLE = 3;\n"
+                                + "\n"
+                                + "    @Status\n"
+                                + "    private final int mStatus;\n"
+                                + "    private final String mName;\n"
+                                + "\n"
+                                + "    private Product(String name, @Status int status) {\n"
+                                + "        mName = name;\n"
+                                + "        mStatus = status;\n"
+                                + "    }\n"
+                                + "    public static class Builder {\n"
+                                + "        @Status\n"
+                                + "        private int mStatus;\n"
+                                + "        private final int mStatus2 = STATUS_AVAILABLE;\n"
+                                + "        private String mName;\n"
+                                + "\n"
+                                + "        public Builder(String name, @Status int status) {\n"
+                                + "            mName = name;\n"
+                                + "            mStatus = status;\n"
+                                + "        }\n"
+                                + "\n"
+                                + "        public Builder setStatus(@Status int status) {\n"
+                                + "            mStatus = status;\n"
+                                + "            return this;\n"
+                                + "        }\n"
+                                + "\n"
+                                + "        public Product build() {\n"
+                                + "            return new Product(mName, mStatus);\n"
+                                + "        }\n"
+                                + "\n"
+                                + "        public Product build2() {\n"
+                                + "            return new Product(mName, mStatus2);\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "}\n"),
+                        copy("src/android/support/annotation/IntDef.java.txt", "src/android/support/annotation/IntDef.java"))
+        );
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ToastDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ToastDetectorTest.java
index 07941b7..821e64f 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ToastDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ToastDetectorTest.java
@@ -25,6 +25,7 @@
         return new ToastDetector();
     }
 
+
     public void test() throws Exception {
         assertEquals(
             "src/test/pkg/ToastTest.java:31: Warning: Toast created but not shown: did you forget to call show() ? [ShowToast]\n" +
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TranslationDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TranslationDetectorTest.java
index 4f53c7d..a2ca117 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TranslationDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TranslationDetectorTest.java
@@ -413,6 +413,30 @@
                 ));
     }
 
+    public void testConfigKeys() throws Exception {
+        // Some developer services create config files merged with your project, but in some
+        // versions they were missing a translatable="false" entry. Since we know these aren't
+        // keys you normally want to translate, let's filter them for users.
+        TranslationDetector.sCompleteRegions = false;
+        assertEquals("No warnings.",
+                lintProject(
+                        xml("res/values/config.xml", ""
+                                + "<resources>\n"
+                                + "    <string name=\"gcm_defaultSenderId\">SENDER_ID</string>\n"
+                                + "    <string name=\"google_app_id\">App Id</string>\n"
+                                + "    <string name=\"ga_trackingID\">Analytics</string>\n"
+                                + "</resources>\n"),
+                        xml("res/values/strings.xml", ""
+                                + "<resources>\n"
+                                + "    <string name=\"app_name\">My Application</string>\n"
+                                + "</resources>\n"),
+                        xml("res/values-nb/strings.xml", ""
+                                + "<resources>\n"
+                                + "    <string name=\"app_name\">Min Applikasjon</string>\n"
+                                + "</resources>\n")
+                ));
+    }
+
     @Override
     protected TestLintClient createClient() {
         if (!getName().startsWith("testResConfigs")) {
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TrustAllX509TrustManagerDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TrustAllX509TrustManagerDetectorTest.java
new file mode 100644
index 0000000..c71fe19
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TrustAllX509TrustManagerDetectorTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2015 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", "ClassNameDiffersFromFileName", "ImplicitArrayToString"})
+public class TrustAllX509TrustManagerDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new TrustAllX509TrustManagerDetector();
+    }
+
+    public void testBroken() throws Exception {
+        assertEquals(
+            "src/test/pkg/InsecureTLSIntentService.java:22: Warning: checkClientTrusted is empty, which could cause insecure network traffic due to trusting arbitrary TLS/SSL certificates presented by peers [TrustAllX509TrustManager]\n" +
+            "        public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {\n" +
+            "                    ~~~~~~~~~~~~~~~~~~\n" +
+            "src/test/pkg/InsecureTLSIntentService.java:26: Warning: checkServerTrusted is empty, which could cause insecure network traffic due to trusting arbitrary TLS/SSL certificates presented by peers [TrustAllX509TrustManager]\n" +
+            "        public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) throws CertificateException {\n" +
+            "                    ~~~~~~~~~~~~~~~~~~\n" +
+            "0 errors, 2 warnings\n" +
+            "",
+            lintProject(
+                    xml("AndroidManifest.xml", ""
+                            + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                            + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                            + "    package=\"test.pkg\"\n"
+                            + "    android:versionCode=\"1\"\n"
+                            + "    android:versionName=\"1.0\" >\n"
+                            + "\n"
+                            + "    <uses-sdk android:minSdkVersion=\"14\" />\n"
+                            + "\n"
+                            + "    <application\n"
+                            + "        android:icon=\"@drawable/ic_launcher\"\n"
+                            + "        android:label=\"@string/app_name\" >\n"
+                            + "        <service\n"
+                            + "            android:name=\".InsecureTLSIntentService\" >\n"
+                            + "        </service>\n"
+                            + "    </application>\n"
+                            + "\n"
+                            + "</manifest>\n"
+                            + "\n"),
+                    java("src/test/pkg/InsecureTLSIntentService.java", ""
+                            + "package test.pkg;\n"
+                            + "\n"
+                            + "import android.app.IntentService;\n"
+                            + "import android.content.Intent;\n"
+                            + "\n"
+                            + "import java.security.GeneralSecurityException;\n"
+                            + "import java.security.cert.CertificateException;\n"
+                            + "\n"
+                            + "import javax.net.ssl.HttpsURLConnection;\n"
+                            + "import javax.net.ssl.SSLContext;\n"
+                            + "import javax.net.ssl.TrustManager;\n"
+                            + "import javax.net.ssl.X509TrustManager;\n"
+                            + "\n"
+                            + "public class InsecureTLSIntentService extends IntentService {\n"
+                            + "    TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() {\n"
+                            + "        @Override\n"
+                            + "        public java.security.cert.X509Certificate[] getAcceptedIssuers() {\n"
+                            + "            return null;\n"
+                            + "        }\n"
+                            + "\n"
+                            + "        @Override\n"
+                            + "        public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {\n"
+                            + "        }\n"
+                            + "\n"
+                            + "        @Override\n"
+                            + "        public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) throws CertificateException {\n"
+                            + "        }\n"
+                            + "    }};\n"
+                            + "\n"
+                            + "    public InsecureTLSIntentService() {\n"
+                            + "        super(\"InsecureTLSIntentService\");\n"
+                            + "    }\n"
+                            + "\n"
+                            + "    @Override\n"
+                            + "    protected void onHandleIntent(Intent intent) {\n"
+                            + "        try {\n"
+                            + "            SSLContext sc = SSLContext.getInstance(\"TLSv1.2\");\n"
+                            + "            sc.init(null, trustAllCerts, new java.security.SecureRandom());\n"
+                            + "            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());\n"
+                            + "        } catch (GeneralSecurityException e) {\n"
+                            + "            System.out.println(e.getStackTrace());\n"
+                            + "        }\n"
+                            + "    }\n"
+                            + "}\n")));
+
+        // TODO: Test bytecode check via library jar?
+                    //"bytecode/InsecureTLSIntentService.java.txt=>src/test/pkg/InsecureTLSIntentService.java",
+                    //"bytecode/InsecureTLSIntentService.class.data=>bin/classes/test/pkg/InsecureTLSIntentService.class",
+                    //"bytecode/InsecureTLSIntentService$1.class.data=>bin/classes/test/pkg/InsecureTLSIntentService$1.class"));
+    }
+
+    public void testCorrect() throws Exception {
+        assertEquals(
+            "No warnings.",
+
+                lintProject(
+                        xml("AndroidManifest.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                                + "    package=\"test.pkg\"\n"
+                                + "    android:versionCode=\"1\"\n"
+                                + "    android:versionName=\"1.0\" >\n"
+                                + "\n"
+                                + "    <uses-sdk android:minSdkVersion=\"14\" />\n"
+                                + "\n"
+                                + "    <application\n"
+                                + "        android:icon=\"@drawable/ic_launcher\"\n"
+                                + "        android:label=\"@string/app_name\" >\n"
+                                + "        <service\n"
+                                + "            android:name=\".ExampleTLSIntentService\" >\n"
+                                + "        </service>\n"
+                                + "    </application>\n"
+                                + "\n"
+                                + "</manifest>\n"),
+                        java("src/test/pkg/ExampleTLSIntentService.java", ""
+                                + "package test.pkg;\n"
+                                + "\n"
+                                + "import android.app.IntentService;\n"
+                                + "import android.content.Intent;\n"
+                                + "\n"
+                                + "import java.io.BufferedInputStream;\n"
+                                + "import java.io.FileInputStream;\n"
+                                + "import java.security.GeneralSecurityException;\n"
+                                + "import java.security.cert.CertificateException;\n"
+                                + "import java.security.cert.CertificateFactory;\n"
+                                + "import java.security.cert.X509Certificate;\n"
+                                + "\n"
+                                + "import javax.net.ssl.HttpsURLConnection;\n"
+                                + "import javax.net.ssl.SSLContext;\n"
+                                + "import javax.net.ssl.TrustManager;\n"
+                                + "import javax.net.ssl.X509TrustManager;\n"
+                                + "\n"
+                                + "public class ExampleTLSIntentService extends IntentService {\n"
+                                + "    TrustManager[] trustManagerExample;\n"
+                                + "\n"
+                                + "    {\n"
+                                + "        trustManagerExample = new TrustManager[]{new X509TrustManager() {\n"
+                                + "            @Override\n"
+                                + "            public X509Certificate[] getAcceptedIssuers() {\n"
+                                + "                try {\n"
+                                + "                    FileInputStream fis = new FileInputStream(\"testcert.pem\");\n"
+                                + "                    BufferedInputStream bis = new BufferedInputStream(fis);\n"
+                                + "                    CertificateFactory cf = CertificateFactory.getInstance(\"X.509\");\n"
+                                + "                    X509Certificate cert = (X509Certificate) cf.generateCertificate(bis);\n"
+                                + "                    return new X509Certificate[]{cert};\n"
+                                + "                } catch (Exception e) {\n"
+                                + "                    throw new RuntimeException(\"Could not load cert\");\n"
+                                + "                }\n"
+                                + "            }\n"
+                                + "\n"
+                                + "            @Override\n"
+                                + "            public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException {\n"
+                                + "                throw new CertificateException(\"Not trusted\");\n"
+                                + "            }\n"
+                                + "\n"
+                                + "            @Override\n"
+                                + "            public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {\n"
+                                + "                throw new CertificateException(\"Not trusted\");\n"
+                                + "            }\n"
+                                + "        }};\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    public ExampleTLSIntentService() {\n"
+                                + "        super(\"ExampleTLSIntentService\");\n"
+                                + "    }\n"
+                                + "\n"
+                                + "    @Override\n"
+                                + "    protected void onHandleIntent(Intent intent) {\n"
+                                + "        try {\n"
+                                + "            SSLContext sc = SSLContext.getInstance(\"TLSv1.2\");\n"
+                                + "            sc.init(null, trustManagerExample, new java.security.SecureRandom());\n"
+                                + "            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());\n"
+                                + "        } catch (GeneralSecurityException e) {\n"
+                                + "            System.out.println(e.getStackTrace());\n"
+                                + "        }\n"
+                                + "    }\n"
+                                + "}\n")));
+    }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TypoDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TypoDetectorTest.java
index 6d07839..5db1f2a 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TypoDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TypoDetectorTest.java
@@ -179,7 +179,7 @@
                    "\"through\" or \"throughout\" ?\n";
         assertEquals("throught", TypoDetector.getTypo(s, TEXT));
         assertEquals(Arrays.asList("thought", "through", "throughout"),
-                TypoDetector.getSuggestions(s, TEXT));
+                TypoDetector.getSuggestions(s, TEXT).getReplacements());
     }
 
     public void testNorwegianDefault() throws Exception {
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UnsafeBroadcastReceiverDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UnsafeBroadcastReceiverDetectorTest.java
new file mode 100644
index 0000000..8788497
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UnsafeBroadcastReceiverDetectorTest.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.tools.lint.checks.UnsafeBroadcastReceiverDetector.PROTECTED_BROADCASTS;
+
+import com.android.annotations.Nullable;
+import com.android.testutils.SdkTestCase;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+
+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.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+@SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName", "StatementWithEmptyBody",
+        "MethodMayBeStatic"})
+public class UnsafeBroadcastReceiverDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new UnsafeBroadcastReceiverDetector();
+    }
+
+    public void testBroken() throws Exception {
+        assertEquals(""
+                + "src/test/pkg/TestReceiver.java:10: Warning: This broadcast receiver declares "
+                + "an intent-filter for a protected broadcast action string, which can only be "
+                + "sent by the system, not third-party applications. However, the receiver's "
+                + "onReceive method does not appear to call getAction to ensure that the "
+                + "received Intent's action string matches the expected value, potentially "
+                + "making it possible for another actor to send a spoofed intent with no "
+                + "action string or a different action string and cause undesired "
+                + "behavior. [UnsafeProtectedBroadcastReceiver]\n"
+                + "    public void onReceive(Context context, Intent intent) {\n"
+                + "                ~~~~~~~~~\n"
+                + "0 errors, 1 warnings\n",
+            lintProject(
+                    copy("res/values/strings.xml"),
+                    xml("AndroidManifest.xml", ""
+                            + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                            + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                            + "    package=\"test.pkg\"\n"
+                            + "    android:versionCode=\"1\"\n"
+                            + "    android:versionName=\"1.0\" >\n"
+                            + "\n"
+                            + "    <uses-sdk android:minSdkVersion=\"14\" />\n"
+                            + "\n"
+                            + "    <application\n"
+                            + "        android:icon=\"@drawable/ic_launcher\"\n"
+                            + "        android:label=\"@string/app_name\" >\n"
+                            + "        <receiver\n"
+                            + "            android:label=\"@string/app_name\"\n"
+                            + "            android:name=\".TestReceiver\" >\n"
+                            + "                <intent-filter>\n"
+                            + "                    <action android:name=\"android.intent.action.BOOT_COMPLETED\"/>\n"
+                            + "                </intent-filter>\n"
+                            + "        </receiver>\n"
+                            + "    </application>\n"
+                            + "\n"
+                            + "</manifest>\n"
+                            + "\n"),
+                    java("src/test/pkg/TestReceiver.java", ""
+                            + "package test.pkg;\n"
+                            + "\n"
+                            + "import android.content.BroadcastReceiver;\n"
+                            + "import android.content.Context;\n"
+                            + "import android.content.Intent;\n"
+                            + "\n"
+                            + "public class TestReceiver extends BroadcastReceiver {\n"
+                            + "\n"
+                            + "    @Override\n"
+                            + "    public void onReceive(Context context, Intent intent) {\n"
+                            + "    }\n"
+                            + "\n"
+                            + "    // Anonymous classes should NOT be counted as a must-register\n"
+                            + "    private static BroadcastReceiver dummy() {\n"
+                            + "        return new BroadcastReceiver() {\n"
+                            + "            @Override\n"
+                            + "            public void onReceive(Context context, Intent intent) {\n"
+                            + "            }\n"
+                            + "        };\n"
+                            + "    }\n"
+                            + "}\n")
+            ));
+    }
+
+    public void testBroken2() throws Exception {
+        assertEquals(
+                "AndroidManifest.xml:12: Warning: BroadcastReceivers that declare an " +
+                "intent-filter for SMS_DELIVER or SMS_RECEIVED must ensure that the caller has " +
+                "the BROADCAST_SMS permission, otherwise it is possible for malicious actors to " +
+                "spoof intents. [UnprotectedSMSBroadcastReceiver]\n" +
+                "        <receiver\n" +
+                "        ^\n" +
+                "0 errors, 1 warnings\n",
+            lintProject(
+                    copy("res/values/strings.xml"),
+                    xml("AndroidManifest.xml", ""
+                            + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                            + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                            + "    package=\"test.pkg\"\n"
+                            + "    android:versionCode=\"1\"\n"
+                            + "    android:versionName=\"1.0\" >\n"
+                            + "\n"
+                            + "    <uses-sdk android:minSdkVersion=\"14\" />\n"
+                            + "\n"
+                            + "    <application\n"
+                            + "        android:icon=\"@drawable/ic_launcher\"\n"
+                            + "        android:label=\"@string/app_name\" >\n"
+                            + "        <receiver\n"
+                            + "            android:label=\"@string/app_name\"\n"
+                            + "            android:name=\".TestReceiver\" >\n"
+                            + "                <intent-filter>\n"
+                            + "                    <action android:name=\"android.provider.Telephony.SMS_RECEIVED\"/>\n"
+                            + "                </intent-filter>\n"
+                            + "        </receiver>\n"
+                            + "    </application>\n"
+                            + "\n"
+                            + "</manifest>\n"
+                            + "\n")
+            ));
+    }
+
+    public void testReferencesIntentVariable() throws Exception {
+        assertEquals(""
+                + "src/test/pkg/TestReceiver.java:10: Warning: This broadcast receiver declares "
+                + "an intent-filter for a protected broadcast action string, which can only be "
+                + "sent by the system, not third-party applications. However, the receiver's "
+                + "onReceive method does not appear to call getAction to ensure that the "
+                + "received Intent's action string matches the expected value, potentially "
+                + "making it possible for another actor to send a spoofed intent with no action "
+                + "string or a different action string and cause undesired behavior. In this "
+                + "case, it is possible that the onReceive method passed the received Intent "
+                + "to another method that checked the action string. If so, this finding can "
+                + "safely be ignored. [UnsafeProtectedBroadcastReceiver]\n"
+                + "    public void onReceive(Context context, Intent intent) {\n"
+                + "                ~~~~~~~~~\n"
+                + "0 errors, 1 warnings\n",
+                lintProject(
+                        copy("res/values/strings.xml"),
+                        xml("AndroidManifest.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                                + "    package=\"test.pkg\"\n"
+                                + "    android:versionCode=\"1\"\n"
+                                + "    android:versionName=\"1.0\" >\n"
+                                + "\n"
+                                + "    <uses-sdk android:minSdkVersion=\"14\" />\n"
+                                + "\n"
+                                + "    <application\n"
+                                + "        android:icon=\"@drawable/ic_launcher\"\n"
+                                + "        android:label=\"@string/app_name\" >\n"
+                                + "        <receiver\n"
+                                + "            android:label=\"@string/app_name\"\n"
+                                + "            android:name=\".TestReceiver\" >\n"
+                                + "                <intent-filter>\n"
+                                + "                    <action android:name=\"android.intent.action.BOOT_COMPLETED\"/>\n"
+                                + "                </intent-filter>\n"
+                                + "        </receiver>\n"
+                                + "    </application>\n"
+                                + "\n"
+                                + "</manifest>\n"
+                                + "\n"),
+                        java("src/test/pkg/TestReceiver.java", ""
+                                + "package test.pkg;\n"
+                                + "\n"
+                                + "import android.content.BroadcastReceiver;\n"
+                                + "import android.content.Context;\n"
+                                + "import android.content.Intent;\n"
+                                + "\n"
+                                + "public class TestReceiver extends BroadcastReceiver {\n"
+                                + "\n"
+                                + "    @Override\n"
+                                + "    public void onReceive(Context context, Intent intent) {\n"
+                                + "        System.out.println(intent);\n"
+                                + "    }\n"
+                                + "}\n")
+                ));
+    }
+
+    public void testCorrect() throws Exception {
+        assertEquals(
+                "No warnings.",
+
+                lintProject(
+                        xml("AndroidManifest.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                                + "    package=\"test.pkg\"\n"
+                                + "    android:versionCode=\"1\"\n"
+                                + "    android:versionName=\"1.0\" >\n"
+                                + "\n"
+                                + "    <uses-sdk android:minSdkVersion=\"14\" />\n"
+                                + "\n"
+                                + "    <application\n"
+                                + "        android:icon=\"@drawable/ic_launcher\"\n"
+                                + "        android:label=\"@string/app_name\" >\n"
+                                + "        <receiver\n"
+                                + "            android:label=\"@string/app_name\"\n"
+                                + "            android:name=\".TestReceiver2\" >\n"
+                                + "                <intent-filter>\n"
+                                + "                    <action android:name=\"android.intent.action.BOOT_COMPLETED\"/>\n"
+                                + "                </intent-filter>\n"
+                                + "        </receiver>\n"
+                                + "    </application>\n"
+                                + "\n"
+                                + "</manifest>\n"
+                                + "\n"),
+                        java("src/test/pkg/TestReceiver2.java", ""
+                                + "package test.pkg;\n"
+                                + "\n"
+                                + "import android.content.BroadcastReceiver;\n"
+                                + "import android.content.Context;\n"
+                                + "import android.content.Intent;\n"
+                                + "\n"
+                                + "public class TestReceiver2 extends BroadcastReceiver {\n"
+                                + "\n"
+                                + "    @Override\n"
+                                + "    public void onReceive(Context context, Intent intent) {\n"
+                                + "      if (intent.getAction() == Intent.ACTION_BOOT_COMPLETED) {\n"
+                                + "      }\n"
+                                + "    }\n"
+                                + "}\n")
+                ));
+    }
+
+    public void testCorrect2() throws Exception {
+        assertEquals(
+                "No warnings.",
+
+                lintProject(
+                        xml("AndroidManifest.xml", ""
+                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                                + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                                + "    package=\"test.pkg\"\n"
+                                + "    android:versionCode=\"1\"\n"
+                                + "    android:versionName=\"1.0\" >\n"
+                                + "\n"
+                                + "    <uses-sdk android:minSdkVersion=\"14\" />\n"
+                                + "\n"
+                                + "    <application\n"
+                                + "        android:icon=\"@drawable/ic_launcher\"\n"
+                                + "        android:label=\"@string/app_name\" >\n"
+                                + "        <receiver\n"
+                                + "            android:label=\"@string/app_name\"\n"
+                                + "            android:name=\".TestReceiver\"\n"
+                                + "            android:permission=\"android.permission.BROADCAST_SMS\" >\n"
+                                + "                <intent-filter>\n"
+                                + "                    <action android:name=\"android.provider.Telephony.SMS_RECEIVED\"/>\n"
+                                + "                </intent-filter>\n"
+                                + "        </receiver>\n"
+                                + "    </application>\n"
+                                + "\n"
+                                + "</manifest>\n"
+                                + "\n")
+                ));
+    }
+
+    public static void testDbUpToDate() throws Exception {
+        List<String> expected = getProtectedBroadcasts();
+        if (expected == null) {
+            return;
+        }
+        List<String> actual = Arrays.asList(PROTECTED_BROADCASTS);
+        if (!expected.equals(actual)) {
+            System.out.println("Correct list of broadcasts:");
+            for (String name : expected) {
+                System.out.println("            \"" + name + "\",");
+            }
+            fail("List of protected broadcast names has changed:\n" +
+                    // Make the diff show what it take to bring the actual results into the
+                    // expected results
+                    SdkTestCase.getDiff(Joiner.on('\n').join(actual),
+                            Joiner.on('\n').join(expected)));
+        }
+    }
+
+    @Nullable
+    private static List<String> getProtectedBroadcasts() throws IOException {
+        String top = System.getenv("ANDROID_BUILD_TOP");   //$NON-NLS-1$
+        if (top == null) {
+            top = "/Users/tnorbye/dev/mnc-dev";
+        }
+
+        // TODO: We should ship this file with the SDK!
+        File file = new File(top, "frameworks/base/core/res/AndroidManifest.xml");
+        if (!file.exists()) {
+            System.out.println("Set $ANDROID_BUILD_TOP to point to the git repository");
+            return null;
+        }
+        String xml = Files.toString(file, Charsets.UTF_8);
+        Document document = XmlUtils.parseDocumentSilently(xml, true);
+        Set<String> list = Sets.newHashSet();
+        if (document != null && document.getDocumentElement() != null) {
+            NodeList children = document.getDocumentElement().getChildNodes();
+            for (int i = 0, n = children.getLength(); i < n; i++) {
+                Node child = children.item(i);
+                short nodeType = child.getNodeType();
+                if (nodeType == Node.ELEMENT_NODE
+                        && child.getNodeName().equals("protected-broadcast")) {
+                    Element element = (Element) child;
+                    String name = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
+                    if (!name.isEmpty()) {
+                        list.add(name);
+                    }
+                }
+            }
+        }
+
+        List<String> expected = Lists.newArrayList(list);
+        Collections.sort(expected);
+        return expected;
+    }
+}
\ No newline at end of file
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UnsafeNativeCodeDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UnsafeNativeCodeDetectorTest.java
new file mode 100644
index 0000000..0cdb63c
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UnsafeNativeCodeDetectorTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2015 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", "JavaLangImport", "ClassNameDiffersFromFileName"})
+public class UnsafeNativeCodeDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new UnsafeNativeCodeDetector();
+    }
+
+    public void testLoad() throws Exception {
+        assertEquals(
+                "src/test/pkg/Load.java:12: Warning: Dynamically loading code using load is risky, please use loadLibrary instead when possible [UnsafeDynamicallyLoadedCode]\n" +
+                "            Runtime.getRuntime().load(\"/data/data/test.pkg/files/libhello.so\");\n" +
+                "            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+                "src/test/pkg/Load.java:14: Warning: Dynamically loading code using load is risky, please use loadLibrary instead when possible [UnsafeDynamicallyLoadedCode]\n" +
+                "            System.load(\"/data/data/test.pkg/files/libhello.so\");\n" +
+                "            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+                "0 errors, 2 warnings\n",
+                lintProject(java("src/test/pkg/Load.java", ""
+                        + "package test.pkg;\n"
+                        + "\n"
+                        + "import java.lang.NullPointerException;\n"
+                        + "import java.lang.Runtime;\n"
+                        + "import java.lang.SecurityException;\n"
+                        + "import java.lang.System;\n"
+                        + "import java.lang.UnsatisfiedLinkError;\n"
+                        + "\n"
+                        + "public class Load {\n"
+                        + "    public static void foo() {\n"
+                        + "        try {\n"
+                        + "            Runtime.getRuntime().load(\"/data/data/test.pkg/files/libhello.so\");\n"
+                        + "            Runtime.getRuntime().loadLibrary(\"hello\"); // ok\n"
+                        + "            System.load(\"/data/data/test.pkg/files/libhello.so\");\n"
+                        + "            System.loadLibrary(\"hello\"); // ok\n"
+                        + "        } catch (SecurityException ignore) {\n"
+                        + "        } catch (UnsatisfiedLinkError ignore) {\n"
+                        + "        } catch (NullPointerException ignore) {\n"
+                        + "        }\n"
+                        + "    }\n"
+                        + "}\n")));
+    }
+
+    public void testNativeCode() throws Exception {
+        assertEquals(""
+                + "assets/hello: Warning: Embedding non-shared library native executables into applications should be avoided when possible, as there is an increased risk that the executables could be tampered with after installation. Instead, native code should be placed in a shared library, and the features of the development environment should be used to place the shared library in the lib directory of the compiled APK. [UnsafeNativeCodeLocation]\n"
+                + "res/raw/hello: Warning: Embedding non-shared library native executables into applications should be avoided when possible, as there is an increased risk that the executables could be tampered with after installation. Instead, native code should be placed in a shared library, and the features of the development environment should be used to place the shared library in the lib directory of the compiled APK. [UnsafeNativeCodeLocation]\n"
+                + "assets/libhello-jni.so: Warning: Shared libraries should not be placed in the res or assets directories. Please use the features of your development environment to place shared libraries in the lib directory of the compiled APK. [UnsafeNativeCodeLocation]\n"
+                + "res/raw/libhello-jni.so: Warning: Shared libraries should not be placed in the res or assets directories. Please use the features of your development environment to place shared libraries in the lib directory of the compiled APK. [UnsafeNativeCodeLocation]\n"
+                + "0 errors, 4 warnings\n",
+               lintProject(
+                       copy("res/raw/hello"),
+                       copy("res/raw/libhello-jni.so"),
+                       copy("res/raw/hello", "assets/hello"),
+                       copy("res/raw/libhello-jni.so", "assets/libhello-jni.so"),
+                       copy("lib/armeabi/hello"),
+                       copy("lib/armeabi/libhello-jni.so")));
+    }
+
+    public void testNoWorkInInteractiveMode() throws Exception {
+        // Make sure we don't scan through all resource folders when just incrementally
+        // editing a Java file
+        assertEquals(
+                "No warnings.",
+                lintProjectIncrementally(
+                        "src/test/pkg/Load.java",
+                        java("src/test/pkg/Load.java", ""
+                                + "package test.pkg;\n"
+                                + "public class Load { }\n"),
+                        copy("res/raw/hello"),
+                        copy("res/raw/libhello-jni.so"),
+                        copy("res/raw/hello", "assets/hello"),
+                        copy("res/raw/libhello-jni.so", "assets/libhello-jni.so"),
+                        copy("lib/armeabi/hello"),
+                        copy("lib/armeabi/libhello-jni.so")));
+    }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UnusedResourceDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UnusedResourceDetectorTest.java
index 673685e..cd99a0b 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UnusedResourceDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UnusedResourceDetectorTest.java
@@ -16,13 +16,34 @@
 
 package com.android.tools.lint.checks;
 
+import static com.android.tools.lint.detector.api.TextFormat.TEXT;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.BuildType;
+import com.android.builder.model.BuildTypeContainer;
+import com.android.builder.model.ClassField;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.ProductFlavorContainer;
+import com.android.builder.model.SourceProvider;
+import com.android.builder.model.Variant;
+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.Project;
+import com.android.tools.lint.detector.api.Severity;
+import com.google.common.collect.ImmutableMap;
 
 import java.io.File;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
 
-@SuppressWarnings("javadoc")
+@SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName"})
 public class UnusedResourceDetectorTest extends AbstractCheckTest {
     private boolean mEnableIds = false;
 
@@ -32,7 +53,15 @@
     }
 
     @Override
+    protected boolean allowCompilationErrors() {
+        // Some of these unit tests are still relying on source code that references
+        // unresolved symbols etc.
+        return true;
+    }
+
+    @Override
     protected boolean isEnabled(Issue issue) {
+        //noinspection SimplifiableIfStatement
         if (issue == UnusedResourceDetector.ISSUE_IDS) {
             return mEnableIds;
         } else {
@@ -42,15 +71,20 @@
 
     public void testUnused() throws Exception {
         mEnableIds = false;
-        assertEquals(
-           "res/layout/accessibility.xml: Warning: The resource R.layout.accessibility appears to be unused [UnusedResources]\n" +
-           "res/layout/main.xml: Warning: The resource R.layout.main appears to be unused [UnusedResources]\n" +
-           "res/layout/other.xml: Warning: The resource R.layout.other appears to be unused [UnusedResources]\n" +
-           "res/values/strings2.xml:3: Warning: The resource R.string.hello appears to be unused [UnusedResources]\n" +
-           "    <string name=\"hello\">Hello</string>\n" +
-           "            ~~~~~~~~~~~~\n" +
-           "0 errors, 4 warnings\n" +
-           "",
+        assertEquals(""
+                + "res/layout/accessibility.xml:2: Warning: The resource R.layout.accessibility appears to be unused [UnusedResources]\n"
+                + "<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\">\n"
+                + "^\n"
+                + "res/layout/main.xml:2: Warning: The resource R.layout.main appears to be unused [UnusedResources]\n"
+                + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                + "^\n"
+                + "res/layout/other.xml:2: Warning: The resource R.layout.other appears to be unused [UnusedResources]\n"
+                + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                + "^\n"
+                + "res/values/strings2.xml:3: Warning: The resource R.string.hello appears to be unused [UnusedResources]\n"
+                + "    <string name=\"hello\">Hello</string>\n"
+                + "            ~~~~~~~~~~~~\n"
+                + "0 errors, 4 warnings\n",
 
             lintProject(
                 "res/values/strings2.xml",
@@ -67,30 +101,25 @@
     public void testUnusedIds() throws Exception {
         mEnableIds = true;
 
-        assertEquals(
-           "res/layout/accessibility.xml: Warning: The resource R.layout.accessibility appears to be unused [UnusedResources]\n" +
-           "Warning: The resource R.layout.main appears to be unused [UnusedResources]\n" +
-           "Warning: The resource R.layout.other appears to be unused [UnusedResources]\n" +
-           "Warning: The resource R.string.hello appears to be unused [UnusedResources]\n" +
-           "res/layout/accessibility.xml:2: Warning: The resource R.id.newlinear appears to be unused [UnusedIds]\n" +
-           "<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\">\n" +
-           "                                                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
-           "res/layout/accessibility.xml:3: Warning: The resource R.id.button1 appears to be unused [UnusedIds]\n" +
-           "    <Button android:text=\"Button\" android:id=\"@+id/button1\" android:layout_width=\"wrap_content\" android:layout_height=\"wrap_content\"></Button>\n" +
-           "                                  ~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
-           "res/layout/accessibility.xml:4: Warning: The resource R.id.android_logo appears to be unused [UnusedIds]\n" +
-           "    <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\" />\n" +
-           "               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
-           "res/layout/accessibility.xml:5: Warning: The resource R.id.android_logo2 appears to be unused [UnusedIds]\n" +
-           "    <ImageButton android:importantForAccessibility=\"yes\" 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\" />\n" +
-           "                                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
-           "Warning: The resource R.id.imageView1 appears to be unused [UnusedIds]\n" +
-           "Warning: The resource R.id.include1 appears to be unused [UnusedIds]\n" +
-           "Warning: The resource R.id.linearLayout2 appears to be unused [UnusedIds]\n" +
-           "0 errors, 11 warnings\n",
+        assertEquals(""
+                + "res/layout/accessibility.xml:2: Warning: The resource R.layout.accessibility appears to be unused [UnusedResources]\n"
+                + "<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\">\n"
+                + "^\n"
+                + "res/layout/accessibility.xml:2: Warning: The resource R.id.newlinear appears to be unused [UnusedIds]\n"
+                + "<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\">\n"
+                + "                                                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "res/layout/accessibility.xml:3: Warning: The resource R.id.button1 appears to be unused [UnusedIds]\n"
+                + "    <Button android:text=\"Button\" android:id=\"@+id/button1\" android:layout_width=\"wrap_content\" android:layout_height=\"wrap_content\"></Button>\n"
+                + "                                  ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "res/layout/accessibility.xml:4: Warning: The resource R.id.android_logo appears to be unused [UnusedIds]\n"
+                + "    <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\" />\n"
+                + "               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "res/layout/accessibility.xml:5: Warning: The resource R.id.android_logo2 appears to be unused [UnusedIds]\n"
+                + "    <ImageButton android:importantForAccessibility=\"yes\" 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\" />\n"
+                + "                                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "0 errors, 5 warnings\n",
 
             lintProject(
-                // Rename .txt files to .java
                 "src/my/pkg/Test.java.txt=>src/my/pkg/Test.java",
                 "gen/my/pkg/R.java.txt=>gen/my/pkg/R.java",
                 "AndroidManifest.xml",
@@ -98,23 +127,48 @@
     }
 
     public void testArrayReference() throws Exception {
-        assertEquals(
-           "res/values/arrayusage.xml:3: Warning: The resource R.array.my_array appears to be unused [UnusedResources]\n" +
-           "<string-array name=\"my_array\">\n" +
-           "              ~~~~~~~~~~~~~~~\n" +
-           "0 errors, 1 warnings\n" +
-           "",
+        assertEquals(""
+                // The string is unused, but only because the array referencing it is unused too.
+                + "res/values/arrayusage.xml:2: Warning: The resource R.string.my_item appears to be unused [UnusedResources]\n"
+                + "<string name=\"my_item\">An Item</string>\n"
+                + "        ~~~~~~~~~~~~~~\n"
+                + "res/values/arrayusage.xml:3: Warning: The resource R.array.my_array appears to be unused [UnusedResources]\n"
+                + "<string-array name=\"my_array\">\n"
+                + "              ~~~~~~~~~~~~~~~\n"
+                + "0 errors, 2 warnings\n",
 
             lintProject(
-                "AndroidManifest.xml",
-                "res/values/arrayusage.xml"));
+                xml("res/values/arrayusage.xml", ""
+                        + "<resources>\n"
+                        + "<string name=\"my_item\">An Item</string>\n"
+                        + "<string-array name=\"my_array\">\n"
+                        + "   <item>@string/my_item</item>\n"
+                        + "</string-array>\n"
+                        + "</resources>\n")
+                     ));
+    }
+
+    public void testArrayReferenceIncluded() throws Exception {
+        assertEquals("No warnings.",
+
+                lintProject(
+                        xml("res/values/arrayusage.xml", ""
+                                + "<resources xmlns:tools=\"http://schemas.android.com/tools\""
+                                + "   tools:keep=\"@array/my_array\">\n"
+                                + "<string name=\"my_item\">An Item</string>\n"
+                                + "<string-array name=\"my_array\">\n"
+                                + "   <item>@string/my_item</item>\n"
+                                + "</string-array>\n"
+                                + "</resources>\n")
+                ));
     }
 
     public void testAttrs() throws Exception {
-        assertEquals(
-           "res/layout/customattrlayout.xml: Warning: The resource R.layout.customattrlayout appears to be unused [UnusedResources]\n" +
-           "0 errors, 1 warnings\n" +
-           "",
+        assertEquals(""
+                + "res/layout/customattrlayout.xml:2: Warning: The resource R.layout.customattrlayout appears to be unused [UnusedResources]\n"
+                + "<foo.bar.ContentFrame\n"
+                + "^\n"
+                + "0 errors, 1 warnings\n",
 
             lintProject(
                 "res/values/customattr.xml",
@@ -177,6 +231,7 @@
                 "AndroidManifest.xml"));
     }
 
+    /* Not sure about this -- why would we ignore drawable XML?
     public void testIgnoreXmlDrawable() throws Exception {
         assertEquals(
            "No warnings.",
@@ -186,19 +241,23 @@
                     "gen/my/pkg/R2.java.txt=>gen/my/pkg/R.java"
             ));
     }
+    */
 
     public void testPlurals() throws Exception {
-        assertEquals(
-           "res/values/plurals.xml:3: Warning: The resource R.plurals.my_plural appears to be unused [UnusedResources]\n" +
-           "    <plurals name=\"my_plural\">\n" +
-           "             ~~~~~~~~~~~~~~~~\n" +
-           "0 errors, 1 warnings\n" +
-           "",
-
+        //noinspection ClassNameDiffersFromFileName
+        assertEquals("No warnings.",
             lintProject(
-                "res/values/strings4.xml",
-                "res/values/plurals.xml",
-                "AndroidManifest.xml"));
+                copy("res/values/strings4.xml"),
+                copy("res/values/plurals.xml"),
+                copy("AndroidManifest.xml"),
+                java("src/test/pkg/Test.java", ""
+                        + "package test.pkg;\n"
+                        + "public class Test {\n"
+                        + "    public void test() {"
+                        + "        int used = R.plurals.my_plural;\n"
+                        + "    }"
+                        + "}")
+            ));
     }
 
     public void testNoMerging() throws Exception {
@@ -325,4 +384,258 @@
                                 + "}\n")
                         ));
     }
+
+    public void testDataBinding() throws Exception {
+        // Make sure that resources referenced only via a data binding expression
+        // are not counted as unused.
+        // Regression test for https://code.google.com/p/android/issues/detail?id=183934
+        mEnableIds = false;
+        assertEquals("No warnings.",
+
+                lintProject(
+                        xml("res/values/resources.xml", ""
+                                + "<resources>\n"
+                                + "    <item type='dimen' name='largePadding'>20dp</item>\n"
+                                + "    <item type='dimen' name='smallPadding'>15dp</item>\n"
+                                + "    <item type='string' name='nameFormat'>%1$s %2$s</item>\n"
+                                + "</resources>"),
+
+                        // Add unit test source which references resources which would otherwise
+                        // be marked as unused
+                        xml("res/layout/db.xml", ""
+                                + "<layout xmlns:android=\"http://schemas.android.com/apk/res/android\""
+                                + "    xmlns:tools=\"http://schemas.android.com/tools\" "
+                                + "    tools:keep=\"@layout/db\">\n"
+                                + "   <data>\n"
+                                + "       <variable name=\"user\" type=\"com.example.User\"/>\n"
+                                + "   </data>\n"
+                                + "   <LinearLayout\n"
+                                + "       android:orientation=\"vertical\"\n"
+                                + "       android:layout_width=\"match_parent\"\n"
+                                + "       android:layout_height=\"match_parent\"\n"
+                                // Data binding expressions
+                                + "       android:padding=\"@{large? @dimen/largePadding : @dimen/smallPadding}\"\n"
+                                + "       android:text=\"@{@string/nameFormat(firstName, lastName)}\" />\n"
+                                + "</layout>")
+                ));
+    }
+
+    public void testPublic() throws Exception {
+        // Resources marked as public should not be listed as potentially unused
+        mEnableIds = false;
+        assertEquals(""
+                + "res/values/resources.xml:4: Warning: The resource R.string.nameFormat appears to be unused [UnusedResources]\n"
+                + "    <item type='string' name='nameFormat'>%1$s %2$s</item>\n"
+                + "                        ~~~~~~~~~~~~~~~~~\n"
+                + "0 errors, 1 warnings\n",
+
+                lintProject(
+                        xml("res/values/resources.xml", ""
+                                + "<resources>\n"
+                                + "    <item type='dimen' name='largePadding'>20dp</item>\n"
+                                + "    <item type='dimen' name='smallPadding'>15dp</item>\n"
+                                + "    <item type='string' name='nameFormat'>%1$s %2$s</item>\n"
+                                + "    <public type='dimen' name='largePadding' />"
+                                + "    <public type='dimen' name='smallPadding' />"
+                                + "</resources>")
+                ));
+    }
+
+    public void testDynamicResources() throws Exception {
+        assertEquals(""
+                        + "UnusedResourceDetectorTest_testDynamicResources: Warning: The resource R.string.cat appears to be unused [UnusedResources]\n"
+                        + "UnusedResourceDetectorTest_testDynamicResources: Warning: The resource R.string.dog appears to be unused [UnusedResources]\n"
+                        + "0 errors, 2 warnings\n",
+
+                lintProject(
+                        "res/layout/layout1.xml=>res/layout/main.xml",
+                        "src/test/pkg/UnusedReferenceDynamic.java.txt=>src/test/pkg/UnusedReferenceDynamic.java",
+                        "AndroidManifest.xml"));
+    }
+
+    public void testStaticImport() throws Exception {
+        // Regression test for https://code.google.com/p/android/issues/detail?id=40293
+        // 40293: Lint reports resource as unused when referenced via "import static"
+        mEnableIds = false;
+        assertEquals(""
+                + "No warnings.",
+
+                lintProject(
+                        xml("res/values/resources.xml", ""
+                                + "<resources>\n"
+                                + "    <item type='dimen' name='largePadding'>20dp</item>\n"
+                                + "    <item type='dimen' name='smallPadding'>15dp</item>\n"
+                                + "    <item type='string' name='nameFormat'>%1$s %2$s</item>\n"
+                                + "</resources>"),
+
+                        // Add unit test source which references resources which would otherwise
+                        // be marked as unused
+                        java("src/test/pkg/TestCode.java", ""
+                                + "package test.pkg;\n"
+                                + "\n"
+                                + "import static test.pkg.R.dimen.*;\n"
+                                + "import static test.pkg.R.string.nameFormat;\n"
+                                + "import test.pkg.R.dimen;\n"
+                                + "\n"
+                                + "public class TestCode {\n"
+                                + "    public void test() {\n"
+                                + "        int x = dimen.smallPadding; // Qualified import\n"
+                                + "        int y = largePadding; // Static wildcard import\n"
+                                + "        int z = nameFormat; // Static explicit import\n"
+                                + "    }\n"
+                                + "}\n"),
+                        java("src/test/pkg/R.java", ""
+                                + "package test.pkg;\n"
+                                + "public class R {\n"
+                                + "    public static class dimen {\n"
+                                + "        public static final int largePadding = 1;\n"
+                                + "        public static final int smallPadding = 2;\n"
+                                + "    }\n"
+                                + "    public static class string {\n"
+                                + "        public static final int nameFormat = 3;\n"
+                                + "    }\n"
+                                + "}")
+                ));
+    }
+
+    public void testStyles() throws Exception {
+        mEnableIds = false;
+        assertEquals(""
+                + "res/values/styles.xml:5: Warning: The resource R.style.UnusedStyle appears to be unused [UnusedResources]\n"
+                + "    <style name=\"UnusedStyle\"/>\n"
+                + "           ~~~~~~~~~~~~~~~~~~\n"
+                + "res/values/styles.xml:6: Warning: The resource R.style.UnusedStyle_Sub appears to be unused [UnusedResources]\n"
+                + "    <style name=\"UnusedStyle.Sub\"/>\n"
+                + "           ~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "res/values/styles.xml:7: Warning: The resource R.style.UnusedStyle_Something_Sub appears to be unused [UnusedResources]\n"
+                + "    <style name=\"UnusedStyle.Something.Sub\" parent=\"UnusedStyle\"/>\n"
+                + "           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "0 errors, 3 warnings\n",
+
+                lintProject(
+                        xml("res/values/styles.xml", ""
+                                + "<resources>\n"
+                                + "    <style name=\"UsedStyle\" parent=\"android:Theme\"/>\n"
+                                + "    <style name=\"UsedStyle.Sub\"/>\n"
+                                + "    <style name=\"UsedStyle.Something.Sub\" parent=\"UsedStyle\"/>\n"
+
+                                + "    <style name=\"UnusedStyle\"/>\n"
+                                + "    <style name=\"UnusedStyle.Sub\"/>\n"
+                                + "    <style name=\"UnusedStyle.Something.Sub\" parent=\"UnusedStyle\"/>\n"
+
+                                + "    <style name=\"ImplicitUsed\" parent=\"android:Widget.ActionBar\"/>\n"
+                                + "</resources>")
+                ));
+    }
+
+    @Override
+    protected TestLintClient createClient() {
+        if (!getName().startsWith("testDynamicResources")) {
+            return super.createClient();
+        }
+
+        // Set up a mock project model for the resource configuration test(s)
+        // where we provide a subset of densities to be included
+
+        return new TestLintClient() {
+            @NonNull
+            @Override
+            protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+                return new Project(this, dir, referenceDir) {
+                    @Override
+                    public boolean isGradleProject() {
+                        return true;
+                    }
+
+                    @Nullable
+                    @Override
+                    public AndroidProject getGradleProjectModel() {
+                        /*
+                        Simulate dynamic resources in this setup:
+                            defaultConfig {
+                                ...
+                                resValue "string", "cat", "Some Data"
+                            }
+                            buildTypes {
+                                debug {
+                                    ...
+                                    resValue "string", "foo", "Some Data"
+                                }
+                                release {
+                                    ...
+                                    resValue "string", "xyz", "Some Data"
+                                    resValue "string", "dog", "Some Data"
+                                }
+                            }
+                         */
+                        ClassField foo = mock(ClassField.class);
+                        when(foo.getName()).thenReturn("foo");
+                        when(foo.getType()).thenReturn("string");
+                        ClassField xyz = mock(ClassField.class);
+                        when(xyz.getName()).thenReturn("xyz");
+                        when(xyz.getType()).thenReturn("string");
+                        ClassField cat = mock(ClassField.class);
+                        when(cat.getName()).thenReturn("cat");
+                        when(cat.getType()).thenReturn("string");
+                        ClassField dog = mock(ClassField.class);
+                        when(dog.getName()).thenReturn("dog");
+                        when(dog.getType()).thenReturn("string");
+
+                        Map<String, ClassField> debugResValues = ImmutableMap.of("foo", foo);
+                        BuildType type1 = mock(BuildType.class);
+                        when(type1.getName()).thenReturn("debug");
+                        when(type1.getResValues()).thenReturn(debugResValues);
+                        Map<String, ClassField> releaseResValues =
+                                ImmutableMap.of("xyz", xyz, "dog", dog);
+                        BuildType type2 = mock(BuildType.class);
+                        when(type2.getName()).thenReturn("release");
+                        when(type2.getResValues()).thenReturn(releaseResValues);
+
+                        BuildTypeContainer container1 = mock(BuildTypeContainer.class);
+                        when(container1.getBuildType()).thenReturn(type1);
+                        BuildTypeContainer container2 = mock(BuildTypeContainer.class);
+                        when(container2.getBuildType()).thenReturn(type2);
+
+                        SourceProvider debugProvider = mock(SourceProvider.class);
+                        when(debugProvider.getResDirectories()).thenReturn(Collections.<File>emptyList());
+                        when(debugProvider.getJavaDirectories()).thenReturn(Collections.<File>emptyList());
+                        SourceProvider releaseProvider = mock(SourceProvider.class);
+                        when(releaseProvider.getResDirectories()).thenReturn(Collections.<File>emptyList());
+                        when(releaseProvider.getJavaDirectories()).thenReturn(Collections.<File>emptyList());
+
+                        when(container1.getSourceProvider()).thenReturn(debugProvider);
+                        when(container2.getSourceProvider()).thenReturn(releaseProvider);
+
+                        Map<String, ClassField> defaultResValues = ImmutableMap.of("cat", cat);
+                        ProductFlavor defaultFlavor = mock(ProductFlavor.class);
+                        when(defaultFlavor.getResValues()).thenReturn(defaultResValues);
+
+                        ProductFlavorContainer defaultContainer =
+                                mock(ProductFlavorContainer.class);
+                        when(defaultContainer.getProductFlavor()).thenReturn(defaultFlavor);
+
+                        AndroidProject project = mock(AndroidProject.class);
+                        when(project.getDefaultConfig()).thenReturn(defaultContainer);
+                        when(project.getBuildTypes())
+                                .thenReturn(Arrays.asList(container1, container2));
+                        return project;
+                    }
+
+                    @Nullable
+                    @Override
+                    public Variant getCurrentVariant() {
+                        Variant variant = mock(Variant.class);
+                        when(variant.getBuildType()).thenReturn("release");
+                        return variant;
+                    }
+                };
+            }
+        };
+    }
+
+    @Override
+    protected void checkReportedError(@NonNull Context context, @NonNull Issue issue,
+            @NonNull Severity severity, @Nullable Location location, @NonNull String message) {
+        assertNotNull(message, UnusedResourceDetector.getUnusedResource(message, TEXT));
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/VectorDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/VectorDetectorTest.java
new file mode 100644
index 0000000..2fe35c0
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/VectorDetectorTest.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2015 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.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidProject;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Project;
+
+import org.intellij.lang.annotations.Language;
+
+import java.io.File;
+
+@SuppressWarnings("javadoc")
+public class VectorDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new VectorDetector();
+    }
+
+    @Language("XML")
+    private static final String VECTOR = ""
+            + "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+            + "        android:height=\"76dp\"\n"
+            + "        android:width=\"76dp\"\n"
+            + "        android:viewportHeight=\"48\"\n"
+            + "        android:viewportWidth=\"48\"\n"
+            + "        android:autoMirrored=\"true\"\n"
+            + "        android:tint=\"?attr/colorControlActivated\">\n"
+            + "\n"
+            + "    <clip-path />\n" // couldn't find any examples
+            + "\n"
+            + "    <group\n"
+            + "        android:name=\"root\"\n"
+            + "        android:translateX=\"24.0\"\n"
+            + "        android:translateY=\"24.0\" >\n"
+            + "        <path\n"
+            + "            android:name=\"progressBar\"\n"
+            + "            android:fillColor=\"#00000000\"\n"
+            + "            android:pathData=\"M0, 0 m 0, -19 a 19,19 0 1,1 0,38 a 19,19 0 1,1 0,-38\"\n"
+            + "            android:strokeColor=\"@color/white\"\n"
+            + "            android:strokeLineCap=\"square\"\n"
+            + "            android:strokeLineJoin=\"miter\"\n"
+            + "            android:strokeWidth=\"4\"\n"
+            + "            android:trimPathEnd=\"0\"\n"
+            + "            android:trimPathOffset=\"0\"\n"
+            + "            android:trimPathStart=\"0\" />\n"
+            + "    </group>\n"
+            + "\n"
+            + "</vector>";
+
+    public void testWarn() throws Exception {
+        assertEquals(""
+                + "res/drawable/foo.xml:6: Warning: This attribute is not supported in images generated from this vector icon for API < 21; check generated icon to make sure it looks acceptable [VectorRaster]\n"
+                + "        android:autoMirrored=\"true\"\n"
+                + "        ~~~~~~~~~~~~~~~~~~~~\n"
+                + "res/drawable/foo.xml:7: Warning: Resource references will not work correctly in images generated for this vector icon for API < 21; check generated icon to make sure it looks acceptable [VectorRaster]\n"
+                + "        android:tint=\"?attr/colorControlActivated\">\n"
+                + "                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "res/drawable/foo.xml:9: Warning: This tag is not supported in images generated from this vector icon for API < 21; check generated icon to make sure it looks acceptable [VectorRaster]\n"
+                + "    <clip-path />\n"
+                + "    ~~~~~~~~~~~~~\n"
+                + "res/drawable/foo.xml:11: Warning: Update Gradle plugin version to 1.5+ to correctly handle <group> tags in generated bitmaps [VectorRaster]\n"
+                + "    <group\n"
+                + "    ^\n"
+                + "res/drawable/foo.xml:19: Warning: Resource references will not work correctly in images generated for this vector icon for API < 21; check generated icon to make sure it looks acceptable [VectorRaster]\n"
+                + "            android:strokeColor=\"@color/white\"\n"
+                + "                                 ~~~~~~~~~~~~\n"
+                + "res/drawable/foo.xml:23: Warning: This attribute is not supported in images generated from this vector icon for API < 21; check generated icon to make sure it looks acceptable [VectorRaster]\n"
+                + "            android:trimPathEnd=\"0\"\n"
+                + "            ~~~~~~~~~~~~~~~~~~~\n"
+                + "res/drawable/foo.xml:24: Warning: This attribute is not supported in images generated from this vector icon for API < 21; check generated icon to make sure it looks acceptable [VectorRaster]\n"
+                + "            android:trimPathOffset=\"0\"\n"
+                + "            ~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "res/drawable/foo.xml:25: Warning: This attribute is not supported in images generated from this vector icon for API < 21; check generated icon to make sure it looks acceptable [VectorRaster]\n"
+                + "            android:trimPathStart=\"0\" />\n"
+                + "            ~~~~~~~~~~~~~~~~~~~~~\n"
+                + "0 errors, 8 warnings\n",
+                lintProject(
+                        xml("res/drawable/foo.xml", VECTOR),
+                        copy("apicheck/minsdk14.xml", "AndroidManifest.xml")
+                ));
+    }
+
+    public void testNoWarningsWithMinSdk21() throws Exception {
+        assertEquals("No warnings.",
+            lintProject(
+                    xml("res/drawable/foo.xml", VECTOR),
+                    copy("apicheck/minsdk21.xml", "AndroidManifest.xml")
+        ));
+    }
+
+    public void testNoWarningsInV21Folder() throws Exception {
+        assertEquals("No warnings.",
+                lintProject(
+                        xml("res/drawable-v21/foo.xml", VECTOR),
+                        copy("apicheck/minsdk14.xml", "AndroidManifest.xml")
+                ));
+    }
+
+    public void testNoGroupWarningWithPlugin15() throws Exception {
+        assertEquals("No warnings.",
+                lintProject(
+                        xml("res/drawable/foo.xml", ""
+                                + "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                                + "        android:height=\"76dp\"\n"
+                                + "        android:width=\"76dp\"\n"
+                                + "        android:viewportHeight=\"48\"\n"
+                                + "        android:viewportWidth=\"48\">\n"
+                                + "\n"
+                                + "    <group />"
+                                + "\n"
+                                + "</vector>"
+                                + ""),
+                        copy("apicheck/minsdk14.xml", "AndroidManifest.xml")
+                ));
+    }
+
+    @Override
+    protected TestLintClient createClient() {
+        return new TestLintClient() {
+            @NonNull
+            @Override
+            protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+                return new Project(this, dir, referenceDir) {
+                    @Override
+                    public boolean isGradleProject() {
+                        return true;
+                    }
+
+                    @Nullable
+                    @Override
+                    public AndroidProject getGradleProjectModel() {
+                        String modelVersion = "1.4.0-alpha2";
+                        if (getName().equals("VectorDetectorTest_testNoGroupWarningWithPlugin15")) {
+                            modelVersion = "1.5.0-alpha1";
+                        }
+                        return PrivateResourceDetectorTest.createMockProject(modelVersion, 3);
+                    }
+                };
+            }
+        };
+    }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ViewTagDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ViewTagDetectorTest.java
index cf21023..8145edb 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ViewTagDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ViewTagDetectorTest.java
@@ -18,37 +18,72 @@
 
 import com.android.tools.lint.detector.api.Detector;
 
-@SuppressWarnings("javadoc")
+@SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName"})
 public class ViewTagDetectorTest extends AbstractCheckTest {
     @Override
     protected Detector getDetector() {
         return new ViewTagDetector();
     }
 
-    public void test() throws Exception {
-        assertEquals(
-            "src/test/pkg/ViewTagTest.java:21: Warning: Avoid setting views as values for setTag: Can lead to memory leaks in versions older than Android 4.0 [ViewTag]\n" +
-            "        view.setTag(android.R.id.button1, group); // ERROR\n" +
-            "             ~~~~~~\n" +
-            "src/test/pkg/ViewTagTest.java:22: Warning: Avoid setting views as values for setTag: Can lead to memory leaks in versions older than Android 4.0 [ViewTag]\n" +
-            "        view.setTag(android.R.id.icon, view.findViewById(android.R.id.icon)); // ERROR\n" +
-            "             ~~~~~~\n" +
-            "src/test/pkg/ViewTagTest.java:23: Warning: Avoid setting cursors as values for setTag: Can lead to memory leaks in versions older than Android 4.0 [ViewTag]\n" +
-            "        view.setTag(android.R.id.icon1, cursor1); // ERROR\n" +
-            "             ~~~~~~\n" +
-            "src/test/pkg/ViewTagTest.java:24: Warning: Avoid setting cursors as values for setTag: Can lead to memory leaks in versions older than Android 4.0 [ViewTag]\n" +
-            "        view.setTag(android.R.id.icon2, cursor2); // ERROR\n" +
-            "             ~~~~~~\n" +
-            "src/test/pkg/ViewTagTest.java:25: Warning: Avoid setting view holders as values for setTag: Can lead to memory leaks in versions older than Android 4.0 [ViewTag]\n" +
-            "        view.setTag(android.R.id.copy, new MyViewHolder()); // ERROR\n" +
-            "             ~~~~~~\n" +
-            "0 errors, 5 warnings\n",
+    private final TestFile mViewTagTest = java("src/test/pkg/ViewTagTest.java", ""
+            + "package test.pkg;\n"
+            + "\n"
+            + "import android.annotation.SuppressLint;\n"
+            + "import android.content.Context;\n"
+            + "import android.database.Cursor;\n"
+            + "import android.database.MatrixCursor;\n"
+            + "import android.view.LayoutInflater;\n"
+            + "import android.view.View;\n"
+            + "import android.view.ViewGroup;\n"
+            + "import android.widget.CursorAdapter;\n"
+            + "import android.widget.ImageView;\n"
+            + "import android.widget.TextView;\n"
+            + "\n"
+            + "@SuppressWarnings(\"unused\")\n"
+            + "public abstract class ViewTagTest {\n"
+            + "    public View newView(Context context, ViewGroup group, Cursor cursor1,\n"
+            + "            MatrixCursor cursor2) {\n"
+            + "        LayoutInflater inflater = LayoutInflater.from(context);\n"
+            + "        View view = inflater.inflate(android.R.layout.activity_list_item, null);\n"
+            + "        view.setTag(android.R.id.background, \"Some random tag\"); // OK\n"
+            + "        view.setTag(android.R.id.button1, group); // ERROR\n"
+            + "        view.setTag(android.R.id.icon, view.findViewById(android.R.id.icon)); // ERROR\n"
+            + "        view.setTag(android.R.id.icon1, cursor1); // ERROR\n"
+            + "        view.setTag(android.R.id.icon2, cursor2); // ERROR\n"
+            + "        view.setTag(android.R.id.copy, new MyViewHolder()); // ERROR\n"
+            + "        return view;\n"
+            + "    }\n"
+            + "\n"
+            + "    @SuppressLint(\"ViewTag\")\n"
+            + "    public static void checkSuppress(Context context, View view) {\n"
+            + "        view.setTag(android.R.id.icon, view.findViewById(android.R.id.icon));\n"
+            + "    }\n"
+            + "\n"
+            + "    private class MyViewHolder {\n"
+            + "        View view;\n"
+            + "    }\n"
+            + "}\n");
 
-            lintProject(
-                    "bytecode/.classpath=>.classpath",
-                    "bytecode/ViewTagTest.java.txt=>src/test/pkg/ViewTagTest.java",
-                    "bytecode/ViewTagTest.class.data=>bin/classes/test/pkg/ViewTagTest.class"
-                    ));
+    public void test() throws Exception {
+        assertEquals(""
+                + "src/test/pkg/ViewTagTest.java:21: Warning: Avoid setting views as values for setTag: Can lead to memory leaks in versions older than Android 4.0 [ViewTag]\n"
+                + "        view.setTag(android.R.id.button1, group); // ERROR\n"
+                + "                                          ~~~~~\n"
+                + "src/test/pkg/ViewTagTest.java:22: Warning: Avoid setting views as values for setTag: Can lead to memory leaks in versions older than Android 4.0 [ViewTag]\n"
+                + "        view.setTag(android.R.id.icon, view.findViewById(android.R.id.icon)); // ERROR\n"
+                + "                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "src/test/pkg/ViewTagTest.java:23: Warning: Avoid setting cursors as values for setTag: Can lead to memory leaks in versions older than Android 4.0 [ViewTag]\n"
+                + "        view.setTag(android.R.id.icon1, cursor1); // ERROR\n"
+                + "                                        ~~~~~~~\n"
+                + "src/test/pkg/ViewTagTest.java:24: Warning: Avoid setting cursors as values for setTag: Can lead to memory leaks in versions older than Android 4.0 [ViewTag]\n"
+                + "        view.setTag(android.R.id.icon2, cursor2); // ERROR\n"
+                + "                                        ~~~~~~~\n"
+                + "src/test/pkg/ViewTagTest.java:25: Warning: Avoid setting view holders as values for setTag: Can lead to memory leaks in versions older than Android 4.0 [ViewTag]\n"
+                + "        view.setTag(android.R.id.copy, new MyViewHolder()); // ERROR\n"
+                + "                                       ~~~~~~~~~~~~~~~~~~\n"
+                + "0 errors, 5 warnings\n",
+
+            lintProject(mViewTagTest));
     }
 
     public void testICS() throws Exception {
@@ -56,10 +91,7 @@
             "No warnings.",
 
             lintProject(
-                    "bytecode/.classpath=>.classpath",
-                    "apicheck/minsdk14.xml=>AndroidManifest.xml",
-                    "bytecode/ViewTagTest.java.txt=>src/test/pkg/ViewTagTest.java",
-                    "bytecode/ViewTagTest.class.data=>bin/classes/test/pkg/ViewTagTest.class"
-                    ));
+                    mViewTagTest,
+                    copy("apicheck/minsdk14.xml", "AndroidManifest.xml")));
     }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WakelockDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WakelockDetectorTest.java
index 9505b3a..e5fdf3a 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WakelockDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WakelockDetectorTest.java
@@ -171,6 +171,19 @@
                 ));
     }
 
+    public void test10() throws Exception {
+        // Regression test for 43212
+        assertEquals(
+                "No warnings.",
+
+                lintProject(
+                        "bytecode/.classpath=>.classpath",
+                        "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+                        "bytecode/WakelockActivity10.java.txt=>src/test/pkg/WakelockActivity10.java",
+                        "bytecode/WakelockActivity10.class.data=>bin/classes/test/pkg/WakelockActivity10.class"
+                ));
+    }
+
     public void testFlags() throws Exception {
         assertEquals(""
                 + "src/test/pkg/PowerManagerFlagTest.java:15: Warning: Should not set both PARTIAL_WAKE_LOCK and ACQUIRE_CAUSES_WAKEUP. If you do not want the screen to turn on, get rid of ACQUIRE_CAUSES_WAKEUP [Wakelock]\n"
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongIdDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongIdDetectorTest.java
index fccd0be..7e0ea7b 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongIdDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongIdDetectorTest.java
@@ -184,4 +184,37 @@
 
                 lintFiles("wrongid/layout3.xml=>res/layout/layout3.xml"));
     }
+
+    public void testPercent() throws Exception {
+        assertEquals(""
+                + "res/layout/test.xml:18: Error: The id \"textView1\" is not defined anywhere. Did you mean textview1 ? [UnknownId]\n"
+                + "            android:layout_below=\"@id/textView1\"\n"
+                + "            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "1 errors, 0 warnings\n",
+
+                lintProject(xml("res/layout/test.xml", ""
+                        + "<android.support.percent.PercentRelativeLayout "
+                        + "     xmlns:android=\"http://schemas.android.com/apk/res/android\""
+                        + "     xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n"
+                        + "     android:layout_width=\"match_parent\"\n"
+                        + "     android:layout_height=\"match_parent\">\n"
+                        + "        <View\n"
+                        + "            android:id=\"@+id/textview1\"\n"
+                        + "            android:layout_gravity=\"center\"\n"
+                        + "            app:layout_widthPercent=\"50%\"\n"
+                        + "            app:layout_heightPercent=\"50%\"/>\n"
+                        + "        <View\n"
+                        + "            android:id=\"@+id/textview2\"\n"
+                        + "            android:layout_below=\"@id/textview1\"\n" // OK
+                        + "            android:layout_width=\"wrap_content\"\n"
+                        + "            android:layout_height=\"wrap_content\"\n"
+                        + "            app:layout_marginStartPercent=\"25%\"\n"
+                        + "            app:layout_marginEndPercent=\"25%\"/>\n"
+                        + "        <View\n"
+                        + "            android:layout_height=\"wrap_content\"\n"
+                        + "            android:layout_below=\"@id/textView1\"\n" // WRONG (case)
+                        + "            app:layout_widthPercent=\"60%\"/>\n"
+                        + "\n"
+                        + "</android.support.percent.PercentRelativeLayout>\n")));
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck.java.txt
index 429b388..9b70b21 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck.java.txt
@@ -91,8 +91,8 @@
     }
 
     private void innerclass() {
-        String setPointerSpeed = permission.SET_POINTER_SPEED;
-        String setPointerSpeed2 = Manifest.permission.SET_POINTER_SPEED;
+        String setPointerSpeed = permission.BLUETOOTH_PRIVILEGED;
+        String setPointerSpeed2 = Manifest.permission.BLUETOOTH_PRIVILEGED;
     }
 
     private void test() {
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest1.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest1.java.txt
index a8775d7..090688c 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest1.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest1.java.txt
@@ -5,7 +5,7 @@
 import org.w3c.dom.DOMLocator;
 
 import android.view.ViewGroup.LayoutParams;
-import android.annotations.tools.SuppressLint;
+import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.ApplicationErrorReport;
 import android.app.ApplicationErrorReport.BatteryInfo;
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest2.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest2.java.txt
index e325413..1400224 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest2.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest2.java.txt
@@ -2,7 +2,7 @@
 
 import org.w3c.dom.DOMLocator;
 
-import android.annotations.tools.SuppressLint;
+import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.ApplicationErrorReport;
 import android.widget.Chronometer;
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest3.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest3.java.txt
index c430ce4..0f17f4b 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest3.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest3.java.txt
@@ -2,7 +2,7 @@
 
 import org.w3c.dom.DOMLocator;
 
-import android.annotations.tools.SuppressLint;
+import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.ApplicationErrorReport;
 import android.widget.Chronometer;
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest4.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest4.java.txt
index 2fa2af8..94152f6 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest4.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest4.java.txt
@@ -1,6 +1,6 @@
 package test.pkg;
 
-import android.annotations.tools.SuppressLint;
+import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.ApplicationErrorReport;
 import android.app.ApplicationErrorReport.BatteryInfo;
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/app_indexing_api_test.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/app_indexing_api_test.xml
new file mode 100644
index 0000000..69d5872
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/app_indexing_api_test.xml
@@ -0,0 +1,28 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.example.helloworld">
+
+    <application
+            android:allowBackup="true"
+            android:icon="@mipmap/ic_launcher"
+            android:label="@string/app_name"
+            android:theme="@style/AppTheme" >
+        <activity
+                android:name=".AppIndexingApiTest"
+                android:configChanges="orientation|keyboardHidden|screenSize"
+                android:label="@string/title_activity_fullscreen"
+                android:theme="@style/FullscreenTheme" >
+            <intent-filter android:label="@string/title_activity_fullscreen">
+                <action android:name="android.intent.action.VIEW" />
+                <data android:scheme="http"
+                      android:host="example.com"
+                      android:pathPrefix="/gizmos" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+            </intent-filter>
+        </activity>
+
+        <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
+    </application>
+
+</manifest>
+
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/app_indexing_api_test_no_manifest.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/app_indexing_api_test_no_manifest.xml
new file mode 100644
index 0000000..4e8cb47
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/app_indexing_api_test_no_manifest.xml
@@ -0,0 +1,25 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.example.helloworld">
+
+    <application
+            android:allowBackup="true"
+            android:icon="@mipmap/ic_launcher"
+            android:label="@string/app_name"
+            android:theme="@style/AppTheme" >
+        <activity
+                android:name=".AppIndexingApiTest"
+                android:configChanges="orientation|keyboardHidden|screenSize"
+                android:label="@string/title_activity_fullscreen"
+                android:theme="@style/FullscreenTheme" >
+            <intent-filter android:label="@string/title_activity_fullscreen">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+            </intent-filter>
+        </activity>
+
+        <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
+    </application>
+
+</manifest>
+
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/AppCompatPrefTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/AppCompatPrefTest.java.txt
index ddb68e4..1a6a269 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/AppCompatPrefTest.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/AppCompatPrefTest.java.txt
@@ -5,7 +5,7 @@
 import android.os.Bundle;
 import android.preference.PreferenceActivity;
 
-public class MyPreferenceActivity extends PreferenceActivity {
+public class AppCompatPrefTest extends PreferenceActivity {
     @TargetApi(Build.VERSION_CODES.HONEYCOMB)
     @Override
     protected void onCreate(Bundle savedInstanceState) {
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/DialogFragment.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/DialogFragment.java.txt
index 2e08a97..2274c5a 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/DialogFragment.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/DialogFragment.java.txt
@@ -3,7 +3,7 @@
 /** Stub to make unit tests able to resolve types without having a real dependency
  * on the appcompat library */
 public abstract class DialogFragment extends Fragment {
-    public abstract void show(FragmentManager manager, String tag);
-    public abstract int show(FragmentTransaction transaction, String tag);
-    public abstract void dismiss();
+    public void show(FragmentManager manager, String tag) { }
+    public int show(FragmentTransaction transaction, String tag) { return 0; }
+    public void dismiss() { }
 }
\ No newline at end of file
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/FragmentTransaction.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/FragmentTransaction.java.txt
index d12bc55..4545dcb 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/FragmentTransaction.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/FragmentTransaction.java.txt
@@ -10,6 +10,7 @@
     public abstract FragmentTransaction attach(Fragment fragment);
     public abstract FragmentTransaction detach(Fragment fragment);
     public abstract FragmentTransaction add(int containerViewId, Fragment fragment);
+    public abstract FragmentTransaction add(Fragment fragment, String tag);
     public abstract FragmentTransaction addToBackStack(String name);
     public abstract FragmentTransaction disallowAddToBackStack();
     public abstract FragmentTransaction setBreadCrumbShortTitle(int res);
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest.java.txt
index 8f0b844..59acc3a 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest.java.txt
@@ -7,7 +7,7 @@
 
 @SuppressWarnings("unused")
 public class CommitTest extends Activity {
-        public void ok1() {
+    public void ok1() {
         getFragmentManager().beginTransaction().commit();
     }
 
@@ -116,4 +116,22 @@
         FragmentTransaction temp = transaction;
         temp.commitAllowingStateLoss();
     }
+
+    public void error5(FragmentTransaction unrelated) {
+        FragmentTransaction transaction;
+        // Comment in between variable declaration and assignment
+        transaction = getFragmentManager().beginTransaction();
+        transaction = unrelated;
+        transaction.commit();
+    }
+
+    public void error6(FragmentTransaction unrelated) {
+        FragmentTransaction transaction;
+        FragmentTransaction transaction2;
+        // Comment in between variable declaration and assignment
+        transaction = getFragmentManager().beginTransaction();
+        transaction2 = transaction;
+        transaction2 = unrelated;
+        transaction2.commit();
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ExampleHostnameVerifier$1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ExampleHostnameVerifier$1.class.data
new file mode 100644
index 0000000..30f03ee
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ExampleHostnameVerifier$1.class.data
Binary files differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ExampleTLSIntentService$1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ExampleTLSIntentService$1.class.data
new file mode 100644
index 0000000..78b84b8
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ExampleTLSIntentService$1.class.data
Binary files differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ExampleTLSIntentService.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ExampleTLSIntentService.class.data
new file mode 100644
index 0000000..7826fa0
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ExampleTLSIntentService.class.data
Binary files differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ExampleTLSIntentService.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ExampleTLSIntentService.java.txt
new file mode 100644
index 0000000..e6f8581
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ExampleTLSIntentService.java.txt
@@ -0,0 +1,62 @@
+package test.pkg;
+
+import android.app.IntentService;
+import android.content.Intent;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.security.GeneralSecurityException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+public class ExampleTLSIntentService extends IntentService {
+    TrustManager[] trustManagerExample;
+
+    {
+        trustManagerExample = new TrustManager[]{new X509TrustManager() {
+            @Override
+            public X509Certificate[] getAcceptedIssuers() {
+                try {
+                    FileInputStream fis = new FileInputStream("testcert.pem");
+                    BufferedInputStream bis = new BufferedInputStream(fis);
+                    CertificateFactory cf = CertificateFactory.getInstance("X.509");
+                    X509Certificate cert = (X509Certificate) cf.generateCertificate(bis);
+                    return new X509Certificate[]{cert};
+                } catch (Exception e) {
+                    throw new RuntimeException("Could not load cert");
+                }
+            }
+
+            @Override
+            public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException {
+                throw new CertificateException("Not trusted");
+            }
+
+            @Override
+            public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {
+                throw new CertificateException("Not trusted");
+            }
+        }};
+    }
+
+    public ExampleTLSIntentService() {
+        super("ExampleTLSIntentService");
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        try {
+            SSLContext sc = SSLContext.getInstance("TLSv1.2");
+            sc.init(null, trustManagerExample, new java.security.SecureRandom());
+            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+        } catch (GeneralSecurityException e) {
+            System.out.println(e.getStackTrace());
+        }
+    }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FloatingActionButton.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FloatingActionButton.class.data
new file mode 100644
index 0000000..f2bdf49
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FloatingActionButton.class.data
Binary files differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FloatingActionButton.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FloatingActionButton.java.txt
new file mode 100644
index 0000000..2742ce7
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FloatingActionButton.java.txt
@@ -0,0 +1,20 @@
+package android.support.design.widget;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.util.AttributeSet;
+import android.widget.ImageButton;
+
+// JUST A UNIT TESTING STUB!
+public abstract class FloatingActionButton extends ImageButton {
+    @SuppressLint("NewApi")
+    public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    public void setBackgroundTintList(ColorStateList tint) {
+        super.setBackgroundTintList(tint);
+    }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$1.class.data
deleted file mode 100644
index ae65532..0000000
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$1.class.data
+++ /dev/null
Binary files differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$2.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$2.class.data
deleted file mode 100644
index 52183c2..0000000
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$2.class.data
+++ /dev/null
Binary files differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$Inner.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$Inner.class.data
deleted file mode 100644
index 3975896..0000000
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$Inner.class.data
+++ /dev/null
Binary files differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$StaticInner.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$StaticInner.class.data
deleted file mode 100644
index 690ee89..0000000
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$StaticInner.class.data
+++ /dev/null
Binary files differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$WithArbitraryLooper.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$WithArbitraryLooper.class.data
deleted file mode 100644
index b389dc5..0000000
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$WithArbitraryLooper.class.data
+++ /dev/null
Binary files differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest.class.data
deleted file mode 100644
index 8b01ef2..0000000
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest.class.data
+++ /dev/null
Binary files differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest.java.txt
deleted file mode 100644
index 06f3298..0000000
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest.java.txt
+++ /dev/null
@@ -1,41 +0,0 @@
-package test.pkg;
-import android.os.Looper;
-import android.os.Handler;
-import android.os.Message;
-
-public class HandlerTest extends Handler { // OK
-    public static class StaticInner extends Handler { // OK
-        public void dispatchMessage(Message msg) {
-            super.dispatchMessage(msg);
-        };
-    }
-    public class Inner extends Handler { // ERROR
-        public void dispatchMessage(Message msg) {
-            super.dispatchMessage(msg);
-        };
-    }
-    void method() {
-        Handler anonymous = new Handler() { // ERROR
-            public void dispatchMessage(Message msg) {
-                super.dispatchMessage(msg);
-            };
-        };
-
-        Looper looper = null;
-        Handler anonymous2 = new Handler(looper) { // OK
-            public void dispatchMessage(Message msg) {
-                super.dispatchMessage(msg);
-            };
-        };
-    }
-
-    public class WithArbitraryLooper extends Handler {
-        public WithArbitraryLooper(String unused, Looper looper) { // OK
-            super(looper, null);
-        }
-
-        public void dispatchMessage(Message msg) {
-            super.dispatchMessage(msg);
-        };
-    }
-}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InsecureHostnameVerifier$1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InsecureHostnameVerifier$1.class.data
new file mode 100644
index 0000000..2fe5b4e
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InsecureHostnameVerifier$1.class.data
Binary files differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InsecureTLSIntentService$1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InsecureTLSIntentService$1.class.data
new file mode 100644
index 0000000..f5562a9
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InsecureTLSIntentService$1.class.data
Binary files differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InsecureTLSIntentService.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InsecureTLSIntentService.class.data
new file mode 100644
index 0000000..7dd0832
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InsecureTLSIntentService.class.data
Binary files differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InsecureTLSIntentService.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InsecureTLSIntentService.java.txt
new file mode 100644
index 0000000..b128037
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InsecureTLSIntentService.java.txt
@@ -0,0 +1,44 @@
+package test.pkg;
+
+import android.app.IntentService;
+import android.content.Intent;
+
+import java.security.GeneralSecurityException;
+import java.security.cert.CertificateException;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+public class InsecureTLSIntentService extends IntentService {
+    TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
+        @Override
+        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
+            return null;
+        }
+
+        @Override
+        public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
+        }
+
+        @Override
+        public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) throws CertificateException {
+        }
+    }};
+
+    public InsecureTLSIntentService() {
+        super("InsecureTLSIntentService");
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        try {
+            SSLContext sc = SSLContext.getInstance("TLSv1.2");
+            sc.init(null, trustAllCerts, new java.security.SecureRandom());
+            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+        } catch (GeneralSecurityException e) {
+            System.out.println(e.getStackTrace());
+        }
+    }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/SecureRandomTest.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/SecureRandomTest.class.data
deleted file mode 100644
index 3236187..0000000
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/SecureRandomTest.class.data
+++ /dev/null
Binary files differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/SecureRandomTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/SecureRandomTest.java.txt
deleted file mode 100644
index c05fbd9..0000000
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/SecureRandomTest.java.txt
+++ /dev/null
@@ -1,42 +0,0 @@
-package test.pkg;
-
-import java.security.SecureRandom;
-import java.util.Random;
-
-public class SecureRandomTest {
-    private static final long FIXED_SEED = 1000L;
-    protected int getDynamicSeed() {  return 1; }
-
-    public void testLiterals() {
-        SecureRandom random1 = new SecureRandom();
-        random1.setSeed(System.currentTimeMillis()); // OK
-        random1.setSeed(getDynamicSeed()); // OK
-        random1.setSeed(0); // Wrong
-        random1.setSeed(1); // Wrong
-        random1.setSeed((int)1023); // Wrong
-        random1.setSeed(1023L); // Wrong
-        random1.setSeed(FIXED_SEED); // Wrong
-    }
-
-    public void testRandomTypeOk() {
-        Random random2 = new Random();
-        random2.setSeed(0); // OK
-    }
-
-    public void testRandomTypeWrong() {
-        Random random3 = new SecureRandom();
-        random3.setSeed(0); // Wrong: owner is java/util/Random, but applied to SecureRandom object
-    }
-
-    public void testBytesOk() {
-        SecureRandom random1 = new SecureRandom();
-        byte[] seed = random1.generateSeed(4);
-        random1.setSeed(seed); // OK
-    }
-
-    public void testBytesWrong() {
-        SecureRandom random2 = new SecureRandom();
-        byte[] seed = new byte[3];
-        random2.setSeed(seed); // Wrong
-    }
-}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/SupportLibraryApiTest.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/SupportLibraryApiTest.class.data
new file mode 100644
index 0000000..8d1fa3b
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/SupportLibraryApiTest.class.data
Binary files differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/SupportLibraryApiTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/SupportLibraryApiTest.java.txt
new file mode 100644
index 0000000..eca796d
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/SupportLibraryApiTest.java.txt
@@ -0,0 +1,24 @@
+package test.pkg;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.support.design.widget.FloatingActionButton;
+import android.util.AttributeSet;
+import android.widget.ImageButton;
+
+public class SupportLibraryApiTest extends FloatingActionButton {
+    public SupportLibraryApiTest(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    public void test1(ColorStateList colors) {
+        setBackgroundTintList(colors); // OK: FAB overrides ImageButton with lower minSDK
+        this.setBackgroundTintList(colors); // OK: FAB overrides ImageButton with lower minSDK
+    }
+
+    public void test2(FloatingActionButton fab, ImageButton button,
+                    ColorStateList colors) {
+        fab.setBackgroundTintList(colors); // OK: FAB overrides ImageButton with lower minSDK
+        button.setBackgroundTintList(colors); // ERROR
+    }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ViewTagTest.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ViewTagTest.class.data
deleted file mode 100644
index f26032c..0000000
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ViewTagTest.class.data
+++ /dev/null
Binary files differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ViewTagTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ViewTagTest.java.txt
deleted file mode 100644
index 8e72fd0..0000000
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ViewTagTest.java.txt
+++ /dev/null
@@ -1,37 +0,0 @@
-package test.pkg;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CursorAdapter;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-@SuppressWarnings("unused")
-public abstract class ViewTagTest {
-    public View newView(Context context, ViewGroup group, Cursor cursor1,
-            MatrixCursor cursor2) {
-        LayoutInflater inflater = LayoutInflater.from(context);
-        View view = inflater.inflate(android.R.layout.activity_list_item, null);
-        view.setTag(android.R.id.background, "Some random tag"); // OK
-        view.setTag(android.R.id.button1, group); // ERROR
-        view.setTag(android.R.id.icon, view.findViewById(android.R.id.icon)); // ERROR
-        view.setTag(android.R.id.icon1, cursor1); // ERROR
-        view.setTag(android.R.id.icon2, cursor2); // ERROR
-        view.setTag(android.R.id.copy, new MyViewHolder()); // ERROR
-        return view;
-    }
-
-    @SuppressLint("ViewTag")
-    public void checkSuppress(Context context, View view) {
-        view.setTag(android.R.id.icon, view.findViewById(android.R.id.icon));
-    }
-
-    private class MyViewHolder {
-        View view;
-    }
-}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity10.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity10.class.data
new file mode 100644
index 0000000..6a26f14
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity10.class.data
Binary files differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity10.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity10.java.txt
new file mode 100644
index 0000000..105d722
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity10.java.txt
@@ -0,0 +1,23 @@
+package test.pkg;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.PowerManager;
+
+public class WakelockActivity10 extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        PowerManager manager = (PowerManager) getSystemService(POWER_SERVICE);
+        PowerManager.WakeLock wakeLock = manager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Test");
+
+        try {
+            wakeLock.acquire();
+            throw new Exception();
+        } catch (Exception e) {
+
+        } finally {
+            wakeLock.release();
+        }
+    }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Compatibility.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Compatibility.gradle
index 520216c..dba0e20 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Compatibility.gradle
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Compatibility.gradle
@@ -1,7 +1,7 @@
 apply plugin: 'com.android.application'
 
 android {
-    compileSdkVersion 19
+    compileSdkVersion 18
     buildToolsVersion "19.0.0"
 
     defaultConfig {
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DynamicResources.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DynamicResources.gradle
new file mode 100644
index 0000000..8a50ea4
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DynamicResources.gradle
@@ -0,0 +1,23 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 23
+    buildToolsVersion "23.1.0"
+
+    defaultConfig {
+        resValue "string", "cat", "Some Data"
+        minSdkVersion 15
+        targetSdkVersion 23
+        versionCode 1
+        versionName "1.0"
+    }
+    buildTypes {
+        debug {
+            resValue "string", "foo", "Some Data"
+        }
+        release {
+            resValue "string", "xyz", "Some Data"
+            resValue "string", "dog", "Some Data"
+        }
+    }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Plus.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Plus.gradle
index 0aa261e..3ddf437 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Plus.gradle
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Plus.gradle
@@ -8,4 +8,5 @@
 dependencies {
     compile 'com.android.support:appcompat-v7:+'
     compile group: 'com.android.support', name: 'support-v4', version: '21.0.+'
+    compile 'com.android.support:appcompat-v7:+@aar'
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/hostnameverifier.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/hostnameverifier.xml
new file mode 100644
index 0000000..9e6acd6
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/hostnameverifier.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="test.pkg"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk android:minSdkVersion="14" />
+
+    <application
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name" >
+        <service
+            android:name=".InsecureHostnameVerifier" >
+        </service>
+    </application>
+
+</manifest>
+
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/hostnameverifier1.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/hostnameverifier1.xml
new file mode 100644
index 0000000..55eebff
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/hostnameverifier1.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="test.pkg"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk android:minSdkVersion="14" />
+
+    <application
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name" >
+        <service
+            android:name=".ExampleHostnameVerifier" >
+        </service>
+    </application>
+
+</manifest>
+
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/lib/armeabi/hello b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/lib/armeabi/hello
new file mode 100755
index 0000000..e84aac3
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/lib/armeabi/hello
Binary files differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/lib/armeabi/libhello-jni.so b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/lib/armeabi/libhello-jni.so
new file mode 100755
index 0000000..6b393b1
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/lib/armeabi/libhello-jni.so
Binary files differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/raw/hello b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/raw/hello
new file mode 100755
index 0000000..e84aac3
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/raw/hello
Binary files differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/raw/libhello-jni.so b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/raw/libhello-jni.so
new file mode 100755
index 0000000..6b393b1
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/raw/libhello-jni.so
Binary files differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/safereceiver1.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/safereceiver1.xml
new file mode 100644
index 0000000..5004bee
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/safereceiver1.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="test.pkg"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk android:minSdkVersion="14" />
+
+    <application
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name" >
+        <receiver
+            android:label="@string/app_name"
+            android:name=".TestReceiver"
+            android:permission="android.permission.BROADCAST_SMS" >
+                <intent-filter>
+                    <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
+                </intent-filter>
+        </receiver>
+    </application>
+
+</manifest>
+
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/AnyRes.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/AnyRes.java.txt
index 353c0a3..53c6c15 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/AnyRes.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/AnyRes.java.txt
@@ -3,11 +3,13 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
-import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
 import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
 import static java.lang.annotation.RetentionPolicy.CLASS;
 
 @Retention(CLASS)
-@Target({METHOD,CONSTRUCTOR})
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
 public @interface AnyRes {
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/DrawableRes.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/DrawableRes.java.txt
index bbb0407..4393dfb 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/DrawableRes.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/DrawableRes.java.txt
@@ -5,12 +5,13 @@
 import java.lang.annotation.Target;
 
 import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
 import static java.lang.annotation.ElementType.METHOD;
 import static java.lang.annotation.ElementType.PARAMETER;
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 @Documented
 @Retention(SOURCE)
-@Target({METHOD, PARAMETER, FIELD})
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
 public @interface DrawableRes {
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/Activity.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/Activity.java.txt
new file mode 100644
index 0000000..80fc034
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/Activity.java.txt
@@ -0,0 +1,6 @@
+package android.app;
+
+import android.content.Context
+
+// Stub class for testing.
+public class Activity extends Context {}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/Api.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/Api.java.txt
new file mode 100644
index 0000000..455a90d
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/Api.java.txt
@@ -0,0 +1,8 @@
+package com.google.android.gms.common.api;
+
+public final class Api<O extends Api.ApiOptions> {
+    public interface ApiOptions {
+        public interface NotRequiredOptions extends Api.ApiOptions {}
+        public static final class NoOptions implements Api.ApiOptions.NotRequiredOptions {}
+    }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/AppIndex.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/AppIndex.java.txt
new file mode 100644
index 0000000..7b1ca76
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/AppIndex.java.txt
@@ -0,0 +1,10 @@
+package com.google.android.gms.appindexing;
+
+import com.google.android.gms.appindexing.AppIndexApi;
+import com.google.android.gms.common.api.Api;
+import com.google.android.gms.common.api.Api.ApiOptions.NoOptions;
+
+public final class AppIndex {
+    public static final AppIndexApi AppIndexApi;
+    public static final Api<NoOptions> APP_INDEX_API;
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/AppIndexApi.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/AppIndexApi.java.txt
new file mode 100644
index 0000000..1451fef
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/AppIndexApi.java.txt
@@ -0,0 +1,27 @@
+package com.google.android.gms.appindexing;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.view.View;
+import com.google.android.gms.appindexing.Action;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.common.api.PendingResult;
+import com.google.android.gms.common.api.Status;
+import java.util.List;
+
+public class AppIndexApi {
+    PendingResult<Status> view(GoogleApiClient var1, Activity var2, Intent var3, String var4, Uri var5, List<AppIndexApi.AppIndexingLink> var6) {}
+
+    PendingResult<Status> viewEnd(GoogleApiClient var1, Activity var2, Intent var3) {}
+
+    PendingResult<Status> view(GoogleApiClient var1, Activity var2, Uri var3, String var4, Uri var5, List<AppIndexApi.AppIndexingLink> var6) {}
+
+    PendingResult<Status> viewEnd(GoogleApiClient var1, Activity var2, Uri var3) {}
+
+    PendingResult<Status> start(GoogleApiClient var1, Action var2) {}
+
+    PendingResult<Status> end(GoogleApiClient var1, Action var2) {}
+
+    public static final class AppIndexingLink {}
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/GoogleApiClient.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/GoogleApiClient.java.txt
new file mode 100644
index 0000000..966b2ff
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/GoogleApiClient.java.txt
@@ -0,0 +1,15 @@
+package com.google.android.gms.common.api;
+
+import com.google.android.gms.common.api.Api;
+import com.google.android.gms.common.api.Api.ApiOptions.NotRequiredOptions;
+
+public abstract class GoogleApiClient {
+    public abstract void connect() {}
+    public abstract void disconnect() {}
+
+    public static final class Builder {
+        public Builder(Context c);
+        public GoogleApiClient.Builder addApi(Api<? extends NotRequiredOptions> api);
+        public GoogleApiClient build();
+    }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestEndMatch.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestEndMatch.java.txt
new file mode 100644
index 0000000..c2016aa
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestEndMatch.java.txt
@@ -0,0 +1,35 @@
+package com.example.helloworld;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.google.android.gms.appindexing.Action;
+import com.google.android.gms.appindexing.AppIndex;
+import com.google.android.gms.common.api.GoogleApiClient;
+
+public class AppIndexingApiTest extends Activity {
+  static final Uri APP_URI = Uri.parse("android-app://com.example.helloworld/http/example.com/gizmos");
+  static final Uri WEB_URL = Uri.parse("http://example.com/gizmos");
+  private GoogleApiClient mClient;
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    mClient = new GoogleApiClient.Builder(this).addApi(AppIndex.APP_INDEX_API).build();
+  }
+
+  @Override
+  public void onStart(){
+    super.onStart();
+    mClient.connect();
+  }
+
+  @Override
+  public void onStop(){
+    super.onStop();
+    final String title = "App Indexing API Title";
+    Action action = Action.newAction(Action.TYPE_VIEW, title, WEB_URL, APP_URI);
+    AppIndex.AppIndexApi.end(mClient, action);
+  }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestGoogleApiClientAddApi.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestGoogleApiClientAddApi.java.txt
new file mode 100644
index 0000000..ce030cb
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestGoogleApiClientAddApi.java.txt
@@ -0,0 +1,40 @@
+package com.example.helloworld;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.google.android.gms.appindexing.Action;
+import com.google.android.gms.appindexing.AppIndex;
+import com.google.android.gms.common.api.GoogleApiClient;
+
+public class AppIndexingApiTest extends Activity {
+  static final Uri APP_URI = Uri.parse("android-app://com.example.helloworld/http/example.com/gizmos");
+  static final Uri WEB_URL = Uri.parse("http://example.com/gizmos");
+  private GoogleApiClient mClient;
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    mClient = new GoogleApiClient.Builder(this).build();
+  }
+
+  @Override
+  public void onStart(){
+    super.onStart();
+    mClient.connect();
+    final String title = "App Indexing API Title";
+    Action action = Action.newAction(Action.TYPE_VIEW, title, WEB_URL, APP_URI);
+    AppIndex.AppIndexApi.start(mClient, action);
+  }
+
+  @Override
+  public void onStop(){
+    super.onStop();
+    final String title = "App Indexing API Title";
+    Action action = Action.newAction(Action.TYPE_VIEW, title, WEB_URL, APP_URI);
+    AppIndex.AppIndexApi.end(mClient, action);
+    mClient.disconnect();
+  }
+}
+
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestNoStartEnd.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestNoStartEnd.java.txt
new file mode 100644
index 0000000..1b154bb
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestNoStartEnd.java.txt
@@ -0,0 +1,34 @@
+package com.example.helloworld;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.google.android.gms.appindexing.Action;
+import com.google.android.gms.appindexing.AppIndex;
+import com.google.android.gms.common.api.GoogleApiClient;
+
+public class AppIndexingApiTest extends Activity {
+  static final Uri APP_URI = Uri.parse("android-app://com.example.helloworld/http/example.com/gizmos");
+  static final Uri WEB_URL = Uri.parse("http://example.com/gizmos");
+  private GoogleApiClient mClient;
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    mClient = new GoogleApiClient.Builder(this).addApi(AppIndex.APP_INDEX_API).build();
+  }
+
+  @Override
+  public void onStart(){
+    super.onStart();
+    mClient.connect();
+  }
+
+  @Override
+  public void onStop(){
+    super.onStop();
+    mClient.disconnect();
+  }
+}
+
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestOk.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestOk.java.txt
new file mode 100644
index 0000000..8c06924
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestOk.java.txt
@@ -0,0 +1,40 @@
+package com.example.helloworld;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.google.android.gms.appindexing.Action;
+import com.google.android.gms.appindexing.AppIndex;
+import com.google.android.gms.common.api.GoogleApiClient;
+
+public class AppIndexingApiTest extends Activity {
+  static final Uri APP_URI = Uri.parse("android-app://com.example.helloworld/http/example.com/gizmos");
+  static final Uri WEB_URL = Uri.parse("http://example.com/gizmos");
+  private GoogleApiClient mClient;
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    mClient = new GoogleApiClient.Builder(this).addApi(AppIndex.APP_INDEX_API).build();
+  }
+
+  @Override
+  public void onStart(){
+    super.onStart();
+    mClient.connect();
+    final String title = "App Indexing API Title";
+    Action action = Action.newAction(Action.TYPE_VIEW, title, WEB_URL, APP_URI);
+    AppIndex.AppIndexApi.start(mClient, action);
+  }
+
+  @Override
+  public void onStop(){
+    super.onStop();
+    final String title = "App Indexing API Title";
+    Action action = Action.newAction(Action.TYPE_VIEW, title, WEB_URL, APP_URI);
+    AppIndex.AppIndexApi.end(mClient, action);
+    mClient.disconnect();
+  }
+}
+
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestStartMatch.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestStartMatch.java.txt
new file mode 100644
index 0000000..c7d5133
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestStartMatch.java.txt
@@ -0,0 +1,36 @@
+package com.example.helloworld;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.google.android.gms.appindexing.Action;
+import com.google.android.gms.appindexing.AppIndex;
+import com.google.android.gms.common.api.GoogleApiClient;
+
+public class AppIndexingApiTest extends Activity {
+  static final Uri APP_URI = Uri.parse("android-app://com.example.helloworld/http/example.com/gizmos");
+  static final Uri WEB_URL = Uri.parse("http://example.com/gizmos");
+  private GoogleApiClient mClient;
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    mClient = new GoogleApiClient.Builder(this).addApi(AppIndex.APP_INDEX_API).build();
+  }
+
+  @Override
+  public void onStart(){
+    super.onStart();
+    final String title = "App Indexing API Title";
+    Action action = Action.newAction(Action.TYPE_VIEW, title, WEB_URL, APP_URI);
+    AppIndex.AppIndexApi.start(mClient, action);
+  }
+
+  @Override
+  public void onStop(){
+    super.onStop();
+    mClient.disconnect();
+  }
+}
+
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestViewEndMatch.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestViewEndMatch.java.txt
new file mode 100644
index 0000000..9fd7994
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestViewEndMatch.java.txt
@@ -0,0 +1,31 @@
+package com.example.helloworld;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.google.android.gms.appindexing.Action;
+import com.google.android.gms.appindexing.AppIndex;
+import com.google.android.gms.common.api.GoogleApiClient;
+
+public class AppIndexingApiTest extends Activity {
+  static final Uri APP_URI = Uri.parse("android-app://com.example.helloworld/http/example.com/gizmos");
+  private GoogleApiClient mClient;
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    mClient = new GoogleApiClient.Builder(this).addApi(AppIndex.APP_INDEX_API).build();
+  }
+
+  @Override
+  public void onStart(){
+    super.onStart();
+  }
+
+  @Override
+  public void onStop(){
+    super.onStop();
+    AppIndex.AppIndexApi.viewEnd(mClient, this, APP_URI);
+  }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestViewMatch.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestViewMatch.java.txt
new file mode 100644
index 0000000..020a9fa
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestViewMatch.java.txt
@@ -0,0 +1,33 @@
+package com.example.helloworld;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.google.android.gms.appindexing.Action;
+import com.google.android.gms.appindexing.AppIndex;
+import com.google.android.gms.common.api.GoogleApiClient;
+
+public class AppIndexingApiTest extends Activity {
+  static final Uri APP_URI = Uri.parse("android-app://com.example.helloworld/http/example.com/gizmos");
+  static final Uri WEB_URL = Uri.parse("http://example.com/gizmos");
+  private GoogleApiClient mClient;
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    mClient = new GoogleApiClient.Builder(this).addApi(AppIndex.APP_INDEX_API).build();
+  }
+
+  @Override
+  public void onStart(){
+    super.onStart();
+    final String title = "App Indexing API Title";
+    AppIndex.AppIndexApi.view(mClient, this, APP_URI, title, WEB_URL, null);
+  }
+
+  @Override
+  public void onStop(){
+    super.onStop();
+  }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestWrongOrder.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestWrongOrder.java.txt
new file mode 100644
index 0000000..924c99a
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestWrongOrder.java.txt
@@ -0,0 +1,40 @@
+package com.example.helloworld;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.google.android.gms.appindexing.Action;
+import com.google.android.gms.appindexing.AppIndex;
+import com.google.android.gms.common.api.GoogleApiClient;
+
+public class AppIndexingApiTest extends Activity {
+  static final Uri APP_URI = Uri.parse("android-app://com.example.helloworld/http/example.com/gizmos");
+  static final Uri WEB_URL = Uri.parse("http://example.com/gizmos");
+  private GoogleApiClient mClient;
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    mClient = new GoogleApiClient.Builder(this).addApi(AppIndex.APP_INDEX_API).build();
+  }
+
+  @Override
+  public void onStart(){
+    super.onStart();
+    final String title = "App Indexing API Title";
+    Action action = Action.newAction(Action.TYPE_VIEW, title, WEB_URL, APP_URI);
+    AppIndex.AppIndexApi.end(mClient, action);
+    mClient.disconnect();
+  }
+
+  @Override
+  public void onStop(){
+    super.onStop();
+    mClient.connect();
+    final String title = "App Indexing API Title";
+    Action action = Action.newAction(Action.TYPE_VIEW, title, WEB_URL, APP_URI);
+    AppIndex.AppIndexApi.start(mClient, action);
+  }
+}
+
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ActionTest1_ignore.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ActionTest1_ignore.java.txt
index 5fea6be..90717cc 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ActionTest1_ignore.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ActionTest1_ignore.java.txt
@@ -3,7 +3,7 @@
 import android.view.MenuItem;
 
 public class ActionTest1 {
-    @SuppressLint("AlwaysShowAction")
+    @android.annotation.SuppressLint("AlwaysShowAction")
     public void foo() {
         System.out.println(MenuItem.SHOW_AS_ACTION_ALWAYS);
     }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/AlarmTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/AlarmTest.java.txt
index 770d00f..523d93d 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/AlarmTest.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/AlarmTest.java.txt
@@ -2,7 +2,7 @@
 
 import android.app.AlarmManager;
 
-public class TestAlarm {
+public class AlarmTest {
     public void test(AlarmManager alarmManager) {
         alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 5000, 60000, null); // OK
         alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 6000, 70000, null); // OK
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Assert.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Assert.java.txt
index 63a9ffd..6b76230 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Assert.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Assert.java.txt
@@ -1,6 +1,6 @@
 package test.pkg;
 
-import android.annotations.tools.SuppressLint;
+import android.annotation.SuppressLint;
 
 public class Assert {
     public Assert(int param, Object param2, Object param3) {
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/DetachedFromWindow.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/DetachedFromWindow.java.txt
index 394ec43..15a06c0 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/DetachedFromWindow.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/DetachedFromWindow.java.txt
@@ -3,26 +3,26 @@
 import android.view.View;
 
 public class DetachedFromWindow {
-    private static class Test1 extends View {
+    private static class Test1 extends ViewWithDefaultConstructor {
         protected void onDetachedFromWindow() {
             // Error
         }
     }
 
-    private static class Test2 extends View {
+    private static class Test2 extends ViewWithDefaultConstructor {
         protected void onDetachedFromWindow(int foo) {
             // OK: not overriding the right method
         }
     }
 
-    private static class Test3 extends View {
+    private static class Test3 extends ViewWithDefaultConstructor {
         protected void onDetachedFromWindow() {
             // OK: Calling super
             super.onDetachedFromWindow();
         }
     }
 
-    private static class Test4 extends View {
+    private static class Test4 extends ViewWithDefaultConstructor {
         protected void onDetachedFromWindow() {
             // Error: missing detach call
             int x = 1;
@@ -37,4 +37,10 @@
             // Regression test for http://b.android.com/73571
         }
     }
+
+    public class ViewWithDefaultConstructor extends View {
+        public ViewWithDefaultConstructor() {
+            super(null);
+        }
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LongSparseArray.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LongSparseArray.java.txt
index 4d8f993..4d93d7c 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LongSparseArray.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LongSparseArray.java.txt
@@ -5,7 +5,7 @@
 
 import android.content.Context;
 
-public class LongSparseArray extends Button {
+public class LongSparseArray {
     public void test() { // but only minSdkVersion >= 17
         Map<Long, String> myStringMap = new HashMap<Long, String>();
     }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/PreferenceActivitySubclassOverridesIsValidFragment.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/PreferenceActivitySubclassOverridesIsValidFragment.java.txt
index 9a3a438..f4aa929 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/PreferenceActivitySubclassOverridesIsValidFragment.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/PreferenceActivitySubclassOverridesIsValidFragment.java.txt
@@ -3,7 +3,7 @@
 import android.preference.PreferenceActivity;
 
 public class PreferenceActivitySubclass extends PreferenceActivity {
-    @Override
+
     protected boolean isValidFragment(String fragmentName) {
         return false;
     }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SQLiteTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SQLiteTest.java.txt
index b10bc01..8ac42a5 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SQLiteTest.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SQLiteTest.java.txt
@@ -3,7 +3,7 @@
 import android.database.sqlite.SQLiteDatabase;
 
 @SuppressWarnings({"unused", "SpellCheckingInspection"})
-public class SqliteTest {
+public class SQLiteTest {
     public interface Tables {
         interface AppKeys {
             String NAME = "appkeys";
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SetJavaScriptEnabled.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SetJavaScriptEnabled.java.txt
index ad8383a..da79803 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SetJavaScriptEnabled.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SetJavaScriptEnabled.java.txt
@@ -4,7 +4,7 @@
 import android.os.Bundle;
 import android.webkit.WebView;
 
-public class HelloWebApp extends Activity {
+public class SetJavaScriptEnabled extends Activity {
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -18,11 +18,20 @@
 
     // Test Suppress
     // Constructor: See issue 35588
-    @SuppressLint("SetJavaScriptEnabled")
-    public HelloWebApp() {
+    @android.annotation.SuppressLint("SetJavaScriptEnabled")
+    public void HelloWebApp() {
         WebView webView = (WebView)findViewById(R.id.webView);
         webView.getSettings().setJavaScriptEnabled(true); // bad
         webView.getSettings().setJavaScriptEnabled(false); // good
         webView.loadUrl("file:///android_asset/www/index.html");
     }
+
+    public static final class R {
+        public static final class layout {
+            public static final int main = 0x7f0a0000;
+        }
+        public static final class id {
+            public static final int webView = 0x7f0a0001;
+        }
+    }
 }
\ No newline at end of file
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SparseLongArray.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SparseLongArray.java.txt
index 862a59f..fa907b8 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SparseLongArray.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SparseLongArray.java.txt
@@ -5,7 +5,7 @@
 
 import android.content.Context;
 
-public class SparseLongArray extends Button {
+public class SparseLongArray {
     public void test() { // but only minSdkVersion >= 18
         Map<Integer, Long> myStringMap = new HashMap<Integer, Long>();
     }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat2.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat2.java.txt
index a09e44b..e959d5c 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat2.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat2.java.txt
@@ -1,7 +1,7 @@
 package test.pkg;
 
 import android.app.Activity;
-import android.os.Bundle;
+import android.content.Context;
 
 public class StringFormat2 extends Activity {
     public static final String buildUserAgent(Context context) {
@@ -11,4 +11,10 @@
         String ua = String.format(base, arg);
         return ua;
     }
-}
+
+    public static final class R {
+        public static final class string {
+            public static final int web_user_agent = 0x7f0a000e;
+        }
+    }
+}
\ No newline at end of file
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat3.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat3.java.txt
index 96c8d03..ae747e4 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat3.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat3.java.txt
@@ -25,4 +25,10 @@
     private static class Article {
         String playsCount;
     }
+
+    private static class R {
+        private static class string {
+            public static final int gridview_views_count = 1;
+        }
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat4.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat4.java.txt
index 88c3a54..2c55a52 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat4.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat4.java.txt
@@ -5,10 +5,22 @@
 
 public class StringFormat4 extends Activity {
     public final void test(Context context) {
-        // lint error:
-        getString(R.string.error_and_source, getString(R.string.data_source));
-        // no lint error:
-        getString(R.string.error_and_source, "data source");
-        String.format(getString(R.string.preferences_about_app_title), getString(R.string.app_name), "");
+        // data_source takes 0 formatting arguments
+        // error_and_source takes two formatting arguments
+        // preferences_about_app_title takes two formatting arguments
+        getString(R.string.error_and_source, getString(R.string.data_source)); // ERROR
+        getString(R.string.error_and_source, getString(R.string.data_source), 5); // OK
+        getString(R.string.error_and_source, "data source"); // ERROR
+        getString(R.string.error_and_source, "data source", 5); // OK
+        String.format(getString(R.string.preferences_about_app_title), getString(R.string.app_name), ""); // OK
     }
-}
+
+    private static class R {
+        private static class string {
+            public static final int error_and_source = 1;
+            public static final int data_source = 2;
+            public static final int preferences_about_app_title = 3;
+            public static final int app_name = 4;
+        }
+    }
+}
\ No newline at end of file
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat5.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat5.java.txt
index 8a75e7a..aa19258 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat5.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat5.java.txt
@@ -1,7 +1,7 @@
 package test.pkg;
-
 import android.app.Activity;
 import android.content.Context;
+import android.content.res.Resources;
 
 public class StringFormat5 extends Activity {
     public final void test(Context context) {
@@ -15,4 +15,11 @@
             return R.string.app_name;
         }
     }
-}
+
+    private static class R {
+        private static class string {
+            public static final int VibrationLevelIs = 1;
+            public static final int app_name = 2;
+        }
+    }
+}
\ No newline at end of file
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat8.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat8.java.txt
index f2d0123..782aa9b 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat8.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat8.java.txt
@@ -1,10 +1,10 @@
 package test.pkg;
-
 import android.app.Activity;
 import android.content.Context;
+import android.content.res.Resources;
 
 public class StringFormat8 extends Activity {
-    public final void test(Context context, float amount) {
+    public final void test(Context context, float amount, Resources resource) {
         Resources resources = getResources();
         String amount1 = resources.getString(R.string.amount_string, amount);
         String amount2 = getResources().getString(R.string.amount_string, amount);
@@ -13,4 +13,11 @@
         String amount5 = getResources().getString(R.string.amount_string, amount, amount); // ERROR
         String misc = String.format(resource.getString(R.string.percent_newline));
     }
-}
+
+    private static class R {
+        private static class string {
+            public static final int amount_string = 1;
+            public static final int percent_newline = 2;
+        }
+    }
+}
\ No newline at end of file
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat9.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat9.java.txt
index 49bad01..c9e5e2f 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat9.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat9.java.txt
@@ -6,4 +6,10 @@
     public String format(Resources resources, int percentUsed) {
         return resources.getString(R.string.toast_percent_copy_quota_used, percentUsed);
     }
-}
+
+    private static class R {
+        private static class string {
+            public static final int toast_percent_copy_quota_used = 1;
+        }
+    }
+}
\ No newline at end of file
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity.java.txt
index c22828f..1f387f2 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity.java.txt
@@ -19,10 +19,10 @@
         boolean won = true;
         String output3 = String.format(score, points);
         String output4 = String.format(score, true);  // wrong
-        String output4 = String.format(score, won);   // wrong
+        String output  = String.format(score, won);   // wrong
         String output5 = String.format(score, 75);
         String.format(getResources().getString(R.string.hello2), target, "How are you");
-        getResources().getString(hello2, target, "How are you");
+        //getResources().getString(R.string.hello, target, "How are you");
         getResources().getString(R.string.hello2, target, "How are you");
     }
 
@@ -32,4 +32,15 @@
         String hello = getResources().getString(R.string.hello);
         String output1 = String.format(hello, target);
     }
+
+    private static class R {
+        private static class string {
+            public static final int hello = 1;
+            public static final int hello2 = 2;
+            public static final int score = 3;
+        }
+        private static class layout {
+            public static final int main = 4;
+        }
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity2.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity2.java.txt
index c039dce..7c1300a 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity2.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity2.java.txt
@@ -17,4 +17,16 @@
         getResources().getString(R.string.formattest6, "hello");
         getResources().getString(R.string.formattest7, "hello");
     }
+
+    private static class R {
+        private static class string {
+            public static final int formattest1 = 1;
+            public static final int formattest2 = 2;
+            public static final int formattest3 = 3;
+            public static final int formattest4 = 4;
+            public static final int formattest5 = 5;
+            public static final int formattest6 = 6;
+            public static final int formattest7 = 7;
+        }
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity3.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity3.java.txt
index b0794c9..9ad2bed 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity3.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity3.java.txt
@@ -12,4 +12,11 @@
         String.format(getResources().getString(R.string.format_90));
         String.format(getResources().getString(R.string.format_80));
     }
+
+    public static final class R {
+        public static final class string {
+            public static final int format_90 = 0x7f0a000e;
+            public static final int format_80 = 0x7f0a000f;
+        }
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity_ignore.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity_ignore.java.txt
index 58717dc..eeb366a 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity_ignore.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity_ignore.java.txt
@@ -2,7 +2,7 @@
 
 import android.app.Activity;
 import android.os.Bundle;
-import android.annotations.tools.SuppressLint;
+import android.annotation.SuppressLint;
 
 public class StringFormatActivity extends Activity {
     /** Called when the activity is first created. */
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SuppressTest5.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SuppressTest5.java.txt
index d79e421..16a8d6c 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SuppressTest5.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SuppressTest5.java.txt
@@ -1,6 +1,6 @@
 package test.pkg;
 
-import android.annotations.tools.SuppressLint;
+import android.annotation.SuppressLint;
 
 @SuppressWarnings("unused")
 public class SuppressTest5 {
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SystemServiceTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SystemServiceTest.java.txt
index 503b4ba..a484cb3 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SystemServiceTest.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SystemServiceTest.java.txt
@@ -23,8 +23,9 @@
                 .getSystemService(DEVICE_POLICY_SERVICE);
     }
 
-    public void clipboard() {
+    public void clipboard(Context context) {
       ClipboardManager clipboard = (ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE);
-      android.content.ClipboardManager clipboard =  (android.content.ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE);
+      android.content.ClipboardManager clipboard1 =  (android.content.ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE);
+      android.text.ClipboardManager clipboard2 =  (android.text.ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE);
     }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ToastTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ToastTest.java.txt
index 44ccaaa..847eb87 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ToastTest.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ToastTest.java.txt
@@ -5,7 +5,7 @@
 import android.os.Bundle;
 import android.widget.Toast;
 
-public class ToastTest {
+public abstract class ToastTest extends Context {
     private Toast createToast(Context context) {
         // Don't warn here
         return Toast.makeText(context, "foo", Toast.LENGTH_LONG);
@@ -47,5 +47,11 @@
         @android.annotation.SuppressLint("ShowToast")
         Toast toast = Toast.makeText(this, "MyToast", Toast.LENGTH_LONG);
     }
+
+    public static final class R {
+        public static final class string {
+            public static final int app_name = 0x7f0a0000;
+        }
+    }
 }
 
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/UnusedReferenceDynamic.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/UnusedReferenceDynamic.java.txt
new file mode 100644
index 0000000..46c2bc0
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/UnusedReferenceDynamic.java.txt
@@ -0,0 +1,14 @@
+package test.pkg;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.design.widget.Snackbar;
+
+public class UnusedReferenceDynamic extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(test.pkg.R.layout.main);
+        Snackbar.make(view, R.string.xyz, Snackbar.LENGTH_LONG);
+    }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ViewHolderTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ViewHolderTest.java.txt
index f5514bf..329ba3f 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ViewHolderTest.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ViewHolderTest.java.txt
@@ -152,4 +152,17 @@
             return rootView;
         }
     }
+
+    public static final class R {
+        public static final class layout {
+            public static final int your_layout = 0x7f0a0000;
+            public static final int laptime_item = 0x7f0a0001;
+        }
+        public static final class id {
+            public static final int text = 0x7f0a0000;
+            public static final int laptimes_list_item_holder = 0x7f0a0001;
+            public static final int laptime_text = 0x7f0a0002;
+            public static final int laptime_text2 = 0x7f0a0003;
+        }
+    }
 }
\ No newline at end of file
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WorldWriteableFile.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WorldWriteableFile.java.txt
index 1e6285b..5d0ba07 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WorldWriteableFile.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WorldWriteableFile.java.txt
@@ -17,6 +17,7 @@
     public void foo() {
         OutputStream out = null;
         SharedPreferences prefs = null;
+        File dir = null;
 
         boolean success = false;
         try {
@@ -29,6 +30,25 @@
             prefs = getSharedPreferences(mContext, MODE_PRIVATE); // ok
             prefs = getSharedPreferences(mContext, MODE_WORLD_WRITEABLE);
             prefs = getSharedPreferences(mContext, MODE_WORLD_READABLE);
+
+            dir = getDir(mFile.getName(), MODE_PRIVATE); // ok
+            dir = getDir(mFile.getName(), MODE_WORLD_WRITEABLE);
+            dir = getDir(mFile.getName(), MODE_WORLD_READABLE);
+
+            mFile.setReadable(true, true); // ok
+            mFile.setReadable(false, true); // ok
+            mFile.setReadable(false, false); // ok
+            mFile.setReadable(true, false);
+            mFile.setReadable(true); // ok
+            mFile.setReadable(false); // ok
+
+            mFile.setWritable(true, true); // ok
+            mFile.setWritable(false, true); // ok
+            mFile.setWritable(false, false); // ok
+            mFile.setWritable(true, false);
+            mFile.setWritable(true); // ok
+            mFile.setWritable(false); // ok
+
             // Flickr.get().downloadPhoto(params[0], Flickr.PhotoSize.LARGE,
             // out);
             success = true;
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongAnnotation.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongAnnotation.java.txt
index 45743ce..5ad48c7 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongAnnotation.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongAnnotation.java.txt
@@ -4,7 +4,7 @@
 import android.view.View;
 
 public class WrongAnnotation {
-    @Override
+
     @SuppressLint("NewApi") // Valid: class-file check on method
     public static void foobar(View view, @SuppressLint("NewApi") int foo) { // Invalid: class-file check
         @SuppressLint("NewApi") // Invalid
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity.java.txt
index 45e53d0..6d01767 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity.java.txt
@@ -13,4 +13,14 @@
         ToggleButton toggleButton = (ToggleButton) findViewById(R.id.button);
         TextView textView = (TextView) findViewById(R.id.edittext);
     }
+
+    public static final class R {
+        public static final class layout {
+            public static final int casts = 0x7f0a0002;
+        }
+        public static final class id {
+            public static final int button = 0x7f0a0000;
+            public static final int edittext = 0x7f0a0001;
+        }
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity2.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity2.java.txt
index 7cd422a..14313b6 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity2.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity2.java.txt
@@ -12,4 +12,11 @@
                 .findViewById(R.id.additional);
         Object x = (AdapterView<?>) bodyView.findViewById(R.id.reminder_lead);
     }
+
+    public static final class R {
+        public static final class id {
+            public static final int additional = 0x7f0a0000;
+            public static final int reminder_lead = 0x7f0a0001;
+        }
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity3.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity3.java.txt
index 1701600..7e01c35 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity3.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity3.java.txt
@@ -8,4 +8,10 @@
     private void test() {
         final Checkable check = (Checkable) findViewById(R.id.additional);
     }
+
+    public static final class R {
+        public static final class id {
+            public static final int additional = 0x7f0a0000;
+        }
+    }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/tls.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/tls.xml
new file mode 100644
index 0000000..9b81403
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/tls.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="test.pkg"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk android:minSdkVersion="14" />
+
+    <application
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name" >
+        <service
+            android:name=".InsecureTLSIntentService" >
+        </service>
+    </application>
+
+</manifest>
+
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/tls1.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/tls1.xml
new file mode 100644
index 0000000..75c8159
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/tls1.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="test.pkg"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk android:minSdkVersion="14" />
+
+    <application
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name" >
+        <service
+            android:name=".ExampleTLSIntentService" >
+        </service>
+    </application>
+
+</manifest>
+
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/unsafereceiver1.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/unsafereceiver1.xml
new file mode 100644
index 0000000..d29bd23
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/unsafereceiver1.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="test.pkg"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk android:minSdkVersion="14" />
+
+    <application
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name" >
+        <receiver
+            android:label="@string/app_name"
+            android:name=".TestReceiver" >
+                <intent-filter>
+                    <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
+                </intent-filter>
+        </receiver>
+    </application>
+
+</manifest>
+
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/JarFileIssueRegistryTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/JarFileIssueRegistryTest.java
index 4a73420..1e82f07 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/JarFileIssueRegistryTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/JarFileIssueRegistryTest.java
@@ -20,10 +20,12 @@
 import com.android.tools.lint.checks.AbstractCheckTest;
 import com.android.tools.lint.detector.api.Detector;
 import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
 import com.google.common.io.Files;
 
 import java.io.File;
 import java.io.StringWriter;
+import java.util.List;
 
 @SuppressWarnings("SpellCheckingInspection")
 public class JarFileIssueRegistryTest extends AbstractCheckTest {
@@ -61,6 +63,16 @@
         assertEquals(1, registry1.getIssues().size());
         assertEquals("AppCompatMethod", registry1.getIssues().get(0).getId());
 
+        // Access detector state. On Java 7/8 this will access the detector class after
+        // the jar loader has been closed; this tests that we still have valid classes.
+        Detector detector =
+                registry1.getIssues().get(0).getImplementation().getDetectorClass().newInstance();
+        detector.getApplicableAsmNodeTypes();
+        assertSame(Speed.NORMAL, detector.getSpeed());
+        List<String> applicableCallNames = detector.getApplicableCallNames();
+        assertNotNull(applicableCallNames);
+        assertTrue(applicableCallNames.contains("getActionBar"));
+
         assertEquals(
                 "Custom lint rule jar " + file2.getPath() + " does not contain a valid "
                         + "registry manifest key (Lint-Registry).\n"
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/CategoryTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/CategoryTest.java
new file mode 100644
index 0000000..435a346
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/CategoryTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015 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.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+
+import junit.framework.TestCase;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.List;
+
+public class CategoryTest extends TestCase {
+    public void testCompare() throws Exception {
+        List<Category> categories = Lists.newArrayList();
+        for (Field field : Category.class.getDeclaredFields()) {
+            if (field.getType() == Category.class &&
+                    (field.getModifiers() & Modifier.STATIC) != 0) {
+                field.setAccessible(true);
+                Object o = field.get(null);
+                if (o instanceof Category) {
+                    categories.add((Category) o);
+                }
+            }
+        }
+
+        Collections.sort(categories);
+
+        assertEquals(""
+                + "Lint\n"
+                + "Correctness\n"
+                + "Correctness:Messages\n"
+                + "Security\n"
+                + "Performance\n"
+                + "Usability:Typography\n"
+                + "Usability:Icons\n"
+                + "Usability\n"
+                + "Accessibility\n"
+                + "Internationalization\n"
+                + "Internationalization:Bidirectional Text",
+                Joiner.on("\n").join(categories));
+    }
+
+    public void testGetName() {
+        assertEquals("Messages", Category.MESSAGES.getName());
+    }
+
+    public void testGetFullName() {
+        assertEquals("Correctness:Messages", Category.MESSAGES.getFullName());
+    }
+
+    public void testEquals() {
+        assertEquals(Category.MESSAGES, Category.MESSAGES);
+        assertEquals(Category.create("Correctness", 100), Category.create("Correctness", 100));
+        assertFalse(Category.MESSAGES.equals(Category.CORRECTNESS));
+        assertFalse(Category.create("Correctness", 100).equals(Category.create("Correct", 100)));
+    }
+}
\ No newline at end of file
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/ConstantEvaluatorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/ConstantEvaluatorTest.java
index c13ef11..4ef36f8 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/ConstantEvaluatorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/ConstantEvaluatorTest.java
@@ -18,6 +18,8 @@
 
 import junit.framework.TestCase;
 
+import org.intellij.lang.annotations.Language;
+
 import java.io.File;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -26,8 +28,10 @@
 import lombok.ast.ForwardingAstVisitor;
 import lombok.ast.VariableDefinitionEntry;
 
+@SuppressWarnings("ClassNameDiffersFromFileName")
 public class ConstantEvaluatorTest extends TestCase {
-    private static void check(Object expected, String source, final String targetVariable) {
+    private static void check(Object expected, @Language("JAVA") String source,
+            final String targetVariable) {
         JavaContext context = LintUtilsTest.parse(source, new File("src/test/pkg/Test.java"));
         assertNotNull(context);
         CompilationUnit unit = (CompilationUnit) context.getCompilationUnit();
@@ -63,6 +67,7 @@
 
     private static void checkStatements(Object expected, String statementsSource,
             final String targetVariable) {
+        @Language("JAVA")
         String source = ""
                 + "package test.pkg;\n"
                 + "public class Test {\n"
@@ -78,6 +83,7 @@
     }
 
     private static void checkExpression(Object expected, String expressionSource) {
+        @Language("JAVA")
         String source = ""
                 + "package test.pkg;\n"
                 + "public class Test {\n"
@@ -106,6 +112,11 @@
         checkExpression(true, "!false");
     }
 
+    public void testChars() throws Exception {
+        checkExpression('a', "'a'");
+        checkExpression('\007', "'\007'");
+    }
+
     public void testCasts() throws Exception {
         checkExpression(1, "(int)1");
         checkExpression(1L, "(long)1");
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/LintUtilsTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/LintUtilsTest.java
index 6956445..34953f8 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/LintUtilsTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/LintUtilsTest.java
@@ -18,12 +18,12 @@
 
 import static com.android.tools.lint.detector.api.LintUtils.computeResourceName;
 import static com.android.tools.lint.detector.api.LintUtils.convertVersion;
-import static com.android.tools.lint.detector.api.LintUtils.escapePropertyValue;
 import static com.android.tools.lint.detector.api.LintUtils.findSubstring;
 import static com.android.tools.lint.detector.api.LintUtils.getFormattedParameters;
 import static com.android.tools.lint.detector.api.LintUtils.getLocaleAndRegion;
 import static com.android.tools.lint.detector.api.LintUtils.isImported;
 import static com.android.tools.lint.detector.api.LintUtils.splitPath;
+import static com.android.utils.SdkUtils.escapePropertyValue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -31,9 +31,9 @@
 import com.android.annotations.Nullable;
 import com.android.builder.model.AndroidProject;
 import com.android.builder.model.ApiVersion;
+import com.android.ide.common.repository.GradleVersion;
 import com.android.sdklib.AndroidVersion;
 import com.android.sdklib.IAndroidTarget;
-import com.android.tools.lint.EcjParser;
 import com.android.tools.lint.LintCliClient;
 import com.android.tools.lint.checks.BuiltinIssueRegistry;
 import com.android.tools.lint.client.api.JavaParser;
@@ -403,7 +403,7 @@
                 new LintCliClient());
         driver.setScope(Scope.JAVA_FILE_SCOPE);
         TestContext context = new TestContext(driver, client, project, javaSource, fullPath);
-        JavaParser parser = new EcjParser(client, project);
+        JavaParser parser = context.getParser();
         parser.prepareJavaParse(Collections.<JavaContext>singletonList(context));
         Node compilationUnit = parser.parseJava(context);
         assertNotNull(javaSource, compilationUnit);
@@ -423,29 +423,29 @@
     }
 
     public void testIsModelOlderThan() throws Exception {
-        AndroidProject project = mock(AndroidProject.class);
-        when(project.getModelVersion()).thenReturn("0.10.4");
+        Project project = mock(Project.class);
+        when(project.getGradleModelVersion()).thenReturn(GradleVersion.parse("0.10.4"));
 
         assertTrue(LintUtils.isModelOlderThan(project, 0, 10, 5));
         assertTrue(LintUtils.isModelOlderThan(project, 0, 11, 0));
         assertTrue(LintUtils.isModelOlderThan(project, 0, 11, 4));
         assertTrue(LintUtils.isModelOlderThan(project, 1, 0, 0));
 
-        project = mock(AndroidProject.class);
-        when(project.getModelVersion()).thenReturn("0.11.0");
+        project = mock(Project.class);
+        when(project.getGradleModelVersion()).thenReturn(GradleVersion.parse("0.11.0"));
 
         assertTrue(LintUtils.isModelOlderThan(project, 1, 0, 0));
         assertFalse(LintUtils.isModelOlderThan(project, 0, 11, 0));
         assertFalse(LintUtils.isModelOlderThan(project, 0, 10, 4));
 
-        project = mock(AndroidProject.class);
-        when(project.getModelVersion()).thenReturn("0.11.5");
+        project = mock(Project.class);
+        when(project.getGradleModelVersion()).thenReturn(GradleVersion.parse("0.11.5"));
 
         assertTrue(LintUtils.isModelOlderThan(project, 1, 0, 0));
         assertFalse(LintUtils.isModelOlderThan(project, 0, 11, 0));
 
-        project = mock(AndroidProject.class);
-        when(project.getModelVersion()).thenReturn("1.0.0");
+        project = mock(Project.class);
+        when(project.getGradleModelVersion()).thenReturn(GradleVersion.parse("1.0.0"));
 
         assertTrue(LintUtils.isModelOlderThan(project, 1, 0, 1));
         assertFalse(LintUtils.isModelOlderThan(project, 1, 0, 0));
@@ -509,7 +509,7 @@
                 String javaSource, File file) {
             //noinspection ConstantConditions
             super(driver, project,
-                    null, file, client.getJavaParser(null));
+                    null, file, client.getJavaParser(project));
 
             mJavaSource = javaSource;
         }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/TextFormatTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/TextFormatTest.java
index 1984c0a..577e05d 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/TextFormatTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/TextFormatTest.java
@@ -18,6 +18,7 @@
 
 import static com.android.SdkConstants.AUTO_URI;
 import static com.android.tools.lint.detector.api.TextFormat.HTML;
+import static com.android.tools.lint.detector.api.TextFormat.HTML_WITH_UNICODE;
 import static com.android.tools.lint.detector.api.TextFormat.RAW;
 import static com.android.tools.lint.detector.api.TextFormat.TEXT;
 
@@ -36,6 +37,14 @@
         assertEquals("foo<br/>\nbar", convertMarkup("foo\nbar", HTML));
         assertEquals("foo<br/>\nbar", convertMarkup("foo\nbar", HTML));
         assertEquals("&lt;&amp;>'\"", convertMarkup("<&>'\"", HTML));
+        assertEquals("&lt;&amp;>'\"", convertMarkup("<&>'\"", HTML_WITH_UNICODE));
+        assertEquals("a\u1234\u2345bc", HTML.convertTo("a&#4660;&#9029;bc", HTML_WITH_UNICODE));
+
+        // Unicode
+        assertEquals("abc&#4660;&#9029;def&lt;",
+                convertMarkup("abc\u1234\u2345def<", HTML));
+        assertEquals("abc\u1234\u2345def&lt;",
+                convertMarkup("abc\u1234\u2345def<", HTML_WITH_UNICODE));
 
         // HTML Formatting
         assertEquals("<code>@TargetApi(11)</code>, ", convertMarkup("`@TargetApi(11)`, ",
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/TypeEvaluatorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/TypeEvaluatorTest.java
new file mode 100644
index 0000000..0bdcb88
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/TypeEvaluatorTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2015 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.client.api.JavaParser.TypeDescriptor;
+
+import junit.framework.TestCase;
+
+import org.intellij.lang.annotations.Language;
+
+import java.io.File;
+import java.util.concurrent.atomic.AtomicReference;
+
+import lombok.ast.CompilationUnit;
+import lombok.ast.Expression;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.VariableDefinitionEntry;
+
+@SuppressWarnings("ClassNameDiffersFromFileName")
+public class TypeEvaluatorTest extends TestCase {
+    private static void check(Object expected, @Language("JAVA") String source,
+            final String targetVariable) {
+        JavaContext context = LintUtilsTest.parse(source, new File("src/test/pkg/Test.java"));
+        assertNotNull(context);
+        CompilationUnit unit = (CompilationUnit) context.getCompilationUnit();
+        assertNotNull(unit);
+
+        // Find the expression
+        final AtomicReference<Expression> reference = new AtomicReference<Expression>();
+        unit.accept(new ForwardingAstVisitor() {
+            @Override
+            public boolean visitVariableDefinitionEntry(VariableDefinitionEntry node) {
+                if (node.astName().astValue().equals(targetVariable)) {
+                    reference.set(node.astInitializer());
+                }
+                return super.visitVariableDefinitionEntry(node);
+            }
+        });
+        Expression expression = reference.get();
+        TypeDescriptor actual = TypeEvaluator.evaluate(context, expression);
+        if (expected == null) {
+            assertNull(actual);
+        } else {
+            assertNotNull("Couldn't compute type for " + source + ", expected " + expected,
+                    actual);
+            String expectedString = expected.toString();
+            if (expectedString.startsWith("class ")) {
+                expectedString = expectedString.substring("class ".length());
+            }
+            assertEquals(expectedString, actual.getName());
+        }
+    }
+
+    private static void checkStatements(Object expected, String statementsSource,
+            final String targetVariable) {
+        @Language("JAVA")
+        String source = ""
+                + "package test.pkg;\n"
+                + "public class Test {\n"
+                + "    public void test() {\n"
+                + "        " + statementsSource + "\n"
+                + "    }\n"
+                + "    public static final int MY_INT_FIELD = 5;\n"
+                + "    public static final boolean MY_BOOLEAN_FIELD = true;\n"
+                + "    public static final String MY_STRING_FIELD = \"test\";\n"
+                + "    public static final String MY_OBJECT_FIELD = \"test\";\n"
+                + "}\n";
+
+        check(expected, source, targetVariable);
+    }
+
+    private static void checkExpression(Object expected, String expressionSource) {
+        @Language("JAVA")
+        String source = ""
+                + "package test.pkg;\n"
+                + "public class Test {\n"
+                + "    public void test() {\n"
+                + "        Object expression = " + expressionSource + ";\n"
+                + "    }\n"
+                + "    public static final int MY_INT_FIELD = 5;\n"
+                + "    public static final boolean MY_BOOLEAN_FIELD = true;\n"
+                + "    public static final String MY_STRING_FIELD = \"test\";\n"
+                + "    public static final String MY_OBJECT_FIELD = \"test\";\n"
+                + "}\n";
+
+        check(expected, source, "expression");
+    }
+
+    public void testNull() throws Exception {
+        checkExpression(null, "null");
+    }
+
+    public void testStrings() throws Exception {
+        checkExpression(String.class, "\"hello\"");
+        checkExpression(String.class, "\"ab\" + \"cd\"");
+    }
+
+    public void testBooleans() throws Exception {
+        checkExpression(Boolean.TYPE, "true");
+        checkExpression(Boolean.TYPE, "false");
+        checkExpression(Boolean.TYPE, "false && true");
+        checkExpression(Boolean.TYPE, "false || true");
+        checkExpression(Boolean.TYPE, "!false");
+    }
+
+    public void testCasts() throws Exception {
+        checkExpression(Integer.TYPE, "(int)1");
+        checkExpression(Long.TYPE, "(long)1");
+        checkExpression(Integer.TYPE, "(int)1.1f");
+        checkExpression(Short.TYPE, "(short)65537");
+        checkExpression(Byte.TYPE, "(byte)1023");
+        checkExpression(Double.TYPE, "(double)1.5f");
+        checkExpression(Double.TYPE, "(double)-5");
+    }
+
+    public void testArithmetic() throws Exception {
+        checkExpression(Integer.TYPE, "1");
+        checkExpression(Long.TYPE, "1L");
+        checkExpression(Integer.TYPE, "1 + 3");
+        checkExpression(Integer.TYPE, "1 - 3");
+        checkExpression(Integer.TYPE, "2 * 5");
+        checkExpression(Integer.TYPE, "10 / 5");
+        checkExpression(Integer.TYPE, "11 % 5");
+        checkExpression(Integer.TYPE, "1 << 3");
+        checkExpression(Integer.TYPE, "32 >> 1");
+        checkExpression(Integer.TYPE, "32 >>> 1");
+        checkExpression(Integer.TYPE, "5 | 1");
+        checkExpression(Integer.TYPE, "5 & 1");
+        checkExpression(Integer.TYPE, "~5");
+        checkExpression(Long.TYPE, "~(long)5");
+        checkExpression(Short.TYPE, "~(short)5");
+        checkExpression(Byte.TYPE, "~(byte)5");
+        checkExpression(Long.TYPE, "-(long)5");
+        checkExpression(Short.TYPE, "-(short)5");
+        checkExpression(Byte.TYPE, "-(byte)5");
+        checkExpression(Double.TYPE, "-(double)5");
+        checkExpression(Float.TYPE, "-(float)5");
+        checkExpression(Integer.TYPE, "1 + -3");
+
+        checkExpression(Boolean.TYPE, "11 == 5");
+        checkExpression(Boolean.TYPE, "11 == 11");
+        checkExpression(Boolean.TYPE, "11 != 5");
+        checkExpression(Boolean.TYPE, "11 != 11");
+        checkExpression(Boolean.TYPE, "11 > 5");
+        checkExpression(Boolean.TYPE, "5 > 11");
+        checkExpression(Boolean.TYPE, "11 < 5");
+        checkExpression(Boolean.TYPE, "5 < 11");
+        checkExpression(Boolean.TYPE, "11 >= 5");
+        checkExpression(Boolean.TYPE, "5 >= 11");
+        checkExpression(Boolean.TYPE, "11 <= 5");
+        checkExpression(Boolean.TYPE, "5 <= 11");
+
+        checkExpression(Float.TYPE, "1.0f + 2.5f");
+    }
+
+    public void testFieldReferences() throws Exception {
+        checkExpression(Integer.TYPE, "MY_INT_FIELD");
+        checkExpression(String.class, "MY_STRING_FIELD");
+        checkExpression(String.class, "\"prefix-\" + MY_STRING_FIELD + \"-postfix\"");
+        checkExpression(Integer.TYPE, "3 - (MY_INT_FIELD + 2)");
+    }
+
+    public void testStatements() throws Exception {
+        checkStatements(Integer.TYPE, ""
+                        + "int x = +5;\n"
+                        + "int y = x;\n"
+                        + "int w;\n"
+                        + "w = -1;\n"
+                        + "int z = x + 5 + w;\n",
+                "z");
+        checkStatements(String.class, ""
+                        + "String initial = \"hello\";\n"
+                        + "String other;\n"
+                        + "other = \" world\";\n"
+                        + "String finalString = initial + other;\n",
+                "finalString");
+    }
+
+    public void testConditionals() throws Exception {
+        checkStatements(Integer.TYPE, ""
+                        + "boolean condition = false;\n"
+                        + "condition = !condition;\n"
+                        + "int z = condition ? -5 : 4;\n",
+                "z");
+        checkStatements(Integer.TYPE, ""
+                        + "boolean condition = true && false;\n"
+                        + "int z = condition ? 5 : -4;\n",
+                "z");
+    }
+
+    public void testConstructorInvocation() throws Exception {
+        checkStatements(String.class, ""
+                        + "Object o = new String(\"test\");\n"
+                        + "Object bar = o;\n",
+                "bar");
+    }
+
+    public void testFieldInitializerType() throws Exception {
+        checkStatements(String.class, ""
+                        + "Object o = MY_OBJECT_FIELD;\n"
+                        + "Object bar = o;\n",
+                "bar");
+    }
+}
\ No newline at end of file
diff --git a/sdk-common/src/main/java/com/android/ide/common/repository/GradleVersion.java b/sdk-common/src/main/java/com/android/ide/common/repository/GradleVersion.java
new file mode 100644
index 0000000..8168ecf
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/repository/GradleVersion.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2016 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.ide.common.repository;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.base.Objects;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Supports versions in the given formats: <ul> <li>major (e.g. 1)</li> <li>major.minor (e.g.
+ * 1.0)</li> <li>major.minor.micro (e.g. 1.1.1)</li> </ul> A version can also be a "preview" (e.g.
+ * 1-alpha1, 1.0.0-rc2) or an unreleased version (or "snapshot") (e.g. 1-SNAPSHOT,
+ * 1.0.0-alpha1-SNAPSHOT).
+ */
+public class GradleVersion implements Comparable<GradleVersion>, Serializable {
+    private static final String PLUS = "+";
+
+    private static final Pattern PREVIEW_PATTERN = Pattern.compile("([a-zA-z]+)[\\-]?([\\d]+)?");
+
+    private final String mRawValue;
+
+    @NonNull
+    private final VersionSegment mMajorSegment;
+
+    @Nullable
+    private final VersionSegment mMinorSegment;
+
+    @Nullable
+    private final VersionSegment mMicroSegment;
+
+    private final int mPreview;
+
+    @Nullable
+    private final String mPreviewType;
+
+    private final boolean mSnapshot;
+
+    @NonNull
+    private final List<VersionSegment> mAdditionalSegments;
+
+    /**
+     * Parses the given version. This method does the same as {@link #parse(String)}, but it does
+     * not throw exceptions if the given value does not conform with any of the supported version
+     * formats.
+     *
+     * @param value the version to parse.
+     * @return the created {@code Version} object, or {@code null} if the given value does not
+     * conform with any of the supported version formats.
+     */
+    @Nullable
+    public static GradleVersion tryParse(@NonNull String value) {
+        try {
+            return parse(value);
+        } catch (RuntimeException ignored) {
+        }
+        return null;
+    }
+
+    /**
+     * Parses the given version.
+     *
+     * @param value the version to parse.
+     * @return the created {@code Version} object.
+     * @throws IllegalArgumentException if the given value does not conform with any of the
+     *                                  supported version formats.
+     */
+    @NonNull
+    public static GradleVersion parse(@NonNull String value) {
+        String version = value;
+        String qualifiers = null;
+        char dash = '-';
+        int dashIndex = value.indexOf(dash);
+        if (dashIndex != -1) {
+            if (dashIndex < value.length() - 1) {
+                qualifiers = value.substring(dashIndex + 1);
+            }
+            version = value.substring(0, dashIndex);
+        }
+
+        try {
+            List<VersionSegment> parsedVersionSegments = splitSegments(version);
+            int segmentCount = parsedVersionSegments.size();
+
+            VersionSegment majorSegment;
+            VersionSegment minorSegment = null;
+            VersionSegment microSegment = null;
+
+            List<VersionSegment> additionalSegments = Lists.newArrayList();
+            if (segmentCount > 0) {
+                majorSegment = parsedVersionSegments.get(0);
+                if (segmentCount > 1) {
+                    minorSegment = parsedVersionSegments.get(1);
+                }
+                if (segmentCount >= 3) {
+                    microSegment = parsedVersionSegments.get(2);
+                }
+                if (segmentCount > 3) {
+                    additionalSegments.addAll(parsedVersionSegments.subList(3, segmentCount));
+                }
+
+                int preview = 0;
+                String previewType = null;
+                boolean snapshot = false;
+
+                if (qualifiers != null) {
+                    if (isSnapshotQualifier(qualifiers)) {
+                        snapshot = true;
+                        qualifiers = null;
+                    }
+                    else {
+                        // find and remove "SNAPSHOT" at the end of the qualifiers.
+                        int lastDashIndex = qualifiers.lastIndexOf(dash);
+                        if (lastDashIndex != -1) {
+                            String mayBeSnapshot = qualifiers.substring(lastDashIndex + 1);
+                            if (isSnapshotQualifier(mayBeSnapshot)) {
+                                snapshot = true;
+                                qualifiers = qualifiers.substring(0, lastDashIndex);
+                            }
+                        }
+                    }
+                    if (!isNullOrEmpty(qualifiers)) {
+                        Matcher matcher = PREVIEW_PATTERN.matcher(qualifiers);
+                        if (matcher.matches()) {
+                            previewType = matcher.group(1);
+                            if (matcher.groupCount() == 2) {
+                                preview = Integer.parseInt(matcher.group(2));
+                            }
+                        } else {
+                            throw parsingFailure(value);
+                        }
+                    }
+                }
+                return new GradleVersion(value, majorSegment, minorSegment, microSegment,
+                        additionalSegments, preview, previewType, snapshot);
+            }
+        } catch (NumberFormatException e) {
+            throw parsingFailure(value, e);
+        }
+        throw parsingFailure(value);
+    }
+
+    @NonNull
+    private static List<VersionSegment> splitSegments(@NonNull String version) {
+        Iterable<String> segments = Splitter.on('.').split(version);
+        List<VersionSegment> parsedSegments = Lists.newArrayListWithCapacity(3);
+
+        for (String segment : segments) {
+            parsedSegments.addAll(parseSegment(segment));
+        }
+
+        return parsedSegments;
+    }
+
+    @NonNull
+    private static List<VersionSegment> parseSegment(@NonNull String text) {
+        int length = text.length();
+        if (length > 1 && text.endsWith(PLUS)) {
+            // Segment has a number and a '+' (e.g. second segment in '2.1+')
+            List<VersionSegment> segments = Lists.newArrayListWithCapacity(2);
+
+            // We need to split '1+' into 2 segments: '1' and '+'
+            segments.add(new VersionSegment(text.substring(0, length - 1)));
+            segments.add(new VersionSegment(PLUS));
+            return segments;
+        }
+        return Collections.singletonList(new VersionSegment(text));
+    }
+
+    private static boolean isSnapshotQualifier(@NonNull String value) {
+        return "SNAPSHOT".equalsIgnoreCase(value) || "dev".equalsIgnoreCase(value);
+    }
+
+    @NonNull
+    private static IllegalArgumentException parsingFailure(@NonNull String value) {
+        return parsingFailure(value, null);
+    }
+
+    @NonNull
+    private static IllegalArgumentException parsingFailure(@NonNull String value,
+            @Nullable Throwable cause) {
+        return new IllegalArgumentException(String.format("'%1$s' is not a valid version", value),
+                cause);
+    }
+
+    public GradleVersion(int major, int minor, int micro) {
+        this((major + "." + minor + "." + micro), new VersionSegment(major),
+                new VersionSegment(minor), new VersionSegment(micro),
+                Collections.<VersionSegment>emptyList(), 0, null, false);
+    }
+
+    private GradleVersion(@NonNull String rawValue,
+            @NonNull VersionSegment majorSegment,
+            @Nullable VersionSegment minorSegment,
+            @Nullable VersionSegment microSegment,
+            @NonNull List<VersionSegment> additionalSegments,
+            int preview,
+            @Nullable String previewType,
+            boolean snapshot) {
+        mRawValue = rawValue;
+        mMajorSegment = majorSegment;
+        mMinorSegment = minorSegment;
+        mMicroSegment = microSegment;
+        mAdditionalSegments = ImmutableList.copyOf(additionalSegments);
+        mPreview = preview;
+        mPreviewType = previewType;
+        mSnapshot = snapshot;
+    }
+
+    public int getMajor() {
+        return valueOf(mMajorSegment);
+    }
+
+    @NonNull
+    public VersionSegment getMajorSegment() {
+        return mMajorSegment;
+    }
+
+    public int getMinor() {
+        return valueOf(mMinorSegment);
+    }
+
+    @Nullable
+    public VersionSegment getMinorSegment() {
+        return mMinorSegment;
+    }
+
+    public int getMicro() {
+        return valueOf(mMicroSegment);
+    }
+
+    private static int valueOf(@Nullable VersionSegment segment) {
+        return segment != null ? segment.getValue() : 0;
+    }
+
+    @Nullable
+    public VersionSegment getMicroSegment() {
+        return mMicroSegment;
+    }
+
+    public int getPreview() {
+        return mPreview;
+    }
+
+    @Nullable
+    public String getPreviewType() {
+        return mPreviewType;
+    }
+
+    public boolean isSnapshot() {
+        return mSnapshot;
+    }
+
+    public int compareTo(@NonNull String version) {
+        return compareTo(parse(version));
+    }
+
+    @Override
+    public int compareTo(@NonNull GradleVersion version) {
+        return compareTo(version, false);
+    }
+
+    public int compareIgnoringQualifiers(@NonNull String version) {
+        return compareIgnoringQualifiers(parse(version));
+    }
+
+    public int compareIgnoringQualifiers(@NonNull GradleVersion version) {
+        return compareTo(version, true);
+    }
+
+    private int compareTo(@NonNull GradleVersion version, boolean ignoreQualifiers) {
+        int delta = getMajor() - version.getMajor();
+        if (delta != 0) {
+            return delta;
+        }
+        delta = getMinor() - version.getMinor();
+        if (delta != 0) {
+            return delta;
+        }
+        delta = getMicro() - version.getMicro();
+        if (delta != 0) {
+            return delta;
+        }
+        if (!ignoreQualifiers) {
+            if (mPreviewType == null) {
+                if (version.mPreviewType != null) {
+                    return 1;
+                }
+            } else if (version.mPreviewType == null) {
+                return -1;
+            } else {
+                delta = mPreviewType.compareToIgnoreCase(version.mPreviewType);
+            }
+            if (delta != 0) {
+                return delta;
+            }
+
+            delta = mPreview - version.mPreview;
+            if (delta != 0) {
+                return delta;
+            }
+            delta = mSnapshot == version.mSnapshot ? 0 : (mSnapshot ? -1 : 1);
+        }
+        return delta;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        GradleVersion that = (GradleVersion) o;
+        return compareTo(that) == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(mMajorSegment, mMinorSegment, mMicroSegment, mPreview, mPreviewType,
+                mSnapshot);
+    }
+
+    @Override
+    public String toString() {
+        return mRawValue;
+    }
+
+    /**
+     * @return version segments present after the "micro" segments. For example, parsing "1.2.3.4.5"
+     * will result in "4" and "5" to be considered "additional" version segments.
+     */
+    @NonNull
+    public List<VersionSegment> getAdditionalSegments() {
+        return mAdditionalSegments;
+    }
+
+    public static class VersionSegment implements Serializable {
+        @NonNull
+        private final String mText;
+
+        private final int mValue;
+
+        VersionSegment(int value) {
+            mText = String.valueOf(value);
+            mValue = value;
+        }
+
+        VersionSegment(@NonNull String text) {
+            mText = text;
+            if (PLUS.equals(text)) {
+                mValue = Integer.MAX_VALUE;
+            } else {
+                // +1 is a valid number which will be parsed correctly but it is not a correct
+                // version segment.
+                if (text.startsWith(PLUS)) {
+                    throw new NumberFormatException("Version segment cannot start with +");
+                }
+                mValue = Integer.parseInt(text);
+            }
+        }
+
+        @NonNull
+        public String getText() {
+            return mText;
+        }
+
+        public int getValue() {
+            return mValue;
+        }
+
+        public boolean acceptsGreaterValue() {
+            return PLUS.equals(mText);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            VersionSegment that = (VersionSegment) o;
+            return Objects.equal(mText, that.mText);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(mText);
+        }
+
+        @Override
+        public String toString() {
+            return mText;
+        }
+    }
+}
diff --git a/sdk-common/src/test/java/com/android/ide/common/repository/GradleVersionTest.java b/sdk-common/src/test/java/com/android/ide/common/repository/GradleVersionTest.java
new file mode 100644
index 0000000..3dcb1c3
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/repository/GradleVersionTest.java
@@ -0,0 +1,455 @@
+/*
+ * Copyright (C) 2016 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.ide.common.repository;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import java.util.List;
+
+public class GradleVersionTest {
+
+    @Test
+    public void testParseOneSegment() {
+        GradleVersion version = GradleVersion.parse("2");
+        assertEquals(2, version.getMajor());
+        assertEquals(0, version.getMinor());
+        assertEquals(0, version.getMicro());
+        assertEquals(0, version.getPreview());
+        assertNull(version.getPreviewType());
+        assertFalse(version.isSnapshot());
+        assertEquals("2", version.toString());
+    }
+
+    @Test
+    public void testParseOneSegmentWithPlus() {
+        GradleVersion version = GradleVersion.parse("+");
+        assertEquals(Integer.MAX_VALUE, version.getMajor());
+        assertEquals(0, version.getMinor());
+        assertEquals(0, version.getMicro());
+        assertEquals(0, version.getPreview());
+        assertNull(version.getPreviewType());
+        assertFalse(version.isSnapshot());
+        assertEquals("+", version.toString());
+    }
+
+    @Test
+    public void testParseOneSegmentWithPreview() {
+        GradleVersion version = GradleVersion.parse("2-alpha1");
+        assertEquals(2, version.getMajor());
+        assertEquals(0, version.getMinor());
+        assertEquals(0, version.getMicro());
+        assertEquals(1, version.getPreview());
+        assertEquals("alpha", version.getPreviewType());
+        assertFalse(version.isSnapshot());
+        assertEquals("2-alpha1", version.toString());
+    }
+
+    @Test
+    public void testParseOneSegmentWithPreviewAndSnapshot() {
+        GradleVersion version = GradleVersion.parse("2-alpha1-SNAPSHOT");
+        assertEquals(2, version.getMajor());
+        assertEquals(0, version.getMinor());
+        assertEquals(0, version.getMicro());
+        assertEquals(1, version.getPreview());
+        assertEquals("alpha", version.getPreviewType());
+        assertTrue(version.isSnapshot());
+        assertEquals("2-alpha1-SNAPSHOT", version.toString());
+    }
+
+    @Test
+    public void testParseOneSegmentWithSnapshot() {
+        GradleVersion version = GradleVersion.parse("2-SNAPSHOT");
+        assertEquals(2, version.getMajor());
+        assertEquals(0, version.getMinor());
+        assertEquals(0, version.getMicro());
+        assertEquals(0, version.getPreview());
+        assertNull(version.getPreviewType());
+        assertTrue(version.isSnapshot());
+        assertEquals("2-SNAPSHOT", version.toString());
+    }
+
+    @Test
+    public void testParseOneSegmentWithSnapshot_lowerCase() {
+        GradleVersion version = GradleVersion.parse("2-snapshot");
+        assertEquals(2, version.getMajor());
+        assertEquals(0, version.getMinor());
+        assertEquals(0, version.getMicro());
+        assertEquals(0, version.getPreview());
+        assertNull(version.getPreviewType());
+        assertTrue(version.isSnapshot());
+        assertEquals("2-snapshot", version.toString());
+    }
+
+    @Test
+    public void testParseOneSegmentWithDev() {
+        GradleVersion version = GradleVersion.parse("2-dev");
+        assertEquals(2, version.getMajor());
+        assertEquals(0, version.getMinor());
+        assertEquals(0, version.getMicro());
+        assertEquals(0, version.getPreview());
+        assertNull(version.getPreviewType());
+        assertTrue(version.isSnapshot());
+        assertEquals("2-dev", version.toString());
+    }
+
+    @Test
+    public void testParseTwoSegments() {
+        GradleVersion version = GradleVersion.parse("1.2");
+        assertEquals(1, version.getMajor());
+        assertEquals(2, version.getMinor());
+        assertEquals(0, version.getMicro());
+        assertEquals(0, version.getPreview());
+        assertNull(version.getPreviewType());
+        assertFalse(version.isSnapshot());
+        assertEquals("1.2", version.toString());
+    }
+
+    @Test
+    public void testParseTwoSegmentsWithPlus() {
+        GradleVersion version = GradleVersion.parse("1.+");
+        assertEquals(1, version.getMajor());
+        assertEquals(Integer.MAX_VALUE, version.getMinor());
+        assertEquals(0, version.getMicro());
+        assertEquals(0, version.getPreview());
+        assertNull(version.getPreviewType());
+        assertFalse(version.isSnapshot());
+        assertEquals("1.+", version.toString());
+    }
+
+    @Test
+    public void testParseTwoSegmentsWithPreview() {
+        GradleVersion version = GradleVersion.parse("1.2-alpha3");
+        assertEquals(1, version.getMajor());
+        assertEquals(2, version.getMinor());
+        assertEquals(0, version.getMicro());
+        assertEquals(3, version.getPreview());
+        assertEquals("alpha", version.getPreviewType());
+        assertFalse(version.isSnapshot());
+        assertEquals("1.2-alpha3", version.toString());
+    }
+
+    @Test
+    public void testParseTwoSegmentsWithPreviewAndSnapshot() {
+        GradleVersion version = GradleVersion.parse("1.2-alpha3-SNAPSHOT");
+        assertEquals(1, version.getMajor());
+        assertEquals(2, version.getMinor());
+        assertEquals(0, version.getMicro());
+        assertEquals(3, version.getPreview());
+        assertEquals("alpha", version.getPreviewType());
+        assertTrue(version.isSnapshot());
+        assertEquals("1.2-alpha3-SNAPSHOT", version.toString());
+    }
+
+    @Test
+    public void testParseTwoSegmentsWithSnapshot() {
+        GradleVersion version = GradleVersion.parse("1.2-SNAPSHOT");
+        assertEquals(1, version.getMajor());
+        assertEquals(2, version.getMinor());
+        assertEquals(0, version.getMicro());
+        assertEquals(0, version.getPreview());
+        assertNull(version.getPreviewType());
+        assertTrue(version.isSnapshot());
+        assertEquals("1.2-SNAPSHOT", version.toString());
+    }
+
+    @Test
+    public void testParseThreeSegments() {
+        GradleVersion version = GradleVersion.parse("1.2.3");
+        assertEquals(1, version.getMajor());
+        assertEquals(2, version.getMinor());
+        assertEquals(3, version.getMicro());
+        assertEquals(0, version.getPreview());
+        assertNull(version.getPreviewType());
+        assertFalse(version.isSnapshot());
+        assertEquals("1.2.3", version.toString());
+    }
+
+    @Test
+    public void testParseThreeSegmentsWithPlus() {
+        GradleVersion version = GradleVersion.parse("1.2.+");
+        assertEquals(1, version.getMajor());
+        assertEquals(2, version.getMinor());
+        assertEquals(Integer.MAX_VALUE, version.getMicro());
+        assertEquals(0, version.getPreview());
+        assertNull(version.getPreviewType());
+        assertFalse(version.isSnapshot());
+        assertEquals("1.2.+", version.toString());
+    }
+
+    @Test
+    public void testParseThreeSegmentsWithPreview() {
+        GradleVersion version = GradleVersion.parse("1.2.3-alpha4");
+        assertEquals(1, version.getMajor());
+        assertEquals(2, version.getMinor());
+        assertEquals(3, version.getMicro());
+        assertEquals(4, version.getPreview());
+        assertEquals("alpha", version.getPreviewType());
+        assertFalse(version.isSnapshot());
+        assertEquals("1.2.3-alpha4", version.toString());
+    }
+
+    @Test
+    public void testParseThreeSegmentsWithPreview2() {
+        GradleVersion version = GradleVersion.parse("1.2.3-alpha-4");
+        assertEquals(1, version.getMajor());
+        assertEquals(2, version.getMinor());
+        assertEquals(3, version.getMicro());
+        assertEquals(4, version.getPreview());
+        assertEquals("alpha", version.getPreviewType());
+        assertFalse(version.isSnapshot());
+        assertEquals("1.2.3-alpha-4", version.toString());
+    }
+
+    @Test
+    public void testParseThreeSegmentsWithPreviewAndSnapshot() {
+        GradleVersion version = GradleVersion.parse("1.2.3-alpha4-SNAPSHOT");
+        assertEquals(1, version.getMajor());
+        assertEquals(2, version.getMinor());
+        assertEquals(3, version.getMicro());
+        assertEquals(4, version.getPreview());
+        assertEquals("alpha", version.getPreviewType());
+        assertTrue(version.isSnapshot());
+        assertEquals("1.2.3-alpha4-SNAPSHOT", version.toString());
+    }
+
+    @Test
+    public void testParseThreeSegmentsWithPreviewAndSnapshot2() {
+        GradleVersion version = GradleVersion.parse("1.2.3-alpha-4-SNAPSHOT");
+        assertEquals(1, version.getMajor());
+        assertEquals(2, version.getMinor());
+        assertEquals(3, version.getMicro());
+        assertEquals(4, version.getPreview());
+        assertEquals("alpha", version.getPreviewType());
+        assertTrue(version.isSnapshot());
+        assertEquals("1.2.3-alpha-4-SNAPSHOT", version.toString());
+    }
+
+    @Test
+    public void testParseThreeSegmentsWithSnapshot() {
+        GradleVersion version = GradleVersion.parse("1.2.3-SNAPSHOT");
+        assertEquals(1, version.getMajor());
+        assertEquals(2, version.getMinor());
+        assertEquals(3, version.getMicro());
+        assertEquals(0, version.getPreview());
+        assertNull(version.getPreviewType());
+        assertTrue(version.isSnapshot());
+    }
+
+    @Test
+    public void testParseWithMoreThanThreeSegments1() {
+        GradleVersion version = GradleVersion.parse("1.2.3.4");
+        assertEquals(1, version.getMajor());
+        assertEquals(2, version.getMinor());
+        assertEquals(3, version.getMicro());
+        assertEquals(0, version.getPreview());
+        assertNull(version.getPreviewType());
+        assertFalse(version.isSnapshot());
+
+        List<GradleVersion.VersionSegment> additional = version.getAdditionalSegments();
+        assertEquals(1, additional.size());
+
+        GradleVersion.VersionSegment fourth = additional.get(0);
+        assertEquals(4, fourth.getValue());
+    }
+
+    @Test
+    public void testParseWithMoreThanThreeSegments2() {
+        GradleVersion version = GradleVersion.parse("1.2.3.4.5-SNAPSHOT");
+        assertEquals(1, version.getMajor());
+        assertEquals(2, version.getMinor());
+        assertEquals(3, version.getMicro());
+        assertEquals(0, version.getPreview());
+        assertNull(version.getPreviewType());
+        assertTrue(version.isSnapshot());
+
+        List<GradleVersion.VersionSegment> additional = version.getAdditionalSegments();
+        assertEquals(2, additional.size());
+
+        GradleVersion.VersionSegment fourth = additional.get(0);
+        assertEquals(4, fourth.getValue());
+
+        GradleVersion.VersionSegment fifth = additional.get(1);
+        assertEquals(5, fifth.getValue());
+    }
+
+
+    @Test
+    public void testParseWithMoreThanThreeSegments3() {
+        GradleVersion version = GradleVersion.parse("1.2.3.4.5.6-alpha9-SNAPSHOT");
+        assertEquals(1, version.getMajor());
+        assertEquals(2, version.getMinor());
+        assertEquals(3, version.getMicro());
+        assertEquals(9, version.getPreview());
+        assertEquals("alpha", version.getPreviewType());
+        assertTrue(version.isSnapshot());
+
+        List<GradleVersion.VersionSegment> additional = version.getAdditionalSegments();
+        assertEquals(3, additional.size());
+
+        GradleVersion.VersionSegment fourth = additional.get(0);
+        assertEquals(4, fourth.getValue());
+
+        GradleVersion.VersionSegment fifth = additional.get(1);
+        assertEquals(5, fifth.getValue());
+
+        GradleVersion.VersionSegment sixth = additional.get(2);
+        assertEquals(6, sixth.getValue());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidVersion1() {
+        GradleVersion.parse("a");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidVersion2() {
+        GradleVersion.parse("a.b");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidVersion3() {
+        GradleVersion.parse("a.b.c");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidVersion4() {
+        GradleVersion.parse("1.2.3-foo-bar");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidVersion5() {
+        GradleVersion.parse("1.2.3-1-2");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidVersion6() {
+        GradleVersion.parse("1.2.3-1");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidVersion7() {
+        GradleVersion.parse("1.2.3-develop");
+    }
+
+    @Test
+    public void testCompare() {
+        assertEquals(0, GradleVersion.parse("1.0.0").compareTo("1.0.0"));
+        assertEquals(0, GradleVersion.parse("1.0.0-alpha1").compareTo("1.0.0-alpha1"));
+        assertEquals(0, GradleVersion.parse("1.0.0-SNAPSHOT").compareTo("1.0.0-SNAPSHOT"));
+
+        assertTrue(GradleVersion.parse("1.0.1").compareTo("1.0.0") > 0);
+        assertTrue(GradleVersion.parse("+").compareTo("1.0.0") > 0);
+        assertTrue(GradleVersion.parse("1.+").compareTo("1.0.0") > 0);
+        assertTrue(GradleVersion.parse("1.0.+").compareTo("1.0.0") > 0);
+
+        assertTrue(GradleVersion.parse("1.0.1").compareTo("1.0.0") > 0);
+        assertTrue(GradleVersion.parse("1.1.0").compareTo("1.0.0") > 0);
+        assertTrue(GradleVersion.parse("1.1.1").compareTo("1.0.0") > 0);
+
+        assertTrue(GradleVersion.parse("1.0.0").compareTo("1.0.1") < 0);
+        assertTrue(GradleVersion.parse("1.0.0").compareTo("1.1.0") < 0);
+        assertTrue(GradleVersion.parse("1.0.0").compareTo("1.1.1") < 0);
+
+        assertTrue(GradleVersion.parse("1.0.0").compareTo("1.0.0-alpha1") > 0);
+        assertTrue(GradleVersion.parse("1.0.0").compareTo("1.0.0-SNAPSHOT") > 0);
+        assertTrue(GradleVersion.parse("1.0.0-alpha2").compareTo("1.0.0-alpha1") > 0);
+        assertTrue(GradleVersion.parse("1.0.0-beta1").compareTo("1.0.0-alpha2") > 0);
+        assertTrue(GradleVersion.parse("1.0.0-rc1").compareTo("1.0.0-alpha2") > 0);
+        assertTrue(GradleVersion.parse("2.0.0-alpha1").compareTo("1.0.0-alpha1") > 0);
+    }
+
+    @Test
+    public void testCompareWithExcludeAllComparison() {
+        assertEquals(0, GradleVersion.parse("1.0.0").compareIgnoringQualifiers("1.0.0"));
+        assertEquals(0, GradleVersion.parse("1.0.0").compareIgnoringQualifiers("1.0.0-alpha1"));
+        assertEquals(0, GradleVersion.parse("1.0.0").compareIgnoringQualifiers("1.0.0-SNAPSHOT"));
+    }
+
+    @Test
+    public void testGetSegments() {
+        GradleVersion version = GradleVersion.parse("1.2.3-SNAPSHOT");
+        assertNotNull(version.getMinorSegment());
+        assertNotNull(version.getMicroSegment());
+        assertEquals("1", version.getMajorSegment().getText());
+        assertEquals("2", version.getMinorSegment().getText());
+        assertEquals("3", version.getMicroSegment().getText());
+        assertFalse(version.getMajorSegment().acceptsGreaterValue());
+        assertFalse(version.getMinorSegment().acceptsGreaterValue());
+        assertFalse(version.getMicroSegment().acceptsGreaterValue());
+
+        version = GradleVersion.parse("1.2.+");
+        assertNotNull(version.getMinorSegment());
+        assertNotNull(version.getMicroSegment());
+        assertEquals("1", version.getMajorSegment().getText());
+        assertEquals("2", version.getMinorSegment().getText());
+        assertEquals("+", version.getMicroSegment().getText());
+        assertFalse(version.getMajorSegment().acceptsGreaterValue());
+        assertFalse(version.getMinorSegment().acceptsGreaterValue());
+        assertTrue(version.getMicroSegment().acceptsGreaterValue());
+
+        version = GradleVersion.parse("+");
+        assertEquals("+", version.getMajorSegment().getText());
+        assertTrue(version.getMajorSegment().acceptsGreaterValue());
+        assertNull(version.getMinorSegment());
+        assertNull(version.getMicroSegment());
+    }
+
+    @Test
+    public void testConstructorWithNumbers() {
+        GradleVersion version = new GradleVersion(1, 2, 3);
+        assertNotNull(version.getMinorSegment());
+        assertNotNull(version.getMicroSegment());
+        assertEquals(1, version.getMajor());
+        assertEquals("1", version.getMajorSegment().getText());
+        assertEquals(2, version.getMinor());
+        assertEquals("2", version.getMinorSegment().getText());
+        assertEquals(3, version.getMicro());
+        assertEquals("3", version.getMicroSegment().getText());
+        assertEquals(0, version.getPreview());
+        assertNull(version.getPreviewType());
+        assertFalse(version.isSnapshot());
+        assertEquals("1.2.3", version.toString());
+    }
+
+    // See https://code.google.com/p/android/issues/detail?id=201325
+    @Test
+    public void testFixFor201325() {
+        // 2.0+ is equivalent to 2.0.+
+        GradleVersion version = GradleVersion.parse("2.0+");
+        assertNotNull(version.getMinorSegment());
+        assertNotNull(version.getMicroSegment());
+        assertEquals(2, version.getMajor());
+        assertEquals("2", version.getMajorSegment().getText());
+        assertEquals(0, version.getMinor());
+        assertEquals("0", version.getMinorSegment().getText());
+        assertTrue(version.getMicroSegment().acceptsGreaterValue());
+        assertEquals(0, version.getPreview());
+        assertNull(version.getPreviewType());
+        assertFalse(version.isSnapshot());
+        assertEquals("2.0+", version.toString());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParseWithPlusAtBeginningOfSegment() {
+        GradleVersion.parse("2.+1");
+    }
+}
\ No newline at end of file
diff --git a/testutils/src/main/java/com/android/testutils/SdkTestCase.java b/testutils/src/main/java/com/android/testutils/SdkTestCase.java
index 75877a5..bf4216b 100644
--- a/testutils/src/main/java/com/android/testutils/SdkTestCase.java
+++ b/testutils/src/main/java/com/android/testutils/SdkTestCase.java
@@ -17,6 +17,7 @@
 
 import com.android.SdkConstants;
 import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
 import com.google.common.base.Charsets;
 import com.google.common.collect.Sets;
 import com.google.common.io.ByteStreams;
@@ -414,8 +415,8 @@
                 stream = new ByteArrayInputStream(contents.getBytes(Charsets.UTF_8));
             } else {
                 stream = getTestResource(sourceRelativePath, true);
+                assertNotNull(sourceRelativePath + " does not exist", stream);
             }
-            assertNotNull(sourceRelativePath + " does not exist", stream);
             int index = targetRelativePath.lastIndexOf('/');
             String relative = null;
             String name = targetRelativePath;
@@ -426,6 +427,23 @@
 
             return makeTestFile(targetDir, name, relative, stream);
         }
+
+        @Nullable
+        public String getContents() {
+            if (contents != null) {
+                return contents;
+            } else if (sourceRelativePath != null) {
+                InputStream stream = getTestResource(sourceRelativePath, true);
+                if (stream != null) {
+                    try {
+                        return new String(ByteStreams.toByteArray(stream), Charsets.UTF_8);
+                    } catch (IOException ignore) {
+                        return "<couldn't open test file " + sourceRelativePath + ">";
+                    }
+                }
+            }
+            return null;
+        }
     }
 
     protected File getTestfile(File targetDir, String relativePath) throws IOException {
@@ -532,4 +550,4 @@
 
         return null;
     }
-}
+}
\ No newline at end of file