Merge "device panel: don't trigger selection listeners on certain updates" into idea133
diff --git a/android/src/com/android/tools/idea/gradle/IdeaAndroidProject.java b/android/src/com/android/tools/idea/gradle/IdeaAndroidProject.java
index 86c0958..7d5bd0d 100644
--- a/android/src/com/android/tools/idea/gradle/IdeaAndroidProject.java
+++ b/android/src/com/android/tools/idea/gradle/IdeaAndroidProject.java
@@ -195,4 +195,8 @@
   public String computePackageName() {
     return getSelectedVariant().getMainArtifact().getPackageName();
   }
+
+  public boolean isLibrary() {
+    return getDelegate().isLibrary();
+  }
 }
diff --git a/android/src/com/android/tools/idea/gradle/messages/CommonMessageGroupNames.java b/android/src/com/android/tools/idea/gradle/messages/CommonMessageGroupNames.java
index ba23135..98a36d2 100644
--- a/android/src/com/android/tools/idea/gradle/messages/CommonMessageGroupNames.java
+++ b/android/src/com/android/tools/idea/gradle/messages/CommonMessageGroupNames.java
@@ -25,6 +25,7 @@
   public static final String UNRESOLVED_ANDROID_DEPENDENCIES = "Unresolved Android dependencies";
   public static final String UNRESOLVED_DEPENDENCIES = "Unresolved dependencies";
   public static final String VARIANT_SELECTION_CONFLICTS = "Variant selection conflicts";
+  public static final String PROJECT_STRUCTURE_CONFLICTS = "Project structure conflicts";
 
   private CommonMessageGroupNames() {
   }
diff --git a/android/src/com/android/tools/idea/gradle/messages/ProjectSyncMessages.java b/android/src/com/android/tools/idea/gradle/messages/ProjectSyncMessages.java
index 6f170f9..effaef2 100644
--- a/android/src/com/android/tools/idea/gradle/messages/ProjectSyncMessages.java
+++ b/android/src/com/android/tools/idea/gradle/messages/ProjectSyncMessages.java
@@ -16,13 +16,17 @@
 package com.android.tools.idea.gradle.messages;
 
 import com.android.tools.idea.gradle.messages.navigatable.ProjectSyncErrorNavigatable;
+import com.android.tools.idea.gradle.service.notification.GradleNotificationExtension;
+import com.android.tools.idea.gradle.service.notification.NotificationHyperlink;
 import com.intellij.openapi.components.ServiceManager;
 import com.intellij.openapi.externalSystem.service.notification.ExternalSystemNotificationManager;
 import com.intellij.openapi.externalSystem.service.notification.NotificationCategory;
 import com.intellij.openapi.externalSystem.service.notification.NotificationData;
 import com.intellij.openapi.externalSystem.service.notification.NotificationSource;
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.pom.Navigatable;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.plugins.gradle.util.GradleConstants;
@@ -58,16 +62,26 @@
     return myNotificationManager.getMessageCount(groupName, NotificationSource.PROJECT_SYNC, null, GradleConstants.SYSTEM_ID);
   }
 
-  public void add(@NotNull final Message message) {
+  public void add(@NotNull final Message message, @NotNull NotificationHyperlink... hyperlinks) {
     Navigatable navigatable = message.getNavigatable();
     if (navigatable instanceof ProjectSyncErrorNavigatable) {
       ((ProjectSyncErrorNavigatable)navigatable).setProject(myProject);
     }
-    NotificationData notification = new NotificationData(
-      message.getGroupName(), StringUtil.join(message.getText(), "\n"),
-      NotificationCategory.convert(message.getType().getValue()), NotificationSource.PROJECT_SYNC,
-      message.getFile() != null ? message.getFile().getPath() : null, message.getLine(), message.getColumn(), false);
+    String title = message.getGroupName();
+    String errorMsg = StringUtil.join(message.getText(), "\n");
+
+    VirtualFile file = message.getFile();
+    String filePath = file != null ? FileUtil.toSystemDependentName(file.getPath()) : null;
+
+    NotificationData notification =
+      new NotificationData(title, errorMsg, NotificationCategory.convert(message.getType().getValue()), NotificationSource.PROJECT_SYNC,
+                           filePath, message.getLine(), message.getColumn(), false);
     notification.setNavigatable(navigatable);
+
+    if (hyperlinks.length > 0) {
+      GradleNotificationExtension.updateNotification(notification, myProject, title, errorMsg, hyperlinks);
+    }
+
     myNotificationManager.showNotification(GradleConstants.SYSTEM_ID, notification);
   }
 
diff --git a/android/src/com/android/tools/idea/gradle/project/AndroidGradleProjectComponent.java b/android/src/com/android/tools/idea/gradle/project/AndroidGradleProjectComponent.java
index ca09d60..eac5ff1 100755
--- a/android/src/com/android/tools/idea/gradle/project/AndroidGradleProjectComponent.java
+++ b/android/src/com/android/tools/idea/gradle/project/AndroidGradleProjectComponent.java
@@ -37,7 +37,6 @@
 import com.intellij.openapi.compiler.CompileContext;
 import com.intellij.openapi.components.AbstractProjectComponent;
 import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.externalSystem.model.ExternalSystemDataKeys;
 import com.intellij.openapi.externalSystem.util.ExternalSystemConstants;
 import com.intellij.openapi.module.JavaModuleType;
@@ -60,8 +59,6 @@
 import java.util.List;
 
 public class AndroidGradleProjectComponent extends AbstractProjectComponent {
-  private static final Logger LOG = Logger.getInstance(AndroidGradleProjectComponent.class);
-
   @NonNls private static final String SHOW_MIGRATE_TO_GRADLE_POPUP = "show.migrate.to.gradle.popup";
 
   @Nullable private Disposable myDisposable;
@@ -117,8 +114,8 @@
 
     StudioBuildStatsPersistenceComponent stats = StudioBuildStatsPersistenceComponent.getInstance();
     if (stats != null) {
-      BuildRecord b = new BuildRecord(StatsKeys.PROJECT_OPENED, isGradleProject ? "gradle" : "not-gradle");
-      stats.addBuildRecord(b);
+      BuildRecord record = new BuildRecord(StatsKeys.PROJECT_OPENED, isGradleProject ? "gradle" : "not-gradle");
+      stats.addBuildRecord(record);
     }
   }
 
diff --git a/android/src/com/android/tools/idea/gradle/project/PostProjectSyncTasksExecutor.java b/android/src/com/android/tools/idea/gradle/project/PostProjectSyncTasksExecutor.java
index deef48a..cbc0318 100755
--- a/android/src/com/android/tools/idea/gradle/project/PostProjectSyncTasksExecutor.java
+++ b/android/src/com/android/tools/idea/gradle/project/PostProjectSyncTasksExecutor.java
@@ -27,7 +27,11 @@
 import com.android.tools.idea.gradle.service.notification.NotificationHyperlink;
 import com.android.tools.idea.gradle.util.ProjectBuilder;
 import com.android.tools.idea.gradle.util.Projects;
-import com.android.tools.idea.gradle.variant.VariantSelectionVerifier;
+import com.android.tools.idea.gradle.variant.Conflict;
+import com.android.tools.idea.gradle.variant.ConflictResolution;
+import com.android.tools.idea.gradle.variant.ConflictSet;
+import com.android.tools.idea.gradle.variant.profiles.ProjectProfileSelectionDialog;
+import com.android.tools.idea.gradle.variant.view.BuildVariantView;
 import com.android.tools.idea.rendering.ProjectResourceRepository;
 import com.android.tools.idea.sdk.DefaultSdks;
 import com.android.tools.idea.startup.AndroidStudioSpecificInitializer;
@@ -56,6 +60,7 @@
 import com.intellij.openapi.vfs.VfsUtil;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.pom.Navigatable;
+import com.intellij.util.SystemProperties;
 import com.intellij.util.io.URLUtil;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -100,7 +105,7 @@
       AndroidGradleProjectComponent.getInstance(myProject).checkForSupportedModules();
     }
 
