Merge "Adds a timeline data method to save speed samples." into studio-1.4-dev
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/AndroidGradleOptions.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/AndroidGradleOptions.java
index 89970b9..277f08a 100644
--- a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/AndroidGradleOptions.java
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/AndroidGradleOptions.java
@@ -82,6 +82,10 @@
         return getString(project, AndroidProject.PROPERTY_APK_LOCATION);
     }
 
+    public static boolean isIntegrationTest() {
+        return Boolean.parseBoolean(System.getenv("INTEGRATION_TEST"));
+    }
+
     @Nullable
     public static Integer getThreadPoolSize(@NonNull Project project) {
         Integer size = getInteger(project, PROPERTY_THREAD_POOL_SIZE);
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/ReportingPlugin.groovy b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/ReportingPlugin.groovy
index 9871cf7..4f834bd 100644
--- a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/ReportingPlugin.groovy
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/ReportingPlugin.groovy
@@ -49,7 +49,6 @@
         mergeReportsTask.group = JavaBasePlugin.VERIFICATION_GROUP
         mergeReportsTask.description = "Merges all the Android test reports from the sub projects."
         mergeReportsTask.reportType = ReportType.MULTI_PROJECT
-        mergeReportsTask.setVariantName("")
 
         mergeReportsTask.conventionMapping.resultsDir = {
             String location = extension.resultsDir != null ?
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TaskManager.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TaskManager.java
index 670c225..a392c57 100644
--- a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TaskManager.java
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TaskManager.java
@@ -1247,7 +1247,7 @@
                             mainConnectedTask.setDescription("Installs and runs instrumentation "
                                     + "tests for all flavors on connected devices.");
                             mainConnectedTask.setReportType(ReportType.MULTI_FLAVOR);
-                            mainConnectedTask.setVariantName("");
+
                             ConventionMappingHelper.map(mainConnectedTask, "resultsDir",
                                     new Callable<File>() {
                                         @Override
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/AndroidReportTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/AndroidReportTask.java
index 38bb68c..9917600 100644
--- a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/AndroidReportTask.java
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/AndroidReportTask.java
@@ -24,6 +24,7 @@
 import com.android.utils.FileUtils;
 import com.google.common.collect.Lists;
 
+import org.gradle.api.DefaultTask;
 import org.gradle.api.GradleException;
 import org.gradle.api.tasks.InputFiles;
 import org.gradle.api.tasks.OutputDirectory;
@@ -38,7 +39,7 @@
  * Task doing test report aggregation.
  */
 
-public class AndroidReportTask extends BaseTask implements AndroidTestTask {
+public class AndroidReportTask extends DefaultTask implements AndroidTestTask {
 
     private final List<AndroidTestTask> subTasks = Lists.newArrayList();
 
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/IncrementalTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/IncrementalTask.java
index f28be32..e60fa86 100644
--- a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/IncrementalTask.java
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/IncrementalTask.java
@@ -14,10 +14,13 @@
  * limitations under the License.
  */
 package com.android.build.gradle.internal.tasks;
+import com.android.annotations.NonNull;
 import com.android.ide.common.res2.FileStatus;
 import com.android.ide.common.res2.SourceSet;
+import com.android.utils.FileUtils;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.common.io.Files;
 
 import org.gradle.api.Action;
 import org.gradle.api.tasks.Optional;
@@ -33,6 +36,8 @@
 
 public abstract class IncrementalTask extends BaseTask {
 
+    public static final String MARKER_NAME = "build_was_incremental";
+
     private File incrementalFolder;
 
     public void setIncrementalFolder(File incrementalFolder) {
@@ -44,6 +49,19 @@
         return incrementalFolder;
     }
 
+    public void setIncrementalMarker() throws IOException {
+        Files.touch(getIncrementalMarkerFile());
+    }
+
+    public void clearIncrementalMarker() throws IOException {
+        FileUtils.deleteIfExists(getIncrementalMarkerFile());
+    }
+
+    @NonNull
+    private File getIncrementalMarkerFile() {
+        return new File(getIncrementalFolder(), MARKER_NAME);
+    }
+
     /**
      * Whether this task can support incremental update.
      *
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/MergeResources.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/MergeResources.java
index 5805f27..a264d05 100644
--- a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/MergeResources.java
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/MergeResources.java
@@ -18,6 +18,7 @@
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
 import com.android.build.gradle.AndroidConfig;
+import com.android.build.gradle.AndroidGradleOptions;
 import com.android.build.gradle.internal.scope.ConventionMappingHelper;
 import com.android.build.gradle.internal.scope.TaskConfigAction;
 import com.android.build.gradle.internal.scope.VariantScope;
@@ -128,6 +129,10 @@
 
     @Override
     protected void doFullTaskAction() throws IOException {
+        if (AndroidGradleOptions.isIntegrationTest()) {
+            clearIncrementalMarker();
+        }
+
         // this is full run, clean the previous output
         File destinationDir = getOutputDir();
         FileUtils.emptyFolder(destinationDir);
@@ -163,6 +168,10 @@
 
     @Override
     protected void doIncrementalTaskAction(Map<File, FileStatus> changedInputs) throws IOException {
+        if (AndroidGradleOptions.isIntegrationTest()) {
+            setIncrementalMarker();
+        }
+
         // create a merger and load the known state.
         ResourceMerger merger = new ResourceMerger();
         try {
diff --git a/build-system/integration-test/build.gradle b/build-system/integration-test/build.gradle
index 36a3527..0463f56 100644
--- a/build-system/integration-test/build.gradle
+++ b/build-system/integration-test/build.gradle
@@ -38,6 +38,7 @@
     CUSTOM_JACK: System.env.CUSTOM_JACK,
     DEBUG_INNER_TEST: System.env.DEBUG_INNER_TEST,
     RECORD_SPANS: System.env.RECORD_SPANS,
+    INTEGRATION_TEST: "true",
 ].findAll { it.value != null }
 
 // These tasks will not depend on publishLocal, so they will run integration
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/VectorDrawableTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/VectorDrawableTest.groovy
index 9b39669..43227a9 100644
--- a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/VectorDrawableTest.groovy
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/VectorDrawableTest.groovy
@@ -16,6 +16,7 @@
 
 package com.android.build.gradle.integration.application
 import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.internal.tasks.IncrementalTask
 import com.android.utils.FileUtils
 import com.google.common.io.Files
 import groovy.transform.CompileStatic
@@ -310,7 +311,10 @@
     }
 
     private void checkIncrementalBuild() {
-        // Do nothing for now, the incremental marker was removed.
-        // TODO: remove the method or re-enable incremental markers.
+        def marker = FileUtils.join(
+                project.testDir, "build", "intermediates", "incremental", "mergeResources",
+                "debug", IncrementalTask.MARKER_NAME)
+
+        assertThat(marker).exists()
     }
 }
diff --git a/common/src/main/java/com/android/utils/FileUtils.java b/common/src/main/java/com/android/utils/FileUtils.java
index 897300a..0243717 100644
--- a/common/src/main/java/com/android/utils/FileUtils.java
+++ b/common/src/main/java/com/android/utils/FileUtils.java
@@ -71,6 +71,20 @@
         }
     }
 
+    public static void delete(File file) throws IOException {
+        boolean result = file.delete();
+        if (!result) {
+            throw new IOException("Failed to delete " + file.getAbsolutePath());
+        }
+    }
+
+    public static void deleteIfExists(File file) throws IOException {
+        boolean result = file.delete();
+        if (!result && file.exists()) {
+            throw new IOException("Failed to delete " + file.getAbsolutePath());
+        }
+    }
+
     public static File join(File dir, String... paths) {
         return new File(dir, Joiner.on(File.separatorChar).join(paths));
     }
diff --git a/ddmlib/src/main/java/com/android/ddmlib/AdbHelper.java b/ddmlib/src/main/java/com/android/ddmlib/AdbHelper.java
index b390f93..18767b1 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/AdbHelper.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/AdbHelper.java
@@ -19,7 +19,6 @@
 import com.android.annotations.Nullable;
 import com.android.ddmlib.log.LogReceiver;
 
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
@@ -75,8 +74,7 @@
      * @throws AdbCommandRejectedException if adb rejects the command
      */
     public static SocketChannel open(InetSocketAddress adbSockAddr,
-            Device device, int devicePort)
-            throws IOException, TimeoutException, AdbCommandRejectedException {
+            Device device, int devicePort) throws IOException, TimeoutException, AdbCommandRejectedException {
 
         SocketChannel adbChan = SocketChannel.open(adbSockAddr);
         try {
@@ -125,8 +123,7 @@
      * @throws IOException in case of I/O error on the connection.
      */
     public static SocketChannel createPassThroughConnection(InetSocketAddress adbSockAddr,
-            Device device, int pid)
-            throws TimeoutException, AdbCommandRejectedException, IOException {
+            Device device, int pid) throws TimeoutException, AdbCommandRejectedException, IOException {
 
         SocketChannel adbChan = SocketChannel.open(adbSockAddr);
         try {
@@ -275,8 +272,7 @@
      * @throws AdbCommandRejectedException if adb rejects the command
      * @throws IOException in case of I/O error on the connection.
      */
-    static RawImage getFrameBuffer(InetSocketAddress adbSockAddr, Device device, long timeout,
-      TimeUnit unit)
+    static RawImage getFrameBuffer(InetSocketAddress adbSockAddr, Device device, long timeout, TimeUnit unit)
             throws TimeoutException, AdbCommandRejectedException, IOException {
 
         RawImage imageParams = new RawImage();
@@ -351,11 +347,9 @@
      */
     @Deprecated
     static void executeRemoteCommand(InetSocketAddress adbSockAddr,
-        String command, IDevice device, IShellOutputReceiver rcvr, int maxTimeToOutputResponse)
-        throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
-        IOException {
-        executeRemoteCommand(adbSockAddr, command, device, rcvr, maxTimeToOutputResponse,
-                TimeUnit.MILLISECONDS);
+            String command, IDevice device, IShellOutputReceiver rcvr, int maxTimeToOutputResponse)
+            throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
+        executeRemoteCommand(adbSockAddr, command, device, rcvr, maxTimeToOutputResponse, TimeUnit.MILLISECONDS);
     }
 
     /**
@@ -381,9 +375,8 @@
      * @see DdmPreferences#getTimeOut()
      */
     static void executeRemoteCommand(InetSocketAddress adbSockAddr,
-        String command, IDevice device, IShellOutputReceiver rcvr, long maxTimeToOutputResponse,
-        TimeUnit maxTimeUnits) throws TimeoutException, AdbCommandRejectedException,
-        ShellCommandUnresponsiveException, IOException {
+            String command, IDevice device, IShellOutputReceiver rcvr, long maxTimeToOutputResponse, TimeUnit maxTimeUnits)
+            throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
 
         executeRemoteCommand(adbSockAddr, AdbService.SHELL, command, device, rcvr, maxTimeToOutputResponse,
                 maxTimeUnits, null /* inputStream */);
@@ -453,8 +446,7 @@
             adbChan.configureBlocking(false);
 
             // if the device is not -1, then we first tell adb we're looking to
-            // talk
-            // to a specific device
+            // talk to a specific device
             setDevice(adbChan, device);
 
             byte[] request = formAdbRequest(adbService.name().toLowerCase() + ":" + command); //$NON-NLS-1$
@@ -511,7 +503,11 @@
                             throw new ShellCommandUnresponsiveException();
                         }
                         Thread.sleep(wait);
-                    } catch (InterruptedException ie) {
+                    }
+                    catch (InterruptedException e) {
+                        Thread.currentThread().interrupt();
+                        // Throw a timeout exception in place of interrupted exception to avoid API changes.
+                        throw new TimeoutException("executeRemoteCommand interrupted with immediate timeout via interruption.");
                     }
                 } else {
                     // reset timeout
@@ -594,7 +590,11 @@
                 } else if (count == 0) {
                     try {
                         Thread.sleep(WAIT_TIME * 5);
-                    } catch (InterruptedException ie) {
+                    }
+                    catch (InterruptedException e) {
+                        Thread.currentThread().interrupt();
+                        // Throw a timeout exception in place of interrupted exception to avoid API changes.
+                        throw new TimeoutException("runLogService interrupted with immediate timeout via interruption.");
                     }
                 } else {
                     if (rcvr != null) {
@@ -629,7 +629,7 @@
      */
     public static void createForward(InetSocketAddress adbSockAddr, Device device,
             String localPortSpec, String remotePortSpec)
-                    throws TimeoutException, AdbCommandRejectedException, IOException {
+            throws TimeoutException, AdbCommandRejectedException, IOException {
 
         SocketChannel adbChan = null;
         try {
@@ -673,7 +673,7 @@
      */
     public static void removeForward(InetSocketAddress adbSockAddr, Device device,
             String localPortSpec, String remotePortSpec)
-                    throws TimeoutException, AdbCommandRejectedException, IOException {
+            throws TimeoutException, AdbCommandRejectedException, IOException {
 
         SocketChannel adbChan = null;
         try {
@@ -748,8 +748,7 @@
      * @param length the length to read or -1 to fill the data buffer completely
      * @param timeout The timeout value in ms. A timeout of zero means "wait forever".
      */
-    static void read(SocketChannel chan, byte[] data, int length, long timeout)
-            throws TimeoutException, IOException {
+    static void read(SocketChannel chan, byte[] data, int length, long timeout) throws TimeoutException, IOException {
         ByteBuffer buf = ByteBuffer.wrap(data, 0, length != -1 ? length : data.length);
         int numWaits = 0;
 
@@ -766,10 +765,14 @@
                     Log.d("ddms", "read: timeout");
                     throw new TimeoutException();
                 }
-                // non-blocking spin
                 try {
+                    // non-blocking spin
                     Thread.sleep(WAIT_TIME);
-                } catch (InterruptedException ie) {
+                }
+                catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    // Throw a timeout exception in place of interrupted exception to avoid API changes.
+                    throw new TimeoutException("Read interrupted with immediate timeout via interruption.");
                 }
                 numWaits++;
             } else {
@@ -801,8 +804,7 @@
      * @throws TimeoutException in case of timeout on the connection.
      * @throws IOException in case of I/O error on the connection.
      */
-    static void write(SocketChannel chan, byte[] data, int length, int timeout)
-            throws TimeoutException, IOException {
+    static void write(SocketChannel chan, byte[] data, int length, int timeout) throws TimeoutException, IOException {
         ByteBuffer buf = ByteBuffer.wrap(data, 0, length != -1 ? length : data.length);
         int numWaits = 0;
 
@@ -819,10 +821,14 @@
                     Log.d("ddms", "write: timeout");
                     throw new TimeoutException();
                 }
-                // non-blocking spin
                 try {
+                    // non-blocking spin
                     Thread.sleep(WAIT_TIME);
-                } catch (InterruptedException ie) {
+                }
+                catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    // Throw a timeout exception in place of interrupted exception to avoid API changes.
+                    throw new TimeoutException("Write interrupted with immediate timeout via interruption.");
                 }
                 numWaits++;
             } else {
@@ -866,8 +872,8 @@
      * @throws AdbCommandRejectedException if adb rejects the command
      * @throws IOException in case of I/O error on the connection.
      */
-    public static void reboot(String into, InetSocketAddress adbSockAddr,
-            Device device) throws TimeoutException, AdbCommandRejectedException, IOException {
+    public static void reboot(String into, InetSocketAddress adbSockAddr, Device device)
+            throws TimeoutException, AdbCommandRejectedException, IOException {
         byte[] request;
         if (into == null) {
             request = formAdbRequest("reboot:"); //$NON-NLS-1$
diff --git a/ddmlib/src/main/java/com/android/ddmlib/Device.java b/ddmlib/src/main/java/com/android/ddmlib/Device.java
index 1cb1a4c..5f61fe4 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/Device.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/Device.java
@@ -395,17 +395,23 @@
         return mHardwareCharacteristics.contains(feature.getCharacteristic());
     }
 
-    private int getApiLevel() {
+    @Override
+    public int getApiLevel() {
         if (mApiLevel > 0) {
             return mApiLevel;
         }
 
+        String buildApi = getProperty(PROP_BUILD_API_LEVEL);
+        if (buildApi == null) {
+            throw new IllegalStateException("Unexpected error: Device does not have a build API level.");
+        }
+
         try {
-            String buildApi = getProperty(PROP_BUILD_API_LEVEL);
-            mApiLevel = buildApi == null ? -1 : Integer.parseInt(buildApi);
+            mApiLevel = Integer.parseInt(buildApi);
             return mApiLevel;
-        } catch (Exception e) {
-            return -1;
+        }
+        catch (NumberFormatException e) {
+            throw new IllegalStateException("Unexpected error: Build API level '" + buildApi + "' is not an integer: ");
         }
     }
 
diff --git a/ddmlib/src/main/java/com/android/ddmlib/IDevice.java b/ddmlib/src/main/java/com/android/ddmlib/IDevice.java
index be45449..1e86e02 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/IDevice.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/IDevice.java
@@ -636,4 +636,9 @@
      * @return the user's region, or null if it's unknown
      */
     String getRegion();
-}
\ No newline at end of file
+
+    /**
+     * Returns the API level of the device.
+     */
+    int getApiLevel();
+}
diff --git a/ddmlib/src/main/java/com/android/ddmlib/NativeStackCallInfo.java b/ddmlib/src/main/java/com/android/ddmlib/NativeStackCallInfo.java
index 72d7c77..4785833 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/NativeStackCallInfo.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/NativeStackCallInfo.java
@@ -65,7 +65,7 @@
             } catch (NumberFormatException e) {
                 // do nothing, the line number will stay at -1
             }
-            if (m.groupCount() == 3) {
+            if (m.groupCount() == 3 && m.group(3) != null) {
                 // A discriminator was found, add that in the source file name.
                 mSourceFile += m.group(3);
             }
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 33e49e6..b99b81c 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,6 +16,7 @@
 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;
@@ -27,6 +28,7 @@
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
 import com.android.annotations.VisibleForTesting;
+import com.android.builder.model.AndroidArtifact;
 import com.android.builder.model.AndroidLibrary;
 import com.android.builder.model.Dependencies;
 import com.android.builder.model.MavenCoordinates;
@@ -43,12 +45,16 @@
 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.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.Splitter;
+import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -59,9 +65,11 @@
 import java.net.URL;
 import java.net.URLConnection;
 import java.net.URLEncoder;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Checks Gradle files for potential errors
@@ -241,6 +249,9 @@
     /** Previous plugin id for libraries */
     public static final String OLD_LIB_PLUGIN_ID = "android-library";
 
+    /** Group ID for GMS */
+    public static final String GMS_GROUP_ID = "com.google.android.gms";
+
     private int mMinSdkVersion;
     private int mCompileSdkVersion;
     private int mTargetSdkVersion;
@@ -794,7 +805,7 @@
                             + "compileSdkVersion < 21 is not necessary");
             }
             return;
-        } else if ("com.google.android.gms".equals(dependency.getGroupId())
+        } else if (GMS_GROUP_ID.equals(dependency.getGroupId())
                 && dependency.getArtifactId() != null) {
 
             // 5.2.08 is not supported; special case and warn about this
@@ -889,13 +900,18 @@
     @Nullable
     private static PreciseRevision getLatestVersionFromRemoteRepo(@NonNull LintClient client,
             @NonNull GradleCoordinate dependency, boolean firstRowOnly, boolean allowPreview) {
+        String groupId = dependency.getGroupId();
+        String artifactId = dependency.getArtifactId();
+        if (groupId == null || artifactId == null) {
+            return null;
+        }
         StringBuilder query = new StringBuilder();
         String encoding = UTF_8.name();
         try {
             query.append("http://search.maven.org/solrsearch/select?q=g:%22");
-            query.append(URLEncoder.encode(dependency.getGroupId(), encoding));
+            query.append(URLEncoder.encode(groupId, encoding));
             query.append("%22+AND+a:%22");
-            query.append(URLEncoder.encode(dependency.getArtifactId(), encoding));
+            query.append(URLEncoder.encode(artifactId, encoding));
         } catch (UnsupportedEncodingException ee) {
             return null;
         }
@@ -1071,6 +1087,13 @@
         }
     }
 
+    /**
+     * If incrementally editing a single build.gradle file, tracks whether we've already
+     * transitively checked GMS versions such that we don't flag the same error on every
+     * single dependency declaration
+     */
+    private boolean mCheckedGms;
+
     private void checkPlayServices(Context context, GradleCoordinate dependency, Object cookie) {
         String groupId = dependency.getGroupId();
         String artifactId = dependency.getArtifactId();
@@ -1087,6 +1110,91 @@
             checkLocalMavenVersions(context, dependency, cookie, groupId, artifactId,
                     repository);
         }
+
+        if (!mCheckedGms) {
+            mCheckedGms = true;
+            // Incremental analysis only? If so, tie the check to
+            // a specific GMS play dependency if only, such that it's highlighted
+            // in the editor
+            if (!context.getScope().contains(Scope.ALL_RESOURCE_FILES)) {
+                // Incremental editing: try flagging them in this file!
+                checkConsistentPlayServices(context, cookie);
+            }
+        }
+    }
+
+    private void checkConsistentPlayServices(@NonNull Context context,
+            @Nullable Object cookie) {
+        // Make sure we're using a consistent version across all play services libraries
+        // (b/22709708)
+
+        Project project = context.getMainProject();
+        if (!project.isGradleProject()) {
+            return;
+        }
+        Variant variant = project.getCurrentVariant();
+        if (variant == null) {
+            return;
+        }
+        AndroidArtifact artifact = variant.getMainArtifact();
+        Collection<AndroidLibrary> libraries = artifact.getDependencies().getLibraries();
+        Multimap<String, MavenCoordinates> versionToCoordinate = ArrayListMultimap.create();
+        for (AndroidLibrary library : libraries) {
+            addGmsLibraryVersions(versionToCoordinate, library);
+        }
+        Set<String> versions = versionToCoordinate.keySet();
+        if (versions.size() > 1) {
+            List<String> sortedVersions = Lists.newArrayList(versions);
+            Collections.sort(sortedVersions, Collections.reverseOrder());
+            MavenCoordinates c1 = versionToCoordinate.get(sortedVersions.get(0)).iterator().next();
+            MavenCoordinates c2 = versionToCoordinate.get(sortedVersions.get(1)).iterator().next();
+            // Not using toString because in the IDE, these are model proxies which display garbage output
+            String example1 = c1.getGroupId() + ":" + c1.getArtifactId() + ":" + c1 .getVersion();
+            String example2 = c2.getGroupId() + ":" + c2.getArtifactId() + ":" + c2 .getVersion();
+            String message = "All com.google.android.gms libraries must use the exact same "
+                + "version specification (mixing versions can lead to runtime crashes). "
+                + "Found versions " + Joiner.on(", ").join(sortedVersions) + ". "
+                + "Examples include " + example1 + " and " + example2;
+
+            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);
+            }
+        }
+    }
+
+    private static void addGmsLibraryVersions(@NonNull Multimap<String, MavenCoordinates> versions,
+            @NonNull AndroidLibrary library) {
+        MavenCoordinates coordinates = library.getResolvedCoordinates();
+        if (coordinates != null && coordinates.getGroupId().equals(GMS_GROUP_ID)) {
+            versions.put(coordinates.getVersion(), coordinates);
+        }
+
+        for (AndroidLibrary dependency : library.getLibraryDependencies()) {
+            addGmsLibraryVersions(versions, dependency);
+        }
+    }
+
+    @Override
+    public void afterCheckProject(@NonNull Context context) {
+        if (context.getProject() == context.getMainProject() &&
+                // Full analysis? Don't tie check to any specific Gradle DSL element
+                context.getScope().contains(Scope.ALL_RESOURCE_FILES)) {
+            checkConsistentPlayServices(context, null);
+        }
     }
 
     private void checkLocalMavenVersions(Context context, GradleCoordinate dependency,
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 66537f5..decacff 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
@@ -39,6 +39,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.VALUE_FALSE;
 import static com.android.xml.AndroidManifest.NODE_ACTION;
 import static com.android.xml.AndroidManifest.NODE_DATA;
 import static com.android.xml.AndroidManifest.NODE_METADATA;
@@ -193,6 +194,15 @@
             Severity.FATAL,
             IMPLEMENTATION);
 
+    /**
+     * Documentation URL for app backup.
+     * <p>
+     * TODO: Replace with stable API doc reference once this moves out of preview
+     * (tracked in https://code.google.com/p/android/issues/detail?id=182113)
+     */
+    private static final String BACKUP_DOCUMENTATION_URL
+            = "https://developer.android.com/preview/backup/index.html";
+
     /** Not explicitly defining allowBackup */
     public static final Issue ALLOW_BACKUP = Issue.create(
             "AllowBackup", //$NON-NLS-1$
@@ -223,8 +233,9 @@
             Category.SECURITY,
             3,
             Severity.WARNING,
-            IMPLEMENTATION).addMoreInfo(
-            "http://developer.android.com/reference/android/R.attr.html#allowBackup");
+            IMPLEMENTATION)
+            .addMoreInfo(BACKUP_DOCUMENTATION_URL)
+            .addMoreInfo("http://developer.android.com/reference/android/R.attr.html#allowBackup");
 
     /** Conflicting permission names */
     public static final Issue UNIQUE_PERMISSION = Issue.create(
@@ -799,7 +810,8 @@
         if (tag.equals(TAG_APPLICATION)) {
             mSeenApplication = true;
             boolean recordLocation = false;
-            if (element.hasAttributeNS(ANDROID_URI, ATTR_ALLOW_BACKUP)
+            String allowBackup = element.getAttributeNS(ANDROID_URI, ATTR_ALLOW_BACKUP);
+            if (allowBackup != null && !allowBackup.isEmpty()
                     || context.getDriver().isSuppressed(context, ALLOW_BACKUP, element)) {
                 mSeenAllowBackup = true;
             } else {
@@ -830,18 +842,27 @@
                     context.report(ALLOW_BACKUP, fullBackupNode, location,
                             "Missing `<full-backup-content>` resource");
                 }
-            } else if (fullBackupNode == null && context.getMainProject().getTargetSdk() >= 23) {
-                Location location = context.getLocation(element);
-                context.report(ALLOW_BACKUP, element, location,
-                        "Should explicitly set `android:fullBackupContent` to `true` or `false` "
-                                + "to opt-in to or out of full app data back-up and restore, or "
-                                + "alternatively to an `@xml` resource which specifies which "
-                                + "files to backup");
-            } else if (fullBackupNode == null && hasGcmReceiver(element)) {
-                Location location = context.getLocation(element);
-                context.report(ALLOW_BACKUP, element, location,
-                        "Should explicitly set `android:fullBackupContent` to avoid backing up "
-                                + "the GCM device specific regId.");
+            } else if (fullBackupNode == null && !VALUE_FALSE.equals(allowBackup)
+                    && context.getMainProject().getTargetSdk() >= 23) {
+                if (hasGcmReceiver(element)) {
+                    Location location = context.getLocation(element);
+                    context.report(ALLOW_BACKUP, element, location, ""
+                            + "On SDK version 23 and up, your app data will be automatically "
+                            + "backed up, and restored on app install. Your GCM regid will not "
+                            + "work across restores, so you must ensure that it is excluded "
+                            + "from the back-up set. Use the attribute "
+                            + "`android:fullBackupContent` to specify an `@xml` resource which "
+                            + "configures which files to backup. More info: "
+                            + BACKUP_DOCUMENTATION_URL);
+                } else {
+                    Location location = context.getLocation(element);
+                    context.report(ALLOW_BACKUP, element, location, ""
+                            + "On SDK version 23 and up, your app data will be automatically "
+                            + "backed up and restored on app install. Consider adding the "
+                            + "attribute `android:fullBackupContent` to specify an `@xml` "
+                            + "resource which configures which files to backup. More info: "
+                            + BACKUP_DOCUMENTATION_URL);
+                }
             }
         } else if (mSeenApplication) {
             if (context.isEnabled(ORDER)) {
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 71e07a2..4ac43b7 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
@@ -76,6 +76,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -83,8 +84,9 @@
 import java.util.Set;
 
 /**
- * <b>NOTE</b>: Most GradleDetector unit tests are in the Studio plugin, as tests
- * for IntellijGradleDetector
+ * NOTE: Many of these tests are duplicated in the Android Studio plugin to
+ * test the custom GradleDetector subclass, IntellijGradleDetector, which
+ * customizes some behavior to be based on top of PSI rather than the Groovy parser.
  */
 public class GradleDetectorTest extends AbstractCheckTest {
 
@@ -526,7 +528,6 @@
                 lintProject("gradle/PreviewDependencies.gradle=>build.gradle"));
     }
 
-
     public void testDependenciesInVariables() throws Exception {
         mEnabled = Collections.singleton(DEPENDENCY);
         assertEquals(""
@@ -538,6 +539,37 @@
                 lintProject("gradle/DependenciesVariable.gradle=>build.gradle"));
     }
 
+    public void testPlayServiceConsistency() throws Exception {
+        // Requires custom model mocks
+        mEnabled = Collections.singleton(COMPATIBILITY);
+        assertEquals(""
+                + "build.gradle:4: Error: All com.google.android.gms libraries must use the "
+                + "exact same version specification (mixing versions can lead to runtime "
+                + "crashes). Found versions 7.5.0, 7.3.0. Examples include "
+                + "com.google.android.gms:play-services-wearable:7.5.0 and "
+                + "com.google.android.gms:play-services-location:7.3.0 [GradleCompatible]\n"
+                + "    compile 'com.google.android.gms:play-services-wearable:7.5.0'\n"
+                + "    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+                + "1 errors, 0 warnings\n",
+
+                lintProjectIncrementally("build.gradle",
+                        "gradle/PlayServices2.gradle=>build.gradle"));
+    }
+
+    public void testPlayServiceConsistencyNonIncremental() throws Exception {
+        // Requires custom model mocks
+        mEnabled = Collections.singleton(COMPATIBILITY);
+        assertEquals(""
+                        + "build.gradle: Error: All com.google.android.gms libraries must use "
+                        + "the exact same version specification (mixing versions can lead to "
+                        + "runtime crashes). Found versions 7.5.0, 7.3.0. Examples include "
+                        + "com.google.android.gms:play-services-wearable:7.5.0 and "
+                        + "com.google.android.gms:play-services-location:7.3.0 [GradleCompatible]\n"
+                        + "1 errors, 0 warnings\n",
+
+                lintProject("gradle/PlayServices2.gradle=>build.gradle"));
+    }
+
     @Override
     protected void checkReportedError(@NonNull Context context, @NonNull Issue issue,
             @NonNull Severity severity, @Nullable Location location, @NonNull String message) {
@@ -638,11 +670,8 @@
             @NonNull
             @Override
             protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
-                if (!"testDependenciesInVariables".equals(getName())) {
-                    return super.createProject(dir, referenceDir);
-                }
-
-                return new Project(this, dir, referenceDir) {
+                if ("testDependenciesInVariables".equals(getName())) {
+                        return new Project(this, dir, referenceDir) {
                     @Override
                     public boolean isGradleProject() {
                         return true;
@@ -676,7 +705,64 @@
                         when(variant.getMainArtifact()).thenReturn(artifact);
                         return variant;
                     }
-                };
+                        };
+                }
+
+                if ("testPlayServiceConsistency".equals(getName())
+                        || "testPlayServiceConsistencyNonIncremental".equals(getName())) {
+                    return new Project(this, dir, referenceDir) {
+                        @Override
+                        public boolean isGradleProject() {
+                            return true;
+                        }
+
+                        @Nullable
+                        @Override
+                        public Variant getCurrentVariant() {
+                        /*
+                        Simulate variant which has an AndroidLibrary with
+                        resolved coordinates
+
+                        b//22709708
+                        com.google.android.gms:play-services-location:7.3.0
+                        com.google.android.gms:play-services-wearable:7.5.0
+                         */
+                            MavenCoordinates coordinates1 = mock(MavenCoordinates.class);
+                            when(coordinates1.getGroupId()).thenReturn("com.google.android.gms");
+                            when(coordinates1.getArtifactId()).thenReturn("play-services-location");
+                            when(coordinates1.getVersion()).thenReturn("7.3.0");
+                            when(coordinates1.toString()).thenReturn("com.google.android.gms:play-services-location:7.3.0");
+
+                            MavenCoordinates coordinates2 = mock(MavenCoordinates.class);
+                            when(coordinates2.getGroupId()).thenReturn("com.google.android.gms");
+                            when(coordinates2.getArtifactId()).thenReturn("play-services-wearable");
+                            when(coordinates2.getVersion()).thenReturn("7.5.0");
+                            when(coordinates2.toString()).thenReturn("com.google.android.gms:play-services-wearable:7.5.0");
+
+                            AndroidLibrary library1 = mock(AndroidLibrary.class);
+                            when(library1.getResolvedCoordinates()).thenReturn(coordinates1);
+                            when(library1.getLintJar()).thenReturn(new File("lint.jar"));
+
+                            AndroidLibrary library2 = mock(AndroidLibrary.class);
+                            when(library2.getResolvedCoordinates()).thenReturn(coordinates2);
+                            when(library2.getLintJar()).thenReturn(new File("lint.jar"));
+
+                            List<AndroidLibrary> libraries = Arrays.asList(library1, library2);
+
+                            Dependencies dependencies = mock(Dependencies.class);
+                            when(dependencies.getLibraries()).thenReturn(libraries);
+
+                            AndroidArtifact artifact = mock(AndroidArtifact.class);
+                            when(artifact.getDependencies()).thenReturn(dependencies);
+
+                            Variant variant = mock(Variant.class);
+                            when(variant.getMainArtifact()).thenReturn(artifact);
+                            return variant;
+                        }
+                    };
+                }
+
+                return super.createProject(dir, referenceDir);
             }
         };
     }
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 31fedb1..db62652 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
@@ -636,7 +636,11 @@
     public void testMissingBackupInTarget23() throws Exception {
         mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
         assertEquals(""
-                + "AndroidManifest.xml:5: Warning: Should explicitly set android:fullBackupContent to true or false to opt-in to or out of full app data back-up and restore, or alternatively to an @xml resource which specifies which files to backup [AllowBackup]\n"
+                + "AndroidManifest.xml:5: Warning: On SDK version 23 and up, your app data will "
+                + "be automatically backed up and restored on app install. Consider "
+                + "adding the attribute android:fullBackupContent to specify an @xml "
+                + "resource which configures which files to backup. More info: "
+                + "https://developer.android.com/preview/backup/index.html [AllowBackup]\n"
                 + "    <application\n"
                 + "    ^\n"
                 + "0 errors, 1 warnings\n",
@@ -656,6 +660,25 @@
                                 + "</manifest>\n")));
     }
 
+    public void testMissingBackupInPreTarget23() throws Exception {
+        mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
+        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"
+                                + "    <uses-sdk android:targetSdkVersion=\"21\" />"
+                                + "\n"
+                                + "    <application\n"
+                                + "        android:label=\"@string/app_name\"\n"
+                                + "        android:theme=\"@style/AppTheme\" >\n"
+                                + "    </application>\n"
+                                + "\n"
+                                + "</manifest>\n")));
+    }
+
     public void testMissingBackupWithoutGcmPreTarget23() throws Exception {
         mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
         assertEquals("No warnings.",
@@ -675,10 +698,14 @@
                                 + "</manifest>\n")));
     }
 
-    public void testMissingBackupWithGcmPreTarget23() throws Exception {
+    public void testMissingBackupWithoutGcmPostTarget23() throws Exception {
         mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
         assertEquals(""
-                + "AndroidManifest.xml:5: Warning: Should explicitly set android:fullBackupContent to avoid backing up the GCM device specific regId. [AllowBackup]\n"
+                + "AndroidManifest.xml:5: Warning: On SDK version 23 and up, your app "
+                + "data will be automatically backed up and restored on app install. "
+                + "Consider adding the attribute android:fullBackupContent to specify "
+                + "an @xml resource which configures which files to backup. "
+                + "More info: https://developer.android.com/preview/backup/index.html [AllowBackup]\n"
                 + "    <application\n"
                 + "    ^\n"
                 + "0 errors, 1 warnings\n",
@@ -688,6 +715,25 @@
                                 + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                                 + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
                                 + "    package=\"com.example.helloworld\" >\n"
+                                + "    <uses-sdk android:targetSdkVersion=\"23\" />"
+                                + "\n"
+                                + "    <application\n"
+                                + "        android:label=\"@string/app_name\"\n"
+                                + "        android:theme=\"@style/AppTheme\" >\n"
+                                + "    </application>\n"
+                                + "\n"
+                                + "</manifest>\n")));
+    }
+
+    public void testMissingBackupWithGcmPreTarget23() throws Exception {
+        mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
+        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"
                                 + "    <uses-sdk android:targetSdkVersion=\"21\" />"
                                 + "\n"
                                 + "    <application\n"
@@ -706,6 +752,72 @@
                                 + "</manifest>\n")));
     }
 
