aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--adtproductbuild/Android.mk133
-rw-r--r--adtproductbuild/Makefile36
-rw-r--r--adtproductbuild/adt_eclipse_ide3
-rw-r--r--adtproductbuild/build.xml2
-rw-r--r--common/src/com/android/SdkConstants.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ActivityMenuListener.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java104
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java95
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyEditor.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFix.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetAttributeFix.java20
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java32
-rwxr-xr-xeclipse/scripts/build_server.sh22
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/ArraySizeDetector.java67
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java4
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/MissingIdDetector.java90
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/PxUsageDetector.java50
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ArraySizeDetectorTest.java13
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MissingIdDetectorTest.java36
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/PxUsageDetectorTest.java34
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/fragment.xml24
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/now_playing_after.xml4
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values-it/stringarrays.xml12
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/pxsp.xml13
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/stringarrays.xml13
-rwxr-xr-xmanifmerger/src/com/android/manifmerger/ManifestMerger.java214
-rwxr-xr-xmanifmerger/src/com/android/manifmerger/XmlUtils.java296
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/ManifestMergerTest.java4
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/11_activity_dup.xml47
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/12_alias_dup.xml26
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/13_service_dup.xml24
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/14_receiver_dup.xml26
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/15_provider_dup.xml24
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/26_permission_dup.xml69
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/60_merge_order.xml317
39 files changed, 1439 insertions, 436 deletions
diff --git a/adtproductbuild/Android.mk b/adtproductbuild/Android.mk
new file mode 100644
index 0000000..e3421d9
--- /dev/null
+++ b/adtproductbuild/Android.mk
@@ -0,0 +1,133 @@
+# Copyright 2012 The Android Open Source Project
+#
+# Makefile rules to build the ADT Eclipse IDE.
+# This is invoked from sdk/eclipse/scripts/build_server.sh using
+# something like "make PRODUCT-sdk-adt_eclipse_ide".
+#
+# Expected env vars:
+# ADT_IDE_DEST_DIR: existing directory where to copy the IDE zip files.
+# ADT_IDE_QUALIFIER: either a date or build number to incorporate in the zip names.
+
+# Expose the ADT Eclipse IDE build only for the SDK builds.
+ifneq (,$(is_sdk_build)$(filter sdk sdk_x86 sdk_mips,$(TARGET_PRODUCT)))
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := adt_eclipse_ide
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_TAGS := optional
+LOCAL_IS_HOST_MODULE := true
+include $(BUILD_SYSTEM)/base_rules.mk
+
+ADT_IDE_MODULE_DEPS := $(TOPDIR)sdk/adtproductbuild/$(LOCAL_MODULE)
+
+ADT_IDE_BUILD_LOG := $(TOPDIR)out/host/eclipse/adtproduct/build/adtproduct.log
+ADT_IDE_ARTIFACT_DIR := $(TOPDIR)out/host/eclipse/adtproduct/build/I.RcpBuild
+ADT_IDE_RELEASE_DIR := $(TOPDIR)out/host/eclipse/adtproduct/release
+
+ADT_IDE_JAVA_LIBS := $(shell $(TOPDIR)sdk/eclipse/scripts/create_all_symlinks.sh -d)
+ADT_IDE_JAVA_DEPS := $(foreach m,$(ADT_IDE_JAVA_LIBS),$(HOST_OUT_JAVA_LIBRARIES)/$(m).jar)
+
+ADT_IDE_JAVA_TARGET := $(ADT_IDE_RELEASE_DIR)/adt_eclipse_ide_java_build
+
+# Common not-quite-phony rule to perform the eclipse build only once
+# This invokes the java builder on eclipse. It generates multiple
+# zipped versions (one per OS, all built at the same time)
+# of the ide as specified in the build.properties file.
+$(ADT_IDE_JAVA_TARGET) : $(TOPDIR)sdk/adtproductbuild/adt_eclipse_ide \
+ $(TOPDIR)sdk/adtproductbuild/build.xml \
+ $(TOPDIR)sdk/adtproductbuild/build.properties \
+ $(ADT_IDE_JAVA_DEPS)
+ @if [[ ! -d $(TOPDIR)prebuilts/eclipse-build-deps ]]; then \
+ echo "*** [adt_eclipse_ide] ERROR: Missing prebuilts/eclipse-build-deps directory. Make sure to run 'repo init -g all;repo sync' first."; \
+ exit 1; \
+ fi
+ $(hide)rm -rf $(TOPDIR)out/host/eclipse/adtproduct/build/plugins
+ $(hide)mkdir -p $(dir $@)
+ $(hide)$(TOPDIR)sdk/eclipse/scripts/create_all_symlinks.sh -c
+ $(hide)cd $(TOPDIR)sdk/adtproductbuild && \
+ rm -f ../../$(ADT_IDE_BUILD_LOG) && mkdir -p ../../$(dir $(ADT_IDE_BUILD_LOG)) && \
+ ( java -jar ../../external/eclipse-basebuilder/basebuilder-3.6.2/org.eclipse.releng.basebuilder/plugins/org.eclipse.equinox.launcher_1.1.0.v20100507.jar \
+ org.eclipse.equinox.launcher.Main \
+ -application org.eclipse.ant.core.antRunner \
+ -configuration ../../out/host/eclipse/adtproduct/build/configuration \
+ -data ../../out/host/eclipse/adtproduct/workspace \
+ 2>&1 && \
+ mv -f ../../$(ADT_IDE_BUILD_LOG) ../../$(ADT_IDE_BUILD_LOG).1 ) \
+ | tee ../../$(ADT_IDE_BUILD_LOG) \
+ | sed 's/^/IDE: /'; \
+ if [[ -f ../../$(ADT_IDE_BUILD_LOG) ]]; then \
+ echo "ADT IDE build failed. Full log:" ; \
+ cat ../../$(ADT_IDE_BUILD_LOG) ; \
+ exit 1 ; \
+ fi
+ $(hide)$(ACP) -fp $(V) $(TOPDIR)sdk/adtproductbuild/adt_eclipse_ide $@
+
+# Defines the zip filename generated for an OS specific android IDE.
+define adt-ide-zip
+$(ADT_IDE_RELEASE_DIR)/android-ide-$(ADT_IDE_QUALIFIER)-$(1).$(2).zip
+endef
+
+# Defines the rule needed to make one of the OS specific android IDE.
+# If ADT_IDE_DEST_DIR it also defines the rule to produce the final dest zip.
+# $1 = the platform (linux|macosx|win32).(gtk|cocoa|win32)
+# $2 = the architecture (x86 or x8_64).
+# $3 = the src zip (from out/host/eclipse/artifacts/RcpBuild-...)
+# $4 = the destination directory (where the unpacked eclipse is created)
+# $5 = the destination zip with the zipped eclipse ide.
+define mk-adt-ide-2
+$(5): $(ADT_IDE_JAVA_TARGET)
+ $(hide) \
+ rm -rf $(V) $(4) && \
+ rm -f $(V) $(5) && \
+ mkdir -p $(4) && \
+ unzip -q $(3) -d $(4) && \
+ sed -i 's/org.eclipse.platform/com.android.ide.eclipse.adt.package.product/g' \
+ $(4)/eclipse/$(if $(filter macosx.cocoa,$(1)),eclipse.app/Contents/MacOS/)eclipse.ini && \
+ sed -i -e 's/org.eclipse.platform.ide/com.android.ide.eclipse.adt.package.product/g' \
+ -e 's/org.eclipse.platform/com.android.ide.eclipse.adt.package/g' \
+ $(4)/eclipse/configuration/config.ini
+ $(hide)cd $(4) && zip -9rq ../$(notdir $(5)) eclipse
+ifneq (,$(ADT_IDE_DEST_DIR))
+$(ADT_IDE_DEST_DIR)/$(notdir $(5)): $(5)
+ @mkdir -p $(ADT_IDE_DEST_DIR)
+ $(hide)cp $(V) $(5) $(ADT_IDE_DEST_DIR)/$(notdir $(5))
+ @echo "ADT IDE copied to $(ADT_IDE_DEST_DIR)/$(notdir $(5))"
+else
+ @echo "ADT IDE available at $(5)"
+endif
+endef
+
+# Defines the rule needed to make one of the OS specific android IDE.
+# This is just a convenience wrapper that calls mk-adt-ide-2 and presets
+# the source and destination zip paths.
+# It also sets the dependencies we need to produce the final dest zip.
+# $1 = the platform (linux|macosx|win32).(gtk|cocoa|win32)
+# $2 = the architecture (x86 or x8_64).
+define mk-adt-ide
+$(call mk-adt-ide-2,$(1),$(2), \
+ $(ADT_IDE_ARTIFACT_DIR)/RcpBuild-$(1).$(2).zip, \
+ $(ADT_IDE_RELEASE_DIR)/android-ide-$(1).$(2), \
+ $(call adt-ide-zip,$(1),$(2)))
+ADT_IDE_MODULE_DEPS += $(call adt-ide-zip,$(1),$(2))
+ifneq (,$(ADT_IDE_DEST_DIR))
+ADT_IDE_MODULE_DEPS += $(ADT_IDE_DEST_DIR)/$(notdir $(call adt-ide-zip,$(1),$(2)))
+endif
+endef
+
+$(eval $(call mk-adt-ide,linux.gtk,x86_64))
+$(eval $(call mk-adt-ide,macosx.cocoa,x86_64))
+$(eval $(call mk-adt-ide,win32.win32,x86_64))
+
+# This rule triggers the build of the 3 ide zip files.
+# The adt_eclipse_ide script is currently a platceholder used
+# to detect when the build was completed. We may use it later
+# as a launch script.
+$(LOCAL_BUILT_MODULE) : $(ADT_IDE_MODULE_DEPS)
+ @mkdir -p $(dir $@)
+ $(hide)$(ACP) -fp $(V) $(TOPDIR)sdk/adtproductbuild/adt_eclipse_ide $@
+ @echo "Packing of ADT IDE done"
+
+endif
+
diff --git a/adtproductbuild/Makefile b/adtproductbuild/Makefile
deleted file mode 100644
index 64d30ef..0000000
--- a/adtproductbuild/Makefile
+++ /dev/null
@@ -1,36 +0,0 @@
-ADT_IDE_BUILD_LOG := out/host/eclipse/adtproduct/build/adtproduct.log
-ADT_IDE_ARTIFACT_DIR := ../../out/host/eclipse/adtproduct/build/I.RcpBuild
-ADT_IDE_RELEASE_DIR := ../../out/host/eclipse/adtproduct/release/
-
-define release-ide
- srczip=$(ADT_IDE_ARTIFACT_DIR)/RcpBuild-$(1).$(2).zip && \
- dstdir=$(ADT_IDE_RELEASE_DIR)/android-ide-$(1).$(2) && \
- rm -rf $(V) $$dstdir && \
- mkdir -p $$dstdir && \
- unzip -q $$srczip -d $$dstdir && \
- if [[ $(1) == "macosx.cocoa" ]]; then eclipseini=eclipse.app/Contents/MacOS/eclipse.ini; else eclipseini=eclipse.ini; fi && \
- sed -i 's/org.eclipse.platform/com.android.ide.eclipse.adt.package.product/g' $$dstdir/eclipse/$$eclipseini && \
- sed -i -e 's/org.eclipse.platform.ide/com.android.ide.eclipse.adt.package.product/g' \
- -e 's/org.eclipse.platform/com.android.ide.eclipse.adt.package/g' $$dstdir/eclipse/configuration/config.ini
-endef
-
-
-all:
- rm -f ../../$(ADT_IDE_BUILD_LOG) && mkdir -p ../../$(dir $(ADT_IDE_BUILD_LOG)) && \
- ( java -jar ../../external/eclipse-basebuilder/basebuilder-3.6.2/org.eclipse.releng.basebuilder/plugins/org.eclipse.equinox.launcher_1.1.0.v20100507.jar \
- org.eclipse.equinox.launcher.Main \
- -application org.eclipse.ant.core.antRunner \
- -configuration ../../out/host/eclipse/adtproduct/build/configuration \
- -data ../../out/host/eclipse/adtproduct/workspace \
- 2>&1 && \
- mv -f ../../$(ADT_IDE_BUILD_LOG) ../../$(ADT_IDE_BUILD_LOG).1 ) \
- | tee ../../$(ADT_IDE_BUILD_LOG) \
- | sed '/SUCCESSFUL/d ; /\[java\]/!b label; s/\s\+\[java\]//; /^\s*$$/d; /Compiling/!d; :label /^\s*$$/d; s/^/ADT: /'; \
- if [[ -f ../../$(ADT_IDE_BUILD_LOG) ]]; then \
- echo "ADT IDE build failed. Full log:" ; \
- cat ../../$(ADT_IDE_BUILD_LOG) ; \
- exit 1 ; \
- fi
- $(call release-ide,linux.gtk,x86_64) ; \
- $(call release-ide,macosx.cocoa,x86_64) ; \
- $(call release-ide,win32.win32,x86_64) ; \
diff --git a/adtproductbuild/adt_eclipse_ide b/adtproductbuild/adt_eclipse_ide
new file mode 100644
index 0000000..ea3355c
--- /dev/null
+++ b/adtproductbuild/adt_eclipse_ide
@@ -0,0 +1,3 @@
+#!/bin/bash
+echo "Placeholder launcher script for the ADT Eclipse IDE."
+exit 1
diff --git a/adtproductbuild/build.xml b/adtproductbuild/build.xml
index 7851625..c91bff0 100644
--- a/adtproductbuild/build.xml
+++ b/adtproductbuild/build.xml
@@ -7,7 +7,7 @@
<!-- Source for target prebuilts -->
<property name="deltaPackTargetSrcDir" value="${ANDROID_SRC}/prebuilts/eclipse/" />
- <property name="targetSrcDir" value="${ANDROID_SRC}/prebuilts/eclipse/" />
+ <property name="targetSrcDir" value="${ANDROID_SRC}/prebuilts/eclipse-build-deps/" />
<!-- Location where build happens and resulting binaries are generated -->
<property name="outDir" value="${ANDROID_SRC}/out/host/eclipse/adtproduct/" />
diff --git a/common/src/com/android/SdkConstants.java b/common/src/com/android/SdkConstants.java
index 8c798c8..04fa987 100644
--- a/common/src/com/android/SdkConstants.java
+++ b/common/src/com/android/SdkConstants.java
@@ -945,7 +945,7 @@ public final class SdkConstants {
public static final String ATTR_REF_PREFIX = "?attr/"; //$NON-NLS-1$
public static final String R_PREFIX = "R."; //$NON-NLS-1$
public static final String R_ID_PREFIX = "R.id."; //$NON-NLS-1$
- public static final String R_LAYOUT_RESOURCE_PREFIX = "R.layout."; //$NON-NLS-1$
+ public static final String R_LAYOUT_RESOURCE_PREFIX = "R.layout."; //$NON-NLS-1$
public static final String R_DRAWABLE_PREFIX = "R.drawable."; //$NON-NLS-1$
public static final String R_ATTR_PREFIX = "R.attr."; //$NON-NLS-1$
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java
index 426dbae..d0d328e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java
@@ -17,7 +17,6 @@
package com.android.ide.eclipse.adt.internal.assetstudio;
import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.DEFAULT_LAUNCHER_ICON;
-
import static java.awt.image.BufferedImage.TYPE_INT_ARGB;
import com.android.annotations.NonNull;
@@ -481,7 +480,7 @@ public class ConfigureAssetSetPage extends WizardPage implements SelectionListen
// Initial image - use the most recently used image, or the default launcher
// icon created in our default projects, if there
if (mValues.imagePath != null) {
- sImagePath = mValues.imagePath.getPath();;
+ sImagePath = mValues.imagePath.getPath();
}
if (sImagePath == null) {
IProject project = mValues.project;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java
index 98f5317..253421f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java
@@ -560,8 +560,8 @@ public final class ProjectCallback extends LegacyCallback {
}
@Override
- public AdapterBinding getAdapterBinding(final ResourceReference adapterView, final Object adapterCookie,
- final Object viewObject) {
+ public AdapterBinding getAdapterBinding(final ResourceReference adapterView,
+ final Object adapterCookie, final Object viewObject) {
// Look for user-recorded preference for layout to be used for previews
if (adapterCookie instanceof UiViewElementNode) {
UiViewElementNode uiNode = (UiViewElementNode) adapterCookie;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ActivityMenuListener.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ActivityMenuListener.java
index 1f85a32..158a647 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ActivityMenuListener.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ActivityMenuListener.java
@@ -88,7 +88,7 @@ class ActivityMenuListener extends SelectionAdapter {
if (current != null) {
MenuItem item = new MenuItem(menu, SWT.PUSH);
- String label = ConfigurationChooser.getActivityLabel(current, true);;
+ String label = ConfigurationChooser.getActivityLabel(current, true);
item.setText( String.format("Open %1$s...", label));
Image image = sharedImages.getImage(ISharedImages.IMG_OBJS_CUNIT);
item.setImage(image);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java
index 2106f8d..2b5589b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java
@@ -17,11 +17,13 @@
package com.android.ide.eclipse.adt.internal.editors.layout.configuration;
import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.api.Rect;
+import com.android.ide.common.resources.ResourceRepository;
import com.android.ide.common.resources.configuration.DensityQualifier;
import com.android.ide.common.resources.configuration.DeviceConfigHelper;
import com.android.ide.common.resources.configuration.FolderConfiguration;
@@ -30,13 +32,17 @@ import com.android.ide.common.resources.configuration.NightModeQualifier;
import com.android.ide.common.resources.configuration.RegionQualifier;
import com.android.ide.common.resources.configuration.ScreenDimensionQualifier;
import com.android.ide.common.resources.configuration.ScreenOrientationQualifier;
+import com.android.ide.common.resources.configuration.ScreenSizeQualifier;
import com.android.ide.common.resources.configuration.UiModeQualifier;
import com.android.ide.common.resources.configuration.VersionQualifier;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.resources.Density;
import com.android.resources.NightMode;
import com.android.resources.ScreenOrientation;
+import com.android.resources.ScreenSize;
import com.android.resources.UiMode;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.IAndroidTarget;
@@ -49,6 +55,7 @@ import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.QualifiedName;
import java.util.List;
+import java.util.Map;
/**
* A {@linkplain Configuration} is a selection of device, orientation, theme,
@@ -397,6 +404,7 @@ public class Configuration {
*/
public void setTheme(String theme) {
mTheme = theme;
+ checkThemePrefix();
}
/**
@@ -507,6 +515,67 @@ public class Configuration {
return sb.toString();
}
+ /** Returns the preferred theme, or null */
+ @Nullable
+ String computePreferredTheme() {
+ IProject project = mConfigChooser.getProject();
+ ManifestInfo manifest = ManifestInfo.get(project);
+
+ // Look up the screen size for the current state
+ ScreenSize screenSize = null;
+ Device device = getDevice();
+ if (device != null) {
+ List<State> states = device.getAllStates();
+ for (State state : states) {
+ FolderConfiguration folderConfig = DeviceConfigHelper.getFolderConfig(state);
+ if (folderConfig != null) {
+ ScreenSizeQualifier qualifier = folderConfig.getScreenSizeQualifier();
+ screenSize = qualifier.getValue();
+ break;
+ }
+ }
+ }
+
+ // Look up the default/fallback theme to use for this project (which
+ // depends on the screen size when no particular theme is specified
+ // in the manifest)
+ String defaultTheme = manifest.getDefaultTheme(getTarget(), screenSize);
+
+ String preferred = defaultTheme;
+ if (getTheme() == null) {
+ // If we are rendering a layout in included context, pick the theme
+ // from the outer layout instead
+
+ String activity = getActivity();
+ if (activity != null) {
+ Map<String, String> activityThemes = manifest.getActivityThemes();
+ preferred = activityThemes.get(activity);
+ }
+ if (preferred == null) {
+ preferred = defaultTheme;
+ }
+ setTheme(preferred);
+ }
+
+ return preferred;
+ }
+
+ private void checkThemePrefix() {
+ if (mTheme != null && !mTheme.startsWith(PREFIX_RESOURCE_REF)) {
+ if (mTheme.isEmpty()) {
+ computePreferredTheme();
+ return;
+ }
+ ResourceRepository frameworkRes = mConfigChooser.getClient().getFrameworkResources();
+ if (frameworkRes != null
+ && frameworkRes.hasResourceItem(ANDROID_STYLE_RESOURCE_PREFIX + mTheme)) {
+ mTheme = ANDROID_STYLE_RESOURCE_PREFIX + mTheme;
+ } else {
+ mTheme = STYLE_RESOURCE_PREFIX + mTheme;
+ }
+ }
+ }
+
/**
* Initializes a string previously created with
* {@link #toPersistentString()}
@@ -555,6 +624,8 @@ public class Configuration {
} else if (mTheme.startsWith(MARKER_PROJECT)) {
mTheme = STYLE_RESOURCE_PREFIX
+ mTheme.substring(MARKER_PROJECT.length());
+ } else {
+ checkThemePrefix();
}
mUiMode = UiMode.getEnum(values[4]);
@@ -604,7 +675,7 @@ public class Configuration {
@Nullable
static Pair<Locale, IAndroidTarget> loadRenderState(ConfigurationChooser chooser) {
IProject project = chooser.getProject();
- if (!project.isAccessible()) {
+ if (project == null || !project.isAccessible()) {
return null;
}
@@ -668,6 +739,9 @@ public class Configuration {
*/
void saveRenderState() {
IProject project = mConfigChooser.getProject();
+ if (project == null) {
+ return;
+ }
try {
// Generate a persistent string from locale+target
StringBuilder sb = new StringBuilder();
@@ -700,7 +774,7 @@ public class Configuration {
* @return an id for the given target; never null
*/
@NonNull
- private static String targetToString(@NonNull IAndroidTarget target) {
+ public static String targetToString(@NonNull IAndroidTarget target) {
return target.getFullName().replace(SEP, ""); //$NON-NLS-1$
}
@@ -715,7 +789,7 @@ public class Configuration {
* @return an {@link IAndroidTarget} that matches the given id, or null
*/
@Nullable
- private static IAndroidTarget stringToTarget(
+ public static IAndroidTarget stringToTarget(
@NonNull ConfigurationChooser chooser,
@NonNull String id) {
List<IAndroidTarget> targetList = chooser.getTargetList();
@@ -731,6 +805,30 @@ public class Configuration {
}
/**
+ * Returns an {@link IAndroidTarget} that corresponds to the given id that was
+ * originally returned by {@link #targetToString}. May be null, if the platform is no
+ * longer available, or if the platform list has not yet been initialized.
+ *
+ * @param id the id that corresponds to the desired platform
+ * @return an {@link IAndroidTarget} that matches the given id, or null
+ */
+ @Nullable
+ public static IAndroidTarget stringToTarget(
+ @NonNull String id) {
+ Sdk currentSdk = Sdk.getCurrent();
+ if (currentSdk != null) {
+ IAndroidTarget[] targets = currentSdk.getTargets();
+ for (IAndroidTarget target : targets) {
+ if (id.equals(targetToString(target))) {
+ return target;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
* Returns the {@link State} by the given name for the given {@link Device}
*
* @param device the device
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java
index b512bcc..7412bf1 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java
@@ -44,7 +44,6 @@ import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.ide.common.resources.configuration.LanguageQualifier;
import com.android.ide.common.resources.configuration.RegionQualifier;
import com.android.ide.common.resources.configuration.ResourceQualifier;
-import com.android.ide.common.resources.configuration.ScreenSizeQualifier;
import com.android.ide.common.sdk.LoadStatus;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
@@ -58,7 +57,6 @@ import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.resources.ResourceType;
import com.android.resources.ScreenOrientation;
-import com.android.resources.ScreenSize;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.devices.Device;
@@ -298,19 +296,42 @@ public class ConfigurationChooser extends Composite
addDisposeListener(this);
}
- IFile getEditedFile() {
+ /**
+ * Returns the edited file
+ *
+ * @return the file
+ */
+ @Nullable
+ public IFile getEditedFile() {
return mEditedFile;
}
- IProject getProject() {
- return mEditedFile.getProject();
+ /**
+ * Returns the project of the edited file
+ *
+ * @return the project
+ */
+ @Nullable
+ public IProject getProject() {
+ if (mEditedFile != null) {
+ return mEditedFile.getProject();
+ } else {
+ return null;
+ }
}
ConfigurationClient getClient() {
return mClient;
}
- ProjectResources getResources() {
+ /**
+ * Returns the project resources for the project being configured by this
+ * chooser
+ *
+ * @return the project resources
+ */
+ @Nullable
+ public ProjectResources getResources() {
return mResources;
}
@@ -328,7 +349,7 @@ public class ConfigurationChooser extends Composite
*
* @return the project target
*/
- IAndroidTarget getProjectTarget() {
+ public IAndroidTarget getProjectTarget() {
return mProjectTarget;
}
@@ -746,6 +767,7 @@ public class ConfigurationChooser extends Composite
if (target != null) {
targetData = Sdk.getCurrent().getTargetData(target);
selectTarget(target);
+ mConfiguration.setTarget(target, true);
}
}
@@ -1588,7 +1610,7 @@ public class ConfigurationChooser extends Composite
String theme = mConfiguration.getTheme();
if (theme == null || theme.isEmpty() || mClient.getIncludedWithin() != null) {
mConfiguration.setTheme(null);
- computePreferredTheme();
+ mConfiguration.computePreferredTheme();
}
assert mConfiguration.getTheme() != null;
}
@@ -1680,6 +1702,14 @@ public class ConfigurationChooser extends Composite
break;
}
}
+ if (!theme.startsWith(PREFIX_RESOURCE_REF)) {
+ // Arbitrary guess
+ if (theme.startsWith("Theme.")) {
+ theme = ANDROID_STYLE_RESOURCE_PREFIX + theme;
+ } else {
+ theme = STYLE_RESOURCE_PREFIX + theme;
+ }
+ }
}
// TODO: Handle the case where you have a theme persisted that isn't available??
@@ -1748,55 +1778,6 @@ public class ConfigurationChooser extends Composite
}
}
- /** Returns the preferred theme, or null */
- @Nullable
- String computePreferredTheme() {
- if (mClient == null) {
- return null;
- }
-
- IProject project = mEditedFile.getProject();
- ManifestInfo manifest = ManifestInfo.get(project);
-
- // Look up the screen size for the current state
- ScreenSize screenSize = null;
- Device device = mConfiguration.getDevice();
- if (device != null) {
- List<State> states = device.getAllStates();
- for (State state : states) {
- FolderConfiguration folderConfig = DeviceConfigHelper.getFolderConfig(state);
- if (folderConfig != null) {
- ScreenSizeQualifier qualifier = folderConfig.getScreenSizeQualifier();
- screenSize = qualifier.getValue();
- break;
- }
- }
- }
-
- // Look up the default/fallback theme to use for this project (which
- // depends on the screen size when no particular theme is specified
- // in the manifest)
- String defaultTheme = manifest.getDefaultTheme(mConfiguration.getTarget(), screenSize);
-
- String preferred = defaultTheme;
- if (mConfiguration.getTheme() == null) {
- // If we are rendering a layout in included context, pick the theme
- // from the outer layout instead
-
- String activity = mConfiguration.getActivity();
- if (activity != null) {
- Map<String, String> activityThemes = manifest.getActivityThemes();
- preferred = activityThemes.get(activity);
- }
- if (preferred == null) {
- preferred = defaultTheme;
- }
- mConfiguration.setTheme(preferred);
- }
-
- return preferred;
- }
-
@Nullable
private String getPreferredActivity(@NonNull IFile file) {
// Store/restore the activity context in the config state to help with
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java
index 239f396..0f6c9eb 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java
@@ -106,7 +106,7 @@ class ThemeMenuAction extends SubmenuAction {
manager.add(new Separator());
}
- String preferred = configChooser.computePreferredTheme();
+ String preferred = configuration.computePreferredTheme();
if (preferred != null && !preferred.equals(currentTheme)) {
manager.add(new SelectThemeAction(configChooser,
ResourceHelper.styleToTheme(preferred),
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java
index 60e9920..5ca8e9c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java
@@ -377,11 +377,15 @@ public class PreviewIconFactory {
*
* @return a pair of possibly null color descriptions
*/
+ @NonNull
private Pair<RGB, RGB> getColorsFromTheme() {
RGB background = null;
RGB foreground = null;
ResourceResolver resources = mPalette.getEditor().getResourceResolver();
+ if (resources == null) {
+ return Pair.of(background, foreground);
+ }
StyleResourceValue theme = resources.getCurrentTheme();
if (theme != null) {
background = resolveThemeColor(resources, "windowBackground"); //$NON-NLS-1$
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyEditor.java
index 72577a5..d2d7878 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyEditor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyEditor.java
@@ -137,7 +137,7 @@ class XmlPropertyEditor extends AbstractTextPropertyEditor {
// TODO: do I have to strip off the @ too?
isFramework = isFramework
|| value.startsWith(ANDROID_PREFIX)
- || value.startsWith(ANDROID_THEME_PREFIX);;
+ || value.startsWith(ANDROID_THEME_PREFIX);
ResourceValue v = resolver.findResValue(text, isFramework);
if (v != null && !value.equals(v.getValue())) {
resValue = v;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFix.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFix.java
index 7683f8d..0b074bb 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFix.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFix.java
@@ -24,6 +24,7 @@ import com.android.tools.lint.checks.DetectMissingPrefix;
import com.android.tools.lint.checks.HardcodedValuesDetector;
import com.android.tools.lint.checks.InefficientWeightDetector;
import com.android.tools.lint.checks.ManifestOrderDetector;
+import com.android.tools.lint.checks.MissingIdDetector;
import com.android.tools.lint.checks.ObsoleteLayoutParamsDetector;
import com.android.tools.lint.checks.PxUsageDetector;
import com.android.tools.lint.checks.ScrollViewChildDetector;
@@ -150,6 +151,7 @@ abstract class LintFix implements ICompletionProposal {
sFixes.put(AccessibilityDetector.ISSUE.getId(), SetAttributeFix.class);
sFixes.put(InefficientWeightDetector.BASELINE_WEIGHTS.getId(), SetAttributeFix.class);
sFixes.put(ManifestOrderDetector.ALLOW_BACKUP.getId(), SetAttributeFix.class);
+ sFixes.put(MissingIdDetector.ISSUE.getId(), SetAttributeFix.class);
sFixes.put(HardcodedValuesDetector.ISSUE.getId(), ExtractStringFix.class);
sFixes.put(UselessViewDetector.USELESS_LEAF.getId(), RemoveUselessViewFix.class);
sFixes.put(UselessViewDetector.USELESS_PARENT.getId(), RemoveUselessViewFix.class);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetAttributeFix.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetAttributeFix.java
index 1d743b8..3227bc3 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetAttributeFix.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetAttributeFix.java
@@ -18,19 +18,26 @@ package com.android.ide.eclipse.adt.internal.lint;
import static com.android.SdkConstants.ATTR_ALLOW_BACKUP;
import static com.android.SdkConstants.ATTR_BASELINE_ALIGNED;
import static com.android.SdkConstants.ATTR_CONTENT_DESCRIPTION;
+import static com.android.SdkConstants.ATTR_ID;
import static com.android.SdkConstants.ATTR_INPUT_TYPE;
import static com.android.SdkConstants.ATTR_PERMISSION;
import static com.android.SdkConstants.ATTR_TRANSLATABLE;
+import static com.android.SdkConstants.NEW_ID_PREFIX;
import static com.android.SdkConstants.VALUE_FALSE;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
import com.android.tools.lint.checks.AccessibilityDetector;
import com.android.tools.lint.checks.InefficientWeightDetector;
import com.android.tools.lint.checks.ManifestOrderDetector;
+import com.android.tools.lint.checks.MissingIdDetector;
import com.android.tools.lint.checks.SecurityDetector;
import com.android.tools.lint.checks.TextFieldDetector;
import com.android.tools.lint.checks.TranslationDetector;
import org.eclipse.core.resources.IMarker;
+import org.eclipse.ui.IEditorPart;
/** Shared fix class for various builtin attributes */
final class SetAttributeFix extends SetPropertyFix {
@@ -52,6 +59,8 @@ final class SetAttributeFix extends SetPropertyFix {
return ATTR_TRANSLATABLE;
} else if (mId.equals(ManifestOrderDetector.ALLOW_BACKUP.getId())) {
return ATTR_ALLOW_BACKUP;
+ } else if (mId.equals(MissingIdDetector.ISSUE.getId())) {
+ return ATTR_ID;
} else {
assert false : mId;
return "";
@@ -81,6 +90,8 @@ final class SetAttributeFix extends SetPropertyFix {
return "Mark this as a non-translatable resource";
} else if (mId.equals(ManifestOrderDetector.ALLOW_BACKUP.getId())) {
return "Set the allowBackup attribute to true or false";
+ } else if (mId.equals(MissingIdDetector.ISSUE.getId())) {
+ return "Set the ID attribute";
} else {
assert false : mId;
return "";
@@ -120,6 +131,15 @@ final class SetAttributeFix extends SetPropertyFix {
return VALUE_FALSE;
} else if (mId.equals(TranslationDetector.MISSING.getId())) {
return VALUE_FALSE;
+ } else if (mId.equals(MissingIdDetector.ISSUE.getId())) {
+ IEditorPart editor = AdtUtils.getActiveEditor();
+ if (editor instanceof AndroidXmlEditor) {
+ AndroidXmlEditor xmlEditor = (AndroidXmlEditor) editor;
+ return DescriptorsUtils.getFreeWidgetId(xmlEditor.getUiRootNode(),
+ "fragment"); //$NON-NLS-1$
+ } else {
+ return NEW_ID_PREFIX;
+ }
}
return super.getProposal();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java
index 53b6c3c..434384c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java
@@ -58,6 +58,7 @@ import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
@@ -66,6 +67,7 @@ import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.ToolFactory;
import org.eclipse.jdt.core.formatter.CodeFormatter;
import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.NullChange;
@@ -856,7 +858,7 @@ class TemplateHandler {
contents = format(mProject, contents, to);
IFile targetFile = getTargetFile(to);
- TextFileChange change = createTextChange(targetFile);
+ TextFileChange change = createNewFileChange(targetFile);
MultiTextEdit rootEdit = new MultiTextEdit();
rootEdit.addChild(new InsertEdit(0, contents));
change.setEdit(rootEdit);
@@ -908,7 +910,7 @@ class TemplateHandler {
return contents;
}
- private static TextFileChange createTextChange(IFile targetFile) {
+ private static TextFileChange createNewFileChange(IFile targetFile) {
String fileName = targetFile.getName();
String message;
if (targetFile.exists()) {
@@ -917,7 +919,29 @@ class TemplateHandler {
message = String.format("Create %1$s", fileName);
}
- TextFileChange change = new TextFileChange(message, targetFile);
+ TextFileChange change = new TextFileChange(message, targetFile) {
+ @Override
+ protected IDocument acquireDocument(IProgressMonitor pm) throws CoreException {
+ IDocument document = super.acquireDocument(pm);
+
+ // In our case, we know we *always* use this TextFileChange
+ // to *create* files, we're not appending to existing files.
+ // However, due to the following bug we can end up with cached
+ // contents of previously deleted files that happened to have the
+ // same file name:
+ // https://bugs.eclipse.org/bugs/show_bug.cgi?id=390402
+ // Therefore, as a workaround, wipe out the cached contents here
+ if (document.getLength() > 0) {
+ try {
+ document.replace(0, document.getLength(), "");
+ } catch (BadLocationException e) {
+ // pass
+ }
+ }
+
+ return document;
+ }
+ };
change.setTextType(fileName.substring(fileName.lastIndexOf('.') + 1));
return change;
}
@@ -989,7 +1013,7 @@ class TemplateHandler {
String newFile = Files.toString(src, Charsets.UTF_8);
newFile = format(mProject, newFile, path);
- TextFileChange addFile = createTextChange(file);
+ TextFileChange addFile = createNewFileChange(file);
addFile.setEdit(new InsertEdit(0, newFile));
mTextChanges.add(addFile);
} else {
diff --git a/eclipse/scripts/build_server.sh b/eclipse/scripts/build_server.sh
index 4c27673..b3b86b7 100755
--- a/eclipse/scripts/build_server.sh
+++ b/eclipse/scripts/build_server.sh
@@ -6,7 +6,7 @@
# $2: Optional build number. If present, will be appended to the date qualifier.
# The build number cannot contain spaces *nor* periods (dashes are ok.)
# -z: Optional, prevents the final zip and leaves the udate-site directory intact.
-# -i: Optional, if present, the Google internal update site will be built. Otherwise,
+# -i: Optional, if present, the Google internal update site will be built. Otherwise,
# the external site will be built
# Workflow:
# - make dx, ddms, ping
@@ -57,16 +57,16 @@ function check_params() {
# Check dest dir exists
[ -n "$DEST_DIR" ] || die "Usage: $0 <destination-directory> [build-number]"
[ -d "$DEST_DIR" ] || die "Destination directory $DEST_DIR must exist."
-}
-
-function build_plugin {
- sdk/eclipse/scripts/create_all_symlinks.sh
# Qualifier is "v" followed by date/time in YYYYMMDDHHSS format and the optional
# build number.
DATE=`date +v%Y%m%d%H%M`
QUALIFIER="$DATE"
[ -n "$BUILD_NUMBER" ] && QUALIFIER="${QUALIFIER}-${BUILD_NUMBER}"
+}
+
+function build_plugin() {
+ sdk/eclipse/scripts/create_all_symlinks.sh
# Compute the final directory name and remove any leftovers from previous
# runs if any.
@@ -106,6 +106,16 @@ function build_plugin {
fi
}
+function build_adt_ide() {
+ ADT_IDE_DEST_DIR="$DEST_DIR" \
+ ADT_IDE_QUALIFIER="$QUALIFIER" \
+ make PRODUCT-sdk-adt_eclipse_ide
+}
+
get_params "$@"
check_params
-build_plugin
+( build_plugin )
+if [[ -z $INTERNAL_BUILD ]]; then
+ ( build_adt_ide )
+fi
+
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ArraySizeDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ArraySizeDetector.java
index 771c247..8898507 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ArraySizeDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ArraySizeDetector.java
@@ -35,6 +35,8 @@ import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.XmlContext;
import com.android.utils.Pair;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
@@ -78,7 +80,9 @@ public class ArraySizeDetector extends ResourceXmlDetector {
ArraySizeDetector.class,
Scope.ALL_RESOURCES_SCOPE);
- private Map<File, Pair<String, Integer>> mFileToArrayCount;
+ //private Map<File, List<Pair<String, Integer>>> mFileToArrayCount;
+ //private Map<File, List<Pair<String, Integer>>> mFileToArrayCount;
+ private Multimap<File, Pair<String, Integer>> mFileToArrayCount;
/** Locations for each array name. Populated during phase 2, if necessary */
private Map<String, Location> mLocations;
@@ -107,7 +111,7 @@ public class ArraySizeDetector extends ResourceXmlDetector {
@Override
public void beforeCheckProject(@NonNull Context context) {
if (context.getPhase() == 1) {
- mFileToArrayCount = new HashMap<File, Pair<String,Integer>>(30);
+ mFileToArrayCount = ArrayListMultimap.create(30, 5);
}
}
@@ -125,35 +129,38 @@ public class ArraySizeDetector extends ResourceXmlDetector {
Collections.sort(keys);
for (File file : keys) {
- Pair<String, Integer> pair = mFileToArrayCount.get(file);
- String name = pair.getFirst();
- if (alreadyReported.contains(name)) {
- continue;
- }
- Integer count = pair.getSecond();
-
- Integer current = countMap.get(name);
- if (current == null) {
- countMap.put(name, count);
- fileMap.put(name, file);
- } else if (!count.equals(current)) {
- alreadyReported.add(name);
-
- if (mLocations == null) {
- mLocations = new HashMap<String, Location>();
- mDescriptions = new HashMap<String, String>();
+ Collection<Pair<String, Integer>> pairs = mFileToArrayCount.get(file);
+ for (Pair<String, Integer> pair : pairs) {
+ String name = pair.getFirst();
+
+ if (alreadyReported.contains(name)) {
+ continue;
+ }
+ Integer count = pair.getSecond();
+
+ Integer current = countMap.get(name);
+ if (current == null) {
+ countMap.put(name, count);
+ fileMap.put(name, file);
+ } else if (!count.equals(current)) {
+ alreadyReported.add(name);
+
+ if (mLocations == null) {
+ mLocations = new HashMap<String, Location>();
+ mDescriptions = new HashMap<String, String>();
+ }
+ mLocations.put(name, null);
+
+ String thisName = file.getParentFile().getName() + File.separator
+ + file.getName();
+ File otherFile = fileMap.get(name);
+ String otherName = otherFile.getParentFile().getName() + File.separator
+ + otherFile.getName();
+ String message = String.format(
+ "Array %1$s has an inconsistent number of items (%2$d in %3$s, %4$d in %5$s)",
+ name, count, thisName, current, otherName);
+ mDescriptions.put(name, message);
}
- mLocations.put(name, null);
-
- String thisName = file.getParentFile().getName() + File.separator
- + file.getName();
- File otherFile = fileMap.get(name);
- String otherName = otherFile.getParentFile().getName() + File.separator
- + otherFile.getName();
- String message = String.format(
- "Array %1$s has an inconsistent number of items (%2$d in %3$s, %4$d in %5$s)",
- name, count, thisName, current, otherName);
- mDescriptions.put(name, message);
}
}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
index 476ebf9..70d741e 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
@@ -55,7 +55,7 @@ public class BuiltinIssueRegistry extends IssueRegistry {
private static final List<Issue> sIssues;
static {
- final int initialCapacity = 111;
+ final int initialCapacity = 113;
List<Issue> issues = new ArrayList<Issue>(initialCapacity);
issues.add(AccessibilityDetector.ISSUE);
@@ -94,6 +94,7 @@ public class BuiltinIssueRegistry extends IssueRegistry {
issues.add(MissingClassDetector.MISSING);
issues.add(MissingClassDetector.INSTANTIATABLE);
issues.add(MissingClassDetector.INNERCLASS);
+ issues.add(MissingIdDetector.ISSUE);
issues.add(HandlerDetector.ISSUE);
issues.add(FragmentDetector.ISSUE);
issues.add(TranslationDetector.EXTRA);
@@ -104,6 +105,7 @@ public class BuiltinIssueRegistry extends IssueRegistry {
issues.add(ProguardDetector.SPLITCONFIG);
issues.add(PxUsageDetector.PX_ISSUE);
issues.add(PxUsageDetector.DP_ISSUE);
+ issues.add(PxUsageDetector.IN_MM_ISSUE);
issues.add(TextFieldDetector.ISSUE);
issues.add(TextViewDetector.ISSUE);
issues.add(UnusedResourceDetector.ISSUE);
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/MissingIdDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/MissingIdDetector.java
new file mode 100644
index 0000000..b02e44a
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/MissingIdDetector.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2011 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.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_ID;
+import static com.android.SdkConstants.ATTR_TAG;
+import static com.android.SdkConstants.VIEW_FRAGMENT;
+
+import com.android.annotations.NonNull;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.XmlContext;
+
+import org.w3c.dom.Element;
+
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * Check which looks for missing id's in views where they are probably needed
+ */
+public class MissingIdDetector extends LayoutDetector {
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "MissingId", //$NON-NLS-1$
+ "Ensures that XML tags like <fragment> specify an id or tag attribute",
+
+ "If you do not specify an android:id or an android:tag attribute on a " +
+ "<fragment> element, then if the activity is restarted (for example for " +
+ "an orientation rotation) you may lose state. From the fragment " +
+ "documentation:\n" +
+ "\n" +
+ "\"Each fragment requires a unique identifier that the system can use " +
+ "to restore the fragment if the activity is restarted (and which you can " +
+ "use to capture the fragment to perform transactions, such as remove it). " +
+ "* Supply the android:id attribute with a unique ID.\n" +
+ "* Supply the android:tag attribute with a unique string.\n" +
+ "If you provide neither of the previous two, the system uses the ID of the " +
+ "container view.",
+
+ Category.CORRECTNESS,
+ 5,
+ Severity.WARNING,
+ MissingIdDetector.class,
+ Scope.RESOURCE_FILE_SCOPE)
+ .setMoreInfo("http://developer.android.com/guide/components/fragments.html"); //$NON-NLS-1$
+
+ /** Constructs a new {@link MissingIdDetector} */
+ public MissingIdDetector() {
+ }
+
+ @Override
+ public @NonNull Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Collections.singletonList(VIEW_FRAGMENT);
+ }
+
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ if (!element.hasAttributeNS(ANDROID_URI, ATTR_ID) &&
+ !element.hasAttributeNS(ANDROID_URI, ATTR_TAG)) {
+ context.report(ISSUE, element, context.getLocation(element),
+ "This <fragment> tag should specify an id or a tag to preserve state " +
+ "across activity restarts", null);
+ }
+ }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/PxUsageDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/PxUsageDetector.java
index ef2768d..d1c5c63 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/PxUsageDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/PxUsageDetector.java
@@ -22,6 +22,8 @@ import static com.android.SdkConstants.TAG_ITEM;
import static com.android.SdkConstants.TAG_STYLE;
import static com.android.SdkConstants.UNIT_DIP;
import static com.android.SdkConstants.UNIT_DP;
+import static com.android.SdkConstants.UNIT_IN;
+import static com.android.SdkConstants.UNIT_MM;
import static com.android.SdkConstants.UNIT_PX;
import com.android.annotations.NonNull;
@@ -48,7 +50,7 @@ import java.util.Collections;
* Also look for non-"sp" text sizes.
*/
public class PxUsageDetector extends LayoutDetector {
- /** The main issue discovered by this detector */
+ /** Using px instead of dp */
public static final Issue PX_ISSUE = Issue.create(
"PxUsage", //$NON-NLS-1$
"Looks for use of the \"px\" dimension",
@@ -69,7 +71,24 @@ public class PxUsageDetector extends LayoutDetector {
Scope.RESOURCE_FILE_SCOPE).setMoreInfo(
"http://developer.android.com/guide/practices/screens_support.html#screen-independence"); //$NON-NLS-1$
- /** The main issue discovered by this detector */
+ /** Using mm/in instead of dp */
+ public static final Issue IN_MM_ISSUE = Issue.create(
+ "InOrMmUsage", //$NON-NLS-1$
+ "Looks for use of the \"mm\" or \"in\" dimensions",
+
+ "Avoid using `mm` (millimeters) or `in` (inches) as the unit for dimensions.\n" +
+ "\n" +
+ "While it should work in principle, unfortunately many devices do not report " +
+ "the correct true physical density, which means that the dimension calculations " +
+ "won't work correctly. You are better off using `dp` (and for font sizes, `sp`.)",
+
+ Category.CORRECTNESS,
+ 4,
+ Severity.WARNING,
+ PxUsageDetector.class,
+ Scope.RESOURCE_FILE_SCOPE);
+
+ /** Using sp instead of dp */
public static final Issue DP_ISSUE = Issue.create(
"SpUsage", //$NON-NLS-1$
"Looks for uses of \"dp\" instead of \"sp\" dimensions for text sizes",
@@ -86,7 +105,7 @@ public class PxUsageDetector extends LayoutDetector {
"font size settings are not respected, so consider adjusting the layout itself " +
"to be more flexible.",
Category.CORRECTNESS,
- 2,
+ 3,
Severity.WARNING,
PxUsageDetector.class,
Scope.RESOURCE_FILE_SCOPE).setMoreInfo(
@@ -134,6 +153,19 @@ public class PxUsageDetector extends LayoutDetector {
context.report(PX_ISSUE, attribute, context.getLocation(attribute),
"Avoid using \"px\" as units; use \"dp\" instead", null);
}
+ } else if (value.endsWith(UNIT_MM) && value.matches("\\d+mm") //$NON-NLS-1$
+ || value.endsWith(UNIT_IN) && value.matches("\\d+in")) { //$NON-NLS-1$
+ if (value.charAt(0) == '0') {
+ // 0mm == 0in == 0dp
+ return;
+ }
+ if (context.isEnabled(IN_MM_ISSUE)) {
+ String unit = value.substring(value.length() - 2);
+ context.report(IN_MM_ISSUE, attribute, context.getLocation(attribute), String.format(
+ "Avoid using \"%1$s\" as units " +
+ "(it does not work accurately on all devices); use \"dp\" instead", unit),
+ null);
+ }
} else if (ATTR_TEXT_SIZE.equals(attribute.getLocalName())
&& (value.endsWith(UNIT_DP) || value.endsWith(UNIT_DIP))
&& (value.matches("\\d+di?p"))) {
@@ -183,6 +215,18 @@ public class PxUsageDetector extends LayoutDetector {
"Avoid using \"px\" as units; use \"dp\" instead", null);
}
}
+ } else if (c == 'm' && text.charAt(j - 1) == 'm' ||
+ c == 'n' && text.charAt(j - 1) == 'i') {
+ text = text.trim();
+ String unit = text.substring(text.length() - 2);
+ if (text.matches("\\d+" + unit)) { //$NON-NLS-1$
+ if (context.isEnabled(IN_MM_ISSUE)) {
+ context.report(IN_MM_ISSUE, item, context.getLocation(textNode),
+ String.format("Avoid using \"%1$s\" as units "
+ + "(it does not work accurately on all devices); "
+ + "use \"dp\" instead", unit), null);
+ }
+ }
} else if (c == 'p' && (text.charAt(j - 1) == 'd'
|| text.charAt(j - 1) == 'i')) { // ends with dp or di
text = text.trim();
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ArraySizeDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ArraySizeDetectorTest.java
index 629f43c..127e7f8 100644
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ArraySizeDetectorTest.java
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ArraySizeDetectorTest.java
@@ -47,6 +47,19 @@ public class ArraySizeDetectorTest extends AbstractCheckTest {
"res/values-es/strings.xml"));
}
+ public void testMultipleArrays() throws Exception {
+ assertEquals(
+ "res/values/stringarrays.xml:3: Warning: Array map_density_desc has an inconsistent number of items (5 in values/stringarrays.xml, 1 in values-it/stringarrays.xml) [InconsistentArrays]\n" +
+ " <string-array name=\"map_density_desc\">\n" +
+ " ^\n" +
+ " res/values-it/stringarrays.xml:6: Declaration with array size (1)\n" +
+ "0 errors, 1 warnings\n",
+
+ lintProject(
+ "res/values-it/stringarrays.xml",
+ "res/values/stringarrays.xml"));
+ }
+
public void testArraySizesSuppressed() throws Exception {
assertEquals(
"No warnings.",
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MissingIdDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MissingIdDetectorTest.java
new file mode 100644
index 0000000..ac6b6c0
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MissingIdDetectorTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+@SuppressWarnings("javadoc")
+public class MissingIdDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new MissingIdDetector();
+ }
+
+ public void test() throws Exception {
+ assertEquals(
+ "res/layout/fragment.xml:7: Warning: This <fragment> tag should specify an id or a tag to preserve state across activity restarts [MissingId]\n" +
+ " <fragment\n" +
+ " ^\n" +
+ "0 errors, 1 warnings\n",
+
+ lintProject("res/layout/fragment.xml"));
+ }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/PxUsageDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/PxUsageDetectorTest.java
index 77ebd70..7b5dd0c 100644
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/PxUsageDetectorTest.java
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/PxUsageDetectorTest.java
@@ -27,11 +27,16 @@ public class PxUsageDetectorTest extends AbstractCheckTest {
public void testPx() throws Exception {
assertEquals(
+ "res/layout/now_playing_after.xml:49: Warning: Avoid using \"mm\" as units (it does not work accurately on all devices); use \"dp\" instead [InOrMmUsage]\n" +
+ " android:layout_width=\"100mm\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/now_playing_after.xml:50: Warning: Avoid using \"in\" as units (it does not work accurately on all devices); use \"dp\" instead [InOrMmUsage]\n" +
+ " android:layout_height=\"120in\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"res/layout/now_playing_after.xml:41: Warning: Avoid using \"px\" as units; use \"dp\" instead [PxUsage]\n" +
" android:layout_width=\"1px\"\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 1 warnings\n" +
- "",
+ "0 errors, 3 warnings\n",
lintFiles("res/layout/now_playing_after.xml"));
}
@@ -43,13 +48,25 @@ public class PxUsageDetectorTest extends AbstractCheckTest {
"res/layout/textsize.xml:16: Warning: Should use \"sp\" instead of \"dp\" for text sizes [SpUsage]\n" +
" android:textSize=\"14dip\" />\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 2 warnings\n" +
- "",
+ "0 errors, 2 warnings\n",
+
lintFiles("res/layout/textsize.xml"));
}
public void testStyles() throws Exception {
assertEquals(
+ "res/values/pxsp.xml:23: Warning: Avoid using \"mm\" as units (it does not work accurately on all devices); use \"dp\" instead [InOrMmUsage]\n" +
+ " <item name=\"android:textSize\">50mm</item>\n" +
+ " ^\n" +
+ "res/values/pxsp.xml:25: Warning: Avoid using \"in\" as units (it does not work accurately on all devices); use \"dp\" instead [InOrMmUsage]\n" +
+ " 50in\n" +
+ " ^\n" +
+ "res/values/pxsp.xml:6: Warning: Should use \"sp\" instead of \"dp\" for text sizes [SpUsage]\n" +
+ " <item name=\"android:textSize\">50dp</item>\n" +
+ " ^\n" +
+ "res/values/pxsp.xml:12: Warning: Should use \"sp\" instead of \"dp\" for text sizes [SpUsage]\n" +
+ " <item name=\"android:textSize\"> 50dip </item>\n" +
+ " ^\n" +
"res/values/pxsp.xml:9: Warning: Avoid using \"px\" as units; use \"dp\" instead [PxUsage]\n" +
" <item name=\"android:textSize\">50px</item>\n" +
" ^\n" +
@@ -59,14 +76,7 @@ public class PxUsageDetectorTest extends AbstractCheckTest {
"res/values/pxsp.xml:18: Warning: Avoid using \"px\" as units; use \"dp\" instead [PxUsage]\n" +
" <item name=\"android:paddingTop\">50px</item>\n" +
" ^\n" +
- "res/values/pxsp.xml:6: Warning: Should use \"sp\" instead of \"dp\" for text sizes [SpUsage]\n" +
- " <item name=\"android:textSize\">50dp</item>\n" +
- " ^\n" +
- "res/values/pxsp.xml:12: Warning: Should use \"sp\" instead of \"dp\" for text sizes [SpUsage]\n" +
- " <item name=\"android:textSize\"> 50dip </item>\n" +
- " ^\n" +
- "0 errors, 5 warnings\n" +
- "",
+ "0 errors, 7 warnings\n",
lintFiles("res/values/pxsp.xml"));
}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/fragment.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/fragment.xml
new file mode 100644
index 0000000..bec72b2
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/fragment.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <fragment
+ android:name="android.app.ListFragment"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <fragment
+ android:name="android.app.DialogFragment"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:tag="mytag" />
+
+ <fragment
+ android:id="@+id/fragment3"
+ android:name="android.preference.PreferenceFragment"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/now_playing_after.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/now_playing_after.xml
index 367a044..64f681c 100644
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/now_playing_after.xml
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/now_playing_after.xml
@@ -46,8 +46,8 @@
android:id="@+id/now_playing_more"
android:src="@drawable/ic_now_playing_logo"
android:padding="12dip"
- android:layout_width="wrap_content"
- android:layout_height="fill_parent"
+ android:layout_width="100mm"
+ android:layout_height="120in"
android:onClick="onNowPlayingLogoClick"
android:scaleType="center" />
</LinearLayout>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values-it/stringarrays.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values-it/stringarrays.xml
new file mode 100644
index 0000000..7bd83e9
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values-it/stringarrays.xml
@@ -0,0 +1,12 @@
+<?xml version='1.0' encoding='utf-8'?>
+<resources>
+ <string-array name="track_type_desc">
+ <item>Pendenza</item>
+ </string-array>
+ <string-array name="map_density_desc">
+ <item>Automatico (mappa leggibile su display HD)</item>
+ </string-array>
+ <string-array name="cache_size_desc">
+ <item>Piccolo (100)</item>
+ </string-array>
+</resources> \ No newline at end of file
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/pxsp.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/pxsp.xml
index 9f334a0..91bf06b 100644
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/pxsp.xml
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/pxsp.xml
@@ -2,13 +2,13 @@
<style name="Style1">
<item name="android:textSize">50sp</item>
</style>
- <style name="Style1">
+ <style name="Style2">
<item name="android:textSize">50dp</item>
</style>
- <style name="Style1">
+ <style name="Style3">
<item name="android:textSize">50px</item>
</style>
- <style name="Style1">
+ <style name="Style4">
<item name="android:textSize"> 50dip </item>
</style>
@@ -19,4 +19,11 @@
<item name="android:paddingBottom">50dip</item>
</style>
+ <style name="Style6">
+ <item name="android:textSize">50mm</item>
+ <item name="android:textSize">
+ 50in
+ </item>
+ </style>
+
</resources>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/stringarrays.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/stringarrays.xml
new file mode 100644
index 0000000..6405580
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/stringarrays.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string-array name="map_density_desc">
+ <item>Automatic (readable map on HD displays)</item>
+ <item>1 map pixel = 1 screen pixel</item>
+ <item>1 map pixel = 1.25 screen pixels</item>
+ <item>1 map pixel = 1.5 screen pixels</item>
+ <item>1 map pixel = 2 screen pixels</item>
+ </string-array>
+ <string-array name="spatial_resolution_desc">
+ <item>5m/yd (fine, default)</item>
+ </string-array>
+</resources> \ No newline at end of file
diff --git a/manifmerger/src/com/android/manifmerger/ManifestMerger.java b/manifmerger/src/com/android/manifmerger/ManifestMerger.java
index e4c3d6f..688cecd 100755
--- a/manifmerger/src/com/android/manifmerger/ManifestMerger.java
+++ b/manifmerger/src/com/android/manifmerger/ManifestMerger.java
@@ -32,9 +32,9 @@ import org.w3c.dom.NodeList;
import java.io.File;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@@ -486,7 +486,7 @@ public class ManifestMerger {
boolean found = false;
for (Element dest : findElements(mMainDoc, path)) {
- if (compareElements(src, dest, false, null /*diff*/, null /*keyAttr*/)) {
+ if (compareElements(dest, src, false, null /*diff*/, null /*keyAttr*/)) {
found = true;
break;
}
@@ -566,7 +566,7 @@ public class ManifestMerger {
for (Element dest : dests) {
// If there's already a similar node in the destination, check it's identical.
StringBuilder diff = new StringBuilder();
- if (compareElements(src, dest, false, diff, keyAttr)) {
+ if (compareElements(dest, src, false, diff, keyAttr)) {
// Same element. Skip.
if (warnDups) {
mLog.conflict(Severity.INFO,
@@ -1192,11 +1192,13 @@ public class ManifestMerger {
}
/**
- * Compares two {@link Element}s recursively. They must be identical with the same
- * structure and order. Whitespace and comments are ignored.
+ * Compares two {@link Element}s recursively.
+ * They must be identical with the same structure.
+ * Order should not matter.
+ * Whitespace and comments are ignored.
*
- * @param e1 The first element to compare.
- * @param e2 The second element to compare with.
+ * @param expected The first element to compare.
+ * @param actual The second element to compare with.
* @param nextSiblings If true, will also compare the following siblings.
* If false, it will just compare the given node.
* @param diff An optional {@link StringBuilder} where to accumulate a diff output.
@@ -1204,197 +1206,22 @@ public class ManifestMerger {
* @return True if {@code e1} and {@code e2} are equal.
*/
private boolean compareElements(
- @NonNull Node e1,
- @NonNull Node e2,
+ @NonNull Node expected,
+ @NonNull Node actual,
boolean nextSiblings,
@Nullable StringBuilder diff,
@Nullable String keyAttr) {
- return compareElements(e1, e2, nextSiblings, diff, 0, keyAttr);
- }
-
- /**
- * Do not call directly. This is an implementation detail for
- * {@link #compareElements(Node, Node, boolean, StringBuilder, String)}.
- */
- private boolean compareElements(
- @NonNull Node e1,
- @NonNull Node e2,
- boolean nextSiblings,
- @Nullable StringBuilder diff,
- int diffOffset,
- @Nullable String keyAttr) {
- while(true) {
- // Find the next non-whitespace text or non-comment in e1.
- while (e1 != null) {
- short t = e1.getNodeType();
-
- if (t == Node.COMMENT_NODE) {
- e1 = e1.getNextSibling();
- } else if (t == Node.TEXT_NODE) {
- String s = e1.getNodeValue().trim();
- if (s.length() == 0) {
- e1 = e1.getNextSibling();
- } else {
- break;
- }
- } else {
- break;
- }
- }
-
- // Find the next non-whitespace text or non-comment in e2.
- while (e2 != null) {
- short t = e2.getNodeType();
-
- if (t == Node.COMMENT_NODE) {
- e2 = e2.getNextSibling();
- } else if (t == Node.TEXT_NODE) {
- String s = e2.getNodeValue().trim();
- if (s.length() == 0) {
- e2 = e2.getNextSibling();
- } else {
- break;
- }
- } else {
- break;
- }
- }
-
- // Same elements, or both null?
- if (e1 == e2 || (e1 == null && e2 == null)) {
- return true;
- }
-
- // Is one null but not the other?
- if ((e1 == null && e2 != null) || (e1 != null && e2 == null)) {
- break; // dumpMismatchAndExit
- }
-
- assert e1 != null;
- assert e2 != null;
-
- // Same type?
- short t = e1.getNodeType();
- if (t != e2.getNodeType()) {
- break; // dumpMismatchAndExit
- }
-
- // Same node name? Must both be null or have the same value.
- String s1 = e1.getNodeName();
- String s2 = e2.getNodeName();
- if ( !( (s1 == null && s2 == null) || (s1 != null && s1.equals(s2)) ) ) {
- break; // dumpMismatchAndExit
- }
-
- // Same node value? Must both be null or have the same value once whitespace is trimmed.
- s1 = e1.getNodeValue();
- s2 = e2.getNodeValue();
- if (s1 != null) {
- s1 = s1.trim();
- }
- if (s2 != null) {
- s2 = s2.trim();
- }
- if ( !( (s1 == null && s2 == null) || (s1 != null && s1.equals(s2)) ) ) {
- break; // dumpMismatchAndExit
- }
-
+ Map<String, String> nsPrefixE = new HashMap<String, String>();
+ Map<String, String> nsPrefixA = new HashMap<String, String>();
+ String sE = XmlUtils.printElement(expected, nsPrefixE, ""); //$NON-NLS-1$
+ String sA = XmlUtils.printElement(actual, nsPrefixA, ""); //$NON-NLS-1$
+ if (sE.equals(sA)) {
+ return true;
+ } else {
if (diff != null) {
- // So far e1 and e2 seem pretty much equal. Dump it to the diff.
- // We need to print to the diff before dealing with the children or attributes.
- // Note: diffOffset + 1 because we want to reserve 2 spaces to write -/+
- diff.append(XmlUtils.dump(e1, diffOffset + 1,
- false /*nextSiblings*/, false /*deep*/, keyAttr));
- }
-
- // Now compare the attributes. When using the w3c.DOM this way, attributes are
- // accessible via the Node/Element attributeMap and are not actually exposed
- // as ATTR_NODEs in the node list. The downside is that we don't really
- // have the proper attribute order but that's not an issue as far as the validity
- // of the XML since attribute order should never matter.
- List<Attr> a1 = XmlUtils.sortedAttributeList(e1.getAttributes());
- List<Attr> a2 = XmlUtils.sortedAttributeList(e2.getAttributes());
- if (a1.size() > 0 || a2.size() > 0) {
-
- int count1 = 0;
- int count2 = 0;
- Map<String, AttrDiff> map = new TreeMap<String, AttrDiff>();
- for (Attr a : a1) {
- AttrDiff ad1 = new AttrDiff(a, "--"); //$NON-NLS-1$
- map.put(ad1.mKey, ad1);
- count1++;
- }
-
- for (Attr a : a2) {
- AttrDiff ad2 = new AttrDiff(a, "++"); //$NON-NLS-1$
- AttrDiff ad1 = map.get(ad2.mKey);
- if (ad1 != null) {
- ad1.mSide = " "; //$NON-NLS-1$
- count1--;
- } else {
- map.put(ad2.mKey, ad2);
- count2++;
- }
- }
-
- if (count1 != 0 || count2 != 0) {
- // We found some items not matching in both sets. Dump the result.
- if (diff != null) {
- for (AttrDiff ad : map.values()) {
- diff.append(ad.mSide)
- .append(XmlUtils.dump(ad.mAttr, diffOffset,
- false /*nextSiblings*/, false /*deep*/,
- keyAttr));
- }
- }
- // Exit without dumping
- return false;
- }
+ XmlUtils.printXmlDiff(diff, sE, sA, nsPrefixE, nsPrefixA, NS_URI + ':' + keyAttr);
}
-
- // Compare recursively for elements.
- if (t == Node.ELEMENT_NODE &&
- !compareElements(
- e1.getFirstChild(), e2.getFirstChild(), true,
- diff, diffOffset + 1, keyAttr)) {
- // Exit without dumping since the recursive call take cares of its own diff
- return false;
- }
-
- if (nextSiblings) {
- e1 = e1.getNextSibling();
- e2 = e2.getNextSibling();
- continue;
- } else {
- return true;
- }
- }
-
- // <INTERCAL COME FROM dumpMismatchAndExit PLEASE>
- if (diff != null) {
- diff.append("--")
- .append(XmlUtils.dump(e1, diffOffset,
- false /*nextSiblings*/, false /*deep*/, keyAttr));
- diff.append("++")
- .append(XmlUtils.dump(e2, diffOffset,
- false /*nextSiblings*/, false /*deep*/, keyAttr));
- }
- return false;
- }
-
- private static class AttrDiff {
- public final String mKey;
- public final Attr mAttr;
- public String mSide;
-
- public AttrDiff(Attr attr, String side) {
- mKey = getKey(attr);
- mAttr = attr;
- mSide = side;
- }
-
- String getKey(Attr attr) {
- return String.format("%s=%s", attr.getNodeName(), attr.getNodeValue());
+ return false;
}
}
@@ -1521,4 +1348,5 @@ public class ManifestMerger {
return new FileAndLine(name, line);
}
+
}
diff --git a/manifmerger/src/com/android/manifmerger/XmlUtils.java b/manifmerger/src/com/android/manifmerger/XmlUtils.java
index 7e92d55..71aac91 100755
--- a/manifmerger/src/com/android/manifmerger/XmlUtils.java
+++ b/manifmerger/src/com/android/manifmerger/XmlUtils.java
@@ -39,6 +39,10 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
@@ -499,4 +503,296 @@ class XmlUtils {
}
};
}
+
+ // -------
+
+ /**
+ * Flatten the element to a string. This "pretty prints" the XML tree starting
+ * from the given node and all its children and attributes.
+ * <p/>
+ * The output is designed to be printed using {@link #printXmlDiff}.
+ *
+ * @param node The root node to print.
+ * @param nsPrefix A map that is filled with all the URI=>prefix found.
+ * The internal string only contains the expanded URIs but this is rather verbose
+ * so when printing the diff these will be replaced by the prefixes collected here.
+ * @param prefix A "space" prefix added at the beginning of each line for indentation
+ * purposes. The diff printer later relies on this to find out the structure.
+ */
+ @NonNull
+ static String printElement(
+ @NonNull Node node,
+ @NonNull Map<String, String> nsPrefix,
+ @NonNull String prefix) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(prefix).append('<');
+ String uri = node.getNamespaceURI();
+ if (uri != null) {
+ sb.append(uri).append(':');
+ nsPrefix.put(uri, node.getPrefix());
+ }
+ sb.append(node.getLocalName());
+ printAttributes(sb, node, nsPrefix, prefix);
+ sb.append(">\n"); //$NON-NLS-1$
+ printChildren(sb, node.getFirstChild(), true, nsPrefix, prefix + " "); //$NON-NLS-1$
+
+ sb.append(prefix).append("</"); //$NON-NLS-1$
+ if (uri != null) {
+ sb.append(uri).append(':');
+ }
+ sb.append(node.getLocalName());
+ sb.append(">\n"); //$NON-NLS-1$
+
+ return sb.toString();
+ }
+
+ /**
+ * Flatten several children elements to a string.
+ * This is an implementation detail for {@link #printElement(Node, Map, String)}.
+ * <p/>
+ * If {@code nextSiblings} is false, the string conversion takes only the given
+ * child element and stops there.
+ * <p/>
+ * If {@code nextSiblings} is true, the string conversion also takes _all_ the siblings
+ * after the given element. The idea is the caller can call this with the first child
+ * of a parent and get a string showing all the children at the same time. They are
+ * sorted to avoid the ordering issue.
+ */
+ @NonNull
+ private static StringBuilder printChildren(
+ @NonNull StringBuilder sb,
+ @NonNull Node child,
+ boolean nextSiblings,
+ @NonNull Map<String, String> nsPrefix,
+ @NonNull String prefix) {
+ ArrayList<String> children = new ArrayList<String>();
+
+ boolean hasText = false;
+ for (; child != null; child = child.getNextSibling()) {
+ short t = child.getNodeType();
+ if (nextSiblings && t == Node.TEXT_NODE) {
+ // We don't typically have meaningful text nodes in an Android manifest.
+ // If there are, just dump them as-is into the element representation.
+ // We do trim whitespace and ignore all-whitespace or empty text nodes.
+ String s = child.getNodeValue().trim();
+ if (s.length() > 0) {
+ sb.append(s);
+ hasText = true;
+ }
+ } else if (t == Node.ELEMENT_NODE) {
+ children.add(printElement(child, nsPrefix, prefix)); //$NON-NLS-1$
+ if (!nextSiblings) {
+ break;
+ }
+ }
+ }
+
+ if (hasText) {
+ sb.append('\n');
+ }
+
+ if (!children.isEmpty()) {
+ Collections.sort(children);
+ for (String s : children) {
+ sb.append(s);
+ }
+ }
+
+ return sb;
+ }
+
+ /**
+ * Flatten several attributes to a string using their alphabethical order.
+ * This is an implementation detail for {@link #printElement(Node, Map, String)}.
+ */
+ @NonNull
+ private static StringBuilder printAttributes(
+ @NonNull StringBuilder sb,
+ @NonNull Node node,
+ @NonNull Map<String, String> nsPrefix,
+ @NonNull String prefix) {
+ ArrayList<String> attrs = new ArrayList<String>();
+
+ NamedNodeMap attrMap = node.getAttributes();
+ if (attrMap != null) {
+ StringBuilder sb2 = new StringBuilder();
+ for (int i = 0; i < attrMap.getLength(); i++) {
+ Node attr = attrMap.item(i);
+ if (attr instanceof Attr) {
+ sb2.setLength(0);
+ sb2.append('@');
+ String uri = attr.getNamespaceURI();
+ if (uri != null) {
+ sb2.append(uri).append(':');
+ nsPrefix.put(uri, attr.getPrefix());
+ }
+ sb2.append(attr.getLocalName());
+ sb2.append("=\"").append(attr.getNodeValue()).append('\"'); //$NON-NLS-1$
+ attrs.add(sb2.toString());
+ }
+ }
+ }
+
+ Collections.sort(attrs);
+
+ for(String attr : attrs) {
+ sb.append('\n');
+ sb.append(prefix).append(" ").append(attr); //$NON-NLS-1$
+ }
+ return sb;
+ }
+
+ //------------
+
+ /**
+ * Computes a quick diff between two strings generated by
+ * {@link #printElement(Node, Map, String)}.
+ * <p/>
+ * This is a <em>not</em> designed to be a full contextual diff.
+ * It just stops at the first difference found, printing up to 3 lines of diff
+ * and backtracking to add prior contextual information to understand the
+ * structure of the element where the first diff line occured (by printing
+ * each parent found till the root one as well as printing the attribute
+ * named by {@code keyAttr}).
+ *
+ * @param sb The string builder where to output is written.
+ * @param expected The expected XML tree (as generated by {@link #printElement}.)
+ * For best result this would be the "destination" XML we're merging into,
+ * e.g. the main manifest.
+ * @param actual The actual XML tree (as generated by {@link #printElement}.)
+ * For best result this would be the "source" XML we're merging from,
+ * e.g. a library manifest.
+ * @param nsPrefixE The map of URI=>prefix for the expected XML tree.
+ * @param nsPrefixA The map of URI=>prefix for the actual XML tree.
+ * @param keyAttr An optional attribute *full* name (uri:local name) to always
+ * insert when writing the contextual lines before a diff line.
+ * For example when writing an Activity, it helps to always insert
+ * the "name" attribute since that's the key element to help the user
+ * identify which node is being dumped.
+ */
+ static void printXmlDiff(
+ StringBuilder sb,
+ String expected,
+ String actual,
+ Map<String, String> nsPrefixE,
+ Map<String, String> nsPrefixA,
+ String keyAttr) {
+ String[] aE = expected.split("\n");
+ String[] aA = actual.split("\n");
+ int lE = aE.length;
+ int lA = aA.length;
+ int lm = lE < lA ? lA : lE;
+ boolean eofE = false;
+ boolean eofA = false;
+ boolean contextE = true;
+ boolean contextA = true;
+ int numDiff = 0;
+
+ StringBuilder sE = new StringBuilder();
+ StringBuilder sA = new StringBuilder();
+
+ outerLoop: for (int i = 0, iE = 0, iA = 0; i < lm; i++) {
+ if (iE < lE && iA < lA && aE[iE].equals(aA[iA])) {
+ if (numDiff > 0) {
+ // If we found a difference, stop now.
+ break outerLoop;
+ }
+ iE++;
+ iA++;
+ continue;
+ } else {
+ // Try to print some context for each side based on previous lines's space prefix.
+ if (contextE) {
+ if (iE > 0) {
+ String p = diffGetPrefix(aE[iE]);
+ for (int kE = iE-1; kE >= 0; kE--) {
+ if (!aE[kE].startsWith(p)) {
+ sE.insert(0, '\n').insert(0, diffReplaceNs(aE[kE], nsPrefixE)).insert(0, " ");
+ if (p.length() == 0) {
+ break;
+ }
+ p = diffGetPrefix(aE[kE]);
+ } else if (aE[kE].contains(keyAttr) || kE == 0) {
+ sE.insert(0, '\n').insert(0, diffReplaceNs(aE[kE], nsPrefixE)).insert(0, " ");
+ }
+ }
+ }
+ contextE = false;
+ }
+ if (iE >= lE) {
+ if (!eofE) {
+ sE.append("--(end reached)\n");
+ eofE = true;
+ }
+ } else {
+ sE.append("--").append(diffReplaceNs(aE[iE++], nsPrefixE)).append('\n');
+ }
+
+ if (contextA) {
+ if (iA > 0) {
+ String p = diffGetPrefix(aA[iA]);
+ for (int kA = iA-1; kA >= 0; kA--) {
+ if (!aA[kA].startsWith(p)) {
+ sA.insert(0, '\n').insert(0, diffReplaceNs(aA[kA], nsPrefixA)).insert(0, " ");
+ p = diffGetPrefix(aA[kA]);
+ if (p.length() == 0) {
+ break;
+ }
+ } else if (aA[kA].contains(keyAttr) || kA == 0) {
+ sA.insert(0, '\n').insert(0, diffReplaceNs(aA[kA], nsPrefixA)).insert(0, " ");
+ }
+ }
+ }
+ contextA = false;
+ }
+ if (iA >= lA) {
+ if (!eofA) {
+ sA.append("++(end reached)\n");
+ eofA = true;
+ }
+ } else {
+ sA.append("++").append(diffReplaceNs(aA[iA++], nsPrefixA)).append('\n');
+ }
+
+ // Dump up to 3 lines of difference
+ numDiff++;
+ if (numDiff == 3) {
+ break outerLoop;
+ }
+ }
+ }
+
+ sb.append(sE);
+ sb.append(sA);
+ }
+
+ /**
+ * Returns all the whitespace at the beginning of a string.
+ * Implementation details for {@link #printXmlDiff} used to find the "parent"
+ * element and include it in the context of the diff.
+ */
+ private static String diffGetPrefix(String str) {
+ int pos = 0;
+ int len = str.length();
+ while (pos < len && str.charAt(pos) == ' ') {
+ pos++;
+ }
+ return str.substring(0, pos);
+ }
+
+ /**
+ * Simplifies a diff line by replacing NS URIs by their prefix.
+ * Implementation details for {@link #printXmlDiff}.
+ */
+ private static String diffReplaceNs(String str, Map<String, String> nsPrefix) {
+ for (Entry<String, String> entry : nsPrefix.entrySet()) {
+ String uri = entry.getKey();
+ String prefix = entry.getValue();
+ if (prefix != null && str.contains(uri)) {
+ str = str.replaceAll(Pattern.quote(uri), Matcher.quoteReplacement(prefix));
+ }
+ }
+ return str;
+ }
+
}
diff --git a/manifmerger/tests/src/com/android/manifmerger/ManifestMergerTest.java b/manifmerger/tests/src/com/android/manifmerger/ManifestMergerTest.java
index 1482792..564fc6d 100755
--- a/manifmerger/tests/src/com/android/manifmerger/ManifestMergerTest.java
+++ b/manifmerger/tests/src/com/android/manifmerger/ManifestMergerTest.java
@@ -145,4 +145,8 @@ public class ManifestMergerTest extends ManifestMergerTestCase {
public void test56_support_gltext_warning() throws Exception {
processTestFiles();
}
+
+ public void test60_merge_order() throws Exception {
+ processTestFiles();
+ }
}
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/11_activity_dup.xml b/manifmerger/tests/src/com/android/manifmerger/data/11_activity_dup.xml
index 5ba6688..ef163b0 100755
--- a/manifmerger/tests/src/com/android/manifmerger/data/11_activity_dup.xml
+++ b/manifmerger/tests/src/com/android/manifmerger/data/11_activity_dup.xml
@@ -366,22 +366,41 @@
</manifest>
+
@errors
E [ManifestMergerTest0_main.xml:32, ManifestMergerTest1_lib1_widget.xml:16] Trying to merge incompatible /manifest/application/activity[@name=com.example.WidgetConfigurationUI] element:
- <activity android:name=com.example.WidgetConfigurationUI>
--- <intent-filter>
-++ (end reached)
+ <activity
+ @android:name="com.example.WidgetConfigurationUI"
+--</activity>
+--(end reached)
+ <activity
+ @android:name="com.example.WidgetConfigurationUI"
+++ <intent-filter>
+++ <action
+++ @android:name="android.appwidget.action.APPWIDGET_CONFIGURE">
E [ManifestMergerTest0_main.xml:38, ManifestMergerTest2_lib2_activity.xml:6] Trying to merge incompatible /manifest/application/activity[@name=com.example.LibActivity] element:
- <activity android:name=com.example.LibActivity>
- @android:icon = @drawable/lib_activity_icon
- @android:label = @string/lib_activity_name
- @android:name = com.example.LibActivity
--- @android:theme = @style/Lib.Theme
+ <activity
+-- @android:name="com.example.LibActivity">
+-- <intent-filter>
+-- <action
+ <activity
+++ @android:name="com.example.LibActivity"
+++ @android:theme="@style/Lib.Theme">
+++ <intent-filter>
E [ManifestMergerTest0_main.xml, ManifestMergerTest3_lib3_alias.xml:19] Trying to merge incompatible /manifest/application/activity[@name=com.example.LibActivity2] element:
- <activity android:name=com.example.LibActivity2>
- <intent-filter>
- <action android:name=android.intent.action.MAIN>
- <category android:name=android.intent.category.LAUNCHER>
--- <category android:name=android.intent.category.MOARCATZPLZ>
-++ (end reached)
+ <activity
+ @android:name="com.example.LibActivity2"
+ @android:name="android.intent.action.MAIN">
+ @android:name="android.intent.category.LAUNCHER">
+-- </intent-filter>
+--</activity>
+--(end reached)
+ <activity
+ @android:name="com.example.LibActivity2"
+ <intent-filter>
+ @android:name="android.intent.action.MAIN">
+ @android:name="android.intent.category.LAUNCHER">
+++ <category
+++ @android:name="android.intent.category.MOARCATZPLZ">
+++ </category>
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/12_alias_dup.xml b/manifmerger/tests/src/com/android/manifmerger/data/12_alias_dup.xml
index 696965f..7b5aed3 100755
--- a/manifmerger/tests/src/com/android/manifmerger/data/12_alias_dup.xml
+++ b/manifmerger/tests/src/com/android/manifmerger/data/12_alias_dup.xml
@@ -192,14 +192,20 @@
P [ManifestMergerTest0_main.xml:6, ManifestMergerTest1_lib1.xml:6] Skipping identical /manifest/application/activity-alias[@name=com.example.alias.MyActivity1] element.
E [ManifestMergerTest0_main.xml:13, ManifestMergerTest1_lib1.xml:14] Trying to merge incompatible /manifest/application/activity-alias[@name=com.example.alias.MyActivity2] element:
- <activity-alias android:name=com.example.alias.MyActivity2>
-++ @android:icon = @drawable/alias_icon2
-++ @android:label = @string/alias_name2
- @android:name = com.example.alias.MyActivity2
- @android:targetActivity = com.example.MainActivity2
+ <activity-alias
+-- @android:icon="@drawable/alias_icon2"
+-- @android:label="@string/alias_name2"
+-- @android:name="com.example.alias.MyActivity2"
+ <activity-alias
+++ @android:name="com.example.alias.MyActivity2"
+++ @android:targetActivity="com.example.MainActivity2">
+++</activity-alias>
E [ManifestMergerTest0_main.xml, ManifestMergerTest2_lib2.xml:6] Trying to merge incompatible /manifest/application/activity-alias[@name=com.example.alias.MyActivity3] element:
- <activity-alias android:name=com.example.alias.MyActivity3>
- @android:icon = @drawable/alias_icon3
- @android:label = @string/alias_name3
- @android:name = com.example.alias.MyActivity3
-++ @android:targetActivity = com.example.MainActivity3
+ <activity-alias
+-- @android:name="com.example.alias.MyActivity3"
+-- @android:targetActivity="com.example.MainActivity3">
+-- <intent-filter>
+ <activity-alias
+++ @android:name="com.example.alias.MyActivity3">
+++ <intent-filter>
+++ <category
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/13_service_dup.xml b/manifmerger/tests/src/com/android/manifmerger/data/13_service_dup.xml
index 36d7e24..4c257fa 100755
--- a/manifmerger/tests/src/com/android/manifmerger/data/13_service_dup.xml
+++ b/manifmerger/tests/src/com/android/manifmerger/data/13_service_dup.xml
@@ -146,10 +146,22 @@
P [ManifestMergerTest0_main.xml:6, ManifestMergerTest1_lib1.xml:6] Skipping identical /manifest/application/service[@name=com.example.AppService1] element.
E [ManifestMergerTest0_main.xml:8, ManifestMergerTest1_lib1.xml:9] Trying to merge incompatible /manifest/application/service[@name=com.example.AppService2] element:
- <service android:name=com.example.AppService2>
--- <intent-filter>
-++ (end reached)
+ <service
+ @android:name="com.example.AppService2">
+--</service>
+--(end reached)
+ <service
+ @android:name="com.example.AppService2">
+++ <intent-filter>
+++ <action
+++ @android:name="android.intent.action.MAIN">
E [ManifestMergerTest0_main.xml, ManifestMergerTest2_lib2.xml:6] Trying to merge incompatible /manifest/application/service[@name=com.example.AppService3] element:
- <service android:name=com.example.AppService3>
--- (end reached)
-++ <intent-filter>
+ <service
+ @android:name="com.example.AppService3">
+-- <intent-filter>
+-- <action
+-- @android:name="android.intent.action.MAIN">
+ <service
+ @android:name="com.example.AppService3">
+++</service>
+++(end reached)
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/14_receiver_dup.xml b/manifmerger/tests/src/com/android/manifmerger/data/14_receiver_dup.xml
index a2547af..777ba22 100755
--- a/manifmerger/tests/src/com/android/manifmerger/data/14_receiver_dup.xml
+++ b/manifmerger/tests/src/com/android/manifmerger/data/14_receiver_dup.xml
@@ -165,12 +165,22 @@
P [ManifestMergerTest0_main.xml:6, ManifestMergerTest1_lib1.xml:6] Skipping identical /manifest/application/receiver[@name=com.example.AppReceiver1] element.
E [ManifestMergerTest0_main.xml:12, ManifestMergerTest1_lib1.xml:13] Trying to merge incompatible /manifest/application/receiver[@name=com.example.AppReceiver2] element:
- <receiver android:name=com.example.AppReceiver2>
-++ @android:icon = @drawable/app_icon
- @android:name = com.example.AppReceiver2
+ <receiver
+-- @android:icon="@drawable/app_icon"
+-- @android:name="com.example.AppReceiver2">
+-- <intent-filter>
+ <receiver
+++ @android:name="com.example.AppReceiver2">
+++</receiver>
+++(end reached)
E [ManifestMergerTest0_main.xml, ManifestMergerTest2_lib2.xml:6] Trying to merge incompatible /manifest/application/receiver[@name=com.example.AppReceiver3] element:
- <receiver android:name=com.example.AppReceiver3>
- <intent-filter>
- <action android:name=com.example.action.ACTION_CUSTOM>
--- @android:name = com.example.action.ACTION_CUSTOM
-++ @android:name = com.example.action.ACTION_CUSTOM1
+ <receiver
+ @android:name="com.example.AppReceiver3">
+ <intent-filter>
+ <action
+-- @android:name="com.example.action.ACTION_CUSTOM1">
+ <receiver
+ @android:name="com.example.AppReceiver3">
+ <intent-filter>
+ <action
+++ @android:name="com.example.action.ACTION_CUSTOM">
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/15_provider_dup.xml b/manifmerger/tests/src/com/android/manifmerger/data/15_provider_dup.xml
index 7938c1e..bd0c8fe 100755
--- a/manifmerger/tests/src/com/android/manifmerger/data/15_provider_dup.xml
+++ b/manifmerger/tests/src/com/android/manifmerger/data/15_provider_dup.xml
@@ -134,12 +134,20 @@
P [ManifestMergerTest0_main.xml:6, ManifestMergerTest1_lib1.xml:6] Skipping identical /manifest/application/provider[@name=com.example.Provider1] element.
E [ManifestMergerTest0_main.xml:8, ManifestMergerTest1_lib1.xml:9] Trying to merge incompatible /manifest/application/provider[@name=com.example.Provider2] element:
- <provider android:name=com.example.Provider2>
--- @android:authorities = com.example.android.apis.app.thingy2
--- @android:enabled = @bool/someConditionalValue2
- @android:name = com.example.Provider2
+ <provider
+-- @android:name="com.example.Provider2">
+--</provider>
+--(end reached)
+ <provider
+++ @android:authorities="com.example.android.apis.app.thingy2"
+++ @android:enabled="@bool/someConditionalValue2"
+++ @android:name="com.example.Provider2">
E [ManifestMergerTest0_main.xml, ManifestMergerTest2_lib2.xml:6] Trying to merge incompatible /manifest/application/provider[@name=com.example.Provider3] element:
- <provider android:name=com.example.Provider3>
- @android:authorities = com.example.android.apis.app.thingy3
-++ @android:enabled = @bool/someConditionalValue
- @android:name = com.example.Provider3
+ <provider
+-- @android:enabled="@bool/someConditionalValue"
+-- @android:name="com.example.Provider3">
+--</provider>
+ <provider
+++ @android:name="com.example.Provider3">
+++</provider>
+++(end reached)
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/26_permission_dup.xml b/manifmerger/tests/src/com/android/manifmerger/data/26_permission_dup.xml
index 3862249..bd9a4f1 100755
--- a/manifmerger/tests/src/com/android/manifmerger/data/26_permission_dup.xml
+++ b/manifmerger/tests/src/com/android/manifmerger/data/26_permission_dup.xml
@@ -269,39 +269,46 @@
@errors
E [ManifestMergerTest0_main.xml:12, ManifestMergerTest1_lib1.xml:4] Trying to merge incompatible /manifest/permission[@name=com.example.DangerWillRobinson] element:
- <permission android:name=com.example.DangerWillRobinson>
--- @android:description = Different description here
-++ @android:description = Insert boring description here
--- @android:icon = @drawable/not_the_same_icon
-++ @android:icon = @drawable/robot
- @android:label = Danger, Will Robinson!
- @android:name = com.example.DangerWillRobinson
- @android:permissionGroup = com.example.MasterControlPermission
- @android:protectionLevel = dangerous
+ <permission
+-- @android:description="Insert boring description here"
+-- @android:icon="@drawable/robot"
+ <permission
+++ @android:description="Different description here"
+++ @android:icon="@drawable/not_the_same_icon"
E [ManifestMergerTest0_main.xml:14, ManifestMergerTest1_lib1.xml:8] Trying to merge incompatible /manifest/permission[@name=com.example.WhatWereYouThinking] element:
- <permission android:name=com.example.WhatWereYouThinking>
- @android:name = com.example.WhatWereYouThinking
- @android:permissionGroup = com.example.MasterControlPermission
--- @android:protectionLevel = normal
-++ @android:protectionLevel = signatureOrSystem
+ <permission
+ @android:name="com.example.WhatWereYouThinking"
+-- @android:protectionLevel="signatureOrSystem">
+ <permission
+ @android:name="com.example.WhatWereYouThinking"
+++ @android:protectionLevel="normal">
E [ManifestMergerTest0_main.xml:16, ManifestMergerTest1_lib1.xml:5] Trying to merge incompatible /manifest/permission-group[@name=com.example.MasterControlPermission] element:
- <permission-group android:name=com.example.MasterControlPermission>
- @android:description = Nobody expects...
-++ @android:icon = @drawable/ignored_icon
- @android:label = the Spanish Inquisition
- @android:name = com.example.MasterControlPermission
+ <permission-group
+-- @android:icon="@drawable/ignored_icon"
+-- @android:label="the Spanish Inquisition"
+-- @android:name="com.example.MasterControlPermission">
+ <permission-group
+++ @android:label="the Spanish Inquisition"
+++ @android:name="com.example.MasterControlPermission">
+++</permission-group>
E [ManifestMergerTest0_main.xml:18, ManifestMergerTest1_lib1.xml:6] Trying to merge incompatible /manifest/permission-tree[@name=com.example.PermTree] element:
- <permission-tree android:name=com.example.PermTree>
-++ @android:label = This is not a label
--- @android:label = This is not the same label
- @android:name = com.example.PermTree
+ <permission-tree
+-- @android:label="This is not a label"
+ <permission-tree
+++ @android:label="This is not the same label"
E [ManifestMergerTest0_main.xml, ManifestMergerTest2_lib2.xml:6] Trying to merge incompatible /manifest/permission[@name=com.example.Permission1] element:
- <permission android:name=com.example.Permission1>
- @android:name = com.example.Permission1
- @android:permissionGroup = com.example.Permission1
-++ @android:protectionLevel = normal
--- @android:protectionLevel = system
+ <permission
+ @android:name="com.example.Permission1"
+-- @android:protectionLevel="normal">
+ <permission
+ @android:name="com.example.Permission1"
+++ @android:protectionLevel="system">
E [ManifestMergerTest0_main.xml, ManifestMergerTest2_lib2.xml:7] Trying to merge incompatible /manifest/permission-tree[@name=com.example.PermTree1] element:
- <permission-tree android:name=com.example.PermTree1>
--- @android:description = Extra description
- @android:name = com.example.PermTree1
+ <permission-tree
+-- @android:name="com.example.PermTree1">
+--</permission-tree>
+--(end reached)
+ <permission-tree
+++ @android:description="Extra description"
+++ @android:name="com.example.PermTree1">
+++</permission-tree>
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/60_merge_order.xml b/manifmerger/tests/src/com/android/manifmerger/data/60_merge_order.xml
new file mode 100755
index 0000000..e820ecb
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/60_merge_order.xml
@@ -0,0 +1,317 @@
+#
+# Test:
+# - When activity / activity-alias / service / receiver / provider are merged,
+# we do a comparison to check whether the elements are already present in the
+# main manifest.
+# - What this checks is that the order of the elements or attributes within
+# the elements should not matter.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1">
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.Activity1"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data
+ android:name="metaName"
+ android:value="metaValue"
+ android:resource="@color/someColor" />
+ </activity>
+
+ <!-- Receiver -->
+ <receiver
+ android:name="com.example.AppReceiver"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="com.example.intent.action.DO_THIS" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="com.example.intent.action.DO_THAT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.TIMEZONE_CHANGED" />
+ <action android:name="android.intent.action.TIME_SET" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.PHONE_STATE"/>
+ </intent-filter>
+ </receiver>
+
+ <activity
+ android:name="com.example.LibActivity"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon"
+ android:theme="@style/Lib.Theme">
+
+ <!-- When comparing duplicate elements, whitespace and comments are ignored. -->
+
+ <intent-filter>
+ <action android:name="com.example.IN_APP_NOTIFY" />
+ <action android:name="com.example.RESPONSE_CODE" />
+ <action android:name="com.example.PURCHASE_STATE_CHANGED" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data
+ android:name="metaName2"
+ android:value="metaValue2"
+ android:resource="@color/someColor2"
+ />
+ </activity>
+ </application>
+</manifest>
+
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <!-- Redefine the same elements as in the main manifest except it changes
+ the attribute order and the the inner elements order. -->
+ <application
+ android:name="com.example.TheApp"
+ android:icon="@drawable/app_icon"
+ android:label="@string/app_name"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:restoreAnyVersion="true"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ >
+
+ <!-- Receiver -->
+ <receiver
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppReceiver"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.TIME_SET" />
+ <action android:name="android.intent.action.TIMEZONE_CHANGED" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="com.example.intent.action.DO_THIS" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.PHONE_STATE"/>
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="com.example.intent.action.DO_THAT" />
+ </intent-filter>
+ </receiver>
+
+ <activity
+ android:theme="@style/Lib.Theme"
+ android:name="com.example.LibActivity"
+ android:icon="@drawable/lib_activity_icon"
+ android:label="@string/lib_activity_name"
+ >
+ <!-- When comparing duplicate elements, whitespace and comments are ignored. -->
+ <intent-filter>
+ <category android:name="android.intent.category.LAUNCHER" />
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ <meta-data
+ android:resource="@color/someColor2"
+ android:value="metaValue2"
+ android:name="metaName2">
+ </meta-data>
+ <intent-filter>
+ <action android:name="com.example.IN_APP_NOTIFY" />
+ <action android:name="com.example.PURCHASE_STATE_CHANGED" />
+ <action android:name="com.example.RESPONSE_CODE" />
+ </intent-filter>
+ </activity>
+
+ <activity
+ android:icon="@drawable/activity_icon"
+ android:label="@string/activity_name"
+ android:name="com.example.Activity1"
+ android:theme="@style/Some.Theme">
+ <meta-data
+ android:value="metaValue"
+ android:name="metaName"
+ android:resource="@color/someColor" />
+ <intent-filter>
+ <category android:name="android.intent.category.LAUNCHER" />
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
+
+
+@lib2
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <!-- The whitespace and alignment is also drastically different here and has
+ no impact whatsoever on the content's comparison.
+ Some empty elements have been 'uncollapsed' with their closing element separated. -->
+ <activity android:label="@string/activity_name" android:icon="@drawable/activity_icon" android:theme="@style/Some.Theme" android:name="com.example.Activity1">
+ <intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter>
+ <intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter>
+ <meta-data android:value="metaValue" android:resource="@color/someColor" android:name="metaName" />
+ </activity>
+ <activity android:label="@string/lib_activity_name" android:icon="@drawable/lib_activity_icon" android:name="com.example.LibActivity" android:theme="@style/Lib.Theme"><intent-filter>
+ <action android:name="com.example.IN_APP_NOTIFY" /> <action android:name="com.example.RESPONSE_CODE" /> <action android:name="com.example.PURCHASE_STATE_CHANGED" />
+ </intent-filter>
+ <intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data android:name="metaName2" android:value="metaValue2" android:resource="@color/someColor2"
+ />
+ </activity>
+
+ <!-- Receiver -->
+ <receiver android:icon="@drawable/app_icon" android:name="com.example.AppReceiver" >
+ <intent-filter><action android:name="android.intent.action.TIME_SET"></action>
+ <action android:name="android.intent.action.TIMEZONE_CHANGED" /></intent-filter>
+ <intent-filter><action android:name="android.intent.action.PHONE_STATE" >
+ </action></intent-filter>
+ <intent-filter><action android:name="android.intent.action.BOOT_COMPLETED" />
+ </intent-filter>
+ <intent-filter><action android:name="com.example.intent.action.DO_THIS" /></intent-filter>
+ <intent-filter><action android:name="com.example.intent.action.DO_THAT" /></intent-filter>
+ </receiver>
+ </application>
+</manifest>
+
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1">
+
+ <application
+ android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
+ android:backupAgent="com.example.app.BackupAgentClass"
+ android:restoreAnyVersion="true"
+ android:allowBackup="true"
+ android:killAfterRestore="true"
+ android:name="com.example.TheApp" >
+
+ <activity
+ android:name="com.example.Activity1"
+ android:label="@string/activity_name"
+ android:icon="@drawable/activity_icon"
+ android:theme="@style/Some.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data
+ android:name="metaName"
+ android:value="metaValue"
+ android:resource="@color/someColor" />
+ </activity>
+
+ <!-- Receiver -->
+ <receiver
+ android:name="com.example.AppReceiver"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="com.example.intent.action.DO_THIS" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="com.example.intent.action.DO_THAT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.TIMEZONE_CHANGED" />
+ <action android:name="android.intent.action.TIME_SET" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.PHONE_STATE"/>
+ </intent-filter>
+ </receiver>
+
+ <activity
+ android:name="com.example.LibActivity"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon"
+ android:theme="@style/Lib.Theme">
+
+ <!-- When comparing duplicate elements, whitespace and comments are ignored. -->
+
+ <intent-filter>
+ <action android:name="com.example.IN_APP_NOTIFY" />
+ <action android:name="com.example.RESPONSE_CODE" />
+ <action android:name="com.example.PURCHASE_STATE_CHANGED" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data
+ android:name="metaName2"
+ android:value="metaValue2"
+ android:resource="@color/someColor2"
+ />
+ </activity>
+ </application>
+</manifest>
+
+@errors
+
+P [ManifestMergerTest0_main.xml:37, ManifestMergerTest1_lib1.xml:26] Skipping identical /manifest/application/activity[@name=com.example.LibActivity] element.
+P [ManifestMergerTest0_main.xml:5, ManifestMergerTest1_lib1.xml:41] Skipping identical /manifest/application/activity[@name=com.example.Activity1] element.
+P [ManifestMergerTest0_main.xml:18, ManifestMergerTest1_lib1.xml:7] Skipping identical /manifest/application/receiver[@name=com.example.AppReceiver] element.
+P [ManifestMergerTest0_main.xml:5, ManifestMergerTest2_lib2.xml:6] Skipping identical /manifest/application/activity[@name=com.example.Activity1] element.
+P [ManifestMergerTest0_main.xml:37, ManifestMergerTest2_lib2.xml:11] Skipping identical /manifest/application/activity[@name=com.example.LibActivity] element.
+P [ManifestMergerTest0_main.xml:18, ManifestMergerTest2_lib2.xml:20] Skipping identical /manifest/application/receiver[@name=com.example.AppReceiver] element.