-    findAndShowVariantSelectionConflicts();
+    findAndShowVariantConflicts();
 
     ProjectResourceRepository.moduleRootsChanged(myProject);
 
@@ -331,16 +336,21 @@
     }
   }
 
-  private void findAndShowVariantSelectionConflicts() {
-    // TODO: enable this code to allow creation of project profiles.
-    //ImmutableList<SelectionConflict> conflicts = VariantSelectionVerifier.getInstance(myProject).findSelectionConflicts();
-    //if (!conflicts.isEmpty()) {
-    //  ProjectProfileSelectionDialog dialog = new ProjectProfileSelectionDialog(myProject, conflicts);
-    //  dialog.show();
-    //}
+  private void findAndShowVariantConflicts() {
+    ConflictSet conflicts = ConflictResolution.findConflicts(myProject);
 
-    VariantSelectionVerifier verifier = new VariantSelectionVerifier(myProject);
-    verifier.findAndShowSelectionConflicts();
+    List<Conflict> structureConflicts = conflicts.getStructureConflicts();
+
+    if (!structureConflicts.isEmpty()) {
+      ConflictResolution.displayStructureConflicts(myProject, structureConflicts);
+      if (SystemProperties.getBooleanProperty("enable.project.profiles", false)) {
+        ProjectProfileSelectionDialog dialog = new ProjectProfileSelectionDialog(myProject, structureConflicts);
+        dialog.show();
+      }
+    }
+
+    List<Conflict> selectionConflicts = conflicts.getSelectionConflicts();
+    ConflictResolution.displaySelectionConflicts(myProject, selectionConflicts);
 
     ProjectSyncMessages messages = ProjectSyncMessages.getInstance(myProject);
     if (!messages.isEmpty()) {
diff --git a/android/src/com/android/tools/idea/gradle/service/notification/GradleNotificationExtension.java b/android/src/com/android/tools/idea/gradle/service/notification/GradleNotificationExtension.java
index c36ed51..8f1d3c8 100644
--- a/android/src/com/android/tools/idea/gradle/service/notification/GradleNotificationExtension.java
+++ b/android/src/com/android/tools/idea/gradle/service/notification/GradleNotificationExtension.java
@@ -345,6 +345,15 @@
                                  @NotNull final Project project,
                                  @NotNull String errorMsg,
                                  @NotNull NotificationHyperlink... hyperlinks) {
+    String title = String.format("Failed to refresh Gradle project '%1$s'", project.getName());
+    updateNotification(notification, project, title, errorMsg, hyperlinks);
+  }
+
+  public static void updateNotification(@NotNull NotificationData notification,
+                                        @NotNull final Project project,
+                                        @NotNull String title,
+                                        @NotNull String errorMsg,
+                                        @NotNull NotificationHyperlink... hyperlinks) {
     String text = errorMsg;
     int hyperlinkCount = hyperlinks.length;
     if (hyperlinkCount > 0) {
@@ -357,7 +366,6 @@
       }
       text += ('\n' + b.toString());
     }
-    String title = String.format("Failed to refresh Gradle project '%1$s'", project.getName());
 
     notification.setTitle(title);
     notification.setMessage(text);
diff --git a/android/src/com/android/tools/idea/gradle/variant/SelectionConflict.java b/android/src/com/android/tools/idea/gradle/variant/Conflict.java
similarity index 91%
rename from android/src/com/android/tools/idea/gradle/variant/SelectionConflict.java
rename to android/src/com/android/tools/idea/gradle/variant/Conflict.java
index 0b0b42b..db64c68 100644
--- a/android/src/com/android/tools/idea/gradle/variant/SelectionConflict.java
+++ b/android/src/com/android/tools/idea/gradle/variant/Conflict.java
@@ -24,7 +24,7 @@
 import java.util.Collection;
 import java.util.List;
 
-public class SelectionConflict {
+public class Conflict {
   @NotNull private final Module mySource;
   @NotNull private final String mySelectedVariant;
 
@@ -35,7 +35,7 @@
 
   private boolean myResolved;
 
-  public SelectionConflict(@NotNull Module source, @NotNull String selectedVariant) {
+  public Conflict(@NotNull Module source, @NotNull String selectedVariant) {
     mySource = source;
     mySelectedVariant = selectedVariant;
   }
@@ -102,19 +102,19 @@
   }
 
   public static class AffectedModule {
-    @NotNull private final SelectionConflict myConflict;
+    @NotNull private final Conflict myConflict;
     @NotNull private final Module myTarget;
     @NotNull private final String myExpectedVariant;
     private boolean mySelected = true;
 
-    AffectedModule(@NotNull SelectionConflict conflict, @NotNull Module target, @NotNull String expectedVariant) {
+    AffectedModule(@NotNull Conflict conflict, @NotNull Module target, @NotNull String expectedVariant) {
       myConflict = conflict;
       myTarget = target;
       myExpectedVariant = expectedVariant;
     }
 
     @NotNull
-    public SelectionConflict getConflict() {
+    public Conflict getConflict() {
       return myConflict;
     }
 
diff --git a/android/src/com/android/tools/idea/gradle/variant/ConflictResolution.java b/android/src/com/android/tools/idea/gradle/variant/ConflictResolution.java
new file mode 100644
index 0000000..34b7628
--- /dev/null
+++ b/android/src/com/android/tools/idea/gradle/variant/ConflictResolution.java
@@ -0,0 +1,239 @@
+/*
+ * 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.idea.gradle.variant;
+
+import com.android.builder.model.AndroidLibrary;
+import com.android.tools.idea.gradle.IdeaAndroidProject;
+import com.android.tools.idea.gradle.messages.Message;
+import com.android.tools.idea.gradle.messages.ProjectSyncMessages;
+import com.android.tools.idea.gradle.service.notification.NotificationHyperlink;
+import com.android.tools.idea.gradle.util.GradleUtil;
+import com.android.tools.idea.gradle.variant.view.BuildVariantView;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.module.ModuleUtilCore;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.android.facet.AndroidFacet;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static com.android.tools.idea.gradle.messages.CommonMessageGroupNames.PROJECT_STRUCTURE_CONFLICTS;
+import static com.android.tools.idea.gradle.messages.CommonMessageGroupNames.VARIANT_SELECTION_CONFLICTS;
+
+public final class ConflictResolution {
+  private ConflictResolution() {
+  }
+
+  public static void updateConflicts(@NotNull Project project) {
+    ConflictSet conflicts = findConflicts(project);
+    List<Conflict> selectionConflicts = conflicts.getSelectionConflicts();
+    displaySelectionConflicts(project, selectionConflicts);
+    BuildVariantView view = BuildVariantView.getInstance(project);
+    view.updateNotification(selectionConflicts);
+    view.updateContents();
+  }
+
+  @NotNull
+  public static ConflictSet findConflicts(@NotNull Project project) {
+    Map<String, Conflict> selectionConflicts = Maps.newHashMap();
+    Map<String, Conflict> structureConflicts = Maps.newHashMap();
+
+    ModuleManager moduleManager = ModuleManager.getInstance(project);
+    for (Module module : moduleManager.getModules()) {
+      IdeaAndroidProject currentProject = getAndroidProject(module);
+      if (currentProject == null || !currentProject.isLibrary()) {
+        continue;
+      }
+      String gradlePath = GradleUtil.getGradlePath(module);
+      assert gradlePath != null;
+
+      String selectedVariant = currentProject.getSelectedVariant().getName();
+
+      for (Module dependent : ModuleUtilCore.getAllDependentModules(module)) {
+        IdeaAndroidProject dependentProject = getAndroidProject(dependent);
+        if (dependentProject == null) {
+          continue;
+        }
+        String expectedVariant = getExpectedVariant(dependentProject, gradlePath);
+        if (StringUtil.isEmpty(expectedVariant)) {
+          continue;
+        }
+        addConflict(structureConflicts, module, selectedVariant, dependent, expectedVariant);
+
+        if (!selectedVariant.equals(expectedVariant)) {
+          addConflict(selectionConflicts, module, selectedVariant, dependent, expectedVariant);
+        }
+      }
+    }
+
+    // Structural conflicts are the ones that have more than one group of modules depending on different variants of another module.
+    List<Conflict> structure = Lists.newArrayList();
+    for (Conflict conflict : structureConflicts.values()) {
+      if (conflict.getVariants().size() > 1) {
+        structure.add(conflict);
+      }
+    }
+
+    List<Conflict> selection = Lists.newArrayList();
+    for (Conflict conflict : selectionConflicts.values()) {
+      if (conflict.getVariants().size() == 1) {
+        selection.add(conflict);
+      }
+    }
+
+    return new ConflictSet(selection, structure);
+  }
+
+  private static void addConflict(@NotNull Map<String, Conflict> allConflicts,
+                                  @NotNull Module source,
+                                  @NotNull String selectedVariant,
+                                  @NotNull Module affected,
+                                  @NotNull String expectedVariant) {
+    String causeName = source.getName();
+    Conflict conflict = allConflicts.get(causeName);
+    if (conflict == null) {
+      conflict = new Conflict(source, selectedVariant);
+      allConflicts.put(causeName, conflict);
+    }
+    conflict.addAffectedModule(affected, expectedVariant);
+  }
+
+  @Nullable
+  private static String getExpectedVariant(@NotNull IdeaAndroidProject dependentProject, @NotNull String dependencyGradlePath) {
+    List<AndroidLibrary> dependencies = GradleUtil.getDirectLibraryDependencies(dependentProject.getSelectedVariant());
+    for (AndroidLibrary dependency : dependencies) {
+      if (!dependencyGradlePath.equals(dependency.getProject())) {
+        continue;
+      }
+      return dependency.getProjectVariant();
+    }
+    return null;
+  }
+
+  public static boolean solveSelectionConflict(@NotNull Conflict conflict) {
+    AndroidFacet facet = AndroidFacet.getInstance(conflict.getSource());
+    if (facet == null || !facet.isGradleProject()) {
+      // project structure may have changed and the conflict is not longer applicable.
+      return true;
+    }
+    IdeaAndroidProject source = facet.getIdeaAndroidProject();
+    if (source == null) {
+      return false;
+    }
+    Collection<String> variants = conflict.getVariants();
+    assert variants.size() == 1;
+    String expectedVariant = ContainerUtil.getFirstItem(variants);
+    if (StringUtil.isNotEmpty(expectedVariant)) {
+      source.setSelectedVariantName(expectedVariant);
+      facet.syncSelectedVariant();
+      return true;
+    }
+    return false;
+  }
+
+  @Nullable
+  private static IdeaAndroidProject getAndroidProject(@NotNull Module module) {
+    AndroidFacet facet = AndroidFacet.getInstance(module);
+    if (facet == null || !facet.isGradleProject()) {
+      return null;
+    }
+    return facet.getIdeaAndroidProject();
+  }
+
+  public static void displaySelectionConflicts(@NotNull final Project project, @NotNull List<Conflict> conflicts) {
+    String groupName = VARIANT_SELECTION_CONFLICTS;
+
+    ProjectSyncMessages messages = ProjectSyncMessages.getInstance(project);
+    messages.removeMessages(groupName);
+
+    for (final Conflict conflict : conflicts) {
+      final Module source = conflict.getSource();
+      String text = String.format("Module '%1$s' has variant '%2$s' selected, ", source.getName(), conflict.getSelectedVariant());
+
+      List<String> expectedVariants = Lists.newArrayList(conflict.getVariants());
+      assert expectedVariants.size() == 1;
+
+      String expectedVariant = expectedVariants.get(0);
+      List<String> modules = Lists.newArrayList();
+      for (Conflict.AffectedModule affected : conflict.getModulesExpectingVariant(expectedVariant)) {
+        modules.add("'" + affected.getTarget().getName() + "'");
+
+      }
+      Collections.sort(modules);
+      text += String.format("but variant '%1$s' is expected by module(s) %2$s.", expectedVariant, modules);
+
+      String hyperlinkText = String.format("Select '%1$s' in \"Build Variants\" window", source.getName());
+      NotificationHyperlink selectInBuildVariantsWindowHyperlink =
+        new NotificationHyperlink("select.conflict.in.variants.window", hyperlinkText) {
+        @Override
+        protected void execute(@NotNull Project project) {
+          BuildVariantView.getInstance(project).selectAndScrollTo(source);
+        }
+      };
+
+      NotificationHyperlink quickFixHyperlink = new NotificationHyperlink("fix.conflict", "Fix problem") {
+        @Override
+        protected void execute(@NotNull Project project) {
+          boolean solved = solveSelectionConflict(conflict);
+          if (solved) {
+            updateConflicts(project);
+          }
+        }
+      };
+
+      Message msg = new Message(groupName, Message.Type.ERROR, text);
+      messages.add(msg, selectInBuildVariantsWindowHyperlink, quickFixHyperlink);
+    }
+  }
+
+  public static void displayStructureConflicts(@NotNull final Project project, @NotNull List<Conflict> conflicts) {
+    String groupName = PROJECT_STRUCTURE_CONFLICTS;
+
+    ProjectSyncMessages messages = ProjectSyncMessages.getInstance(project);
+    messages.removeMessages(groupName);
+
+    for (Conflict conflict : conflicts) {
+      List<String> text = Lists.newArrayList();
+      final Module conflictSource = conflict.getSource();
+      text.add(String.format("Module '%1$s' has variant '%2$s' selected.", conflictSource.getName(), conflict.getSelectedVariant()));
+
+      List<String> expectedVariants = Lists.newArrayList(conflict.getVariants());
+      Collections.sort(expectedVariants);
+
+      for (String expectedVariant : expectedVariants) {
+        List<String> modules = Lists.newArrayList();
+        for (Conflict.AffectedModule affected : conflict.getModulesExpectingVariant(expectedVariant)) {
+          modules.add("'" + affected.getTarget().getName() + "'");
+
+        }
+        Collections.sort(modules);
+        text.add(String.format("- Variant '%1$s' expected by module(s) %2$s.", expectedVariant, modules));
+        text.add("");
+      }
+
+      messages.add(new Message(groupName, Message.Type.ERROR, text.toArray(new String[text.size()])));
+    }
+  }
+}
diff --git a/android/src/com/android/tools/idea/gradle/variant/ConflictSet.java b/android/src/com/android/tools/idea/gradle/variant/ConflictSet.java
new file mode 100644
index 0000000..a86a13c
--- /dev/null
+++ b/android/src/com/android/tools/idea/gradle/variant/ConflictSet.java
@@ -0,0 +1,56 @@
+/*
+ * 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.idea.gradle.variant;
+
+import com.google.common.collect.ImmutableList;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Set of all variant-selection-related conflicts. We classify these conflicts in 2 groups:
+ * <ol>
+ * <li>
+ * <b>Selection conflicts.</b> These conflicts occur when module A depends on module B/variant X but module B has variant Y selected
+ * instead. These conflicts can be easily fixed by selecting the right variant in the "Build Variants" tool window.
+ * </li>
+ * <b>Structure conflicts.</b> These conflicts occur when there are multiple modules depending on different variants of a single module.
+ * For example, module A depends on module E/variant X, module B depends on module E/variant Y and module C depends on module E/variant Z.
+ * These conflicts cannot be resolved through the "Build Variants" tool window because regardless of the variant is selected on module E,
+ * we will always have a selection conflict. These conflicts can be resolved by importing a subset of modules into the IDE (i.e. project
+ * profiles.)
+ * </ol>
+ */
+public class ConflictSet {
+  @NotNull private final ImmutableList<Conflict> mySelectionConflicts;
+  @NotNull private final ImmutableList<Conflict> myStructureConflicts;
+
+  ConflictSet(@NotNull Collection<Conflict> selectionConflicts, @NotNull Collection<Conflict> structureConflicts) {
+    mySelectionConflicts = ImmutableList.copyOf(selectionConflicts);
+    myStructureConflicts = ImmutableList.copyOf(structureConflicts);
+  }
+
+  @NotNull
+  public List<Conflict> getSelectionConflicts() {
+    return mySelectionConflicts;
+  }
+
+  @NotNull
+  public List<Conflict> getStructureConflicts() {
+    return myStructureConflicts;
+  }
+}
diff --git a/android/src/com/android/tools/idea/gradle/variant/VariantSelectionVerifier.java b/android/src/com/android/tools/idea/gradle/variant/VariantSelectionVerifier.java
deleted file mode 100644
index d07ad62..0000000
--- a/android/src/com/android/tools/idea/gradle/variant/VariantSelectionVerifier.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * 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.idea.gradle.variant;
-
-import com.android.builder.model.AndroidLibrary;
-import com.android.tools.idea.gradle.IdeaAndroidProject;
-import com.android.tools.idea.gradle.messages.AbstractNavigatable;
-import com.android.tools.idea.gradle.messages.Message;
-import com.android.tools.idea.gradle.messages.ProjectSyncMessages;
-import com.android.tools.idea.gradle.util.GradleUtil;
-import com.android.tools.idea.gradle.variant.view.BuildVariantView;
-import com.google.common.collect.*;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.module.Module;
-import com.intellij.openapi.module.ModuleManager;
-import com.intellij.openapi.module.ModuleUtilCore;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.pom.Navigatable;
-import org.jetbrains.android.facet.AndroidFacet;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.*;
-
-import static com.android.tools.idea.gradle.messages.CommonMessageGroupNames.VARIANT_SELECTION_CONFLICTS;
-
-public class VariantSelectionVerifier {
-  @NotNull private final Project myProject;
-
-  public VariantSelectionVerifier(@NotNull Project project) {
-    myProject = project;
-  }
-
-  public void findAndShowSelectionConflicts() {
-    ImmutableList<SelectionConflict> conflicts = findSelectionConflicts();
-
-    ProjectSyncMessages messages = ProjectSyncMessages.getInstance(myProject);
-    messages.removeMessages(VARIANT_SELECTION_CONFLICTS);
-
-    for (SelectionConflict conflict : conflicts) {
-      List<String> text = Lists.newArrayList();
-      final Module conflictSource = conflict.getSource();
-      text.add(String.format("Module '%1$s' has variant '%2$s' selected.", conflictSource.getName(), conflict.getSelectedVariant()));
-
-
-      List<String> expectedVariants = Lists.newArrayList(conflict.getVariants());
-      Collections.sort(expectedVariants);
-
-      for (String expectedVariant : expectedVariants) {
-        List<String> modules = Lists.newArrayList();
-        for (SelectionConflict.AffectedModule affected : conflict.getModulesExpectingVariant(expectedVariant)) {
-          modules.add("'" + affected.getTarget().getName() + "'");
-
-        }
-        Collections.sort(modules);
-        text.add(String.format("- Variant '%1$s' expected by module(s) %2$s.", expectedVariant, modules));
-        text.add("");
-      }
-
-      Navigatable navigateToConflictSource = new AbstractNavigatable() {
-        @Override
-        public void navigate(boolean requestFocus) {
-          BuildVariantView.getInstance(myProject).selectAndScrollTo(conflictSource);
-        }
-
-        @Override
-        public boolean canNavigate() {
-          return true;
-        }
-      };
-
-      messages.add(new Message(VARIANT_SELECTION_CONFLICTS, Message.Type.ERROR, navigateToConflictSource, text.toArray(new String[text.size()])));
-    }
-
-    BuildVariantView.getInstance(myProject).updateNotification(conflicts);
-  }
-
-  @NotNull
-  public ImmutableList<SelectionConflict> findSelectionConflicts() {
-    Map<String, SelectionConflict> conflictsByModuleName = Maps.newHashMap();
-
-    ModuleManager moduleManager = ModuleManager.getInstance(myProject);
-    for (Module module : moduleManager.getModules()) {
-      String gradlePath = GradleUtil.getGradlePath(module);
-      if (StringUtil.isEmpty(gradlePath)) {
-        continue;
-      }
-      IdeaAndroidProject libraryProject = getAndroidProject(module, true);
-      if (libraryProject == null || !libraryProject.getDelegate().isLibrary()) {
-        continue;
-      }
-
-      String selectedVariant = libraryProject.getSelectedVariant().getName();
-
-      for (Module dependent : ModuleUtilCore.getAllDependentModules(module)) {
-        IdeaAndroidProject androidProject = getAndroidProject(dependent, false);
-        if (androidProject == null) {
-          continue;
-        }
-
-        for (AndroidLibrary library : GradleUtil.getDirectLibraryDependencies(androidProject.getSelectedVariant())) {
-          if (!gradlePath.equals(library.getProject())) {
-            continue;
-          }
-          String expected = library.getProjectVariant();
-          if (StringUtil.isNotEmpty(expected) && !selectedVariant.equals(expected)) {
-            String causeName = module.getName();
-            SelectionConflict conflict = conflictsByModuleName.get(causeName);
-            if (conflict == null) {
-              conflict = new SelectionConflict(module, selectedVariant);
-              conflictsByModuleName.put(causeName, conflict);
-            }
-            conflict.addAffectedModule(dependent, expected);
-          }
-        }
-      }
-    }
-
-    // Make sure conflict has all modules affected by it.
-    Collection<SelectionConflict> conflicts = conflictsByModuleName.values();
-    for (SelectionConflict conflict : conflicts) {
-      Module source = conflict.getSource();
-      String gradlePath = GradleUtil.getGradlePath(source);
-      assert gradlePath != null;
-
-      for (Module dependent : ModuleUtilCore.getAllDependentModules(source)) {
-        IdeaAndroidProject androidProject = getAndroidProject(dependent, false);
-        if (androidProject == null) {
-          continue;
-        }
-        for (AndroidLibrary library : GradleUtil.getDirectLibraryDependencies(androidProject.getSelectedVariant())) {
-          String expected = library.getProjectVariant();
-          if (!gradlePath.equals(library.getProject())) {
-            continue;
-          }
-          if (!StringUtil.isEmpty(expected) && !conflict.isAffectingDirectly(dependent)) {
-            conflict.addAffectedModule(dependent, expected);
-          }
-        }
-      }
-    }
-
-    return ImmutableList.copyOf(conflicts);
-  }
-
-  @Nullable
-  private static IdeaAndroidProject getAndroidProject(@NotNull Module module, boolean libraryProject) {
-    AndroidFacet facet = AndroidFacet.getInstance(module);
-    if (facet == null || !facet.isGradleProject()) {
-      return null;
-    }
-    IdeaAndroidProject androidProject = facet.getIdeaAndroidProject();
-    if (androidProject != null && androidProject.getDelegate().isLibrary() == libraryProject) {
-      return androidProject;
-    }
-    return null;
-  }
-}
diff --git a/android/src/com/android/tools/idea/gradle/variant/profiles/ProjectProfileSelectionDialog.java b/android/src/com/android/tools/idea/gradle/variant/profiles/ProjectProfileSelectionDialog.java
index 7fd29b7..b4eed4b 100644
--- a/android/src/com/android/tools/idea/gradle/variant/profiles/ProjectProfileSelectionDialog.java
+++ b/android/src/com/android/tools/idea/gradle/variant/profiles/ProjectProfileSelectionDialog.java
@@ -19,7 +19,7 @@
 import com.android.builder.model.Variant;
 import com.android.tools.idea.gradle.IdeaAndroidProject;
 import com.android.tools.idea.gradle.util.GradleUtil;
-import com.android.tools.idea.gradle.variant.SelectionConflict;
+import com.android.tools.idea.gradle.variant.Conflict;
 import com.google.common.base.Joiner;
 import com.google.common.base.Objects;
 import com.google.common.collect.HashMultimap;
@@ -71,7 +71,7 @@
     new SimpleTextAttributes(SimpleTextAttributes.STYLE_STRIKEOUT, SimpleTextAttributes.GRAY_ATTRIBUTES.getFgColor());
 
   @NotNull private final Project myProject;
-  @NotNull private final List<SelectionConflict> myConflicts;
+  @NotNull private final List<Conflict> myConflicts;
 
   @NotNull private final JPanel myPanel;
 
@@ -80,12 +80,12 @@
   @NotNull private CheckboxTreeView myConflictTree;
   @NotNull private DetailsComponent myConflictDetails;
 
-  public ProjectProfileSelectionDialog(@NotNull Project project, @NotNull List<SelectionConflict> conflicts) {
+  public ProjectProfileSelectionDialog(@NotNull Project project, @NotNull List<Conflict> conflicts) {
     super(project);
     myProject = project;
     myConflicts = conflicts;
 
-    for (SelectionConflict conflict : conflicts) {
+    for (Conflict conflict : conflicts) {
       conflict.refreshStatus();
     }
 
@@ -129,7 +129,7 @@
 
             if (!moduleElement.myConflicts.isEmpty()) {
               boolean allResolved = true;
-              for (SelectionConflict conflict : moduleElement.myConflicts) {
+              for (Conflict conflict : moduleElement.myConflicts) {
                 if (!conflict.isResolved()) {
                   allResolved = false;
                   break;
@@ -198,7 +198,7 @@
             continue;
           }
 
-          SelectionConflict conflict = getConflict(dependency);
+          Conflict conflict = getConflict(dependency);
           modulesByGradlePath.put(gradlePath, dependency);
 
           DependencyTreeElement dependencyElement =
@@ -287,10 +287,10 @@
             }
             CheckedTreeNode moduleNode = (CheckedTreeNode)child;
             data = moduleNode.getUserObject();
-            if (!(data instanceof SelectionConflict.AffectedModule)) {
+            if (!(data instanceof Conflict.AffectedModule)) {
               continue;
             }
-            SelectionConflict.AffectedModule affected = (SelectionConflict.AffectedModule)data;
+            Conflict.AffectedModule affected = (Conflict.AffectedModule)data;
             boolean checked = node.isChecked();
             if (module.equals(affected.getTarget()) && moduleNode.isChecked() != checked) {
               affected.setSelected(checked);
@@ -317,8 +317,8 @@
   }
 
   @Nullable
-  private SelectionConflict getConflict(@NotNull Module source) {
-    for (SelectionConflict conflict : myConflicts) {
+  private Conflict getConflict(@NotNull Module source) {
+    for (Conflict conflict : myConflicts) {
       if (source.equals(conflict.getSource())) {
         return conflict;
       }
@@ -424,7 +424,7 @@
     ConflictsTableModel tableModel = (ConflictsTableModel)myConflictsTable.getModel();
     ConflictTableRow row = tableModel.myRows.get(selectedIndex);
 
-    SelectionConflict conflict = row.myConflict;
+    Conflict conflict = row.myConflict;
     myConflictDetails.setText("Conflict Detail: " + conflict.getSource().getName());
 
     List<String> variants = Lists.newArrayList(conflict.getVariants());
@@ -434,7 +434,7 @@
       CheckedTreeNode variantNode = new CheckedTreeNode(variant);
       myConflictTree.myRoot.add(variantNode);
 
-      for (SelectionConflict.AffectedModule module : conflict.getModulesExpectingVariant(variant)) {
+      for (Conflict.AffectedModule module : conflict.getModulesExpectingVariant(variant)) {
         CheckedTreeNode moduleNode = new CheckedTreeNode(module);
         variantNode.add(moduleNode);
       }
@@ -471,7 +471,7 @@
         show = true;
       }
       else {
-        for (SelectionConflict conflict : moduleElement.myConflicts) {
+        for (Conflict conflict : moduleElement.myConflicts) {
           if (selectedConflictSources.contains(conflict.getSource())) {
             show = true;
             break;
@@ -493,8 +493,8 @@
         if (value instanceof DefaultMutableTreeNode) {
           Object data = ((DefaultMutableTreeNode)value).getUserObject();
           ColoredTreeCellRenderer textRenderer = getTextRenderer();
-          if (data instanceof SelectionConflict.AffectedModule) {
-            textRenderer.append(((SelectionConflict.AffectedModule)data).getTarget().getName());
+          if (data instanceof Conflict.AffectedModule) {
+            textRenderer.append(((Conflict.AffectedModule)data).getTarget().getName());
             textRenderer.setIcon(AllIcons.Actions.Module);
           }
           else if (data instanceof String) {
@@ -510,10 +510,10 @@
       @Override
       protected void onNodeStateChanged(@NotNull CheckedTreeNode node) {
         Object data = node.getUserObject();
-        if (!(data instanceof SelectionConflict.AffectedModule)) {
+        if (!(data instanceof Conflict.AffectedModule)) {
           return;
         }
-        SelectionConflict.AffectedModule affected = (SelectionConflict.AffectedModule)data;
+        Conflict.AffectedModule affected = (Conflict.AffectedModule)data;
         Module module = affected.getTarget();
 
         Enumeration moduleNodes = myProjectStructureTree.myRoot.children();
@@ -564,10 +564,10 @@
     final List<ConflictTableRow> myRows = Lists.newArrayList();
     final Function<List<ConflictTableRow>, Void> myFilterFunction;
 
-    ConflictsTableModel(@NotNull List<SelectionConflict> conflicts, Function<List<ConflictTableRow>, Void> filterFunction) {
+    ConflictsTableModel(@NotNull List<Conflict> conflicts, Function<List<ConflictTableRow>, Void> filterFunction) {
       super(COLUMN_NAMES, conflicts.size());
       myFilterFunction = filterFunction;
-      for (SelectionConflict conflict : conflicts) {
+      for (Conflict conflict : conflicts) {
         myRows.add(new ConflictTableRow(conflict));
       }
       Collections.sort(myRows);
@@ -604,10 +604,10 @@
   }
 
   private static class ConflictTableRow implements Comparable<ConflictTableRow> {
-    final SelectionConflict myConflict;
+    final Conflict myConflict;
     boolean myFilter;
 
-    ConflictTableRow(SelectionConflict conflict) {
+    ConflictTableRow(Conflict conflict) {
       myConflict = conflict;
     }
 
@@ -717,13 +717,13 @@
 
   private static class ModuleTreeElement {
     @NotNull final Module myModule;
-    @NotNull final List<SelectionConflict> myConflicts = Lists.newArrayList();
+    @NotNull final List<Conflict> myConflicts = Lists.newArrayList();
 
     ModuleTreeElement(@NotNull Module module) {
       myModule = module;
     }
 
-    void addConflict(@NotNull SelectionConflict conflict) {
+    void addConflict(@NotNull Conflict conflict) {
       myConflicts.add(conflict);
     }
   }
@@ -733,12 +733,12 @@
     @NotNull final String myGradlePath;
 
     @Nullable final String myVariant;
-    @Nullable final SelectionConflict myConflict;
+    @Nullable final Conflict myConflict;
 
     DependencyTreeElement(@NotNull Module module,
                           @NotNull String gradlePath,
                           @Nullable String variant,
-                          @Nullable SelectionConflict conflict) {
+                          @Nullable Conflict conflict) {
       myGradlePath = gradlePath;
       myVariant = variant;
       myModule = module;
diff --git a/android/src/com/android/tools/idea/gradle/variant/view/BuildVariantUpdater.java b/android/src/com/android/tools/idea/gradle/variant/view/BuildVariantUpdater.java
index e3b6a30..6925303 100644
--- a/android/src/com/android/tools/idea/gradle/variant/view/BuildVariantUpdater.java
+++ b/android/src/com/android/tools/idea/gradle/variant/view/BuildVariantUpdater.java
@@ -24,7 +24,7 @@
 import com.android.tools.idea.gradle.customizer.android.DependenciesModuleCustomizer;
 import com.android.tools.idea.gradle.util.GradleUtil;
 import com.android.tools.idea.gradle.util.ProjectBuilder;
-import com.android.tools.idea.gradle.variant.VariantSelectionVerifier;
+import com.android.tools.idea.gradle.variant.ConflictResolution;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.intellij.openapi.application.ApplicationManager;
@@ -67,7 +67,7 @@
       public void execute() {
         Module updatedModule = doUpdate(project, moduleName, buildVariantName, facets);
         if (updatedModule != null) {
-          new VariantSelectionVerifier(project).findAndShowSelectionConflicts();
+          ConflictResolution.updateConflicts(project);
         }
 
         if (!facets.isEmpty()) {
diff --git a/android/src/com/android/tools/idea/gradle/variant/view/BuildVariantView.java b/android/src/com/android/tools/idea/gradle/variant/view/BuildVariantView.java
index 317b239..e96f66d 100644
--- a/android/src/com/android/tools/idea/gradle/variant/view/BuildVariantView.java
+++ b/android/src/com/android/tools/idea/gradle/variant/view/BuildVariantView.java
@@ -18,7 +18,7 @@
 import com.android.tools.idea.gradle.GradleSyncState;
 import com.android.tools.idea.gradle.IdeaAndroidProject;
 import com.android.tools.idea.gradle.messages.ProjectSyncMessages;
-import com.android.tools.idea.gradle.variant.SelectionConflict;
+import com.android.tools.idea.gradle.variant.Conflict;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
@@ -211,7 +211,7 @@
     return androidFacet != null ? androidFacet.getIdeaAndroidProject() : null;
   }
 
-  public void updateNotification(ImmutableList<SelectionConflict> conflicts) {
+  public void updateNotification(List<Conflict> conflicts) {
     myErrorPanel.removeAll();
     myConflictSources.clear();
 
@@ -227,14 +227,15 @@
       myErrorPanel.add(notification);
     }
 
-    for (SelectionConflict conflict : conflicts) {
+    for (Conflict conflict : conflicts) {
       myConflictSources.add(conflict.getSource().getName());
     }
   }
 
   public void selectAndScrollTo(@NotNull Module module) {
     String name = module.getName();
-    for (int row = 0; row < myVariantsTable.getRowCount() - 1; row++) {
+    int rowCount = myVariantsTable.getRowCount();
+    for (int row = 0; row < rowCount; row++) {
       if (name.equals(myVariantsTable.getValueAt(row, MODULE_COLUMN_INDEX))) {
         myVariantsTable.getSelectionModel().setSelectionInterval(row, row);
         myVariantsTable.getColumnModel().getSelectionModel().setSelectionInterval(MODULE_COLUMN_INDEX, MODULE_COLUMN_INDEX);
diff --git a/android/src/com/android/tools/idea/rendering/LayoutPsiPullParser.java b/android/src/com/android/tools/idea/rendering/LayoutPsiPullParser.java
index 36568d7..f75d0c8 100644
--- a/android/src/com/android/tools/idea/rendering/LayoutPsiPullParser.java
+++ b/android/src/com/android/tools/idea/rendering/LayoutPsiPullParser.java
@@ -19,6 +19,7 @@
 import com.android.ide.common.rendering.api.ILayoutPullParser;
 import com.android.ide.common.res2.ValueXmlHelper;
 import com.android.resources.Density;
+import com.android.resources.ResourceFolderType;
 import com.google.common.base.Splitter;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
@@ -64,7 +65,7 @@
   @Nullable
   protected String myAndroidPrefix;
 
-  private boolean myProvideViewCookies = true;
+  protected boolean myProvideViewCookies = true;
 
   /**
    * Constructs a new {@link LayoutPsiPullParser}, a parser dedicated to the special case of
@@ -75,6 +76,30 @@
    */
   @NotNull
   public static LayoutPsiPullParser create(@NotNull XmlFile file, @NotNull RenderLogger logger) {
+    if (ResourceHelper.getFolderType(file) == ResourceFolderType.MENU) {
+      return new LayoutPsiPullParser(file, logger) {
+        @Nullable
+        @Override
+        public Object getViewCookie() {
+          if (myProvideViewCookies) {
+            Element element = getCurrentNode();
+            if (element != null) {
+              // <menu> tags means that we are adding a sub-menu. Since we don't show the submenu, we
+              // return the enclosing tag.
+              if (element.tag.equals(FD_RES_MENU)) {
+                Element previousElement = getPreviousNode();
+                if (previousElement != null) {
+                  return previousElement.cookie;
+                }
+              }
+              return element.cookie;
+            }
+          }
+
+          return null;
+        }
+      };
+    }
     return new LayoutPsiPullParser(file, logger);
   }
 
@@ -183,6 +208,15 @@
   }
 
   @Nullable
+  protected final Element getPreviousNode() {
+    if (myNodeStack.size() > 1) {
+      return myNodeStack.get(myNodeStack.size() - 2);
+    }
+
+    return null;
+  }
+
+  @Nullable
   protected final Attribute getAttribute(int i) {
     if (myParsingState != START_TAG) {
       throw new IndexOutOfBoundsException();
diff --git a/android/src/com/android/tools/idea/rendering/LayoutlibCallback.java b/android/src/com/android/tools/idea/rendering/LayoutlibCallback.java
index f09f7a4..a45c105 100644
--- a/android/src/com/android/tools/idea/rendering/LayoutlibCallback.java
+++ b/android/src/com/android/tools/idea/rendering/LayoutlibCallback.java
@@ -21,7 +21,6 @@
 import com.android.ide.common.rendering.legacy.LegacyCallback;
 import com.android.ide.common.resources.ResourceResolver;
 import com.android.resources.ResourceType;
-import com.android.tools.idea.model.ManifestInfo;
 import com.android.tools.lint.detector.api.LintUtils;
 import com.android.utils.HtmlBuilder;
 import com.android.utils.SdkUtils;
@@ -76,11 +75,11 @@
   @Nullable private final Object myCredential;
   @Nullable private String myNamespace;
   @Nullable private RenderLogger myLogger;
-  @NotNull private ViewLoader myClassLoader;
+  @NotNull private final ViewLoader myClassLoader;
   @Nullable private String myLayoutName;
   @Nullable private ILayoutPullParser myLayoutEmbeddedParser;
   @Nullable private ResourceResolver myResourceResolver;
-  @NotNull private ActionBarHandler myActionBarHandler;
+  @NotNull private final ActionBarHandler myActionBarHandler;
   private boolean myUsed = false;
   private Set<File> myParserFiles;
   private int myParserCount;
@@ -269,7 +268,7 @@
   @Nullable
   @Override
   public ILayoutPullParser getParser(@NotNull ResourceValue layoutResource) {
-    return getParser(layoutResource.getName(), new File(layoutResource.getName()));
+    return getParser(layoutResource.getName(), new File(layoutResource.getValue()));
   }
 
   @Nullable
@@ -307,17 +306,22 @@
     // contents rather than the most recently saved file contents.
     if (xml != null && xml.isFile()) {
       File parent = xml.getParentFile();
-      if (parent != null && parent.getName().startsWith(FD_RES_LAYOUT)) {
-        VirtualFile file = LocalFileSystem.getInstance().findFileByIoFile(xml);
-        if (file != null) {
-          PsiManager psiManager = PsiManager.getInstance(myModule.getProject());
-          PsiFile psiFile = psiManager.findFile(file);
-          if (psiFile instanceof XmlFile) {
-            assert myLogger != null;
-            LayoutPsiPullParser parser = LayoutPsiPullParser.create((XmlFile)psiFile, myLogger);
-            // For included layouts, don't see view cookies; we want the leaf to point back to the include tag
-            parser.setProvideViewCookies(false);
-            return parser;
+      if (parent != null) {
+        String parentName = parent.getName();
+        if (parentName.startsWith(FD_RES_LAYOUT) || parentName.startsWith(FD_RES_MENU)) {
+          VirtualFile file = LocalFileSystem.getInstance().findFileByIoFile(xml);
+          if (file != null) {
+            PsiManager psiManager = PsiManager.getInstance(myModule.getProject());
+            PsiFile psiFile = psiManager.findFile(file);
+            if (psiFile instanceof XmlFile) {
+              assert myLogger != null;
+              LayoutPsiPullParser parser = LayoutPsiPullParser.create((XmlFile)psiFile, myLogger);
+              if (parentName.startsWith(FD_RES_LAYOUT)) {
+                // For included layouts, don't see view cookies; we want the leaf to point back to the include tag
+                parser.setProvideViewCookies(false);
+              }
+              return parser;
+            }
           }
         }
       }
diff --git a/android/src/com/android/tools/idea/sdk/CheckAndroidSdkUpdates.java b/android/src/com/android/tools/idea/sdk/CheckAndroidSdkUpdates.java
index 5a37988..bc4b8fb 100755
--- a/android/src/com/android/tools/idea/sdk/CheckAndroidSdkUpdates.java
+++ b/android/src/com/android/tools/idea/sdk/CheckAndroidSdkUpdates.java
@@ -58,12 +58,12 @@
   private static final Logger LOG = Logger.getInstance("#com.android.tools.idea.sdk.CheckAndroidSdkUpdates");
   private static final NotificationGroup NOTIFICATION_GROUP = NotificationGroup.balloonGroup("Android SDK Notification Group");
 
-  private static long mCheckTimestampMs;
-  private static BackgroundableProcessIndicator sIndicator;
+  private static long ourCheckTimestampMs;
+  private static BackgroundableProcessIndicator ourIndicator;
 
   public static void checkNow(@NotNull Project project) {
     long now = System.currentTimeMillis();
-    if (now - mCheckTimestampMs <= RemoteSdk.DEFAULT_EXPIRATION_PERIOD_MS) {
+    if (now - ourCheckTimestampMs <= RemoteSdk.DEFAULT_EXPIRATION_PERIOD_MS) {
       LOG.info("Skip: too early");
       return;
     }
@@ -84,11 +84,11 @@
     }
 
 
-    if (sIndicator == null) {
-      mCheckTimestampMs = now;
+    if (ourIndicator == null) {
+      ourCheckTimestampMs = now;
       SdkUpdateCheckTask task = new SdkUpdateCheckTask(project, sdkData);
-      sIndicator = new BackgroundableProcessIndicator(task);
-      ProgressManager.getInstance().runProcessWithProgressAsynchronously(task, sIndicator);
+      ourIndicator = new BackgroundableProcessIndicator(task);
+      ProgressManager.getInstance().runProcessWithProgressAsynchronously(task, ourIndicator);
     }
   }
 
@@ -196,7 +196,7 @@
         LOG.info("Android SDK: " + n + " updates found");
 
       } finally {
-        sIndicator = null;
+        ourIndicator = null;
       }
     }
 
diff --git a/android/src/com/android/tools/idea/stats/BuildRecord.java b/android/src/com/android/tools/idea/stats/BuildRecord.java
index 9791b05..00e1e8b 100755
--- a/android/src/com/android/tools/idea/stats/BuildRecord.java
+++ b/android/src/com/android/tools/idea/stats/BuildRecord.java
@@ -78,7 +78,7 @@
 
   /**
    * Returns the equivalent of {@link System#currentTimeMillis()} in the UTC timezone.
-   * @return Now's timestamp, in millisconds since the epoch, in the UTC timezone.
+   * @return Now's timestamp, in milliseconds since the epoch, in the UTC timezone.
    */
   public static long utcNow() {
     Calendar c = Calendar.getInstance();
diff --git a/android/src/com/android/tools/idea/stats/StatsTimeCollector.java b/android/src/com/android/tools/idea/stats/StatsTimeCollector.java
index 4a021ef..485e7ca 100755
--- a/android/src/com/android/tools/idea/stats/StatsTimeCollector.java
+++ b/android/src/com/android/tools/idea/stats/StatsTimeCollector.java
@@ -33,7 +33,6 @@
  * with the Usage Statistics setting panel. This is by design.</em>
  */
 public class StatsTimeCollector {
-
   private static final boolean isEnabled = StudioBuildStatsPersistenceComponent.getInstance() != null;
   private static final TObjectLongHashMap<String> myTimestampMap = new TObjectLongHashMap<String>();
 
@@ -69,17 +68,19 @@
     }
     try {
       long now = System.currentTimeMillis();
-      long start = 0;
+      long start;
       synchronized (myTimestampMap) {
         start = myTimestampMap.remove(key);
       }
       if (start > 0 && start < now) {
-        StudioBuildStatsPersistenceComponent i = StudioBuildStatsPersistenceComponent.getInstance();
-        if (i != null) {
-          i.addBuildRecord(new BuildRecord(key, Long.toString(now - start)));
+        StudioBuildStatsPersistenceComponent stats = StudioBuildStatsPersistenceComponent.getInstance();
+        if (stats != null) {
+          BuildRecord record = new BuildRecord(key, Long.toString(now - start));
+          stats.addBuildRecord(record);
         }
       }
-    } catch (Throwable ignore) {}
+    } catch (Throwable ignore) {
+    }
   }
 
 }
diff --git a/android/src/com/android/tools/idea/stats/StudioBuildStatsPersistenceComponent.java b/android/src/com/android/tools/idea/stats/StudioBuildStatsPersistenceComponent.java
index ae7f94e..607b3cf 100755
--- a/android/src/com/android/tools/idea/stats/StudioBuildStatsPersistenceComponent.java
+++ b/android/src/com/android/tools/idea/stats/StudioBuildStatsPersistenceComponent.java
@@ -20,9 +20,11 @@
 import com.intellij.openapi.application.Application;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.components.*;
+import com.intellij.util.concurrency.SequentialTaskExecutor;
 import org.jdom.Element;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
+import org.jetbrains.ide.PooledThreadExecutor;
 
 import java.util.ArrayList;
 import java.util.LinkedList;
@@ -63,6 +65,8 @@
 
   private final LinkedList<BuildRecord> myRecords = new LinkedList<BuildRecord>();
 
+  private final SequentialTaskExecutor myTaskExecutor = new SequentialTaskExecutor(PooledThreadExecutor.INSTANCE);
+
   /**
    * Retrieves an instance of the component or null if not available or configured.
    *
@@ -82,7 +86,17 @@
    *
    * @param newRecord to be added to the internal queue.
    */
-  public void addBuildRecord(@NotNull BuildRecord newRecord) {
+  public void addBuildRecord(@NotNull final BuildRecord newRecord) {
+    myTaskExecutor.execute(new Runnable() {
+      @Override
+      public void run() {
+        addBuildRecordImmediately(newRecord);
+      }
+    });
+  }
+
+  @VisibleForTesting
+  void addBuildRecordImmediately(@NotNull BuildRecord newRecord) {
     // Skip if there is no Application, allowing this to run using non-idea unit tests.
     Application app = ApplicationManager.getApplication();
     if (app != null && !app.isUnitTestMode()) {
diff --git a/android/src/org/jetbrains/android/inspections/ResourceTypeInspection.java b/android/src/org/jetbrains/android/inspections/ResourceTypeInspection.java
index e545d24..b05319e 100644
--- a/android/src/org/jetbrains/android/inspections/ResourceTypeInspection.java
+++ b/android/src/org/jetbrains/android/inspections/ResourceTypeInspection.java
@@ -362,8 +362,6 @@
         String qualifiedName = annotation.getQualifiedName();
         if (qualifiedName == null) {
           continue;
-        } else if (ApplicationManager.getApplication().isUnitTestMode() && qualifiedName.indexOf('.') == -1) {
-          qualifiedName = SUPPORT_ANNOTATIONS_PREFIX + qualifiedName;
         }
 
         if (INT_DEF_ANNOTATION.equals(qualifiedName) || STRING_DEF_ANNOTATION.equals(qualifiedName)) {
diff --git a/android/testData/inspections/resourceType/flow/src/X.java b/android/testData/inspections/resourceType/flow/src/X.java
index 5901c13..62ec4cc 100644
--- a/android/testData/inspections/resourceType/flow/src/X.java
+++ b/android/testData/inspections/resourceType/flow/src/X.java
@@ -46,17 +46,17 @@
     }
 
     private static class MimeTypes {
-        @StyleRes
-        @DrawableRes
+        @android.support.annotation.StyleRes
+        @android.support.annotation.DrawableRes
         public static int styleAndDrawable;
 
-        @StyleRes
+        @android.support.annotation.StyleRes
         public static int style;
 
-        @DrawableRes
+        @android.support.annotation.DrawableRes
         public static int drawable;
 
-        @DrawableRes
+        @android.support.annotation.DrawableRes
         public static int getIconForExt(String ext) {
             return R.drawable.my_drawable;
         }
@@ -71,12 +71,12 @@
             return R.drawable.my_drawable;
         }
 
-        @StringRes
+        @android.support.annotation.StringRes
         public static int getAnnotatedString() {
             return R.string.my_string;
         }
 
-        @DrawableRes
+        @android.support.annotation.DrawableRes
         public static int getAnnotatedDrawable() {
             return R.drawable.my_drawable;
         }
diff --git a/android/testData/inspections/resourceType/simple/src/X.java b/android/testData/inspections/resourceType/simple/src/X.java
index 2b899fb..7a1fadf 100644
--- a/android/testData/inspections/resourceType/simple/src/X.java
+++ b/android/testData/inspections/resourceType/simple/src/X.java
@@ -35,7 +35,7 @@
         boolean error4 = resources.getBoolean(flow2);
     }
 
-    @DrawableRes
+    @android.support.annotation.DrawableRes
     public int testResourceTypeReturnValues(Context context, boolean useString) {
         if (useString) {
             return R.string.app_name; // error
diff --git a/android/testSrc/com/android/tools/idea/gradle/variant/VariantSelectionVerifierTest.java b/android/testSrc/com/android/tools/idea/gradle/variant/ConflictResolutionTest.java
similarity index 83%
rename from android/testSrc/com/android/tools/idea/gradle/variant/VariantSelectionVerifierTest.java
rename to android/testSrc/com/android/tools/idea/gradle/variant/ConflictResolutionTest.java
index 75d43fb..2548fd3 100644
--- a/android/testSrc/com/android/tools/idea/gradle/variant/VariantSelectionVerifierTest.java
+++ b/android/testSrc/com/android/tools/idea/gradle/variant/ConflictResolutionTest.java
@@ -18,7 +18,6 @@
 import com.android.tools.idea.gradle.IdeaAndroidProject;
 import com.android.tools.idea.gradle.facet.AndroidGradleFacet;
 import com.android.tools.idea.gradle.stubs.android.*;
-import com.google.common.collect.ImmutableList;
 import com.intellij.facet.FacetManager;
 import com.intellij.facet.ModifiableFacetModel;
 import com.intellij.openapi.application.ApplicationManager;
@@ -35,22 +34,18 @@
 import java.util.List;
 
 /**
- * Tests for {@link VariantSelectionVerifier}
+ * Tests for {@link ConflictResolution}
  */
-public class VariantSelectionVerifierTest extends IdeaTestCase {
+public class ConflictResolutionTest extends IdeaTestCase {
   private Module myLibModule;
   private IdeaAndroidProject myApp;
   private IdeaAndroidProject myLib;
   private String myLibGradlePath;
 
-  private VariantSelectionVerifier myVerifier;
-
   @Override
   protected void setUp() throws Exception {
     super.setUp();
 
-    myVerifier = new VariantSelectionVerifier(myProject);
-
     myLibModule = createModule("lib");
     myLibGradlePath = ":lib";
 
@@ -137,37 +132,37 @@
     }
   }
 
-  public void testFindFirstConflictWithoutConflict() {
+  public void testFindSelectionConflictsWithoutConflict() {
     setUpDependencyOnLibrary("debug");
-    ImmutableList<SelectionConflict> conflicts = myVerifier.findSelectionConflicts();
+    List<Conflict> conflicts = ConflictResolution.findConflicts(myProject).getSelectionConflicts();
     assertTrue(conflicts.isEmpty());
   }
 
-  public void testFindFirstConflictWithoutEmptyVariantDependency() {
+  public void testFindSelectionConflictsWithoutEmptyVariantDependency() {
     setUpDependencyOnLibrary("");
-    ImmutableList<SelectionConflict> conflicts = myVerifier.findSelectionConflicts();
+    List<Conflict> conflicts = ConflictResolution.findConflicts(myProject).getSelectionConflicts();
     assertTrue(conflicts.isEmpty());
   }
 
-  public void testFindFirstConflictWithoutNullVariantDependency() {
+  public void testFindSelectionConflictsWithoutNullVariantDependency() {
     setUpDependencyOnLibrary(null);
-    ImmutableList<SelectionConflict> conflicts = myVerifier.findSelectionConflicts();
+    List<Conflict> conflicts = ConflictResolution.findConflicts(myProject).getSelectionConflicts();
     assertTrue(conflicts.isEmpty());
   }
 
-  public void testFindFirstConflictWithConflict() {
+  public void testFindSelectionConflictsWithConflict() {
     setUpDependencyOnLibrary("release");
-    ImmutableList<SelectionConflict> conflicts = myVerifier.findSelectionConflicts();
+    List<Conflict> conflicts = ConflictResolution.findConflicts(myProject).getSelectionConflicts();
     assertEquals(1, conflicts.size());
 
-    SelectionConflict conflict = conflicts.get(0);
+    Conflict conflict = conflicts.get(0);
     assertSame(myLibModule, conflict.getSource());
     assertSame("debug", conflict.getSelectedVariant());
 
-    List<SelectionConflict.AffectedModule> affectedModules = conflict.getAffectedModules();
+    List<Conflict.AffectedModule> affectedModules = conflict.getAffectedModules();
     assertEquals(1, affectedModules.size());
 
-    SelectionConflict.AffectedModule affectedModule = affectedModules.get(0);
+    Conflict.AffectedModule affectedModule = affectedModules.get(0);
     assertSame(myModule, affectedModule.getTarget());
     assertSame("release", affectedModule.getExpectedVariant());
   }
diff --git a/android/testSrc/com/android/tools/idea/stats/StudioBuildStatsPersistenceComponentTest.java b/android/testSrc/com/android/tools/idea/stats/StudioBuildStatsPersistenceComponentTest.java
index f8304b2..b167ffb 100755
--- a/android/testSrc/com/android/tools/idea/stats/StudioBuildStatsPersistenceComponentTest.java
+++ b/android/testSrc/com/android/tools/idea/stats/StudioBuildStatsPersistenceComponentTest.java
@@ -74,7 +74,7 @@
           new KeyString("key2", "value 2"),
         });
 
-    myComponent.addBuildRecord(b1);
+    myComponent.addBuildRecordImmediately(b1);
 
     assertEquals(1, myComponent.getRecords().size());
     assertEquals(b1, myComponent.getRecords().getFirst());
@@ -102,8 +102,8 @@
           new KeyString("key2", "value 44"),
         });
 
-    myComponent.addBuildRecord(b1);
-    myComponent.addBuildRecord(b2);
+    myComponent.addBuildRecordImmediately(b1);
+    myComponent.addBuildRecordImmediately(b2);
 
     Element element2 = myComponent.getState();
     assertNotNull(element2);