+    public void testMissingBackupWithGcmPostTarget23() throws Exception {
+        mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
+        assertEquals(""
+                + "AndroidManifest.xml:5: Warning: On SDK version 23 and up, your app "
+                + "data will be automatically backed up, and restored on app install. "
+                + "Your GCM regid will not work across restores, so you must ensure that "
+                + "it is excluded from the back-up set. Use the attribute "
+                + "android:fullBackupContent to specify an @xml resource which "
+                + "configures which files to backup. More info: "
+                + "https://developer.android.com/preview/backup/index.html [AllowBackup]\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"
+                                + "    <uses-sdk android:targetSdkVersion=\"23\" />"
+                                + "\n"
+                                + "    <application\n"
+                                + "        android:label=\"@string/app_name\"\n"
+                                + "        android:theme=\"@style/AppTheme\" >"
+                                + "        <receiver\n"
+                                + "            android:name=\".GcmBroadcastReceiver\"\n"
+                                + "            android:permission=\"com.google.android.c2dm.permission.SEND\" >\n"
+                                + "            <intent-filter>\n"
+                                + "                <action android:name=\"com.google.android.c2dm.intent.RECEIVE\" />\n"
+                                + "                <category android:name=\"com.example.gcm\" />\n"
+                                + "            </intent-filter>\n"
+                                + "        </receiver>\n"
+                                + "    </application>\n"
+                                + "\n"
+                                + "</manifest>\n")));
+    }
+
+    public void testNoMissingFullBackupWithDoNotAllowBackup() throws Exception {
+        // Regression test for https://code.google.com/p/android/issues/detail?id=181805
+        mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
+        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"
+                                + "    <uses-sdk android:targetSdkVersion=\"21\" />"
+                                + "\n"
+                                + "    <application\n"
+                                + "        android:label=\"@string/app_name\"\n"
+                                + "        android:allowBackup=\"false\"\n"
+                                + "        android:theme=\"@style/AppTheme\" >"
+                                + "        <receiver\n"
+                                + "            android:name=\".GcmBroadcastReceiver\"\n"
+                                + "            android:permission=\"com.google.android.c2dm.permission.SEND\" >\n"
+                                + "            <intent-filter>\n"
+                                + "                <action android:name=\"com.google.android.c2dm.intent.RECEIVE\" />\n"
+                                + "                <category android:name=\"com.example.gcm\" />\n"
+                                + "            </intent-filter>\n"
+                                + "        </receiver>\n"
+                                + "    </application>\n"
+                                + "\n"
+                                + "</manifest>\n")));
+    }
+
     // Custom project which locates all manifest files in the project rather than just
     // being hardcoded to the root level
 
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/PlayServices2.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/PlayServices2.gradle
new file mode 100644
index 0000000..34f329d
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/PlayServices2.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'android'
+
+dependencies {
+    compile 'com.google.android.gms:play-services-wearable:7.5.0'
+    compile 'com.google.android.gms:play-services-location:7.3.0'
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/resources/ResourceResolver.java b/sdk-common/src/main/java/com/android/ide/common/resources/ResourceResolver.java
index 23f471b..ca1c93d 100644
--- a/sdk-common/src/main/java/com/android/ide/common/resources/ResourceResolver.java
+++ b/sdk-common/src/main/java/com/android/ide/common/resources/ResourceResolver.java
@@ -853,7 +853,7 @@
 
         @Override
         public ResourceValue findResValue(String reference, boolean forceFrameworkOnly) {
-            if (!mLookupChain.isEmpty() && reference.startsWith(PREFIX_RESOURCE_REF)) {
+            if (!mLookupChain.isEmpty() && reference != null && reference.startsWith(PREFIX_RESOURCE_REF)) {
                 ResourceValue prev = mLookupChain.get(mLookupChain.size() - 1);
                 if (!reference.equals(prev.getValue())) {
                     ResourceValue next = new ResourceValue(prev.getResourceType(), prev.getName(),