Added quick fix and link to "Build Variants" window to variant-selection errors.

This CL adds the following hyperlinks to error messages, shown in the
"Messages" window, that are related to variant-selection conflicts:

- "Quick fix." This hyperlink will fix the conflict and update the state
  of the IDE.
- "Show conflict source in window." This hyperlink will select the source
  of the conflict in the "Build Variants" window.

Change-Id: I78420fa2be6e6da94d8807f4b3d4c1b454dd324f
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/PostProjectSyncTasksExecutor.java b/android/src/com/android/tools/idea/gradle/project/PostProjectSyncTasksExecutor.java
index 15e080e..cbc0318 100755
--- a/android/src/com/android/tools/idea/gradle/project/PostProjectSyncTasksExecutor.java
+++ b/android/src/com/android/tools/idea/gradle/project/PostProjectSyncTasksExecutor.java
@@ -28,17 +28,16 @@
 import com.android.tools.idea.gradle.util.ProjectBuilder;
 import com.android.tools.idea.gradle.util.Projects;
 import com.android.tools.idea.gradle.variant.Conflict;
-import com.android.tools.idea.gradle.variant.ConflictDisplay;
-import com.android.tools.idea.gradle.variant.ConflictFinder;
+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;
 import com.android.tools.idea.stats.StatsKeys;
 import com.android.tools.idea.stats.StatsTimeCollector;
 import com.android.tools.idea.templates.TemplateManager;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.intellij.jarFinder.InternetAttachSourceProvider;
@@ -106,7 +105,7 @@
       AndroidGradleProjectComponent.getInstance(myProject).checkForSupportedModules();
     }
 
-    findAndShowVariantSelectionConflicts();
+    findAndShowVariantConflicts();
 
     ProjectResourceRepository.moduleRootsChanged(myProject);
 
@@ -337,16 +336,21 @@
     }
   }
 
-  private void findAndShowVariantSelectionConflicts() {
-    ConflictSet conflicts = ConflictFinder.findConflicts(myProject);
+  private void findAndShowVariantConflicts() {
+    ConflictSet conflicts = ConflictResolution.findConflicts(myProject);
 
-    ImmutableList<Conflict> structureConflicts = conflicts.getStructureConflicts();
-    if (!structureConflicts.isEmpty() && SystemProperties.getBooleanProperty("enable.project.profiles", false)) {
-      ProjectProfileSelectionDialog dialog = new ProjectProfileSelectionDialog(myProject, structureConflicts);
-      dialog.show();
+    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();
+      }
     }
 
-    ConflictDisplay.displayConflicts(myProject, conflicts);
+    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/ConflictDisplay.java b/android/src/com/android/tools/idea/gradle/variant/ConflictDisplay.java
deleted file mode 100644
index adc3086..0000000
--- a/android/src/com/android/tools/idea/gradle/variant/ConflictDisplay.java
+++ /dev/null
@@ -1,92 +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.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.variant.view.BuildVariantView;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.intellij.openapi.module.Module;
-import com.intellij.openapi.project.Project;
-import com.intellij.pom.Navigatable;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Collections;
-import java.util.List;
-
-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 ConflictDisplay {
-  private ConflictDisplay() {
-  }
-
-  /**
-   * Displays in the "Messages" window the given variant-related conflicts of the given project.
-   *
-   * @param project   the given project.
-   * @param conflicts the conflicts to display.
-   */
-  public static void displayConflicts(@NotNull Project project, @NotNull ConflictSet conflicts) {
-    ImmutableList<Conflict> selectionConflicts = conflicts.getSelectionConflicts();
-    displaySelectionConflicts(project, selectionConflicts, VARIANT_SELECTION_CONFLICTS);
-    BuildVariantView.getInstance(project).updateNotification(selectionConflicts);
-    displaySelectionConflicts(project, conflicts.getStructureConflicts(), PROJECT_STRUCTURE_CONFLICTS);
-  }
-
-  private static void displaySelectionConflicts(@NotNull final Project project,
-                                                @NotNull ImmutableList<Conflict> conflicts,
-                                                @NotNull String groupName) {
-    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("");
-      }
-
-      Navigatable navigateToConflictSource = new AbstractNavigatable() {
-        @Override
-        public void navigate(boolean requestFocus) {
-          BuildVariantView.getInstance(project).selectAndScrollTo(conflictSource);
-        }
-
-        @Override
-        public boolean canNavigate() {
-          return true;
-        }
-      };
-
-      messages.add(new Message(groupName, Message.Type.ERROR, navigateToConflictSource, text.toArray(new String[text.size()])));
-    }
-  }
-}
diff --git a/android/src/com/android/tools/idea/gradle/variant/ConflictFinder.java b/android/src/com/android/tools/idea/gradle/variant/ConflictFinder.java
deleted file mode 100644
index adcb876..0000000
--- a/android/src/com/android/tools/idea/gradle/variant/ConflictFinder.java
+++ /dev/null
@@ -1,121 +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.util.GradleUtil;
-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 org.jetbrains.android.facet.AndroidFacet;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.List;
-import java.util.Map;
-
-public class ConflictFinder {
-  @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;
-  }
-
-  @Nullable
-  private static IdeaAndroidProject getAndroidProject(@NotNull Module module) {
-    AndroidFacet facet = AndroidFacet.getInstance(module);
-    if (facet == null || !facet.isGradleProject()) {
-      return null;
-    }
-    return facet.getIdeaAndroidProject();
-  }
-}
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
index b0263dc..a86a13c 100644
--- a/android/src/com/android/tools/idea/gradle/variant/ConflictSet.java
+++ b/android/src/com/android/tools/idea/gradle/variant/ConflictSet.java
@@ -19,6 +19,7 @@
 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:
