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;