Merge "Tweak resource type inspection test" into idea133
diff --git a/android/src/com/android/tools/idea/gradle/project/GradleProjectImporter.java b/android/src/com/android/tools/idea/gradle/project/GradleProjectImporter.java
index 8be620b..4c12d74 100755
--- a/android/src/com/android/tools/idea/gradle/project/GradleProjectImporter.java
+++ b/android/src/com/android/tools/idea/gradle/project/GradleProjectImporter.java
@@ -463,7 +463,9 @@
       newProject.save();
     }
 
-    Projects.open(newProject);
+    if (!newProject.isOpen()) {
+      Projects.open(newProject);
+    }
     if (!ProjectValidator.validate(newProject, projectRootDir)) {
       // The project failed validation. Bail out on Gradle import, but create a top-level module so that the entire project directory
       // contents will show up in the project window and the user can edit files to fix the validation problems.
diff --git a/android/src/com/android/tools/idea/gradle/project/ProjectValidator.java b/android/src/com/android/tools/idea/gradle/project/ProjectValidator.java
index 9ab6f5a..697bc9b 100644
--- a/android/src/com/android/tools/idea/gradle/project/ProjectValidator.java
+++ b/android/src/com/android/tools/idea/gradle/project/ProjectValidator.java
@@ -125,7 +125,7 @@
                        @Nullable Location location,
                        @NonNull String message,
                        @Nullable Object data) {
-      myFatalError |= severity.compareTo(Severity.ERROR) >= 0;
+      myFatalError |= severity.compareTo(Severity.ERROR) <= 0;
 
       File file = location != null ? location.getFile() : null;
       VirtualFile virtualFile = file != null ? LocalFileSystem.getInstance().findFileByIoFile(file) : null;
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/templates/Template.java b/android/src/com/android/tools/idea/templates/Template.java
index 12f0e35..910c710 100755
--- a/android/src/com/android/tools/idea/templates/Template.java
+++ b/android/src/com/android/tools/idea/templates/Template.java
@@ -41,7 +41,6 @@
 import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.vfs.LocalFileSystem;
 import com.intellij.openapi.vfs.VfsUtil;
-import com.intellij.openapi.vfs.VfsUtilCore;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.util.SystemProperties;
 import freemarker.cache.TemplateLoader;
@@ -154,7 +153,9 @@
   public static final String ATTR_CONSTRAINTS = "constraints";
   public static final String ATTR_VISIBILITY = "visibility";
   public static final String ATTR_SOURCE_URL = "href";
-
+  public static final String ATTR_TEMPLATE_MERGE_STRATEGY = "templateMergeStrategy";
+  public static final String VALUE_MERGE_STRATEGY_REPLACE = "replace";
+  public static final String VALUE_MERGE_STRATEGY_PRESERVE = "preserve";
   public static final String CATEGORY_ACTIVITIES = "activities";
   public static final String CATEGORY_PROJECTS = "gradle-projects";
   public static final String CATEGORY_OTHER = "other";
@@ -677,12 +678,19 @@
 
       for (Node node : nodes) {
         if (node.getNodeType() == Node.ELEMENT_NODE) {
-          Element element = (Element) node;
+          // Chances are, we will put the node from the clean template into the original document, so import it.
+          Element element = (Element) currentDocument.importNode(node, true);
+          String mergeStrategy = element.getAttribute(ATTR_TEMPLATE_MERGE_STRATEGY);
+          // Remove the "templateMergeStrategy" attribute from the final output.
+          element.removeAttribute(ATTR_TEMPLATE_MERGE_STRATEGY);
+
           String name = getResourceId(element);
           Node replace = name != null ? old.get(name) : null;
           if (replace != null) {
-            // There is an existing item with the same id: just replace it
-            // ACTUALLY -- let's NOT change it.
+            // There is an existing item with the same id. Either replace it
+            // or preserve it depending on the "templateMergeStrategy" attribute.
+            // If that attribute does not exist, default to preserving it.
+
             // Let's say you've used the activity wizard once, and it
             // emits some configuration parameter as a resource that
             // it depends on, say "padding". Then the user goes and
@@ -690,16 +698,17 @@
             // Now running the wizard a *second* time for some new activity,
             // we should NOT go and set the value back to the template's
             // default!
-            //root.replaceChild(node, replace);
-
-            // ... ON THE OTHER HAND... What if it's a parameter class
-            // (where the template rewrites a common attribute). Here it's
-            // really confusing if the new parameter is not set. This is
-            // really an error in the template, since we shouldn't have conflicts
-            // like that, but we need to do something to help track this down.
-            LOG.warn("Warning: Ignoring name conflict in resource file for name " + name);
+            if (VALUE_MERGE_STRATEGY_REPLACE.equals(mergeStrategy)) {
+              root.replaceChild(element, replace);
+              modified = true;
+            } else if (VALUE_MERGE_STRATEGY_PRESERVE.equals(mergeStrategy)) {
+              // Preserve the existing value.
+            } else {
+              // No explicit directive given, preserve the original value by default.
+              LOG.warn("Warning: Ignoring name conflict in resource file for name " + name);
+            }
           } else {
-            root.appendChild(currentDocument.importNode(node, true));
+            root.appendChild(element);
             modified = true;
           }
         }
diff --git a/android/src/org/jetbrains/android/run/AndroidRunConfiguration.java b/android/src/org/jetbrains/android/run/AndroidRunConfiguration.java
index df8224d..c70fc61 100755
--- a/android/src/org/jetbrains/android/run/AndroidRunConfiguration.java
+++ b/android/src/org/jetbrains/android/run/AndroidRunConfiguration.java
@@ -35,7 +35,6 @@
 import com.intellij.openapi.module.Module;
 import com.intellij.openapi.options.SettingsEditor;
 import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.Comparing;
 import com.intellij.openapi.util.Computable;
 import com.intellij.openapi.util.Pair;
 import com.intellij.openapi.util.io.FileUtil;
@@ -46,9 +45,7 @@
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.search.GlobalSearchScope;
 import com.intellij.psi.search.ProjectScope;
-import com.intellij.refactoring.listeners.RefactoringElementAdapter;
 import com.intellij.refactoring.listeners.RefactoringElementListener;
-import com.intellij.util.xmlb.Accessor;
 import org.jetbrains.android.dom.AndroidDomUtil;
 import org.jetbrains.android.dom.manifest.*;
 import org.jetbrains.android.facet.AndroidFacet;
diff --git a/android/src/org/jetbrains/android/run/AndroidRunConfigurationBase.java b/android/src/org/jetbrains/android/run/AndroidRunConfigurationBase.java
index fd8d5a7..6d2df25 100644
--- a/android/src/org/jetbrains/android/run/AndroidRunConfigurationBase.java
+++ b/android/src/org/jetbrains/android/run/AndroidRunConfigurationBase.java
@@ -17,6 +17,7 @@
 package org.jetbrains.android.run;
 
 import com.android.ddmlib.AndroidDebugBridge;
+import com.android.ddmlib.IDevice;
 import com.android.sdklib.internal.avd.AvdInfo;
 import com.android.sdklib.internal.avd.AvdManager;
 import com.android.tools.idea.gradle.util.Projects;
@@ -64,10 +65,10 @@
   private static final String GRADLE_SYNC_FAILED_ERR_MSG = "Gradle project sync failed. Please fix your project and try again.";
 
   /**
-   * A map from launch configuration name to set of devices used in that launch configuration.
+   * A map from launch configuration name to the state of devices at the time of the launch.
    * We want this list of devices persisted across launches, but not across invocations of studio, so we use a static variable.
    */
-  private static Map<String,Set<String>> ourLastUsedDevices = new ConcurrentHashMap<String, Set<String>>();
+  private static Map<String, DeviceStateAtLaunch> ourLastUsedDevices = new ConcurrentHashMap<String, DeviceStateAtLaunch>();
 
   public String TARGET_SELECTION_MODE = TargetSelectionMode.EMULATOR.name();
   public boolean USE_LAST_SELECTED_DEVICE = false;
@@ -168,12 +169,12 @@
     TARGET_SELECTION_MODE = mode.name();
   }
 
-  public void setDevicesUsedInLaunch(@NotNull Set<String> devices) {
-    ourLastUsedDevices.put(getName(), devices);
+  public void setDevicesUsedInLaunch(@NotNull Set<IDevice> usedDevices, @NotNull Set<IDevice> availableDevices) {
+    ourLastUsedDevices.put(getName(), new DeviceStateAtLaunch(usedDevices, availableDevices));
   }
 
   @Nullable
-  public Set<String> getDevicesUsedInLastLaunch() {
+  public DeviceStateAtLaunch getDevicesUsedInLastLaunch() {
     return ourLastUsedDevices.get(getName());
   }
 
diff --git a/android/src/org/jetbrains/android/run/AndroidRunningState.java b/android/src/org/jetbrains/android/run/AndroidRunningState.java
index f42db34..255475e 100644
--- a/android/src/org/jetbrains/android/run/AndroidRunningState.java
+++ b/android/src/org/jetbrains/android/run/AndroidRunningState.java
@@ -31,6 +31,7 @@
 import com.android.tools.idea.gradle.service.notification.SyncProjectHyperlink;
 import com.android.tools.idea.gradle.util.GradleUtil;
 import com.android.tools.idea.model.AndroidModuleInfo;
+import com.google.common.collect.Sets;
 import com.intellij.CommonBundle;
 import com.intellij.execution.DefaultExecutionResult;
 import com.intellij.execution.ExecutionException;
@@ -225,10 +226,14 @@
 
     if (myTargetChooser instanceof ManualTargetChooser) {
       if (myConfiguration.USE_LAST_SELECTED_DEVICE) {
-        Set<String> devicesUsedInLastLaunch = myConfiguration.getDevicesUsedInLastLaunch();
+        DeviceStateAtLaunch lastLaunchState = myConfiguration.getDevicesUsedInLastLaunch();
 
-        if (devicesUsedInLastLaunch != null) {
-          myTargetDevices = getDevicesStillOnline(devicesUsedInLastLaunch);
+        if (lastLaunchState != null) {
+          Set<IDevice> onlineDevices = getOnlineDevices();
+          if (lastLaunchState.matchesCurrentAvailableDevices(onlineDevices)) {
+            Collection<IDevice> usedDevices = lastLaunchState.filterByUsed(onlineDevices);
+            myTargetDevices = usedDevices.toArray(new IDevice[usedDevices.size()]);
+          }
         }
 
         if (myTargetDevices.length > 1 && !mySupportMultipleDevices) {
@@ -261,10 +266,10 @@
 
           if (chooser.useSameDevicesAgain()) {
             myConfiguration.USE_LAST_SELECTED_DEVICE = true;
-            myConfiguration.setDevicesUsedInLaunch(getDeviceNames(selectedDevices));
+            myConfiguration.setDevicesUsedInLaunch(Sets.newHashSet(selectedDevices), getOnlineDevices());
           } else {
             myConfiguration.USE_LAST_SELECTED_DEVICE = false;
-            myConfiguration.setDevicesUsedInLaunch(Collections.<String>emptySet());
+            myConfiguration.setDevicesUsedInLaunch(Collections.<IDevice>emptySet(), Collections.<IDevice>emptySet());
           }
         }
       }
@@ -280,35 +285,13 @@
     return new DefaultExecutionResult(console, myProcessHandler);
   }
 
-  private static Set<String> getDeviceNames(@NotNull IDevice[] selectedDevices) {
-    Set<String> s = new HashSet<String>(selectedDevices.length);
-
-    for (IDevice d : selectedDevices) {
-      String name = d.getName();
-      if (name != null) {
-        s.add(name);
-      }
-    }
-
-    return s;
-  }
-
-  private IDevice[] getDevicesStillOnline(@NotNull Set<String> devicesUsedInLastLaunch) {
+  private Set<IDevice> getOnlineDevices() {
     AndroidDebugBridge debugBridge = myFacet.getDebugBridge();
     if (debugBridge == null) {
-      return EMPTY_DEVICE_ARRAY;
+      return Collections.emptySet();
     }
 
-    IDevice[] devices = debugBridge.getDevices();
-    List<IDevice> onlineDevices = new ArrayList<IDevice>(devices.length);
-
-    for (IDevice d : devices) {
-      if (devicesUsedInLastLaunch.contains(d.getName())) {
-        onlineDevices.add(d);
-      }
-    }
-
-    return onlineDevices.toArray(new IDevice[onlineDevices.size()]);
+    return Sets.newHashSet(debugBridge.getDevices());
   }
 
   @Nullable
@@ -628,7 +611,6 @@
       return;
     }
 
-    assert myPackageName != null;
     myTestPackageName = computeTestPackageName(myFacet, myPackageName);
 
     setTargetPackageName(myPackageName);
@@ -784,7 +766,7 @@
   Boolean isCompatibleDevice(@NotNull IDevice device) {
     if (myTargetChooser instanceof EmulatorTargetChooser) {
       if (device.isEmulator()) {
-        String avdName = device.isEmulator() ? device.getAvdName() : null;
+        String avdName = device.getAvdName();
         if (myAvdName != null) {
           return myAvdName.equals(avdName);
         }
@@ -795,8 +777,8 @@
       return !device.isEmulator();
     }
     else if (myTargetChooser instanceof ManualTargetChooser && myConfiguration.USE_LAST_SELECTED_DEVICE) {
-      Set<String> devicesUsedInLastLaunch = myConfiguration.getDevicesUsedInLastLaunch();
-      return devicesUsedInLastLaunch != null && devicesUsedInLastLaunch.contains(device.getName());
+      DeviceStateAtLaunch lastLaunchState = myConfiguration.getDevicesUsedInLastLaunch();
+      return lastLaunchState != null && lastLaunchState.usedDevice(device);
     }
     return false;
   }
diff --git a/android/src/org/jetbrains/android/run/DeviceStateAtLaunch.java b/android/src/org/jetbrains/android/run/DeviceStateAtLaunch.java
new file mode 100644
index 0000000..f202e0e
--- /dev/null
+++ b/android/src/org/jetbrains/android/run/DeviceStateAtLaunch.java
@@ -0,0 +1,78 @@
+/*
+ * 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 org.jetbrains.android.run;
+
+import com.android.ddmlib.IDevice;
+import com.google.common.collect.Sets;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+import java.util.Set;
+
+/** A simple container that maintains the state of devices at the time of a particular launch. */
+public class DeviceStateAtLaunch {
+  /** devices used in a particular launch. */
+  private final Set<String> myDevicesUsedInLaunch;
+
+  /** all the devices available at the time of the launch. */
+  private final Set<String> myDevicesAvailableAtLaunch;
+
+  public DeviceStateAtLaunch(@NotNull Collection<IDevice> usedDevices, @NotNull Collection<IDevice> allDevices) {
+    myDevicesUsedInLaunch = serialize(usedDevices);
+    myDevicesAvailableAtLaunch = serialize(allDevices);
+  }
+
+  /** Filters the given set of devices by only selecting those devices that were actually used in this launch. */
+  public Collection<IDevice> filterByUsed(@NotNull Collection<IDevice> devices) {
+    Set<IDevice> used = Sets.newHashSetWithExpectedSize(myDevicesUsedInLaunch.size());
+
+    for (IDevice d : devices) {
+      if (myDevicesUsedInLaunch.contains(d.getSerialNumber())) {
+        used.add(d);
+      }
+    }
+
+    return used;
+  }
+
+  /** Whether the devices available now were the same set of devices available at the time of this launch. */
+  public boolean matchesCurrentAvailableDevices(@NotNull Collection<IDevice> devices) {
+    if (myDevicesAvailableAtLaunch.size() != devices.size()) {
+      return false;
+    }
+
+    for (IDevice d : devices) {
+      if (!myDevicesAvailableAtLaunch.contains(d.getSerialNumber())) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  /** Whether the given device was used in this launch. */
+  public boolean usedDevice(@NotNull IDevice device) {
+    return myDevicesUsedInLaunch.contains(device.getSerialNumber());
+  }
+
+  private static Set<String> serialize(Collection<IDevice> usedDevices) {
+    Set<String> s = Sets.newHashSetWithExpectedSize(usedDevices.size());
+    for (IDevice d : usedDevices) {
+      s.add(d.getSerialNumber());
+    }
+    return s;
+  }
+}
diff --git a/android/src/org/jetbrains/android/run/testing/AndroidTestRunConfiguration.java b/android/src/org/jetbrains/android/run/testing/AndroidTestRunConfiguration.java
index 5163eaf..fb28d0a 100644
--- a/android/src/org/jetbrains/android/run/testing/AndroidTestRunConfiguration.java
+++ b/android/src/org/jetbrains/android/run/testing/AndroidTestRunConfiguration.java
@@ -44,8 +44,6 @@
 import com.intellij.openapi.util.Pair;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.psi.*;
-import com.intellij.psi.util.PsiTreeUtil;
-import com.intellij.psi.util.PsiUtil;
 import com.intellij.refactoring.listeners.RefactoringElementAdapter;
 import com.intellij.refactoring.listeners.RefactoringElementListener;
 import org.jetbrains.android.dom.manifest.Instrumentation;