@@ -44,12 +45,12 @@
   }
 
   @NotNull
-  public ImmutableList<Conflict> getSelectionConflicts() {
+  public List<Conflict> getSelectionConflicts() {
     return mySelectionConflicts;
   }
 
   @NotNull
-  public ImmutableList<Conflict> getStructureConflicts() {
+  public List<Conflict> getStructureConflicts() {
     return myStructureConflicts;
   }
 }
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 923ec10..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,9 +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.ConflictDisplay;
-import com.android.tools.idea.gradle.variant.ConflictFinder;
-import com.android.tools.idea.gradle.variant.ConflictSet;
+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;
@@ -69,8 +67,7 @@
       public void execute() {
         Module updatedModule = doUpdate(project, moduleName, buildVariantName, facets);
         if (updatedModule != null) {
-          ConflictSet conflicts = ConflictFinder.findConflicts(project);
-          ConflictDisplay.displayConflicts(project, conflicts);
+          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 cda91b4..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
@@ -211,7 +211,7 @@
     return androidFacet != null ? androidFacet.getIdeaAndroidProject() : null;
   }
 
-  public void updateNotification(ImmutableList<Conflict> conflicts) {
+  public void updateNotification(List<Conflict> conflicts) {
     myErrorPanel.removeAll();
     myConflictSources.clear();
 
@@ -234,7 +234,8 @@
 
   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/testSrc/com/android/tools/idea/gradle/variant/ConflictFinderTest.java b/android/testSrc/com/android/tools/idea/gradle/variant/ConflictResolutionTest.java
similarity index 91%
rename from android/testSrc/com/android/tools/idea/gradle/variant/ConflictFinderTest.java
rename to android/testSrc/com/android/tools/idea/gradle/variant/ConflictResolutionTest.java
index e0c9a93..2548fd3 100644
--- a/android/testSrc/com/android/tools/idea/gradle/variant/ConflictFinderTest.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,9 +34,9 @@
 import java.util.List;
 
 /**
- * Tests for {@link ConflictFinder}
+ * Tests for {@link ConflictResolution}
  */
-public class ConflictFinderTest extends IdeaTestCase {
+public class ConflictResolutionTest extends IdeaTestCase {
   private Module myLibModule;
   private IdeaAndroidProject myApp;
   private IdeaAndroidProject myLib;
@@ -135,25 +134,25 @@
 
   public void testFindSelectionConflictsWithoutConflict() {
     setUpDependencyOnLibrary("debug");
-    ImmutableList<Conflict> conflicts = ConflictFinder.findConflicts(myProject).getSelectionConflicts();
+    List<Conflict> conflicts = ConflictResolution.findConflicts(myProject).getSelectionConflicts();
     assertTrue(conflicts.isEmpty());
   }
 
   public void testFindSelectionConflictsWithoutEmptyVariantDependency() {
     setUpDependencyOnLibrary("");
-    ImmutableList<Conflict> conflicts = ConflictFinder.findConflicts(myProject).getSelectionConflicts();
+    List<Conflict> conflicts = ConflictResolution.findConflicts(myProject).getSelectionConflicts();
     assertTrue(conflicts.isEmpty());
   }
 
   public void testFindSelectionConflictsWithoutNullVariantDependency() {
     setUpDependencyOnLibrary(null);
-    ImmutableList<Conflict> conflicts = ConflictFinder.findConflicts(myProject).getSelectionConflicts();
+    List<Conflict> conflicts = ConflictResolution.findConflicts(myProject).getSelectionConflicts();
     assertTrue(conflicts.isEmpty());
   }
 
   public void testFindSelectionConflictsWithConflict() {
     setUpDependencyOnLibrary("release");
-    ImmutableList<Conflict> conflicts = ConflictFinder.findConflicts(myProject).getSelectionConflicts();
+    List<Conflict> conflicts = ConflictResolution.findConflicts(myProject).getSelectionConflicts();
     assertEquals(1, conflicts.size());
 
     Conflict conflict = conflicts.get(0);