aboutsummaryrefslogtreecommitdiffstats
path: root/manifmerger
diff options
context:
space:
mode:
authorRaphael <raphael@google.com>2012-01-11 12:01:57 -0800
committerAndroid (Google) Code Review <android-gerrit@google.com>2012-01-11 12:01:57 -0800
commit2f370f84db49e23c4aa35a5949d7cefcb6c20ce4 (patch)
treef007e69927e1baebd7aa5eb5980086eb7edbebe8 /manifmerger
parent2dcaaaaafc501496d47948b8318060bbd17b60bb (diff)
parent2ccd27ce4e32da8832d2810f152b1752074bfd41 (diff)
downloadsdk-2f370f84db49e23c4aa35a5949d7cefcb6c20ce4.zip
sdk-2f370f84db49e23c4aa35a5949d7cefcb6c20ce4.tar.gz
sdk-2f370f84db49e23c4aa35a5949d7cefcb6c20ce4.tar.bz2
Merge "Manifest Merger tool."
Diffstat (limited to 'manifmerger')
-rw-r--r--manifmerger/.classpath10
-rw-r--r--manifmerger/.gitignore1
-rw-r--r--manifmerger/.project17
-rwxr-xr-xmanifmerger/.settings/org.eclipse.jdt.core.prefs77
-rw-r--r--manifmerger/Android.mk5
-rw-r--r--manifmerger/etc/Android.mk10
-rw-r--r--manifmerger/etc/manifest.txt2
-rwxr-xr-xmanifmerger/etc/manifmerger87
-rw-r--r--manifmerger/src/Android.mk16
-rwxr-xr-xmanifmerger/src/com/android/manifmerger/ArgvParser.java116
-rw-r--r--manifmerger/src/com/android/manifmerger/Main.java114
-rwxr-xr-xmanifmerger/src/com/android/manifmerger/ManifestMerger.java1293
-rwxr-xr-xmanifmerger/src/com/android/manifmerger/XmlUtils.java458
-rwxr-xr-xmanifmerger/tests/Android.mk28
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/ManifestMergerTest.java140
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/ManifestMergerTestCase.java412
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/00_noop.xml225
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/01_ignore_app_attr.xml68
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/02_ignore_instrumentation.xml62
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/10_activity_merge.xml378
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/11_activity_dup.xml387
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/12_alias_dup.xml205
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/13_service_dup.xml155
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/14_receiver_dup.xml176
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/15_provider_dup.xml145
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/20_uses_lib_merge.xml176
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/21_uses_lib_errors.xml202
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/25_permission_merge.xml255
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/26_permission_dup.xml303
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/28_uses_perm_merge.xml152
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/30_uses_sdk_ok.xml80
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/32_uses_sdk_minsdk_ok.xml64
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/33_uses_sdk_minsdk_conflict.xml110
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/36_uses_sdk_targetsdk_warning.xml73
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/40_uses_feat_merge.xml178
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/41_uses_feat_errors.xml205
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/45_uses_feat_gles_once.xml124
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/47_uses_feat_gles_conflict.xml160
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/50_uses_conf_warning.xml158
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/52_support_screens_warning.xml158
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/54_compat_screens_warning.xml200
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/56_support_gltext_warning.xml148
42 files changed, 7333 insertions, 0 deletions
diff --git a/manifmerger/.classpath b/manifmerger/.classpath
new file mode 100644
index 0000000..d042f23
--- /dev/null
+++ b/manifmerger/.classpath
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry excluding="**/Android.mk" kind="src" path="src"/>
+ <classpathentry kind="src" path="tests/src"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/SdkLib"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/common"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/manifmerger/.gitignore b/manifmerger/.gitignore
new file mode 100644
index 0000000..ba077a4
--- /dev/null
+++ b/manifmerger/.gitignore
@@ -0,0 +1 @@
+bin
diff --git a/manifmerger/.project b/manifmerger/.project
new file mode 100644
index 0000000..0d4dcb4
--- /dev/null
+++ b/manifmerger/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>ManifestMerger</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/manifmerger/.settings/org.eclipse.jdt.core.prefs b/manifmerger/.settings/org.eclipse.jdt.core.prefs
new file mode 100755
index 0000000..8c01a02
--- /dev/null
+++ b/manifmerger/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,77 @@
+#Tue Dec 27 15:48:05 PST 2011
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=ignore
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=enabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=ignore
+org.eclipse.jdt.core.compiler.problem.emptyStatement=warning
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=ignore
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=ignore
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nullReference=warning
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=ignore
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=ignore
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=enabled
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/manifmerger/Android.mk b/manifmerger/Android.mk
new file mode 100644
index 0000000..245744a
--- /dev/null
+++ b/manifmerger/Android.mk
@@ -0,0 +1,5 @@
+# Copyright 2011 The Android Open Source Project
+#
+MANIFMERGER_LOCAL_DIR := $(call my-dir)
+include $(MANIFMERGER_LOCAL_DIR)/src/Android.mk
+include $(MANIFMERGER_LOCAL_DIR)/etc/Android.mk
diff --git a/manifmerger/etc/Android.mk b/manifmerger/etc/Android.mk
new file mode 100644
index 0000000..6b180ab
--- /dev/null
+++ b/manifmerger/etc/Android.mk
@@ -0,0 +1,10 @@
+# Copyright 2011 The Android Open Source Project
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_PREBUILT_EXECUTABLES := manifmerger
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_HOST_PREBUILT)
+
diff --git a/manifmerger/etc/manifest.txt b/manifmerger/etc/manifest.txt
new file mode 100644
index 0000000..bfc9524
--- /dev/null
+++ b/manifmerger/etc/manifest.txt
@@ -0,0 +1,2 @@
+Main-Class: com.android.manifestmerger.Main
+Class-Path: sdklib.jar
diff --git a/manifmerger/etc/manifmerger b/manifmerger/etc/manifmerger
new file mode 100755
index 0000000..1e2c2a7
--- /dev/null
+++ b/manifmerger/etc/manifmerger
@@ -0,0 +1,87 @@
+#!/bin/sh
+# Copyright 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.
+
+# Set up prog to be the path of this script, including following symlinks,
+# and set up progdir to be the fully-qualified pathname of its directory.
+prog="$0"
+while [ -h "${prog}" ]; do
+ newProg=`/bin/ls -ld "${prog}"`
+ newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
+ if expr "x${newProg}" : 'x/' >/dev/null; then
+ prog="${newProg}"
+ else
+ progdir=`dirname "${prog}"`
+ prog="${progdir}/${newProg}"
+ fi
+done
+oldwd=`pwd`
+progdir=`dirname "${prog}"`
+cd "${progdir}"
+progdir=`pwd`
+prog="${progdir}"/`basename "${prog}"`
+cd "${oldwd}"
+
+jarfile=manifmerger.jar
+frameworkdir="$progdir"
+if [ ! -r "$frameworkdir/$jarfile" ]
+then
+ frameworkdir=`dirname "$progdir"`/tools/lib
+fi
+if [ ! -r "$frameworkdir/$jarfile" ]
+then
+ frameworkdir=`dirname "$progdir"`/framework
+fi
+if [ ! -r "$frameworkdir/$jarfile" ]
+then
+ echo `basename "$prog"`": can't find $jarfile"
+ exit 1
+fi
+
+
+# Check args.
+if [ debug = "$1" ]; then
+ # add this in for debugging
+ java_debug=-agentlib:jdwp=transport=dt_socket,server=y,address=8050,suspend=y
+ shift 1
+else
+ java_debug=
+fi
+
+java_cmd="java"
+
+# Mac OS X needs an additional arg, or you get an "illegal thread" complaint.
+if [ `uname` = "Darwin" ]; then
+ os_opts="-XstartOnFirstThread"
+else
+ os_opts=
+fi
+
+if [ `uname` = "Linux" ]; then
+ export GDK_NATIVE_WINDOWS=true
+fi
+
+if [ "$OSTYPE" = "cygwin" ] ; then
+ jarpath=`cygpath -w "$frameworkdir/$jarfile"`
+ progdir=`cygpath -w "$progdir"`
+else
+ jarpath="$frameworkdir/$jarfile"
+fi
+
+# need to use "java.ext.dirs" because "-jar" causes classpath to be ignored
+exec "$java_cmd" \
+ -Xmx256M $os_opts $java_debug \
+ -Dcom.android.manifmergerdir="$progdir" \
+ -classpath "$jarpath" \
+ com.android.manifestmerger.Main "$@"
diff --git a/manifmerger/src/Android.mk b/manifmerger/src/Android.mk
new file mode 100644
index 0000000..5e533eb
--- /dev/null
+++ b/manifmerger/src/Android.mk
@@ -0,0 +1,16 @@
+# Copyright 2011 The Android Open Source Project
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_JAVA_RESOURCE_DIRS :=
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_JAR_MANIFEST := ../etc/manifest.txt
+LOCAL_JAVA_LIBRARIES := \
+ common \
+ sdklib
+LOCAL_MODULE := manifmerger
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/manifmerger/src/com/android/manifmerger/ArgvParser.java b/manifmerger/src/com/android/manifmerger/ArgvParser.java
new file mode 100755
index 0000000..5aed998
--- /dev/null
+++ b/manifmerger/src/com/android/manifmerger/ArgvParser.java
@@ -0,0 +1,116 @@
+/*
+ * 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.manifmerger;
+
+import com.android.sdklib.ISdkLog;
+import com.android.sdklib.util.CommandLineParser;
+
+import java.util.List;
+
+
+/**
+ * Specific command-line flags for the {@link ManifestMerger}.
+ */
+class ArgvParser extends CommandLineParser {
+
+ /*
+ * Steps needed to add a new action:
+ * - Each action is defined as a "verb object" followed by parameters.
+ * - Either reuse a VERB_ constant or define a new one.
+ * - Either reuse an OBJECT_ constant or define a new one.
+ * - Add a new entry to mAction with a one-line help summary.
+ * - In the constructor, add a define() call for each parameter (either mandatory
+ * or optional) for the given action.
+ */
+
+ public final static String VERB_MERGE = "merge"; //$NON-NLS-1$
+ public static final String KEY_OUT = "out"; //$NON-NLS-1$
+ public static final String KEY_MAIN = "main"; //$NON-NLS-1$
+ public static final String KEY_LIBS = "libs"; //$NON-NLS-1$
+
+ /**
+ * Action definitions for ManifestMerger command line.
+ * <p/>
+ * This list serves two purposes: first it is used to know which verb/object
+ * actions are acceptable on the command-line; second it provides a summary
+ * for each action that is printed in the help.
+ * <p/>
+ * Each entry is a string array with:
+ * <ul>
+ * <li> the verb.
+ * <li> an object (use #NO_VERB_OBJECT if there's no object).
+ * <li> a description.
+ * <li> an alternate form for the object (e.g. plural).
+ * </ul>
+ */
+ private final static String[][] ACTIONS = {
+
+ { VERB_MERGE, NO_VERB_OBJECT,
+ "Merge two or more manifests." },
+
+ };
+
+ public ArgvParser(ISdkLog logger) {
+ super(logger, ACTIONS);
+
+ // The following defines the parameters of the actions defined in mAction.
+
+ // --- merge manifest ---
+
+ define(Mode.STRING, true,
+ VERB_MERGE, NO_VERB_OBJECT, "o", KEY_OUT, //$NON-NLS-1$
+ "Output path (where to write the merged manifest). Use - for stdout.", null);
+
+ define(Mode.STRING, true,
+ VERB_MERGE, NO_VERB_OBJECT, "1", KEY_MAIN, //$NON-NLS-1$
+ "Path of the main manifest (what to merge *into*)", null);
+
+ define(Mode.STRING_ARRAY, true,
+ VERB_MERGE, NO_VERB_OBJECT, "2", KEY_LIBS, //$NON-NLS-1$
+ "Paths of library manifests to be merged into the main one.",
+ null);
+ }
+
+ @Override
+ public boolean acceptLackOfVerb() {
+ return true;
+ }
+
+ // -- some helpers for generic action flags
+
+ /** Helper to retrieve the --out value. */
+ public String getParamOut() {
+ return (String) getValue(null, null, KEY_OUT);
+ }
+
+ /** Helper to retrieve the --main value. */
+ public String getParamMain() {
+ return (String) getValue(null, null, KEY_MAIN);
+ }
+
+ /**
+ * Helper to retrieve the --libs values.
+ */
+ public String[] getParamLibs() {
+ Object v = getValue(null, null, KEY_LIBS);
+ if (v instanceof List<?>) {
+ List<?> a = (List<?>) v;
+ return a.toArray(new String[a.size()]);
+ }
+ return null;
+ }
+}
diff --git a/manifmerger/src/com/android/manifmerger/Main.java b/manifmerger/src/com/android/manifmerger/Main.java
new file mode 100644
index 0000000..e2e6f30
--- /dev/null
+++ b/manifmerger/src/com/android/manifmerger/Main.java
@@ -0,0 +1,114 @@
+/*
+ * 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.manifmerger;
+
+import com.android.sdklib.ISdkLog;
+
+import java.io.File;
+
+/**
+ * Command-line entry point of the Manifest Merger.
+ * The goal of the manifest merger is to merge library manifest into a main application manifest.
+ * See {@link ManifestMerger} for the exact merging rules.
+ * <p/>
+ * The command-line version creates a {@link ManifestMerger}
+ * which takes file arguments from the command-line and dumps all errors and warnings on the
+ * stdout/stderr console.
+ * <p/>
+ * Usage: <br/>
+ * {@code $ manifmerger merge --main main_manifest.xml --libs lib1.xml lib2.xml --out result.xml}
+ * <p/>
+ * When used as a library, please call {@link ManifestMerger#process(File, File, File[])} directly.
+ */
+public class Main {
+
+ /** Logger object. Use this to print normal output, warnings or errors. Never null. */
+ private ISdkLog mSdkLog;
+ /** Command line parser. Never null. */
+ private ArgvParser mArgvParser;
+
+ public static void main(String[] args) {
+ new Main().run(args);
+ }
+
+ /**
+ * Runs the sdk manager app
+ */
+ private void run(String[] args) {
+ createLogger();
+
+ mArgvParser = new ArgvParser(mSdkLog);
+ mArgvParser.parseArgs(args);
+
+ // Create a new ManifestMerger and call its process method.
+ // It will take care of validating its own arguments.
+ ManifestMerger mm = new ManifestMerger(mSdkLog);
+
+ String[] libPaths = mArgvParser.getParamLibs();
+ File[] libFiles = new File[libPaths.length];
+ for (int n = libPaths.length - 1; n >= 0; n--) {
+ libFiles[n] = new File(libPaths[n]);
+ }
+
+ boolean ok = mm.process(
+ new File(mArgvParser.getParamOut()),
+ new File(mArgvParser.getParamMain()),
+ libFiles
+ );
+ System.exit(ok ? 0 : 1);
+ }
+
+ /**
+ * Creates the {@link #mSdkLog} object.
+ * This logger prints to the attached console.
+ */
+ private void createLogger() {
+ mSdkLog = new ISdkLog() {
+ @Override
+ public void error(Throwable t, String errorFormat, Object... args) {
+ if (errorFormat != null) {
+ System.err.printf("Error: " + errorFormat, args);
+ if (!errorFormat.endsWith("\n")) {
+ System.err.printf("\n");
+ }
+ }
+ if (t != null) {
+ System.err.printf("Error: %s\n", t.getMessage());
+ }
+ }
+
+ @Override
+ public void warning(String warningFormat, Object... args) {
+ System.out.printf("Warning: " + warningFormat, args);
+ if (!warningFormat.endsWith("\n")) {
+ System.out.printf("\n");
+ }
+ }
+
+ @Override
+ public void printf(String msgFormat, Object... args) {
+ System.out.printf(msgFormat, args);
+ }
+ };
+ }
+
+ /** For testing */
+ public void setLogger(ISdkLog logger) {
+ mSdkLog = logger;
+ }
+
+}
diff --git a/manifmerger/src/com/android/manifmerger/ManifestMerger.java b/manifmerger/src/com/android/manifmerger/ManifestMerger.java
new file mode 100755
index 0000000..6eac978
--- /dev/null
+++ b/manifmerger/src/com/android/manifmerger/ManifestMerger.java
@@ -0,0 +1,1293 @@
+/*
+ * 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.manifmerger;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.ISdkLog;
+import com.android.sdklib.SdkConstants;
+import com.android.sdklib.xml.AndroidXPathFactory;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+
+/**
+ * Merges a library manifest into a main application manifest.
+ * <p/>
+ * To use, create with {@link ManifestMerger#ManifestMerger(ISdkLog)} then
+ * call {@link ManifestMerger#process(File, File, File[])}.
+ * <p/>
+ * <pre> Merge operations:
+ * - root manifest: attributes ignored, warn if defined.
+ * - application:
+ * {@code @attributes}: ignored in libs
+ * C- activity / activity-alias / service / receiver / provider
+ * => Merge as-is. Error if exists in the destination (same {@code @name})
+ * unless the definitions are exactly the same.
+ * New elements are always merged at the end of the application element.
+ * => Indicate if there's a dup.
+ * D- uses-library
+ * => Merge. OK if already exists same {@code @name}.
+ * => Merge {@code @required}: true>false.
+ * A- instrumentation:
+ * => Do not merge. ignore the ones from libs.
+ * C- permission / permission-group / permission-tree:
+ * => Merge as-is. Error if exists in the destination (same {@code @name})
+ * unless the definitions are exactly the same.
+ * C- uses-permission:
+ * => Add. OK if already defined.
+ * E- uses-sdk:
+ * {@code @minSdkVersion}: error if dest&lt;lib. Never automatically change dest minsdk.
+ * {@code @targetSdkVersion}: warning if dest&lt;lib.
+ * Never automatically change dest targetsdk.
+ * {@code @maxSdkVersion}: obsolete, ignored. Not used in comparisons and not merged.
+ * D- uses-feature with {@code @name}:
+ * => Merge with same {@code @name}
+ * => Merge {@code @required}: true>false.
+ * - Do not merge any {@code @glEsVersion} attribute at this point.
+ * F- uses-feature with {@code @glEsVersion}:
+ * => Error if defined in lib+dest with dest&lt;lib. Never automatically change dest.
+ * B- uses-configuration:
+ * => There can be many. Error if source defines one that is not an exact match in dest.
+ * (e.g. right now app must manually define something that matches exactly each lib)
+ * B- supports-screens / compatible-screens:
+ * => Do not merge.
+ * => Error (warn?) if defined in lib and not strictly the same as in dest.
+ * B- supports-gl-texture:
+ * => Do not merge. Can have more than one.
+ * => Error (warn?) if defined in lib and not present as-is in dest.
+ *
+ * Strategies:
+ * A = Ignore, do not merge (no-op).
+ * B = Do not merge but if defined in both must match equally.
+ * C = Must not exist in dest or be exactly the same (key is the {@code @name} attribute).
+ * D = Add new or merge with same key {@code @name}, adjust {@code @required} true>false.
+ * E, F = Custom strategies; see above.
+ *
+ * What happens when merging libraries with conflicting information?
+ * Say for example a main manifest has a minSdkVersion of 3, whereas libraries have
+ * a minSdkVersion of 4 and 11. We could have 2 point of views:
+ * - Play it safe: If we have a library with a minSdkVersion of 11, it means this
+ * library code knows it can't work reliably on a lower API level. So the safest end
+ * result would be a merged manifest with the highest minSdkVersion of all libraries.
+ * - Trust the main manifest: When an app declares a given minSdkVersion, it also expects
+ * to run a given range of devices. If we change the final minSdkVersion, the app won't
+ * be available on as many devices as the developer might expect. And as a counterpoint
+ * to issue 1, the app may be careful and not call the library without checking the
+ * necessary features or APIs are available before hand.
+ * Both points of views are conflicting. The solution taken here is to be conservative
+ * and generate an error rather than merge and change a value that might be surprising.
+ * On the other hand this can be problematic and force a developer to keep the main
+ * manifest in sync with the libraries ones, in essence reducing the usefulness of the
+ * automated merge to pure trivial cases. The idea is to just start this way and enhance
+ * or revisit the mechanism later.
+ * </pre>
+ */
+public class ManifestMerger {
+
+ /** Logger object. Never null. */
+ private ISdkLog mSdkLog;
+ private XPath mXPath;
+ private Document mMainDoc;
+
+ private String NS_URI = SdkConstants.NS_RESOURCES;
+ private String NS_PREFIX = AndroidXPathFactory.DEFAULT_NS_PREFIX;
+ private int destMinSdk;
+
+ public ManifestMerger(ISdkLog log) {
+ mSdkLog = log;
+ }
+
+ /**
+ * Performs the merge operation.
+ * <p/>
+ * This does NOT stop on errors, in an attempt to accumulate as much
+ * info as possible to return to the user.
+ * Unless it failed to read the main manifest, a result file will be
+ * created. However if process() returns false, the file should not
+ * be used except for debugging purposes.
+ *
+ * @param outputFile The output path to generate. Can be the same as the main path.
+ * @param mainFile The main manifest paths to read. What we merge into.
+ * @param libraryFiles The library manifest paths to read. Must not be null.
+ * @return True if the merge was completed, false otherwise.
+ */
+ public boolean process(File outputFile, File mainFile, File[] libraryFiles) {
+ Document mainDoc = XmlUtils.parseDocument(mainFile, mSdkLog);
+ if (mainDoc == null) {
+ return false;
+ }
+
+ boolean success = process(mainDoc, libraryFiles);
+
+ if (!XmlUtils.printXmlFile(mainDoc, outputFile, mSdkLog)) {
+ success = false;
+ }
+ return success;
+ }
+
+ /**
+ * Performs the merge operation in-place in the given DOM.
+ * <p/>
+ * This does NOT stop on errors, in an attempt to accumulate as much
+ * info as possible to return to the user.
+ *
+ * @param mainDoc The document to merge into. Will be modified in-place.
+ * @param libraryFiles The library manifest paths to read. Must not be null.
+ * @return True on success, false if any error occurred (printed to the {@link ISdkLog}).
+ */
+ public boolean process(Document mainDoc, File[] libraryFiles) {
+
+ boolean success = true;
+ mMainDoc = mainDoc;
+
+ String prefix = XmlUtils.lookupNsPrefix(mainDoc, SdkConstants.NS_RESOURCES);
+ mXPath = AndroidXPathFactory.newXPath(prefix);
+
+ for (File libFile : libraryFiles) {
+ Document libDoc = XmlUtils.parseDocument(libFile, mSdkLog);
+ if (libDoc == null || !mergeLibDoc(libDoc)) {
+ success = false;
+ }
+ }
+
+ mXPath = null;
+ mMainDoc = null;
+ return success;
+ }
+
+ // --------
+
+ /**
+ * Merges the given library manifest into the destination manifest.
+ * See {@link ManifestMerger} for merge details.
+ *
+ * @param libDoc The library document to merge from. Must not be null.
+ * @return True on success, false if any error occurred (printed to the {@link ISdkLog}).
+ */
+ private boolean mergeLibDoc(Document libDoc) {
+
+ boolean err = false;
+
+ // Strategy B
+ err |= !doNotMergeCheckEqual("/manifest/uses-configuration", libDoc); //$NON-NLS-1$
+ err |= !doNotMergeCheckEqual("/manifest/supports-screens", libDoc); //$NON-NLS-1$
+ err |= !doNotMergeCheckEqual("/manifest/compatible-screens", libDoc); //$NON-NLS-1$
+ err |= !doNotMergeCheckEqual("/manifest/supports-gl-texture", libDoc); //$NON-NLS-1$
+
+ // Strategy C
+ err |= !mergeNewOrEqual(
+ "/manifest/application/activity", //$NON-NLS-1$
+ "name", //$NON-NLS-1$
+ libDoc,
+ true);
+ err |= !mergeNewOrEqual(
+ "/manifest/application/activity-alias", //$NON-NLS-1$
+ "name", //$NON-NLS-1$
+ libDoc,
+ true);
+ err |= !mergeNewOrEqual(
+ "/manifest/application/service", //$NON-NLS-1$
+ "name", //$NON-NLS-1$
+ libDoc,
+ true);
+ err |= !mergeNewOrEqual(
+ "/manifest/application/receiver", //$NON-NLS-1$
+ "name", //$NON-NLS-1$
+ libDoc,
+ true);
+ err |= !mergeNewOrEqual(
+ "/manifest/application/provider", //$NON-NLS-1$
+ "name", //$NON-NLS-1$
+ libDoc,
+ true);
+ err |= !mergeNewOrEqual(
+ "/manifest/permission", //$NON-NLS-1$
+ "name", //$NON-NLS-1$
+ libDoc,
+ false);
+ err |= !mergeNewOrEqual(
+ "/manifest/permission-group", //$NON-NLS-1$
+ "name", //$NON-NLS-1$
+ libDoc,
+ false);
+ err |= !mergeNewOrEqual(
+ "/manifest/permission-tree", //$NON-NLS-1$
+ "name", //$NON-NLS-1$
+ libDoc,
+ false);
+ err |= !mergeNewOrEqual(
+ "/manifest/uses-permission", //$NON-NLS-1$
+ "name", //$NON-NLS-1$
+ libDoc,
+ false);
+
+ // Strategy D
+ err |= !mergeAdjustRequired(
+ "/manifest/application/uses-library", //$NON-NLS-1$
+ "name", //$NON-NLS-1$
+ "required", //$NON-NLS-1$
+ libDoc,
+ null /*alternateKeyAttr*/);
+ err |= !mergeAdjustRequired(
+ "/manifest/uses-feature", //$NON-NLS-1$
+ "name", //$NON-NLS-1$
+ "required", //$NON-NLS-1$
+ libDoc,
+ "glEsVersion" /*alternateKeyAttr*/);
+
+ // Strategy E
+ err |= !checkSdkVersion(libDoc);
+
+ // Strategy F
+ err |= !checkGlEsVersion(libDoc);
+
+ return !err;
+ }
+
+ /**
+ * Do not merge anything. Instead it checks that the requested elements from the
+ * given library are all present and equal in the destination and prints a warning
+ * if it's not the case.
+ * <p/>
+ * For example if a library supports a given screen configuration, print a
+ * warning if the main manifest doesn't indicate the app supports the same configuration.
+ * We should not merge it since we don't want to silently give the impression an app
+ * supports a configuration just because it uses a library which does.
+ * On the other hand we don't want to silently ignore this fact.
+ * <p/>
+ * TODO there should be a way to silence this warning.
+ * The current behavior is certainly arbitrary and needs to be tweaked somehow.
+ *
+ * @param path The XPath of the elements to merge from the library. Must not be null.
+ * @param libDoc The library document to merge from. Must not be null.
+ * @return True on success, false if any error occurred (printed to the {@link ISdkLog}).
+ */
+ private boolean doNotMergeCheckEqual(String path, Document libDoc) {
+
+ for (Element src : findElements(libDoc, path)) {
+
+ boolean found = false;
+
+ for (Element dest : findElements(mMainDoc, path)) {
+ if (compareElements(src, dest, false, null /*diff*/, null /*keyAttr*/)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ mSdkLog.warning("[%1$s] %2$s missing from %3$s:\n%4$s",
+ fileLineInfo(src, "library"),
+ path,
+ xmlFileName(mMainDoc, "main manifest"),
+ XmlUtils.dump(src, false /*nextSiblings*/));
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Merges the requested elements from the library in the main document.
+ * The key attribute name is used to identify the same elements.
+ * Merged elements must either not exist in the destination or be identical.
+ * <p/>
+ * When merging, append to the end of the application element.
+ * Also merges any preceding whitespace and up to one comment just prior to the merged element.
+ *
+ * @param path The XPath of the elements to merge from the library. Must not be null.
+ * @param keyAttr The Android-namespace attribute used as key to identify similar elements.
+ * E.g. "name" for "android:name"
+ * @param libDoc The library document to merge from. Must not be null.
+ * @param warnDups When true, will print a warning when a library definition is already
+ * present in the destination and is equal.
+ * @return True on success, false if any error occurred (printed to the {@link ISdkLog}).
+ */
+ private boolean mergeNewOrEqual(
+ String path,
+ String keyAttr,
+ Document libDoc,
+ boolean warnDups) {
+
+ // The parent of XPath /p1/p2/p3 is /p1/p2. To find it, delete the last "/segment"
+ int pos = path.lastIndexOf('/');
+ assert pos > 1;
+ String parentPath = path.substring(0, pos);
+ Element parent = findFirstElement(mMainDoc, parentPath);
+ assert parent != null;
+ if (parent == null) {
+ mSdkLog.error(null, "[%1$s] Could not find element %2$s.",
+ xmlFileName(mMainDoc, "main manifest"),
+ parentPath);
+ return false;
+ }
+
+ boolean success = true;
+
+ nextSource: for (Element src : findElements(libDoc, path)) {
+ Attr attr = src.getAttributeNodeNS(NS_URI, keyAttr);
+ String name = attr == null ? "" : attr.getNodeValue(); //$NON-NLS-1$
+ if (name.length() == 0) {
+ mSdkLog.error(null, "[%1$s] Undefined '%2$s' attribute in %3$s.",
+ fileLineInfo(src, "library"),
+ keyAttr, path);
+ success = false;
+ continue;
+ }
+
+ // Look for the same item in the destination
+ List<Element> dests = findElements(mMainDoc, path, keyAttr, name);
+ if (dests.size() > 1) {
+ // This should not be happening. We'll just use the first one found in this case.
+ mSdkLog.warning("[%1$s] has more than one %2$s[@%3$s=%4$s] element.",
+ fileLineInfo(dests.get(0), "main manifest"),
+ path, keyAttr, name);
+ }
+ 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)) {
+ // Same element. Skip.
+ if (warnDups) {
+ mSdkLog.printf("[%1$s, %2$s] Skipping identical %3$s[@%4$s=%5$s] element.",
+ fileLineInfo(src, "library"),
+ fileLineInfo(dest, "main manifest"),
+ path, keyAttr, name);
+ }
+ continue nextSource;
+ } else {
+ // Print the diff we got from the comparison.
+ mSdkLog.error(null,
+ "[%1$s, %2$s] Trying to merge incompatible %3$s[@%4$s=%5$s] element:\n%6$s",
+ fileLineInfo(src, "library"),
+ fileLineInfo(dest, "main manifest"),
+ path, keyAttr, name, diff.toString());
+ success = false;
+ continue nextSource;
+ }
+ }
+
+ // Ready to merge element src. Select which previous siblings to merge.
+ Node start = selectPreviousSiblings(src);
+
+ insertAtEndOf(parent, start, src);
+ }
+
+ return success;
+ }
+
+ /**
+ * Merge elements as identified by their key name attribute.
+ * The element must have an option boolean "required" attribute which can be either "true" or
+ * "false". Default is true if the attribute is misisng. When merging, a "false" is superseded
+ * by a "true" (explicit or implicit).
+ * <p/>
+ * When merging, this does NOT merge any other attributes than {@code keyAttr} and
+ * {@code requiredAttr}.
+ *
+ * @param path The XPath of the elements to merge from the library. Must not be null.
+ * @param keyAttr The Android-namespace attribute used as key to identify similar elements.
+ * E.g. "name" for "android:name"
+ * @param requiredAttr The name of the Android-namespace boolean attribute that must be merged.
+ * Typically should be "required".
+ * @param libDoc The library document to merge from. Must not be null.
+ * @param alternateKeyAttr When non-null, this is an alternate valid key attribute. If the
+ * default key attribute is missing, we won't output a warning if the alternate one is
+ * present.
+ * @return True on success, false if any error occurred (printed to the {@link ISdkLog}).
+ */
+ private boolean mergeAdjustRequired(
+ String path,
+ String keyAttr,
+ String requiredAttr,
+ Document libDoc,
+ @Nullable String alternateKeyAttr) {
+
+ // The parent of XPath /p1/p2/p3 is /p1/p2. To find it, delete the last "/segment"
+ int pos = path.lastIndexOf('/');
+ assert pos > 1;
+ String parentPath = path.substring(0, pos);
+ Element parent = findFirstElement(mMainDoc, parentPath);
+ assert parent != null;
+ if (parent == null) {
+ mSdkLog.error(null, "[%1$s] Could not find element %2$s.",
+ xmlFileName(mMainDoc, "main manifest"),
+ parentPath);
+ return false;
+ }
+
+ boolean success = true;
+
+ for (Element src : findElements(libDoc, path)) {
+ Attr attr = src.getAttributeNodeNS(NS_URI, keyAttr);
+ String name = attr == null ? "" : attr.getNodeValue().trim(); //$NON-NLS-1$
+ if (name.length() == 0) {
+ if (alternateKeyAttr != null) {
+ attr = src.getAttributeNodeNS(NS_URI, alternateKeyAttr);
+ String s = attr == null ? "" : attr.getNodeValue().trim(); //$NON-NLS-1$
+ if (s.length() != 0) {
+ // This element lacks the keyAttr but has the alternateKeyAttr. Skip it.
+ continue;
+ }
+ }
+
+ mSdkLog.error(null, "[%1$s] Undefined '%2$s' attribute in %3$s.",
+ fileLineInfo(src, "library"),
+ keyAttr, path);
+ success = false;
+ continue;
+ }
+
+ // Look for the same item in the destination
+ List<Element> dests = findElements(mMainDoc, path, keyAttr, name);
+ if (dests.size() > 1) {
+ // This should not be happening. We'll just use the first one found in this case.
+ mSdkLog.warning("[%1$s] has more than one %2$s[@%3$s=%4$s] element.",
+ fileLineInfo(dests.get(0), "main manifest"),
+ path, keyAttr, name);
+ }
+ if (dests.size() > 0) {
+ attr = src.getAttributeNodeNS(NS_URI, requiredAttr);
+ String value = attr == null ? "true" : attr.getNodeValue(); //$NON-NLS-1$
+ if (value == null || !(value.equals("true") || value.equals("false"))) {
+ mSdkLog.warning("[%1$s] Invalid attribute '%2$s' in %3$s[@%4$s=%5$s] element:\nExpected 'true' or 'false' but found '%6$s'.",
+ fileLineInfo(src, "library"),
+ requiredAttr, path, keyAttr, name, value);
+ continue;
+ }
+ boolean boolE = Boolean.parseBoolean(value);
+
+ for (Element dest : dests) {
+ // Destination node exists. Compare the required attributes.
+
+ attr = dest.getAttributeNodeNS(NS_URI, requiredAttr);
+ value = attr == null ? "true" : attr.getNodeValue(); //$NON-NLS-1$
+ if (value == null || !(value.equals("true") || value.equals("false"))) {
+ mSdkLog.warning("[%1$s] Invalid attribute '%2$s' in %3$s[@%4$s=%5$s] element:\nExpected 'true' or 'false' but found '%6$s'.",
+ fileLineInfo(dest, "main manifest"),
+ requiredAttr, path, keyAttr, name, value);
+ continue;
+ }
+ boolean boolD = Boolean.parseBoolean(value);
+
+ if (!boolD && boolE) {
+ // Required attributes differ: destination is false and source was true
+ // so we need to change the destination to true.
+
+ // If attribute was already in the destination, change it in place
+ if (attr != null) {
+ attr.setNodeValue("true"); //$NON-NLS-1$
+ } else {
+ // Otherwise, do nothing. The destination doesn't have the
+ // required=true attribute, and true is the default value.
+ // Consequently not setting is the right thing to do.
+
+ // -- code snippet for reference --
+ // If we wanted to create a new attribute, we'd use the code
+ // below. There's a simpler call to d.setAttributeNS(ns, name, value)
+ // but experience shows that it would create a new prefix out of the
+ // blue instead of looking it up.
+ //
+ // Attr a = d.getOwnerDocument().createAttributeNS(NS_URI, requiredAttr);
+ // String prefix = d.lookupPrefix(NS_URI);
+ // if (prefix != null) {
+ // a.setPrefix(prefix);
+ // }
+ // a.setValue("true"); //$NON-NLS-1$
+ // d.setAttributeNodeNS(attr);
+ }
+ }
+ }
+ } else {
+ // Destination doesn't exist. We simply merge the source element.
+ // Select which previous siblings to merge.
+ Node start = selectPreviousSiblings(src);
+
+ Node node = insertAtEndOf(parent, start, src);
+
+ NamedNodeMap attrs = node.getAttributes();
+ if (attrs != null) {
+ for (int i = 0; i < attrs.getLength(); i++) {
+ Node a = attrs.item(i);
+ if (a.getNodeType() == Node.ATTRIBUTE_NODE) {
+ boolean keep = NS_URI.equals(a.getNamespaceURI());
+ if (keep) {
+ name = a.getLocalName();
+ keep = keyAttr.equals(name) || requiredAttr.equals(name);
+ }
+ if (!keep) {
+ attrs.removeNamedItemNS(NS_URI, name);
+ // Restart the loop from index 0 since there's no
+ // guarantee on the order of the nodes in the "map".
+ // This makes it O(n+2n) at most, where n is [2..3] in
+ // a typical case.
+ i = -1;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return success;
+ }
+
+
+
+ /**
+ * Checks (but does not merge) uses-feature glEsVersion attribute using the following rules:
+ * <pre>
+ * - Error if defined in lib+dest with dest&lt;lib.
+ * - Never automatically change dest.
+ * - Default implied value is 1.0 (0x00010000).
+ * </pre>
+ *
+ * @param libDoc The library document to merge from. Must not be null.
+ * @return True on success, false if any error occurred (printed to the {@link ISdkLog}).
+ */
+ private boolean checkGlEsVersion(Document libDoc) {
+
+ String parentPath = "/manifest"; //$NON-NLS-1$
+ Element parent = findFirstElement(mMainDoc, parentPath);
+ assert parent != null;
+ if (parent == null) {
+ mSdkLog.error(null, "[%1$s] Could not find element %2$s.",
+ xmlFileName(mMainDoc, "main manifest"),
+ parentPath);
+ return false;
+ }
+
+ // Find the max glEsVersion on the destination side
+ String path = "/manifest/uses-feature"; //$NON-NLS-1$
+ String keyAttr = "glEsVersion"; //$NON-NLS-1$
+ long destGlEsVersion = 0x00010000L; // default minimum is 1.0
+ Element destNode = null;
+ boolean result = true;
+ for (Element dest : findElements(mMainDoc, path)) {
+ Attr attr = dest.getAttributeNodeNS(NS_URI, keyAttr);
+ String value = attr == null ? "" : attr.getNodeValue().trim(); //$NON-NLS-1$
+ if (value.length() != 0) {
+ try {
+ // Note that the value can be an hex number such as 0x00020001 so we
+ // need Integer.decode instead of Integer.parseInt.
+ // Note: Integer.decode cannot handle "ffffffff", see JDK issue 6624867
+ // so we just treat the version as a long and test like this, ignoring
+ // the fact that a value of 0xFFFF/.0xFFFF is probably invalid anyway
+ // in the context of glEsVersion.
+ long version = Long.decode(value);
+ if (version >= destGlEsVersion) {
+ destGlEsVersion = version;
+ destNode = dest;
+ } else if (version < 0x00010000) {
+ mSdkLog.warning("[%1$s] Ignoring <uses-feature android:glEsVersion='%2$s'> because it's smaller than 1.0.",
+ fileLineInfo(dest, "main manifest"),
+ value);
+ }
+ } catch (NumberFormatException e) {
+ // Note: NumberFormatException.toString() has no interesting information
+ // so we don't output it.
+ mSdkLog.error(null,
+ "[%1$s] Failed to parse <uses-feature android:glEsVersion='%2$s'>: must be an integer in the form 0x00020001.",
+ fileLineInfo(dest, "main manifest"),
+ value);
+ result = false;
+ }
+ }
+ }
+
+ // If we found at least one valid with no error, use that, otherwise bail out.
+ if (!result && destNode == null) {
+ return false;
+ }
+
+ // Now find the max glEsVersion on the source side.
+
+ long srcGlEsVersion = 0x00010000L; // default minimum is 1.0
+ Element srcNode = null;
+ result = true;
+ for (Element src : findElements(libDoc, path)) {
+ Attr attr = src.getAttributeNodeNS(NS_URI, keyAttr);
+ String value = attr == null ? "" : attr.getNodeValue().trim(); //$NON-NLS-1$
+ if (value.length() != 0) {
+ try {
+ // See comment on Long.decode above.
+ long version = Long.decode(value);
+ if (version >= srcGlEsVersion) {
+ srcGlEsVersion = version;
+ srcNode = src;
+ } else if (version < 0x00010000) {
+ mSdkLog.warning("[%1$s] Ignoring <uses-feature android:glEsVersion='%2$s'> because it's smaller than 1.0.",
+ fileLineInfo(src, "library"),
+ value);
+ }
+ } catch (NumberFormatException e) {
+ // Note: NumberFormatException.toString() has no interesting information
+ // so we don't output it.
+ mSdkLog.error(null,
+ "[%1$s] Failed to parse <uses-feature android:glEsVersion='%2$s'>: must be an integer in the form 0x00020001.",
+ fileLineInfo(src, "library"),
+ value);
+ result = false;
+ }
+ }
+ }
+
+ if (srcNode != null && destGlEsVersion < srcGlEsVersion) {
+ mSdkLog.warning(
+ "[%1$s, %2$s] Main manifest has <uses-feature android:glEsVersion='0x%3$08x'> but library uses glEsVersion='0x%4$08x'%5$s",
+ fileLineInfo(srcNode, "library"),
+ fileLineInfo(destNode == null ? mMainDoc : destNode, "main manifest"),
+ destGlEsVersion,
+ srcGlEsVersion,
+ destNode != null ? "" : //$NON-NLS-1$
+ "\nNote: main manifest lacks a <uses-feature android:glEsVersion> declaration, and thus defaults to glEsVersion=0x00010000."
+ );
+ result = false;
+ }
+
+ return result;
+ }
+
+ /**
+ * Checks (but does not merge) uses-sdk attribues using the following rules:
+ * <pre>
+ * - {@code @minSdkVersion}: error if dest&lt;lib. Never automatically change dest minsdk.
+ * - {@code @targetSdkVersion}: warning if dest&lt;lib. Never automatically change destination.
+ * - {@code @maxSdkVersion}: obsolete, ignored. Not used in comparisons and not merged.
+ * </pre>
+ * @param libDoc The library document to merge from. Must not be null.
+ * @return True on success, false if any error occurred (printed to the {@link ISdkLog}).
+ */
+ private boolean checkSdkVersion(Document libDoc) {
+
+ boolean result = true;
+
+ Element destUsesSdk = findFirstElement(mMainDoc, "/manifest/uses-sdk"); //$NON-NLS-1$
+ Element srcUsesSdk = findFirstElement(libDoc, "/manifest/uses-sdk"); //$NON-NLS-1$
+
+ AtomicInteger destValue = new AtomicInteger(1);
+ AtomicInteger srcValue = new AtomicInteger(1);
+ AtomicBoolean destImplied = new AtomicBoolean(true);
+ AtomicBoolean srcImplied = new AtomicBoolean(true);
+
+ // Check minSdkVersion
+ destMinSdk = 1;
+ result = extractSdkVersionAttribute(
+ libDoc,
+ destUsesSdk, srcUsesSdk,
+ "min", //$NON-NLS-1$
+ destValue, srcValue,
+ destImplied, srcImplied);
+
+ if (result) {
+ // Make it an error for an application to use a library with a greater
+ // minSdkVersion. This means the library code may crash unexpectedly.
+ // TODO it would be nice to be able to work around this in case the
+ // user think s/he knows what s/he's doing.
+ // We could define a simple XML comment flag: <!-- @NoMinSdkVersionMergeError -->
+
+ destMinSdk = destValue.get();
+
+ if (destMinSdk < srcValue.get()) {
+ mSdkLog.error(null,
+ "[%1$s, %2$s] Main manifest has <uses-sdk android:minSdkVersion='%3$d'> but library uses minSdkVersion='%4$d'%5$s",
+ fileLineInfo(srcUsesSdk == null ? libDoc : srcUsesSdk, "library"),
+ fileLineInfo(destUsesSdk == null ? mMainDoc : destUsesSdk, "main manifest"),
+ destMinSdk,
+ srcValue.get(),
+ !destImplied.get() ? "" : //$NON-NLS-1$
+ "\nNote: main manifest lacks a <uses-sdk android:minSdkVersion> declaration, which defaults to value 1."
+ );
+ result = false;
+ }
+ }
+
+ // Check targetSdkVersion.
+
+ // Note that destValue/srcValue purposely defaults to whatever minSdkVersion was last read
+ // since that's their definition when missing.
+ destImplied.set(true);
+ srcImplied.set(true);
+
+ boolean result2 = extractSdkVersionAttribute(
+ libDoc,
+ destUsesSdk, srcUsesSdk,
+ "target", //$NON-NLS-1$
+ destValue, srcValue,
+ destImplied, srcImplied);
+
+ result &= result2;
+ if (result2) {
+ // Make it a warning for an application to use a library with a greater
+ // targetSdkVersion.
+
+ int destTargetSdk = destImplied.get() ? destMinSdk : destValue.get();
+
+ if (destTargetSdk < srcValue.get()) {
+ mSdkLog.warning(
+ "[%1$s, %2$s] Main manifest has <uses-sdk android:targetSdkVersion='%3$d'> but library uses targetSdkVersion='%4$d'%5$s",
+ fileLineInfo(srcUsesSdk == null ? libDoc : srcUsesSdk, "library"),
+ fileLineInfo(destUsesSdk == null ? mMainDoc : destUsesSdk, "main manifest"),
+ destTargetSdk,
+ srcValue.get(),
+ !destImplied.get() ? "" : //$NON-NLS-1$
+ "\nNote: main manifest lacks a <uses-sdk android:targetSdkVersion> declaration, which defaults to value minSdkVersion or 1."
+ );
+ result = false;
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Implementation detail for {@link #checkSdkVersion(Document)}.
+ * Note that the various atomic out-variables must be preset to their default before
+ * the call.
+ * <p/>
+ * destValue/srcValue will be filled with the integer value of the field, if present
+ * and a correct number, in which case destImplied/destImplied are also set to true.
+ * Otherwise the values and the implied variables are left untouched.
+ */
+ private boolean extractSdkVersionAttribute(
+ Document libDoc,
+ Element destUsesSdk,
+ Element srcUsesSdk,
+ String attr,
+ AtomicInteger destValue,
+ AtomicInteger srcValue,
+ AtomicBoolean destImplied,
+ AtomicBoolean srcImplied) {
+ String s = destUsesSdk == null ? "" //$NON-NLS-1$
+ : destUsesSdk.getAttributeNS(NS_URI, attr + "SdkVersion"); //$NON-NLS-1$
+
+ assert s != null;
+ s = s.trim();
+ try {
+ if (s.length() > 0) {
+ destValue.set(Integer.parseInt(s));
+ destImplied.set(false);
+ }
+ } catch (NumberFormatException e) {
+ // Note: NumberFormatException.toString() has no interesting information
+ // so we don't output it.
+ mSdkLog.error(null,
+ "[%1$s] Failed to parse <uses-sdk %2$sSdkVersion='%3$s'>: must be an integer number.",
+ fileLineInfo(destUsesSdk == null ? mMainDoc : destUsesSdk, "main manifest"),
+ attr,
+ s);
+ return false;
+ }
+
+ s = srcUsesSdk == null ? "" //$NON-NLS-1$
+ : srcUsesSdk.getAttributeNS(NS_URI, attr + "SdkVersion"); //$NON-NLS-1$
+ assert s != null;
+ s = s.trim();
+ try {
+ if (s.length() > 0) {
+ srcValue.set(Integer.parseInt(s));
+ srcImplied.set(false);
+ }
+ } catch (NumberFormatException e) {
+ mSdkLog.error(null,
+ "[%1$s] Failed to parse <uses-sdk %2$sSdkVersion='%3$s'>: must be an integer number.",
+ fileLineInfo(srcUsesSdk == null ? libDoc : srcUsesSdk, "library"),
+ attr,
+ s);
+ return false;
+ }
+
+ return true;
+ }
+
+
+ // -----
+
+
+ /**
+ * Given an element E, select which previous siblings we want to merge.
+ * We want to include any whitespace up to the closing of the previous element.
+ * We also want to include up preceding comment nodes and their preceding whitespace.
+ * <p/>
+ * This may returns either {@code end} or a previous sibling. Never returns null.
+ */
+ @NonNull
+ private Node selectPreviousSiblings(Node end) {
+
+ Node start = end;
+ Node prev = start.getPreviousSibling();
+ while (prev != null) {
+ short t = prev.getNodeType();
+ if (t == Node.TEXT_NODE) {
+ String text = prev.getNodeValue();
+ if (text == null || text.trim().length() != 0) {
+ // Not whitespace, we don't want it.
+ break;
+ }
+ } else if (t == Node.COMMENT_NODE) {
+ // It's a comment. We'll take it.
+ } else {
+ // Not a comment node nor a whitespace text. We don't want it.
+ break;
+ }
+ start = prev;
+ prev = start.getPreviousSibling();
+ }
+
+ return start;
+ }
+
+ /**
+ * Inserts all siblings from {@code start} to {@code end} at the end
+ * of the given destination element.
+ * <p/>
+ * Implementation detail: this clones the source nodes into the destination.
+ *
+ * @param dest The destination at the end of which to insert. Cannot be null.
+ * @param start The first element to insert. Must not be null.
+ * @param end The last element to insert (included). Must not be null.
+ * Must be a direct "next sibling" of the start node.
+ * Can be equal to the start node to insert just that one node.
+ * @return The copy of the {@code end} node in the destination document or null
+ * if no such copy was created and added to the destination.
+ */
+ private Node insertAtEndOf(Element dest, Node start, Node end) {
+ // Check whether we'll need to adjust URI prefixes
+ String destPrefix = mMainDoc.lookupPrefix(NS_URI);
+ String srcPrefix = start.getOwnerDocument().lookupPrefix(NS_URI);
+ boolean needPrefixChange = destPrefix != null && !destPrefix.equals(srcPrefix);
+
+ // First let's figure out the insertion point.
+ // We want the end of the last 'content' element of the
+ // destination element and basically we want to insert right
+ // before the last whitespace of the destination element.
+ Node target = dest.getLastChild();
+ while (target != null) {
+ if (target.getNodeType() == Node.TEXT_NODE) {
+ String text = target.getNodeValue();
+ if (text == null || text.trim().length() != 0) {
+ // Not whitespace, insert after.
+ break;
+ }
+ } else {
+ // Not text. Insert after
+ break;
+ }
+ target = target.getPreviousSibling();
+ }
+ if (target != null) {
+ target = target.getNextSibling();
+ }
+
+ // Destination and start..end must not be part of the same document
+ // because we try to import below. If they were, it would mess the
+ // structure.
+ assert dest.getOwnerDocument() == mMainDoc;
+ assert dest.getOwnerDocument() != start.getOwnerDocument();
+ assert start.getOwnerDocument() == end.getOwnerDocument();
+
+ while (start != null) {
+ Node node = mMainDoc.importNode(start, true /*deep*/);
+ if (needPrefixChange) {
+ changePrefix(node, srcPrefix, destPrefix);
+ }
+ dest.insertBefore(node, target);
+
+ if (start == end) {
+ return node;
+ }
+ start = start.getNextSibling();
+ }
+ return null;
+ }
+
+ /**
+ * Changes the namespace prefix of all nodes, recursively.
+ *
+ * @param node The node to process, as well as all it's descendants. Can be null.
+ * @param srcPrefix The prefix to match.
+ * @param destPrefix The new prefix to replace with.
+ */
+ private void changePrefix(Node node, String srcPrefix, String destPrefix) {
+ for (; node != null; node = node.getNextSibling()) {
+ if (srcPrefix.equals(node.getPrefix())) {
+ node.setPrefix(destPrefix);
+ }
+ Node child = node.getFirstChild();
+ if (child != null) {
+ changePrefix(child, srcPrefix, destPrefix);
+ }
+ }
+ }
+
+ /**
+ * Compares two {@link Element}s recursively. They must be identical with the same
+ * structure and order. Whitespace and comments are ignored.
+ *
+ * @param e1 The first element to compare.
+ * @param e2 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.
+ * @param keyAttr An optional key attribute to always add to elements when dumping a diff.
+ * @return True if {@code e1} and {@code e2} are equal.
+ */
+ private boolean compareElements(
+ @NonNull Node e1,
+ @NonNull Node e2,
+ 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
+ }
+
+ 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;
+ }
+ }
+
+ // 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());
+ }
+ }
+
+ /**
+ * Finds the first element matching the given XPath expression in the given document.
+ *
+ * @param doc The document where to find the expression.
+ * @param path The XPath expression. It must yield an {@link Element} node type.
+ * @return The {@link Element} found or null.
+ */
+ @Nullable
+ private Element findFirstElement(
+ @NonNull Document doc,
+ @NonNull String path) {
+ Node result;
+ try {
+ result = (Node) mXPath.evaluate(path, doc, XPathConstants.NODE);
+ if (result instanceof Element) {
+ return (Element) result;
+ }
+
+ if (result != null) {
+ mSdkLog.error(null,
+ "Unexpected Node type %s when evaluating %s", //$NON-NLS-1$
+ result.getClass().getName(), path);
+ }
+ } catch (XPathExpressionException e) {
+ mSdkLog.error(e, "XPath error on expr %s", path); //$NON-NLS-1$
+ }
+ return null;
+ }
+
+ /**
+ * Finds zero or more elements matching the given XPath expression in the given document.
+ *
+ * @param doc The document where to find the expression.
+ * @param path The XPath expression. Only {@link Element}s nodes will be returned.
+ * @return A list of {@link Element} found, possibly empty but never null.
+ */
+ private List<Element> findElements(
+ @NonNull Document doc,
+ @NonNull String path) {
+ return findElements(doc, path, null, null);
+ }
+
+
+ /**
+ * Finds zero or more elements matching the given XPath expression in the given document.
+ * <p/>
+ * Furthermore, the elements must have an attribute matching the given attribute name
+ * and value if provided. (If you don't need to match an attribute, use the other version.)
+ * <p/>
+ * Note that if you provide {@code attrName} as non-null then the {@code attrValue}
+ * must be non-null too. In this case the XPath expression will be modified to add
+ * the check by naively appending a "[name='value']" filter.
+ *
+ * @param doc The document where to find the expression.
+ * @param path The XPath expression. Only {@link Element}s nodes will be returned.
+ * @param attrName The name of the optional attribute to match. Can be null.
+ * @param attrValue The value of the optiona attribute to match.
+ * Can be null if {@code attrName} is null, otherwise must be non-null.
+ * @return A list of {@link Element} found, possibly empty but never null.
+ *
+ * @see #findElements(Document, String)
+ */
+ private List<Element> findElements(
+ @NonNull Document doc,
+ @NonNull String path,
+ @Nullable String attrName,
+ @Nullable String attrValue) {
+ List<Element> elements = new ArrayList<Element>();
+
+ if (attrName != null) {
+ assert attrValue != null;
+ // Generate expression /manifest/application/activity[@android:name='my.fqcn']
+ path = String.format("%1$s[@%2$s:%3$s='%4$s']", //$NON-NLS-1$
+ path, NS_PREFIX, attrName, attrValue);
+ }
+
+ try {
+ NodeList results = (NodeList) mXPath.evaluate(path, doc, XPathConstants.NODESET);
+ if (results != null && results.getLength() > 0) {
+ for (int i = 0; i < results.getLength(); i++) {
+ Node n = results.item(i);
+ assert n instanceof Element;
+ if (n instanceof Element) {
+ elements.add((Element) n);
+ } else {
+ mSdkLog.error(null,
+ "Unexpected Node type %s when evaluating %s", //$NON-NLS-1$
+ n.getClass().getName(), path);
+ }
+ }
+ }
+
+ } catch (XPathExpressionException e) {
+ mSdkLog.error(e, "XPath error on expr %s", path); //$NON-NLS-1$
+ }
+
+ return elements;
+ }
+
+ /**
+ * Tries to returns the base filename used from which the XML was parsed.
+ * @param node Any node from a document parsed by {@link XmlUtils#parseDocument(File, ISdkLog)}.
+ * @param defaultName The string to return if the XML filename cannot be determined.
+ * @return The base filename used from which the XML was parsed or the default name.
+ */
+ private String xmlFileName(Node node, String defaultName) {
+ File f = XmlUtils.extractXmlFilename(node);
+ if (f != null) {
+ return f.getName();
+ } else {
+ return defaultName;
+ }
+ }
+
+ /**
+ * Tries to returns the base filename & line number from which the XML node was parsed.
+ *
+ * @param node Any node from a document parsed by {@link XmlUtils#parseDocument(File, ISdkLog)}.
+ * @param defaultName The string to return if the XML filename cannot be determined.
+ * @return The base filename used from which the XML was parsed with the line number
+ * (if available) or the default name.
+ */
+ private String fileLineInfo(Node node, String defaultName) {
+ String name = xmlFileName(node, defaultName);
+ int line = XmlUtils.extractLineNumber(node);
+ if (line <= 0) {
+ return name;
+ } else {
+ return name + ':' + line;
+ }
+ }
+
+}
diff --git a/manifmerger/src/com/android/manifmerger/XmlUtils.java b/manifmerger/src/com/android/manifmerger/XmlUtils.java
new file mode 100755
index 0000000..d18eebb
--- /dev/null
+++ b/manifmerger/src/com/android/manifmerger/XmlUtils.java
@@ -0,0 +1,458 @@
+/*
+ * 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.manifmerger;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.ISdkLog;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXParseException;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+/**
+ * A few XML handling utilities.
+ */
+class XmlUtils {
+
+ private static final String DATA_ORIGIN_FILE = "origin_file"; //$NON-NLS-1$
+ private static final String DATA_LINE_NUMBER = "line#"; //$NON-NLS-1$
+
+ /**
+ * Parses the given XML file as a DOM document.
+ * The parser does not validate the DTD nor any kind of schema.
+ * It is namespace aware.
+ * <p/>
+ * This adds a user tag with the original {@link File} to the returned document.
+ * You can retrieve this file later by using {@link #extractXmlFilename(Node)}.
+ *
+ * @param xmlFile The XML {@link File} to parse. Must not be null.
+ * @param log An {@link ISdkLog} for reporting errors. Must not be null.
+ * @return A new DOM {@link Document}, or null.
+ */
+ @Nullable
+ static Document parseDocument(@NonNull final File xmlFile, @NonNull final ISdkLog log) {
+ try {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ InputSource is = new InputSource(new FileReader(xmlFile));
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+
+ // We don't want the default handler which prints errors to stderr.
+ builder.setErrorHandler(new ErrorHandler() {
+ @Override
+ public void warning(SAXParseException e) {
+ log.printf("Warning when parsing %s: %s", xmlFile.getName(), e.toString());
+ }
+ @Override
+ public void fatalError(SAXParseException e) {
+ log.printf("Fatal error when parsing %s: %s", xmlFile.getName(), e.toString());
+ }
+ @Override
+ public void error(SAXParseException e) {
+ log.printf("Error when parsing %s: %s", xmlFile.getName(), e.toString());
+ }
+ });
+
+ Document doc = builder.parse(is);
+ doc.setUserData(DATA_ORIGIN_FILE, xmlFile, null /*handler*/);
+ findLineNumbers(doc, 1);
+
+ return doc;
+
+ } catch (FileNotFoundException e) {
+ log.error(null, "XML file not found: %s", xmlFile.getName());
+
+ } catch (Exception e) {
+ log.error(e, "Failed to parse XML file: %s", xmlFile.getName());
+ }
+
+ return null;
+ }
+
+ /**
+ * Parses the given XML string as a DOM document.
+ * The parser does not validate the DTD nor any kind of schema.
+ * It is namespace aware.
+ *
+ * @param xml The XML string to parse. Must not be null.
+ * @param log An {@link ISdkLog} for reporting errors. Must not be null.
+ * @return A new DOM {@link Document}, or null.
+ */
+ @Nullable
+ static Document parseDocument(@NonNull String xml, @NonNull ISdkLog log) {
+ try {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ InputSource is = new InputSource(new StringReader(xml));
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document doc = builder.parse(is);
+ findLineNumbers(doc, 1);
+ return doc;
+ } catch (Exception e) {
+ log.error(e, "Failed to parse XML string");
+ }
+
+ return null;
+ }
+
+ /**
+ * Extracts the origin {@link File} that {@link #parseDocument(File, ISdkLog)}
+ * added to the XML document.
+ *
+ * @param xmlNode Any node from a document returned by {@link #parseDocument(File, ISdkLog)}.
+ * @return The {@link File} object used to create the document or null.
+ */
+ @Nullable
+ static File extractXmlFilename(@Nullable Node xmlNode) {
+ if (xmlNode != null && xmlNode.getNodeType() != Node.DOCUMENT_NODE) {
+ xmlNode = xmlNode.getOwnerDocument();
+ }
+ if (xmlNode != null) {
+ Object data = xmlNode.getUserData(DATA_ORIGIN_FILE);
+ if (data instanceof File) {
+ return (File) data;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * This is a CRUDE INEXACT HACK to decorate the DOM with some kind of line number
+ * information for elements. It's inexact because by the time we get the DOM we
+ * already have lost all the information about whitespace between attributes.
+ * <p/>
+ * Also we don't even try to deal with \n vs \r vs \r\n insanity. This only counts
+ * the \n occuring in text nodes to determine line advances, which is clearly flawed.
+ * <p/>
+ * However it's good enough for testing, and we'll replace it by a PositionXmlParser
+ * once it's moved into com.android.util.
+ */
+ private static int findLineNumbers(Node node, int line) {
+ for (; node != null; node = node.getNextSibling()) {
+ node.setUserData(DATA_LINE_NUMBER, Integer.valueOf(line), null /*handler*/);
+
+ if (node.getNodeType() == Node.TEXT_NODE) {
+ String text = node.getNodeValue();
+ if (text.length() > 0) {
+ for (int pos = 0; (pos = text.indexOf('\n', pos)) != -1; pos++) {
+ ++line;
+ }
+ }
+ }
+
+ Node child = node.getFirstChild();
+ if (child != null) {
+ line = findLineNumbers(child, line);
+ }
+ }
+ return line;
+ }
+
+ /**
+ * Extracts the line number that {@link #findLineNumbers} added to the XML nodes.
+ *
+ * @param xmlNode Any node from a document returned by {@link #parseDocument(File, ISdkLog)}.
+ * @return The line number if found or 0.
+ */
+ @Nullable
+ static int extractLineNumber(@Nullable Node xmlNode) {
+ if (xmlNode != null) {
+ Object data = xmlNode.getUserData(DATA_LINE_NUMBER);
+ if (data instanceof Integer) {
+ return ((Integer) data).intValue();
+ }
+ }
+
+ return 0;
+ }
+
+ /**
+ * Find the prefix for the given NS_URI in the document.
+ *
+ * @param doc The document root.
+ * @param nsUri The Namespace URI to look for.
+ * @return The namespace prefix if found or null.
+ */
+ static String lookupNsPrefix(Document doc, String nsUri) {
+ // Note: if this is not available, there's an alternate implementation at
+ // com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode.lookupNamespacePrefix(Node, String)
+ return doc.lookupPrefix(nsUri);
+ }
+
+ /**
+ * Outputs the given XML {@link Document} to the file {@code outFile}.
+ *
+ * TODO right now reformats the document. Needs to output as-is, respecting white-space.
+ *
+ * @param doc The document to output. Must not be null.
+ * @param outFile The {@link File} where to write the document.
+ * @param log A log in case of error.
+ * @return True if the file was written, false in case of error.
+ */
+ static boolean printXmlFile(
+ @NonNull Document doc,
+ @NonNull File outFile,
+ @NonNull ISdkLog log) {
+ // Quick thing based on comments from http://stackoverflow.com/questions/139076
+ try {
+ Transformer tf = TransformerFactory.newInstance().newTransformer();
+ tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); //$NON-NLS-1$
+ tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); //$NON-NLS-1$
+ tf.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$
+ tf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", //$NON-NLS-1$
+ "4"); //$NON-NLS-1$
+ tf.transform(new DOMSource(doc), new StreamResult(outFile));
+ return true;
+ } catch (TransformerException e) {
+ log.error(e, "Failed to write XML file: %s", outFile);
+ return false;
+ }
+ }
+
+ /**
+ * Outputs the given XML {@link Document} as a string.
+ *
+ * TODO right now reformats the document. Needs to output as-is, respecting white-space.
+ *
+ * @param doc The document to output. Must not be null.
+ * @param log A log in case of error.
+ * @return A string representation of the XML. Null in case of error.
+ */
+ static String printXmlString(
+ @NonNull Document doc,
+ @NonNull ISdkLog log) {
+ try {
+ Transformer tf = TransformerFactory.newInstance().newTransformer();
+ tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); //$NON-NLS-1$
+ tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); //$NON-NLS-1$
+ tf.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$
+ tf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", //$NON-NLS-1$
+ "4"); //$NON-NLS-1$
+ StringWriter sw = new StringWriter();
+ tf.transform(new DOMSource(doc), new StreamResult(sw));
+ return sw.toString();
+ } catch (TransformerException e) {
+ log.error(e, "Failed to write XML file");
+ return null;
+ }
+ }
+
+ /**
+ * Dumps the structure of the DOM to a simple text string.
+ *
+ * @param node The first node to dump (recursively). Can be null.
+ * @param nextSiblings If true, will also dump the following siblings.
+ * If false, it will just process the given node.
+ * @return A string representation of the Node structure, useful for debugging.
+ */
+ @NonNull
+ static String dump(@Nullable Node node, boolean nextSiblings) {
+ return dump(node, 0 /*offset*/, nextSiblings, true /*deep*/, null /*keyAttr*/);
+ }
+
+
+ /**
+ * Dumps the structure of the DOM to a simple text string.
+ * Each line is terminated with a \n separator.
+ *
+ * @param node The first node to dump. Can be null.
+ * @param offsetIndex The offset to add at the begining of each line. Each offset is
+ * converted into 2 space characters.
+ * @param nextSiblings If true, will also dump the following siblings.
+ * If false, it will just process the given node.
+ * @param deep If true, this will recurse into children.
+ * @param keyAttr An optional attribute *local* name to insert when writing an element.
+ * For example when writing an Activity, it helps to always insert "name" attribute.
+ * @return A string representation of the Node structure, useful for debugging.
+ */
+ @NonNull
+ static String dump(
+ @Nullable Node node,
+ int offsetIndex,
+ boolean nextSiblings,
+ boolean deep,
+ @Nullable String keyAttr) {
+ StringBuilder sb = new StringBuilder();
+
+ String offset = ""; //$NON-NLS-1$
+ for (int i = 0; i < offsetIndex; i++) {
+ offset += " "; //$NON-NLS-1$
+ }
+
+ if (node == null) {
+ sb.append(offset).append("(end reached)\n");
+
+ } else {
+ for (; node != null; node = node.getNextSibling()) {
+ String type = null;
+ short t = node.getNodeType();
+ switch(t) {
+ case Node.ELEMENT_NODE:
+ String attr = "";
+ if (keyAttr != null) {
+ NamedNodeMap attrs = node.getAttributes();
+ if (attrs != null) {
+ for (int i = 0; i < attrs.getLength(); i++) {
+ Node a = attrs.item(i);
+ if (a != null && keyAttr.equals(a.getLocalName())) {
+ attr = String.format(" %1$s=%2$s",
+ a.getNodeName(), a.getNodeValue());
+ break;
+ }
+ }
+ }
+ }
+ sb.append(String.format("%1$s<%2$s%3$s>\n",
+ offset, node.getNodeName(), attr));
+ break;
+ case Node.COMMENT_NODE:
+ sb.append(String.format("%1$s<!-- %2$s -->\n",
+ offset, node.getNodeValue()));
+ break;
+ case Node.TEXT_NODE:
+ String txt = node.getNodeValue().trim();
+ if (txt.length() == 0) {
+ // Keep this for debugging. TODO make it a flag
+ // to dump whitespace on debugging. Otherwise ignore it.
+ // txt = "[whitespace]";
+ break;
+ }
+ sb.append(String.format("%1$s%2$s\n", offset, txt));
+ break;
+ case Node.ATTRIBUTE_NODE:
+ sb.append(String.format("%1$s @%2$s = %3$s\n",
+ offset, node.getNodeName(), node.getNodeValue()));
+ break;
+ case Node.CDATA_SECTION_NODE:
+ type = "cdata"; //$NON-NLS-1$
+ break;
+ case Node.DOCUMENT_NODE:
+ type = "document"; //$NON-NLS-1$
+ break;
+ case Node.PROCESSING_INSTRUCTION_NODE:
+ type = "PI"; //$NON-NLS-1$
+ break;
+ default:
+ type = Integer.toString(t);
+ }
+
+ if (type != null) {
+ sb.append(String.format("%1$s[%2$s] <%3$s> %4$s\n",
+ offset, type, node.getNodeName(), node.getNodeValue()));
+ }
+
+ if (deep) {
+ List<Attr> attrs = sortedAttributeList(node.getAttributes());
+ for (Attr attr : attrs) {
+ sb.append(String.format("%1$s @%2$s = %3$s\n",
+ offset, attr.getNodeName(), attr.getNodeValue()));
+ }
+
+ Node child = node.getFirstChild();
+ if (child != null) {
+ sb.append(dump(child, offsetIndex+1, true, true, keyAttr));
+ }
+ }
+
+ if (!nextSiblings) {
+ break;
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Returns a sorted list of attributes.
+ * The list is never null and does not contain null items.
+ *
+ * @param attrMap A Node map as returned by {@link Node#getAttributes()}.
+ * Can be null, in which case an empty list is returned.
+ * @return A non-null, possible empty, list of all nodes that are actual {@link Attr},
+ * sorted by increasing attribute name.
+ */
+ @NonNull
+ static List<Attr> sortedAttributeList(@Nullable NamedNodeMap attrMap) {
+ List<Attr> list = new ArrayList<Attr>();
+
+ if (attrMap != null) {
+ for (int i = 0; i < attrMap.getLength(); i++) {
+ Node attr = attrMap.item(i);
+ if (attr instanceof Attr) {
+ list.add((Attr) attr);
+ }
+ }
+ }
+
+ if (list.size() > 1) {
+ // Sort it by attribute name
+ Collections.sort(list, getAttrComparator());
+ }
+
+ return list;
+ }
+
+ /**
+ * Returns a comparator for {@link Attr}, alphabetically sorted by name.
+ * The "name" attribute is special and always sorted to the front.
+ */
+ @NonNull
+ static Comparator<? super Attr> getAttrComparator() {
+ return new Comparator<Attr>() {
+ @Override
+ public int compare(Attr a1, Attr a2) {
+ String s1 = a1 == null ? "" : a1.getNodeName(); //$NON-NLS-1$
+ String s2 = a2 == null ? "" : a2.getNodeValue(); //$NON-NLS-1$
+
+ int prio1 = s1.equals("name") ? 0 : 1; //$NON-NLS-1$
+ int prio2 = s2.equals("name") ? 0 : 1; //$NON-NLS-1$
+ if (prio1 == 0 || prio2 == 0) {
+ return prio1 - prio2;
+ }
+
+ return s1.compareTo(s2);
+ }
+ };
+ }
+}
diff --git a/manifmerger/tests/Android.mk b/manifmerger/tests/Android.mk
new file mode 100755
index 0000000..c1dcea9
--- /dev/null
+++ b/manifmerger/tests/Android.mk
@@ -0,0 +1,28 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Only compile source java files in this lib.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_JAVA_RESOURCE_DIRS := src
+
+LOCAL_MODULE := manifmerger-tests
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_JAVA_LIBRARIES := manifmerger junit
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/manifmerger/tests/src/com/android/manifmerger/ManifestMergerTest.java b/manifmerger/tests/src/com/android/manifmerger/ManifestMergerTest.java
new file mode 100755
index 0000000..6a78a5d
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/ManifestMergerTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2011 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.manifmerger;
+
+
+/**
+ * Unit tests for {@link ManifestMerger}.
+ */
+public class ManifestMergerTest extends ManifestMergerTestCase {
+
+ /*
+ * Wait, I hear you, where are the tests?
+ *
+ * processTestFiles() uses loadTestData(), which infers the data filename
+ * from the caller method name.
+ * E.g. the method "test00_noop" will use the data file named "data/00_noop.xml".
+ *
+ * We could simplify this even further by simply iterating on the data
+ * files and getting rid of the test methods; however there's some value in
+ * having tests break on a method name that easily points to the data file.
+ */
+
+ public void test00_noop() throws Exception {
+ processTestFiles();
+ }
+
+ public void test01_ignore_app_attr() throws Exception {
+ processTestFiles();
+ }
+
+ public void test02_ignore_instrumentation() throws Exception {
+ processTestFiles();
+ }
+
+ public void test10_activity_merge() throws Exception {
+ processTestFiles();
+ }
+
+ public void test11_activity_dup() throws Exception {
+ processTestFiles();
+ }
+
+ public void test12_alias_dup() throws Exception {
+ processTestFiles();
+ }
+
+ public void test13_service_dup() throws Exception {
+ processTestFiles();
+ }
+
+ public void test14_receiver_dup() throws Exception {
+ processTestFiles();
+ }
+
+ public void test15_provider_dup() throws Exception {
+ processTestFiles();
+ }
+
+ public void test20_uses_lib_merge() throws Exception {
+ processTestFiles();
+ }
+
+ public void test21_uses_lib_errors() throws Exception {
+ processTestFiles();
+ }
+
+ public void test25_permission_merge() throws Exception {
+ processTestFiles();
+ }
+
+ public void test26_permission_dup() throws Exception {
+ processTestFiles();
+ }
+
+ public void test28_uses_perm_merge() throws Exception {
+ processTestFiles();
+ }
+
+ public void test30_uses_sdk_ok() throws Exception {
+ processTestFiles();
+ }
+
+ public void test32_uses_sdk_minsdk_ok() throws Exception {
+ processTestFiles();
+ }
+
+ public void test33_uses_sdk_minsdk_conflict() throws Exception {
+ processTestFiles();
+ }
+
+ public void test36_uses_sdk_targetsdk_warning() throws Exception {
+ processTestFiles();
+ }
+
+ public void test40_uses_feat_merge() throws Exception {
+ processTestFiles();
+ }
+
+ public void test41_uses_feat_errors() throws Exception {
+ processTestFiles();
+ }
+
+ public void test45_uses_feat_gles_once() throws Exception {
+ processTestFiles();
+ }
+
+ public void test47_uses_feat_gles_conflict() throws Exception {
+ processTestFiles();
+ }
+
+ public void test50_uses_conf_warning() throws Exception {
+ processTestFiles();
+ }
+
+ public void test52_support_screens_warning() throws Exception {
+ processTestFiles();
+ }
+
+ public void test54_compat_screens_warning() throws Exception {
+ processTestFiles();
+ }
+
+ public void test56_support_gltext_warning() throws Exception {
+ processTestFiles();
+ }
+}
diff --git a/manifmerger/tests/src/com/android/manifmerger/ManifestMergerTestCase.java b/manifmerger/tests/src/com/android/manifmerger/ManifestMergerTestCase.java
new file mode 100755
index 0000000..66b80e8
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/ManifestMergerTestCase.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2011 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.manifmerger;
+
+import com.android.annotations.NonNull;
+import com.android.sdklib.mock.MockLog;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * Some utilities to reduce repetitions in the {@link ManifestMergerTest}s.
+ * <p/>
+ * See {@link #loadTestData(String)} for an explanation of the data file format.
+ */
+abstract class ManifestMergerTestCase extends TestCase {
+
+ /**
+ * Delimiter that indicates the test must fail.
+ * An XML output and errors are still generated and checked.
+ */
+ private static final String DELIM_FAILS = "fails";
+ /**
+ * Delimiter that starts a library XML content.
+ * The delimiter name must be in the form {@code @libSomeName} and it will be
+ * used as the base for the test file name. Using separate lib names is encouraged
+ * since it makes the error output easier to read.
+ */
+ private static final String DELIM_LIB = "lib";
+ /**
+ * Delimiter that starts the main manifest XML content.
+ */
+ private static final String DELIM_MAIN = "main";
+ /**
+ * Delimiter that starts the resulting XML content, whatever is generated by the merge.
+ */
+ private static final String DELIM_RESULT = "result";
+ /**
+ * Delimiter that starts the SdkLog output.
+ * The logger prints each entry on its lines, prefixed with E for errors,
+ * W for warnings and P for regular printfs.
+ */
+ private static final String DELIM_ERRORS = "errors";
+
+ static class TestFiles {
+ private final File mMain;
+ private final File[] mLibs;
+ private final File mActualResult;
+ private final String mExpectedResult;
+ private final String mExpectedErrors;
+ private final boolean mShouldFail;
+
+ /** Files used by a given test case. */
+ public TestFiles(
+ boolean shouldFail,
+ @NonNull File main,
+ @NonNull File[] libs,
+ @NonNull File actualResult,
+ @NonNull String expectedResult,
+ @NonNull String expectedErrors) {
+ mShouldFail = shouldFail;
+ mMain = main;
+ mLibs = libs;
+ mActualResult = actualResult;
+ mExpectedResult = expectedResult;
+ mExpectedErrors = expectedErrors;
+ }
+
+ public boolean getShouldFail() {
+ return mShouldFail;
+ }
+
+ @NonNull
+ public File getMain() {
+ return mMain;
+ }
+
+ @NonNull
+ public File[] getLibs() {
+ return mLibs;
+ }
+
+ @NonNull
+ public File getActualResult() {
+ return mActualResult;
+ }
+
+ @NonNull
+ public String getExpectedResult() {
+ return mExpectedResult;
+ }
+
+ public String getExpectedErrors() {
+ return mExpectedErrors;
+ }
+
+ // Try to delete any temp file potentially created.
+ public void cleanup() {
+ if (mMain != null && mMain.isFile()) {
+ mMain.delete();
+ }
+
+ if (mActualResult != null && mActualResult.isFile()) {
+ mActualResult.delete();
+ }
+
+ for (File f : mLibs) {
+ if (f != null && f.isFile()) {
+ f.delete();
+ }
+ }
+ }
+ }
+
+ /**
+ * Calls {@link #loadTestData(String)} by
+ * inferring the data filename from the caller's method name.
+ * <p/>
+ * The caller method name must be composed of "test" + the leaf filename.
+ * Extensions ".xml" or ".txt" are implied.
+ * <p/>
+ * E.g. to use the data file "12_foo.xml", simply call this from a method
+ * named "test12_foo".
+ *
+ * @return A new {@link TestFiles} instance. Never null.
+ * @throws Exception when things go wrong.
+ * @see #loadTestData(String)
+ */
+ @NonNull
+ TestFiles loadTestData() throws Exception {
+ StackTraceElement[] stack = Thread.currentThread().getStackTrace();
+ for (int i = 0, n = stack.length; i < n; i++) {
+ StackTraceElement caller = stack[i];
+ String name = caller.getMethodName();
+ if (name.startsWith("test")) {
+ return loadTestData(name.substring(4));
+ }
+ }
+
+ throw new IllegalArgumentException("No caller method found which name started with 'test'");
+ }
+
+ /**
+ * Loads test data for a given test case.
+ * The input (main + libs) are stored in temp files.
+ * A new destination temp file is created to store the actual result output.
+ * The expected result is actually kept in a string.
+ * <p/>
+ * Data File Syntax:
+ * <ul>
+ * <li> Lines starting with # are ignored (anywhere, as long as # is the first char).
+ * <li> Lines before the first {@code @delimiter} are ignored.
+ * <li> Empty lines just after the {@code @delimiter}
+ * and before the first &lt; XML line are ignored.
+ * <li> Valid delimiters are {@code @main} for the XML of the main app manifest.
+ * <li> Following delimiters are {@code @libXYZ}, read in the order of definition.
+ * The name can be anything as long as it starts with "{@code @lib}".
+ * </ul>
+ *
+ * @param filename The test data filename. If no extension is provided, this will
+ * try with .xml or .txt. Must not be null.
+ * @return A new {@link TestFiles} instance. Must not be null.
+ * @throws Exception when things fail to load properly.
+ */
+ @NonNull
+ TestFiles loadTestData(@NonNull String filename) throws Exception {
+
+ String resName = "data" + File.separator + filename;
+ InputStream is = null;
+ BufferedReader reader = null;
+ BufferedWriter writer = null;
+
+ try {
+ is = this.getClass().getResourceAsStream(resName);
+ if (is == null && !filename.endsWith(".xml")) {
+ String resName2 = resName + ".xml";
+ is = this.getClass().getResourceAsStream(resName2);
+ if (is != null) {
+ filename = resName2;
+ }
+ }
+ if (is == null && !filename.endsWith(".txt")) {
+ String resName3 = resName + ".txt";
+ is = this.getClass().getResourceAsStream(resName3);
+ if (is != null) {
+ filename = resName3;
+ }
+ }
+ assertNotNull("Test data file not found for " + filename, is);
+
+ reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
+
+ // Get the temporary directory to use. Just create a temp file, extracts its
+ // directory and remove the file.
+ File tempFile = File.createTempFile(this.getClass().getSimpleName(), ".tmp");
+ File tempDir = tempFile.getParentFile();
+ if (!tempFile.delete()) {
+ tempFile.deleteOnExit();
+ }
+
+ String line = null;
+ String delimiter = null;
+ boolean skipEmpty = true;
+
+ boolean shouldFail = false;
+ StringBuilder expectedResult = new StringBuilder();
+ StringBuilder expectedErrors = new StringBuilder();
+ File mainFile = null;
+ File actualResultFile = null;
+ List<File> libFiles = new ArrayList<File>();
+ int tempIndex = 0;
+
+ while ((line = reader.readLine()) != null) {
+ if (skipEmpty && line.trim().length() == 0) {
+ continue;
+ }
+ if (line.length() > 0 && line.charAt(0) == '#') {
+ continue;
+ }
+ if (line.length() > 0 && line.charAt(0) == '@') {
+ delimiter = line.substring(1);
+ assertTrue(
+ "Unknown delimiter @" + delimiter + " in " + filename,
+ delimiter.startsWith(DELIM_LIB) ||
+ delimiter.equals(DELIM_MAIN) ||
+ delimiter.equals(DELIM_RESULT) ||
+ delimiter.equals(DELIM_ERRORS) ||
+ delimiter.equals(DELIM_FAILS));
+
+ skipEmpty = true;
+
+ if (writer != null) {
+ try {
+ writer.close();
+ } catch (IOException ignore) {}
+ writer = null;
+ }
+
+ if (delimiter.equals(DELIM_FAILS)) {
+ shouldFail = true;
+
+ } else if (!delimiter.equals(DELIM_ERRORS)) {
+ tempFile = new File(tempDir, String.format("%1$s%2$d_%3$s.xml",
+ this.getClass().getSimpleName(),
+ tempIndex++,
+ delimiter.replaceAll("[^a-zA-Z0-9_-]", "")
+ ));
+ tempFile.deleteOnExit();
+
+ if (delimiter.startsWith(DELIM_LIB)) {
+ libFiles.add(tempFile);
+
+ } else if (delimiter.equals(DELIM_MAIN)) {
+ mainFile = tempFile;
+
+ } else if (delimiter.equals(DELIM_RESULT)) {
+ actualResultFile = tempFile;
+
+ } else {
+ fail("Unexpected data file delimiter @" + delimiter +
+ " in " + filename);
+ }
+
+ if (!delimiter.equals(DELIM_RESULT)) {
+ writer = new BufferedWriter(new FileWriter(tempFile));
+ }
+ }
+
+ continue;
+ }
+ if (delimiter != null &&
+ skipEmpty &&
+ line.length() > 0 &&
+ line.charAt(0) != '#' &&
+ line.charAt(0) != '@') {
+ skipEmpty = false;
+ }
+ if (writer != null) {
+ writer.write(line);
+ writer.write('\n');
+ } else if (DELIM_RESULT.equals(delimiter)) {
+ expectedResult.append(line).append('\n');
+ } else if (DELIM_ERRORS.equals(delimiter)) {
+ expectedErrors.append(line).append('\n');
+ }
+ }
+
+ assertNotNull("Missing @" + DELIM_MAIN + " in " + filename, mainFile);
+ assertNotNull("Missing @" + DELIM_RESULT + " in " + filename, actualResultFile);
+
+ return new TestFiles(
+ shouldFail,
+ mainFile,
+ libFiles.toArray(new File[libFiles.size()]),
+ actualResultFile,
+ expectedResult.toString(),
+ expectedErrors.toString());
+
+ } catch (UnsupportedEncodingException e) {
+ // BufferedReader failed to decode UTF-8, O'RLY?
+ throw e;
+
+ } finally {
+ if (writer != null) {
+ try {
+ writer.close();
+ } catch (IOException ignore) {}
+ }
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException ignore) {}
+ }
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException ignore) {}
+ }
+ }
+ }
+
+ /**
+ * Loads the data test files using {@link #loadTestData()} and then
+ * invokes {@link #processTestFiles(TestFiles)} to test them.
+ *
+ * @see #loadTestData()
+ * @see #processTestFiles(TestFiles)
+ */
+ void processTestFiles() throws Exception {
+ processTestFiles(loadTestData());
+ }
+
+ /**
+ * Processes the data from the given {@link TestFiles} by
+ * invoking {@link ManifestMerger#process(File, File, File[])}:
+ * the given library files are applied consecutively to the main XML
+ * document and the output is generated.
+ * <p/>
+ * Then the expected and actual outputs are loaded into a DOM,
+ * dumped again to a String using an XML transform and compared.
+ * This makes sure only the structure is checked and that any
+ * formatting is ignored in the comparison.
+ *
+ * @param testFiles The test files to process. Must not be null.
+ * @throws Exception when this go wrong.
+ */
+ void processTestFiles(TestFiles testFiles) throws Exception {
+ MockLog log = new MockLog();
+ ManifestMerger merger = new ManifestMerger(log);
+ boolean processOK = merger.process(testFiles.getActualResult(),
+ testFiles.getMain(),
+ testFiles.getLibs());
+
+ String expectedErrors = testFiles.getExpectedErrors().trim();
+ StringBuilder actualErrors = new StringBuilder();
+ for (String s : log.getMessages()) {
+ actualErrors.append(s);
+ if (!s.endsWith("\n")) {
+ actualErrors.append('\n');
+ }
+ }
+ assertEquals("Error generated during merging",
+ expectedErrors, actualErrors.toString().trim());
+
+ if (testFiles.getShouldFail()) {
+ assertFalse("Merge process() returned true, expected false", processOK);
+ } else {
+ assertTrue("Merge process() returned false, expected true", processOK);
+ }
+
+ // Test result XML. There should always be one created
+ // since the process action does not stop on errors.
+ log.clear();
+ String actual = XmlUtils.printXmlString(
+ XmlUtils.parseDocument(testFiles.getActualResult(), log),
+ log);
+ assertEquals("Error parsing actual result XML", "[]", log.toString());
+ log.clear();
+ String expected = XmlUtils.printXmlString(
+ XmlUtils.parseDocument(testFiles.getExpectedResult(), log),
+ log);
+ assertEquals("Error parsing expected result XML", "[]", log.toString());
+ assertEquals("Error comparing expected to actual result", expected, actual);
+
+ testFiles.cleanup();
+ }
+
+}
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/00_noop.xml b/manifmerger/tests/src/com/android/manifmerger/data/00_noop.xml
new file mode 100755
index 0000000..fa0cac0
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/00_noop.xml
@@ -0,0 +1,225 @@
+#
+# Syntax:
+# - Lines starting with # are ignored (anywhere, as long as # is the first char).
+# - Lines before the first @delimiter are ignored.
+# - Empty lines just after the @delimiter and before the first < XML line are ignored.
+# - Valid delimiters are @main for the XML of the main app manifest.
+# - Following delimiters are @libXYZ, read in the order of definition. The name can be
+# anything as long as it starts with "@lib".
+# - Last delimiter should be @result.
+#
+
+@main
+
+<!--
+ This is a canonical manifest that has some uses-permissions,
+ the usual uses-sdk and supports-screens, an app with an activity,
+ customer receiver & service and a widget.
+-->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <!-- Typical analytics permissions. -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <!-- Touchscreen feature, optional to make sure we can run on devices with no touch screen. -->
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <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.MainActivity"
+ 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>
+ </activity>
+
+ <!-- Receiver -->
+ <receiver
+ android:name="com.example.AppReceiver"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+ <!-- Broadcast Receiver for a widget. -->
+ <receiver
+ android:label="@string/widget_name"
+ android:icon="@drawable/widget_icon"
+ android:name="com.example.WidgetReceiver" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data
+ android:name="android.appwidget.provider"
+ android:resource="@xml/widget_provider"
+ />
+ </receiver>
+
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService" />
+
+ <!-- Activity to configure widget -->
+ <activity
+ android:icon="@drawable/widget_icon"
+ android:label="Configure Widget"
+ android:name="com.example.WidgetConfigurationUI"
+ android:theme="@style/Theme.WidgetConfigurationUI" >
+ <intent-filter >
+ <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
+ </intent-filter>
+ </activity>
+
+ <!-- Provider extracted from ApiDemos -->
+ <provider android:name=".app.LoaderThrottle$SimpleProvider"
+ android:authorities="com.example.android.apis.app.LoaderThrottle"
+ android:enabled="@bool/atLeastHoneycomb" />
+
+ </application>
+
+</manifest>
+
+@lib1
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+</manifest>
+
+
+@lib2
+
+# An empty library is not supported. It must be a valid XML file.
+<manifest/>
+
+@result
+
+<!--
+ This is a canonical manifest that has some uses-permissions,
+ the usual uses-sdk and supports-screens, an app with an activity,
+ customer receiver & service and a widget.
+-->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <!-- Typical analytics permissions. -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <!-- Touchscreen feature, optional to make sure we can run on devices with no touch screen. -->
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <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.MainActivity"
+ 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>
+ </activity>
+
+ <!-- Receiver -->
+ <receiver
+ android:name="com.example.AppReceiver"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+ <!-- Broadcast Receiver for a widget. -->
+ <receiver
+ android:label="@string/widget_name"
+ android:icon="@drawable/widget_icon"
+ android:name="com.example.WidgetReceiver" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data
+ android:name="android.appwidget.provider"
+ android:resource="@xml/widget_provider"
+ />
+ </receiver>
+
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService" />
+
+ <!-- Activity to configure widget -->
+ <activity
+ android:icon="@drawable/widget_icon"
+ android:label="Configure Widget"
+ android:name="com.example.WidgetConfigurationUI"
+ android:theme="@style/Theme.WidgetConfigurationUI" >
+ <intent-filter >
+ <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
+ </intent-filter>
+ </activity>
+
+ <!-- Provider extracted from ApiDemos -->
+ <provider android:name=".app.LoaderThrottle$SimpleProvider"
+ android:authorities="com.example.android.apis.app.LoaderThrottle"
+ android:enabled="@bool/atLeastHoneycomb" />
+
+ </application>
+
+</manifest>
+
+
+@errors
+
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/01_ignore_app_attr.xml b/manifmerger/tests/src/com/android/manifmerger/data/01_ignore_app_attr.xml
new file mode 100755
index 0000000..b8d02e6
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/01_ignore_app_attr.xml
@@ -0,0 +1,68 @@
+#
+# Test:
+# - Attributes from the application element in a library are ignored.
+# - Comments from nodes ignored in libraries are not merged either.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- Source comments are preserved as-is. -->
+ <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" >
+ </application>
+
+</manifest>
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- The attributes from <application> in a library are not merged nor checked.
+ This comment is ignored. -->
+ <application
+ android:label="@string/lib_name"
+ android:icon="@drawable/lib_icon"
+ android:name="com.example.TheLib" >
+ </application>
+
+</manifest>
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- Source comments are preserved as-is. -->
+ <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" >
+ </application>
+
+</manifest>
+
+@errors
+
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/02_ignore_instrumentation.xml b/manifmerger/tests/src/com/android/manifmerger/data/02_ignore_instrumentation.xml
new file mode 100755
index 0000000..ed0dbbc
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/02_ignore_instrumentation.xml
@@ -0,0 +1,62 @@
+#
+# Test:
+# - Instrumentation element from libraries are not merged in main manifest.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <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" >
+ </application>
+
+</manifest>
+
+@lib1
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- Instrumentation is not merged from libraries. -->
+ <instrumentation
+ android:targetPackage="com.example.app1"
+ android:name="android.test.InstrumentationTestRunner" />
+
+</manifest>
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <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" >
+ </application>
+
+</manifest>
+
+@errors
+
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/10_activity_merge.xml b/manifmerger/tests/src/com/android/manifmerger/data/10_activity_merge.xml
new file mode 100755
index 0000000..56c0b53
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/10_activity_merge.xml
@@ -0,0 +1,378 @@
+#
+# Test:
+# - Activities from libraries are merged in the main manifest.
+# - Acts on activity / activity-alias / service / receiver / provider.
+# - Elements are merged as-is with the first comment element preceding them.
+# - Whitespace preceding the merged elements is transfered over too.
+#
+# Note:
+# - New elements are always merged at the end of the application element.
+# - It's an error if an element with the same @name attribute is defined
+# or merged more than once unless the definition is *exactly* the same,
+# the "same" being defined by the exact XML elements, whitespace excluded.
+#
+# This tests that a normal merge is done as expected.
+# There's a warning because one of the activities from lib2 is already defined
+# in the main but it's purely identical so it's not an error.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <!-- Typical analytics permissions. -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <!-- Touchscreen feature, optional to make sure we can run on devices with no touch screen. -->
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <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.MainActivity"
+ 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>
+ </activity>
+
+ <!-- Receiver -->
+ <receiver
+ android:name="com.example.AppReceiver"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+ <!-- This is exactly the same as in lib2_activity -->
+ <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="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+
+ </activity>
+
+ <!-- end of the main manifest's application element. Note that the
+ merger will insert at the end of this comment, in the specific
+ order activity, activity-alias, service, receiver and provider. -->
+
+ </application>
+
+</manifest>
+
+
+@lib1_widget
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <application
+ android:label="@string/lib_name"
+ android:name="com.example.WidgetLibrary" >
+
+ <!-- Broadcast Receiver for a widget. -->
+ <receiver
+ android:label="@string/widget_name"
+ android:icon="@drawable/widget_icon"
+ android:name="com.example.WidgetReceiver" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data
+ android:name="android.appwidget.provider"
+ android:resource="@xml/widget_provider"
+ />
+ </receiver>
+
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService" />
+
+ <!-- Activity to configure widget -->
+ <activity
+ android:icon="@drawable/widget_icon"
+ android:label="Configure Widget"
+ android:name="com.example.WidgetConfigurationUI"
+ android:theme="@style/Theme.WidgetConfigurationUI" >
+ <intent-filter >
+ <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
+
+
+@lib2_activity
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <application
+ android:label="@string/lib_name"
+ android:name="com.example.WidgetLibrary" >
+
+ <!-- This won't be merged because there's already an identical definition in the main. -->
+ <activity
+ android:name="com.example.LibActivity"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon"
+ android:theme="@style/Lib.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <!-- Provider extracted from ApiDemos -->
+ <provider android:name=".app.LoaderThrottle$SimpleProvider"
+ android:authorities="com.example.android.apis.app.LoaderThrottle"
+ android:enabled="@bool/atLeastHoneycomb" />
+
+ <!-- This one does not conflict with the main -->
+ <activity
+ android:name="com.example.LibActivity2"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon"
+ android:theme="@style/Lib.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@lib3_alias
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- This comment is ignored. -->
+
+ <application
+ android:label="@string/lib_name"
+ android:name="com.example.WidgetLibrary" >
+
+ <!-- The first comment just before the element
+ is carried over as-is.
+ -->
+ <!-- Formatting is preserved. -->
+ <!-- All consecutive comments are taken together. -->
+
+ <activity-alias
+ android:name="com.example.alias.MyActivity"
+ android:targetActivity="com.example.MainActivity"
+ android:label="@string/alias_name"
+ android:icon="@drawable/alias_icon"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ <!-- This is a dup of the 2nd activity in lib2 -->
+ <activity
+ android:name="com.example.LibActivity2"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon"
+ android:theme="@style/Lib.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <!-- Typical analytics permissions. -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <!-- Touchscreen feature, optional to make sure we can run on devices with no touch screen. -->
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <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.MainActivity"
+ 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>
+ </activity>
+
+ <!-- Receiver -->
+ <receiver
+ android:name="com.example.AppReceiver"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+ <!-- This is exactly the same as in lib2_activity -->
+ <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="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+
+ </activity>
+
+ <!-- end of the main manifest's application element. Note that the
+ merger will insert at the end of this comment, in the specific
+ order activity, activity-alias, service, receiver and provider. -->
+
+# from @lib1_widget
+ <!-- Activity to configure widget -->
+ <activity
+ android:icon="@drawable/widget_icon"
+ android:label="Configure Widget"
+ android:name="com.example.WidgetConfigurationUI"
+ android:theme="@style/Theme.WidgetConfigurationUI" >
+ <intent-filter >
+ <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
+ </intent-filter>
+ </activity>
+
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService" />
+
+ <!-- Broadcast Receiver for a widget. -->
+ <receiver
+ android:label="@string/widget_name"
+ android:icon="@drawable/widget_icon"
+ android:name="com.example.WidgetReceiver" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data
+ android:name="android.appwidget.provider"
+ android:resource="@xml/widget_provider"
+ />
+ </receiver>
+
+# from @lib2_activity
+ <!-- This one does not conflict with the main -->
+ <activity
+ android:name="com.example.LibActivity2"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon"
+ android:theme="@style/Lib.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <!-- Provider extracted from ApiDemos -->
+ <provider android:name=".app.LoaderThrottle$SimpleProvider"
+ android:authorities="com.example.android.apis.app.LoaderThrottle"
+ android:enabled="@bool/atLeastHoneycomb" />
+
+# from @lib3_alias
+ <!-- The first comment just before the element
+ is carried over as-is.
+ -->
+ <!-- Formatting is preserved. -->
+ <!-- All consecutive comments are taken together. -->
+
+ <activity-alias
+ android:name="com.example.alias.MyActivity"
+ android:targetActivity="com.example.MainActivity"
+ android:label="@string/alias_name"
+ android:icon="@drawable/alias_icon"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ </application>
+
+</manifest>
+
+@errors
+
+P [ManifestMergerTest2_lib2_activity.xml:6, ManifestMergerTest0_main.xml:31] Skipping identical /manifest/application/activity[@name=com.example.LibActivity] element.
+P [ManifestMergerTest3_lib3_alias.xml:19, ManifestMergerTest0_main.xml] Skipping identical /manifest/application/activity[@name=com.example.LibActivity2] element.
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
new file mode 100755
index 0000000..48c7b27
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/11_activity_dup.xml
@@ -0,0 +1,387 @@
+#
+# Test:
+# - Activities from libraries are merged in the main manifest.
+# - Acts on activity / activity-alias / service / receiver / provider.
+# - Elements are merged as-is with the first comment element preceding them.
+# - Whitespace preceding the merged elements is transfered over too.
+#
+# Note:
+# - New elements are always merged at the end of the application element.
+# - It's an error if an element with the same @name attribute is defined
+# or merged more than once unless the definition is *exactly* the same,
+# the "same" being defined by the exact XML elements, whitespace excluded.
+#
+# This tests that an error is generated because the libraries define
+# activities which are already in the main, with slightly different XML content:
+# number and *order* of elements must match, attributes must match.
+#
+
+@fails
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <!-- Typical analytics permissions. -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <!-- Touchscreen feature, optional to make sure we can run on devices with no touch screen. -->
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <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.MainActivity"
+ 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>
+ </activity>
+
+ <!-- Receiver -->
+ <receiver
+ android:name="com.example.AppReceiver"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+
+ <!-- Conflict with lib1 -->
+ <activity
+ android:icon="@drawable/widget_icon"
+ android:label="Configure Widget"
+ android:name="com.example.WidgetConfigurationUI"
+ android:theme="@style/Theme.WidgetConfigurationUI" >
+ <!-- missing the intent-filter -->
+ </activity>
+
+
+ <!-- Conflict with lib2 -->
+ <activity
+ android:name="com.example.LibActivity"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon">
+ <!-- missing attribute android:theme="@style/Lib.Theme" -->
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@lib1_widget
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <application
+ android:label="@string/lib_name"
+ android:name="com.example.WidgetLibrary" >
+
+ <!-- Broadcast Receiver for a widget. -->
+ <receiver
+ android:label="@string/widget_name"
+ android:icon="@drawable/widget_icon"
+ android:name="com.example.WidgetReceiver" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data
+ android:name="android.appwidget.provider"
+ android:resource="@xml/widget_provider"
+ />
+ </receiver>
+
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService" />
+
+ <!-- Activity to configure widget -->
+ <activity
+ android:icon="@drawable/widget_icon"
+ android:label="Configure Widget"
+ android:name="com.example.WidgetConfigurationUI"
+ android:theme="@style/Theme.WidgetConfigurationUI" >
+ <intent-filter >
+ <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
+
+
+@lib2_activity
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <application
+ android:label="@string/lib_name"
+ android:name="com.example.WidgetLibrary" >
+
+ <!-- This won't be merged because there's already an identical definition in the main. -->
+ <activity
+ android:name="com.example.LibActivity"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon"
+ android:theme="@style/Lib.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <!-- Provider extracted from ApiDemos -->
+ <provider android:name=".app.LoaderThrottle$SimpleProvider"
+ android:authorities="com.example.android.apis.app.LoaderThrottle"
+ android:enabled="@bool/atLeastHoneycomb" />
+
+ <!-- This one does not conflict with the main -->
+ <activity
+ android:name="com.example.LibActivity2"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon"
+ android:theme="@style/Lib.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@lib3_alias
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- This comment is ignored. -->
+
+ <application
+ android:label="@string/lib_name"
+ android:name="com.example.WidgetLibrary" >
+
+ <!-- The first comment just before the element
+ is carried over as-is.
+ -->
+ <!-- Formatting is preserved. -->
+ <!-- All consecutive comments are taken together. -->
+
+ <activity-alias
+ android:name="com.example.alias.MyActivity"
+ android:targetActivity="com.example.MainActivity"
+ android:label="@string/alias_name"
+ android:icon="@drawable/alias_icon"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ <!-- This will conflict with the 2nd one from lib2 -->
+ <activity
+ android:name="com.example.LibActivity2"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon"
+ android:theme="@style/Lib.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="android.intent.category.MOARCATZPLZ" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <!-- Typical analytics permissions. -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <!-- Touchscreen feature, optional to make sure we can run on devices with no touch screen. -->
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <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.MainActivity"
+ 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>
+ </activity>
+
+ <!-- Receiver -->
+ <receiver
+ android:name="com.example.AppReceiver"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+
+ <!-- Conflict with lib1 -->
+ <activity
+ android:icon="@drawable/widget_icon"
+ android:label="Configure Widget"
+ android:name="com.example.WidgetConfigurationUI"
+ android:theme="@style/Theme.WidgetConfigurationUI" >
+ <!-- missing the intent-filter -->
+ </activity>
+
+
+ <!-- Conflict with lib2 -->
+ <activity
+ android:name="com.example.LibActivity"
+ android:label="@string/lib_activity_name"
+ android:icon="@drawable/lib_activity_icon">
+ <!-- missing attribute android:theme="@style/Lib.Theme" -->
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+# from @lib1_widget
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService" />
+
+ <!-- Broadcast Receiver for a widget. -->
+ <receiver
+ android:label="@string/widget_name"
+ android:icon="@drawable/widget_icon"
+ android:name="com.example.WidgetReceiver" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data
+ android:name="android.appwidget.provider"
+ android:resource="@xml/widget_provider"
+ />
+ </receiver>
+
+# from @lib2_activity
+ <!-- This one does not conflict with the main -->
+ <activity android:icon="@drawable/lib_activity_icon" android:label="@string/lib_activity_name" android:name="com.example.LibActivity2" android:theme="@style/Lib.Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <!-- Provider extracted from ApiDemos -->
+ <provider android:name=".app.LoaderThrottle$SimpleProvider"
+ android:authorities="com.example.android.apis.app.LoaderThrottle"
+ android:enabled="@bool/atLeastHoneycomb" />
+
+# from @lib3_alias
+ <!-- The first comment just before the element
+ is carried over as-is.
+ -->
+ <!-- Formatting is preserved. -->
+ <!-- All consecutive comments are taken together. -->
+
+ <activity-alias
+ android:name="com.example.alias.MyActivity"
+ android:targetActivity="com.example.MainActivity"
+ android:label="@string/alias_name"
+ android:icon="@drawable/alias_icon"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ </application>
+
+</manifest>
+
+@errors
+
+E [ManifestMergerTest1_lib1_widget.xml:16, ManifestMergerTest0_main.xml:32] Trying to merge incompatible /manifest/application/activity[@name=com.example.WidgetConfigurationUI] element:
+ <activity android:name=com.example.WidgetConfigurationUI>
+-- <intent-filter>
+++ (end reached)
+E [ManifestMergerTest2_lib2_activity.xml:6, ManifestMergerTest0_main.xml:38] 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
+E [ManifestMergerTest3_lib3_alias.xml:19, ManifestMergerTest0_main.xml] 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)
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
new file mode 100755
index 0000000..e96c8a2
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/12_alias_dup.xml
@@ -0,0 +1,205 @@
+#
+# Test:
+# - Activities from libraries are merged in the main manifest.
+# - Acts on activity / activity-alias / service / receiver / provider.
+# - Elements are merged as-is with the first comment element preceding them.
+# - Whitespace preceding the merged elements is transfered over too.
+#
+# Note:
+# - New elements are always merged at the end of the application element.
+# - It's an error if an element with the same @name attribute is defined
+# or merged more than once unless the definition is *exactly* the same,
+# the "same" being defined by the exact XML elements, whitespace excluded.
+#
+# This tests that an error is generated because the libraries define
+# aliases which are already defined differently.
+#
+
+@fails
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+
+ <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-alias
+ android:name="com.example.alias.MyActivity1"
+ android:targetActivity="com.example.MainActivity1"
+ android:label="@string/alias_name1"
+ android:icon="@drawable/alias_icon1"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ <activity-alias
+ android:name="com.example.alias.MyActivity2"
+ android:targetActivity="com.example.MainActivity2"
+ android:label="@string/alias_name2"
+ android:icon="@drawable/alias_icon2"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ </application>
+
+</manifest>
+
+@lib1
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <application
+ android:label="@string/lib_name1"
+ android:name="com.example.Library1" >
+
+ <!-- Same as 1 in main -->
+ <activity-alias
+ android:name="com.example.alias.MyActivity1"
+ android:targetActivity="com.example.MainActivity1"
+ android:label="@string/alias_name1"
+ android:icon="@drawable/alias_icon1"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ <!-- Differs from 2 in main -->
+ <activity-alias
+ android:name="com.example.alias.MyActivity2"
+ android:targetActivity="com.example.MainActivity2">
+ </activity-alias>
+
+ <!-- A new one defined by lib1 -->
+ <activity-alias
+ android:name="com.example.alias.MyActivity3"
+ android:targetActivity="com.example.MainActivity3"
+ android:label="@string/alias_name3"
+ android:icon="@drawable/alias_icon3"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ </application>
+
+</manifest>
+
+@lib2
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <application
+ android:label="@string/lib_name2"
+ android:name="com.example.Library2" >
+
+ <!-- Conflicts with 3 from lib1 -->
+ <activity-alias
+ android:name="com.example.alias.MyActivity3"
+ android:label="@string/alias_name3"
+ android:icon="@drawable/alias_icon3">
+ <intent-filter>
+ <category android:name="android.intent.category.LAUNCHER2" />
+ </intent-filter>
+ </activity-alias>
+ </application>
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+
+ <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-alias
+ android:name="com.example.alias.MyActivity1"
+ android:targetActivity="com.example.MainActivity1"
+ android:label="@string/alias_name1"
+ android:icon="@drawable/alias_icon1"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ <activity-alias
+ android:name="com.example.alias.MyActivity2"
+ android:targetActivity="com.example.MainActivity2"
+ android:label="@string/alias_name2"
+ android:icon="@drawable/alias_icon2"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ <!-- A new one defined by lib1 -->
+ <activity-alias
+ android:name="com.example.alias.MyActivity3"
+ android:targetActivity="com.example.MainActivity3"
+ android:label="@string/alias_name3"
+ android:icon="@drawable/alias_icon3"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ </application>
+
+</manifest>
+
+@errors
+
+P [ManifestMergerTest1_lib1.xml:6, ManifestMergerTest0_main.xml:6] Skipping identical /manifest/application/activity-alias[@name=com.example.alias.MyActivity1] element.
+E [ManifestMergerTest1_lib1.xml:14, ManifestMergerTest0_main.xml:13] 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
+E [ManifestMergerTest2_lib2.xml:6, ManifestMergerTest0_main.xml] 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
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
new file mode 100755
index 0000000..bc7c0c9
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/13_service_dup.xml
@@ -0,0 +1,155 @@
+#
+# Test:
+# - Activities from libraries are merged in the main manifest.
+# - Acts on activity / activity-alias / service / receiver / provider.
+# - Elements are merged as-is with the first comment element preceding them.
+# - Whitespace preceding the merged elements is transfered over too.
+#
+# Note:
+# - New elements are always merged at the end of the application element.
+# - It's an error if an element with the same @name attribute is defined
+# or merged more than once unless the definition is *exactly* the same,
+# the "same" being defined by the exact XML elements, whitespace excluded.
+#
+# This tests that an error is generated because the libraries define
+# services which are already defined differently.
+#
+
+@fails
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+
+ <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" >
+
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService1" />
+
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService2" />
+
+ </application>
+
+</manifest>
+
+@lib1
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <application
+ android:label="@string/lib_name1"
+ android:name="com.example.Library1" >
+
+ <!-- Same as 1 in main -->
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService1" />
+
+ <!-- Differs from 2 in main -->
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService2" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </service>
+
+ <!-- A new one defined by lib1 -->
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService3" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </service>
+
+ </application>
+
+</manifest>
+
+@lib2
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <application
+ android:label="@string/lib_name2"
+ android:name="com.example.Library2" >
+
+ <!-- Conflicts with 3 from lib1 -->
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService3" />
+
+ </application>
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+
+ <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" >
+
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService1" />
+
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService2" />
+
+ <!-- A new one defined by lib1 -->
+ <service
+ android:icon="@drawable/app_icon"
+ android:name="com.example.AppService3" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </service>
+
+ </application>
+
+</manifest>
+
+@errors
+
+P [ManifestMergerTest1_lib1.xml:6, ManifestMergerTest0_main.xml:6] Skipping identical /manifest/application/service[@name=com.example.AppService1] element.
+E [ManifestMergerTest1_lib1.xml:9, ManifestMergerTest0_main.xml:8] Trying to merge incompatible /manifest/application/service[@name=com.example.AppService2] element:
+ <service android:name=com.example.AppService2>
+-- <intent-filter>
+++ (end reached)
+E [ManifestMergerTest2_lib2.xml:6, ManifestMergerTest0_main.xml] Trying to merge incompatible /manifest/application/service[@name=com.example.AppService3] element:
+ <service android:name=com.example.AppService3>
+-- (end reached)
+++ <intent-filter>
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
new file mode 100755
index 0000000..328f1fc
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/14_receiver_dup.xml
@@ -0,0 +1,176 @@
+#
+# Test:
+# - Activities from libraries are merged in the main manifest.
+# - Acts on activity / activity-alias / service / receiver / provider.
+# - Elements are merged as-is with the first comment element preceding them.
+# - Whitespace preceding the merged elements is transfered over too.
+#
+# Note:
+# - New elements are always merged at the end of the application element.
+# - It's an error if an element with the same @name attribute is defined
+# or merged more than once unless the definition is *exactly* the same,
+# the "same" being defined by the exact XML elements, whitespace excluded.
+#
+# This tests that an error is generated because the libraries define
+# receivers which are already defined differently.
+#
+
+@fails
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+
+ <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" >
+
+ <receiver
+ android:name="com.example.AppReceiver1"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+ <receiver
+ android:name="com.example.AppReceiver2"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+ </application>
+
+</manifest>
+
+@lib1
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <application
+ android:label="@string/lib_name1"
+ android:name="com.example.Library1" >
+
+ <!-- Same as 1 in main -->
+ <receiver
+ android:name="com.example.AppReceiver1"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+ <!-- Differs from 2 in main -->
+ <receiver
+ android:name="com.example.AppReceiver2" />
+
+ <!-- A new one defined by lib1 -->
+ <receiver
+ android:name="com.example.AppReceiver3"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM1" />
+ <action android:name="com.example.action.ACTION_CUSTOM2" />
+ <action android:name="com.example.action.ACTION_CUSTOM3" />
+ </intent-filter>
+ </receiver>
+
+ </application>
+
+</manifest>
+
+@lib2
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <application
+ android:label="@string/lib_name2"
+ android:name="com.example.Library2" >
+
+ <!-- Conflicts with 3 from lib1 -->
+ <receiver
+ android:name="com.example.AppReceiver3"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+ </application>
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+
+ <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" >
+
+ <receiver
+ android:name="com.example.AppReceiver1"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+ <receiver
+ android:name="com.example.AppReceiver2"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM" />
+ </intent-filter>
+ </receiver>
+
+ <!-- A new one defined by lib1 -->
+ <receiver
+ android:name="com.example.AppReceiver3"
+ android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="com.example.action.ACTION_CUSTOM1" />
+ <action android:name="com.example.action.ACTION_CUSTOM2" />
+ <action android:name="com.example.action.ACTION_CUSTOM3" />
+ </intent-filter>
+ </receiver>
+
+ </application>
+
+</manifest>
+
+@errors
+
+P [ManifestMergerTest1_lib1.xml:6, ManifestMergerTest0_main.xml:6] Skipping identical /manifest/application/receiver[@name=com.example.AppReceiver1] element.
+E [ManifestMergerTest1_lib1.xml:13, ManifestMergerTest0_main.xml:12] 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
+E [ManifestMergerTest2_lib2.xml:6, ManifestMergerTest0_main.xml] 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
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
new file mode 100755
index 0000000..91fe270
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/15_provider_dup.xml
@@ -0,0 +1,145 @@
+#
+# Test:
+# - Activities from libraries are merged in the main manifest.
+# - Acts on activity / activity-alias / service / receiver / provider.
+# - Elements are merged as-is with the first comment element preceding them.
+# - Whitespace preceding the merged elements is transfered over too.
+#
+# Note:
+# - New elements are always merged at the end of the application element.
+# - It's an error if an element with the same @name attribute is defined
+# or merged more than once unless the definition is *exactly* the same,
+# the "same" being defined by the exact XML elements, whitespace excluded.
+#
+# This tests that an error is generated because the libraries define
+# providers which are already defined differently.
+#
+
+@fails
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+
+ <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" >
+
+ <provider
+ android:name="com.example.Provider1"
+ android:authorities="com.example.android.apis.app.thingy1"
+ android:enabled="@bool/someConditionalValue" />
+
+ <provider
+ android:name="com.example.Provider2" />
+
+ </application>
+
+</manifest>
+
+@lib1
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <application
+ android:label="@string/lib_name1"
+ android:name="com.example.Library1" >
+
+ <!-- Same as MyActivity1 in main -->
+ <provider
+ android:name="com.example.Provider1"
+ android:authorities="com.example.android.apis.app.thingy1"
+ android:enabled="@bool/someConditionalValue" />
+
+ <!-- Differs from MyActivity2 in main -->
+ <provider
+ android:name="com.example.Provider2"
+ android:authorities="com.example.android.apis.app.thingy2"
+ android:enabled="@bool/someConditionalValue2" />
+
+ <!-- A new one defined by lib1 -->
+ <provider
+ android:name="com.example.Provider3"
+ android:authorities="com.example.android.apis.app.thingy3"
+ android:enabled="@bool/someConditionalValue" />
+
+ </application>
+
+</manifest>
+
+@lib2
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <application
+ android:label="@string/lib_name2"
+ android:name="com.example.Library2" >
+
+ <!-- Conflicts with 3 from lib1 -->
+ <provider
+ android:name="com.example.Provider3"
+ android:authorities="com.example.android.apis.app.thingy3" />
+ </application>
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+
+ <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" >
+
+ <provider
+ android:name="com.example.Provider1"
+ android:authorities="com.example.android.apis.app.thingy1"
+ android:enabled="@bool/someConditionalValue" />
+
+ <provider
+ android:name="com.example.Provider2" />
+
+ <!-- A new one defined by lib1 -->
+ <provider
+ android:name="com.example.Provider3"
+ android:authorities="com.example.android.apis.app.thingy3"
+ android:enabled="@bool/someConditionalValue" />
+
+ </application>
+
+</manifest>
+
+@errors
+
+P [ManifestMergerTest1_lib1.xml:6, ManifestMergerTest0_main.xml:6] Skipping identical /manifest/application/provider[@name=com.example.Provider1] element.
+E [ManifestMergerTest1_lib1.xml:9, ManifestMergerTest0_main.xml:8] 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
+E [ManifestMergerTest2_lib2.xml:6, ManifestMergerTest0_main.xml] 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
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/20_uses_lib_merge.xml b/manifmerger/tests/src/com/android/manifmerger/data/20_uses_lib_merge.xml
new file mode 100755
index 0000000..9e37577
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/20_uses_lib_merge.xml
@@ -0,0 +1,176 @@
+#
+# Test merge of uses-library:
+# - Merge is OK if destination already has one with the same @name.
+# - required defaults to "true"
+# - when merging, a required=true (explicit or implicit) overwrites a required=false.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <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" >
+
+ <!-- A library that is implicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary0_DefaultTrue" />
+
+ <!-- A library that is implicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary1_DefaultTrue" />
+
+ <!-- A library that is explicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary2_RequiredTrue"
+ android:required="true" />
+
+ <!-- A library that is explicitly marked as required=false -->
+ <uses-library
+ android:name="com.example.SomeLibrary3_RequiredFalse"
+ android:required="false" />
+
+ <!-- A library that is explicitly marked as required=false -->
+ <uses-library
+ android:name="com.example.SomeLibrary4_RequiredFalse"
+ android:required="false" />
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <application
+ android:label="@string/lib_name1"
+ android:name="com.example.Library1" >
+
+ <!-- Same as 1 from main, marking it as required=false -->
+ <uses-library
+ android:name="com.example.SomeLibrary1_DefaultTrue"
+ android:required="false" />
+
+ <!-- Same as 3 from main -->
+ <uses-library
+ android:name="com.example.SomeLibrary3_RequiredFalse"
+ android:required="false" />
+
+ <!-- Same as 4 from main -->
+ <uses-library
+ android:name="com.example.SomeLibrary4_RequiredFalse"
+ android:required="false" />
+
+ <!-- Add a new lib that is implicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary5_RequiredTrue"
+ android:required="true" />
+
+ <!-- Add a new lib that is implicitly marked as required=false -->
+ <uses-library
+ android:name="com.example.SomeLibrary6_RequiredFalse"
+ android:required="false" />
+
+ </application>
+</manifest>
+
+
+@lib2
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <application
+ android:label="@string/lib_name1"
+ android:name="com.example.Library1" >
+
+ <!-- Overrides 3, changing it from required=false to true -->
+ <uses-library
+ android:name="com.example.SomeLibrary3_RequiredFalse"
+ android:required="true" />
+
+ <!-- Same as 4 from main -->
+ <uses-library
+ android:name="com.example.SomeLibrary4_RequiredFalse"
+ android:required="false" />
+
+ <!-- Overrides 6, but implicitly declaring required=True -->
+ <uses-library
+ android:name="com.example.SomeLibrary6_RequiredFalse" />
+
+ </application>
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <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" >
+
+ <!-- A library that is implicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary0_DefaultTrue" />
+
+ <!-- A library that is implicitly marked as required=true -->
+# required=false from lib1 is ignored, it stays at the default
+ <uses-library
+ android:name="com.example.SomeLibrary1_DefaultTrue" />
+
+ <!-- A library that is explicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary2_RequiredTrue"
+ android:required="true" />
+
+ <!-- A library that is explicitly marked as required=false -->
+# lib1 keeps it required=false but lib2 makes it switch to required=true
+ <uses-library
+ android:name="com.example.SomeLibrary3_RequiredFalse"
+ android:required="true" />
+
+ <!-- A library that is explicitly marked as required=false -->
+ <uses-library
+ android:name="com.example.SomeLibrary4_RequiredFalse"
+ android:required="false" />
+
+# new from lib1
+ <!-- Add a new lib that is implicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary5_RequiredTrue"
+ android:required="true" />
+
+# new from lib1, but lib2 makes it switch to required=true
+ <!-- Add a new lib that is implicitly marked as required=false -->
+ <uses-library
+ android:name="com.example.SomeLibrary6_RequiredFalse"
+ android:required="true" />
+
+ </application>
+
+</manifest>
+
+
+@errors
+
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/21_uses_lib_errors.xml b/manifmerger/tests/src/com/android/manifmerger/data/21_uses_lib_errors.xml
new file mode 100755
index 0000000..da244c6
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/21_uses_lib_errors.xml
@@ -0,0 +1,202 @@
+#
+# Test merge of uses-library:
+# - Merge is OK if destination already has one with the same @name.
+# - required defaults to "true"
+# - when merging, a required=true (explicit or implicit) overwrites a required=false.
+#
+
+@fails
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <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" >
+
+ <!-- A library that is implicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary0_DefaultTrue" />
+
+ <!-- A library that is implicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary1_DefaultTrue" />
+
+ <!-- A library that is explicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary2_RequiredTrue"
+ android:required="booh!" />
+
+ <!-- A library that is explicitly marked as required=false -->
+ <uses-library
+ android:name="com.example.SomeLibrary3_RequiredFalse"
+ android:required="false" />
+
+ <!-- A library that is explicitly marked as required=false. Duplicated. -->
+ <uses-library
+ android:name="com.example.SomeLibrary3_RequiredFalse"
+ android:required="false" />
+
+ <!-- A library that is explicitly marked as required=false -->
+ <uses-library
+ android:name="com.example.SomeLibrary4_RequiredFalse"
+ android:required="false" />
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <application
+ android:label="@string/lib_name1"
+ android:name="com.example.Library1" >
+
+ <!-- Error: android:name attribute is missing. -->
+ <uses-library />
+ <uses-library android:required="false" />
+ <uses-library android:required="true" />
+
+ <!-- Same as 2 from main. Warning/ignore because dest required isn't true/false. -->
+ <uses-library
+ android:name="com.example.SomeLibrary2_RequiredTrue"
+ android:required="true" />
+
+ <!-- Same as 3 from main. Warning because destination as a duplicate. -->
+ <uses-library
+ android:name="com.example.SomeLibrary3_RequiredFalse"
+ android:required="false" />
+
+ <!-- Same as 4 from main. Warning because required isn't true or false. -->
+ <uses-library
+ android:name="com.example.SomeLibrary4_RequiredFalse"
+ android:required="foo" />
+
+ <!-- Add a new lib that is implicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary5_RequiredTrue"
+ android:required="true" />
+
+ <!-- Add a new lib that is implicitly marked as required=false -->
+ <uses-library
+ android:name="com.example.SomeLibrary6_RequiredFalse"
+ android:required="false" />
+
+ </application>
+</manifest>
+
+
+@lib2
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <application
+ android:label="@string/lib_name1"
+ android:name="com.example.Library1" >
+
+ <!-- Overrides 3, changing it from required=false to true -->
+ <uses-library
+ android:name="com.example.SomeLibrary3_RequiredFalse"
+ android:required="true" />
+
+ <!-- Same as 4 from main -->
+ <uses-library
+ android:name="com.example.SomeLibrary4_RequiredFalse"
+ android:required="false" />
+
+ <!-- Overrides 6, but implicitly declaring required=True -->
+ <uses-library
+ android:name="com.example.SomeLibrary6_RequiredFalse" />
+
+ </application>
+</manifest>
+
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <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" >
+
+ <!-- A library that is implicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary0_DefaultTrue" />
+
+ <!-- A library that is implicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary1_DefaultTrue" />
+
+ <!-- A library that is explicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary2_RequiredTrue"
+ android:required="booh!" />
+
+ <!-- A library that is explicitly marked as required=false -->
+# lib1 keeps it required=false but lib2 makes it switch to required=true
+ <uses-library
+ android:name="com.example.SomeLibrary3_RequiredFalse"
+ android:required="true" />
+
+ <!-- A library that is explicitly marked as required=false. Duplicated. -->
+ <uses-library
+ android:name="com.example.SomeLibrary3_RequiredFalse"
+ android:required="true" />
+
+ <!-- A library that is explicitly marked as required=false -->
+ <uses-library
+ android:name="com.example.SomeLibrary4_RequiredFalse"
+ android:required="false" />
+
+# new from lib1
+ <!-- Add a new lib that is implicitly marked as required=true -->
+ <uses-library
+ android:name="com.example.SomeLibrary5_RequiredTrue"
+ android:required="true" />
+
+# new from lib1, but lib2 makes it switch to required=true
+ <!-- Add a new lib that is implicitly marked as required=false -->
+ <uses-library
+ android:name="com.example.SomeLibrary6_RequiredFalse"
+ android:required="true" />
+
+ </application>
+
+</manifest>
+
+
+@errors
+
+E [ManifestMergerTest1_lib1.xml:6] Undefined 'name' attribute in /manifest/application/uses-library.
+E [ManifestMergerTest1_lib1.xml:7] Undefined 'name' attribute in /manifest/application/uses-library.
+E [ManifestMergerTest1_lib1.xml:8] Undefined 'name' attribute in /manifest/application/uses-library.
+W [ManifestMergerTest0_main.xml:12] Invalid attribute 'required' in /manifest/application/uses-library[@name=com.example.SomeLibrary2_RequiredTrue] element:
+Expected 'true' or 'false' but found 'booh!'.
+W [ManifestMergerTest0_main.xml:15] has more than one /manifest/application/uses-library[@name=com.example.SomeLibrary3_RequiredFalse] element.
+W [ManifestMergerTest1_lib1.xml:17] Invalid attribute 'required' in /manifest/application/uses-library[@name=com.example.SomeLibrary4_RequiredFalse] element:
+Expected 'true' or 'false' but found 'foo'.
+W [ManifestMergerTest0_main.xml:15] has more than one /manifest/application/uses-library[@name=com.example.SomeLibrary3_RequiredFalse] element.
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/25_permission_merge.xml b/manifmerger/tests/src/com/android/manifmerger/data/25_permission_merge.xml
new file mode 100755
index 0000000..26782b8
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/25_permission_merge.xml
@@ -0,0 +1,255 @@
+#
+# Text permission, permission-group and permission-tree:
+# - Libraries can add any of these elements as long as they don't conflict
+# with the destination: either the element must not be at all in the destination
+# (as identified by the name) or it must match exactly.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <permission
+ android:description="Insert boring description here"
+ android:icon="@drawable/robot"
+ android:label="Danger, Will Robinson!"
+ android:name="com.example.DangerWillRobinson"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="dangerous" />
+
+ <permission
+ android:name="com.example.WhatWereYouThinking"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="signatureOrSystem" />
+
+ <permission-group
+ android:description="Nobody expects..."
+ android:icon="@drawable/ignored_icon"
+ android:label="the Spanish Inquisition"
+ android:name="com.example.MasterControlPermission" />
+
+ <permission-tree
+ android:label="This is not a label"
+ android:name="com.example.PermTree" />
+
+ <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.MainActivity"
+ 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>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Same permissions as main manifest -->
+ <permission
+ android:description="Insert boring description here"
+ android:icon="@drawable/robot"
+ android:label="Danger, Will Robinson!"
+ android:name="com.example.DangerWillRobinson"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="dangerous" />
+ <permission-group
+ android:description="Nobody expects..."
+ android:icon="@drawable/ignored_icon"
+ android:label="the Spanish Inquisition"
+ android:name="com.example.MasterControlPermission" />
+ <permission-tree
+ android:label="This is not a label"
+ android:name="com.example.PermTree" />
+
+ <!-- Added by lib1. -->
+ <permission
+ android:name="com.example.Permission1"
+ android:permissionGroup="com.example.Permission1"
+ android:protectionLevel="normal" />
+
+ <permission-group
+ android:description="This is getting"
+ android:label="too silly"
+ android:name="com.example.EnoughWithTheQuotes" />
+
+ <permission-tree
+ android:name="com.example.PermTree1" />
+
+</manifest>
+
+
+@lib2
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Redefine one permission from main manifest -->
+ <permission
+ android:description="Insert boring description here"
+ android:icon="@drawable/robot"
+ android:label="Danger, Will Robinson!"
+ android:name="com.example.DangerWillRobinson"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="dangerous" />
+ <!-- And one from lib1. -->
+ <permission
+ android:name="com.example.Permission1"
+ android:permissionGroup="com.example.Permission1"
+ android:protectionLevel="normal" />
+ <permission-tree
+ android:name="com.example.PermTree1" />
+
+ <!-- Added by lib2. -->
+ <permission
+ android:name="com.example.SensiblePermission2"
+ android:permissionGroup="com.example.SensibleGroup2"
+ android:protectionLevel="normal" />
+
+ <permission-group
+ android:name="com.example.SensibleGroup2" />
+
+ <permission-tree
+ android:name="com.example.PermTree2" />
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <permission
+ android:description="Insert boring description here"
+ android:icon="@drawable/robot"
+ android:label="Danger, Will Robinson!"
+ android:name="com.example.DangerWillRobinson"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="dangerous" />
+
+ <permission
+ android:name="com.example.WhatWereYouThinking"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="signatureOrSystem" />
+
+ <permission-group
+ android:description="Nobody expects..."
+ android:icon="@drawable/ignored_icon"
+ android:label="the Spanish Inquisition"
+ android:name="com.example.MasterControlPermission" />
+
+ <permission-tree
+ android:label="This is not a label"
+ android:name="com.example.PermTree" />
+
+ <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.MainActivity"
+ 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>
+ </activity>
+
+ </application>
+
+# Added by lib1
+ <!-- Added by lib1. -->
+ <permission
+ android:name="com.example.Permission1"
+ android:permissionGroup="com.example.Permission1"
+ android:protectionLevel="normal" />
+
+ <permission-group
+ android:description="This is getting"
+ android:label="too silly"
+ android:name="com.example.EnoughWithTheQuotes" />
+
+ <permission-tree
+ android:name="com.example.PermTree1" />
+
+# Added by lib2
+ <!-- Added by lib2. -->
+ <permission
+ android:name="com.example.SensiblePermission2"
+ android:permissionGroup="com.example.SensibleGroup2"
+ android:protectionLevel="normal" />
+
+ <permission-group
+ android:name="com.example.SensibleGroup2" />
+
+ <permission-tree
+ android:name="com.example.PermTree2" />
+
+</manifest>
+
+
+@errors
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
new file mode 100755
index 0000000..e4be0e2
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/26_permission_dup.xml
@@ -0,0 +1,303 @@
+#
+# Text permission, permission-group and permission-tree:
+# - Libraries can add any of these elements as long as they don't conflict
+# with the destination: either the element must not be at all in the destination
+# (as identified by the name) or it must match exactly.
+#
+# This one tests that duplicate definitions that are strictly equal generate errors
+# with some (hopefully useful) diff.
+#
+
+@fails
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <permission
+ android:description="Insert boring description here"
+ android:icon="@drawable/robot"
+ android:label="Danger, Will Robinson!"
+ android:name="com.example.DangerWillRobinson"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="dangerous" />
+
+ <permission
+ android:name="com.example.WhatWereYouThinking"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="signatureOrSystem" />
+
+ <permission-group
+ android:description="Nobody expects..."
+ android:icon="@drawable/ignored_icon"
+ android:label="the Spanish Inquisition"
+ android:name="com.example.MasterControlPermission" />
+
+ <permission-tree
+ android:label="This is not a label"
+ android:name="com.example.PermTree" />
+
+ <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.MainActivity"
+ 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>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Similar permissions as main manifest, but with slight conflicts -->
+ <permission
+ android:description="Different description here"
+ android:icon="@drawable/not_the_same_icon"
+ android:label="Danger, Will Robinson!"
+ android:name="com.example.DangerWillRobinson"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="dangerous" />
+ <!-- missing icon: --><permission-group
+ android:description="Nobody expects..."
+ android:label="the Spanish Inquisition"
+ android:name="com.example.MasterControlPermission" />
+ <permission-tree
+ android:label="This is not the same label"
+ android:name="com.example.PermTree" />
+
+ <!-- different protectionLevel --><permission
+ android:name="com.example.WhatWereYouThinking"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="normal" />
+
+ <!-- Added by lib1. -->
+ <permission
+ android:name="com.example.Permission1"
+ android:permissionGroup="com.example.Permission1"
+ android:protectionLevel="normal" />
+
+ <permission-group
+ android:description="This is getting"
+ android:label="too silly"
+ android:name="com.example.EnoughWithTheQuotes" />
+
+ <permission-tree
+ android:name="com.example.PermTree1" />
+
+</manifest>
+
+
+@lib2
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Redefine one permission from main manifest -->
+ <permission
+ android:description="Insert boring description here"
+ android:icon="@drawable/robot"
+ android:label="Danger, Will Robinson!"
+ android:name="com.example.DangerWillRobinson"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="dangerous" />
+ <!-- And one from lib1, with a slight variation. -->
+ <permission
+ android:name="com.example.Permission1"
+ android:permissionGroup="com.example.Permission1"
+ android:protectionLevel="system" />
+ <permission-tree
+ android:description="Extra description"
+ android:name="com.example.PermTree1" />
+
+ <!-- Added by lib2. -->
+ <permission
+ android:name="com.example.SensiblePermission2"
+ android:permissionGroup="com.example.SensibleGroup2"
+ android:protectionLevel="normal" />
+
+ <permission-group
+ android:name="com.example.SensibleGroup2" />
+
+ <permission-tree
+ android:name="com.example.PermTree2" />
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <permission
+ android:description="Insert boring description here"
+ android:icon="@drawable/robot"
+ android:label="Danger, Will Robinson!"
+ android:name="com.example.DangerWillRobinson"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="dangerous" />
+
+ <permission
+ android:name="com.example.WhatWereYouThinking"
+ android:permissionGroup="com.example.MasterControlPermission"
+ android:protectionLevel="signatureOrSystem" />
+
+ <permission-group
+ android:description="Nobody expects..."
+ android:icon="@drawable/ignored_icon"
+ android:label="the Spanish Inquisition"
+ android:name="com.example.MasterControlPermission" />
+
+ <permission-tree
+ android:label="This is not a label"
+ android:name="com.example.PermTree" />
+
+ <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.MainActivity"
+ 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>
+ </activity>
+
+ </application>
+
+# Added by lib1
+ <!-- Added by lib1. -->
+ <permission
+ android:name="com.example.Permission1"
+ android:permissionGroup="com.example.Permission1"
+ android:protectionLevel="normal" />
+
+ <permission-group
+ android:description="This is getting"
+ android:label="too silly"
+ android:name="com.example.EnoughWithTheQuotes" />
+
+ <permission-tree
+ android:name="com.example.PermTree1" />
+
+# Added by lib2
+ <!-- Added by lib2. -->
+ <permission
+ android:name="com.example.SensiblePermission2"
+ android:permissionGroup="com.example.SensibleGroup2"
+ android:protectionLevel="normal" />
+
+ <permission-group
+ android:name="com.example.SensibleGroup2" />
+
+ <permission-tree
+ android:name="com.example.PermTree2" />
+
+</manifest>
+
+
+@errors
+
+E [ManifestMergerTest1_lib1.xml:4, ManifestMergerTest0_main.xml:12] 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
+E [ManifestMergerTest1_lib1.xml:8, ManifestMergerTest0_main.xml:14] 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
+E [ManifestMergerTest1_lib1.xml:5, ManifestMergerTest0_main.xml:16] 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
+E [ManifestMergerTest1_lib1.xml:6, ManifestMergerTest0_main.xml:18] 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
+E [ManifestMergerTest2_lib2.xml:6, ManifestMergerTest0_main.xml] 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
+E [ManifestMergerTest2_lib2.xml:7, ManifestMergerTest0_main.xml] 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
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/28_uses_perm_merge.xml b/manifmerger/tests/src/com/android/manifmerger/data/28_uses_perm_merge.xml
new file mode 100755
index 0000000..42f79e2
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/28_uses_perm_merge.xml
@@ -0,0 +1,152 @@
+#
+# Text uses-permission:
+# - Libraries can add any of these elements as long as they don't conflict
+# with the destination: either the element must not be at all in the destination
+# (as identified by the name) or it must match exactly.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <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.MainActivity"
+ 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>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Same permissions as main manifest -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <!-- Library 1 wants to know what you're running. -->
+ <uses-permission android:name="android.permission.GET_TASKS" />
+
+ <!-- Are you calling me? -->
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+
+</manifest>
+
+
+@lib2
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Redefine one permission from main manifest -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <!-- And one from lib1. -->
+ <uses-permission android:name="android.permission.GET_TASKS" />
+
+ <!-- Lib2 wants to know it all. -->
+ <uses-permission android:name="android.permission.READ_LOGS"/>
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <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.MainActivity"
+ 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>
+ </activity>
+
+ </application>
+
+# Added by lib1
+ <!-- Library 1 wants to know what you're running. -->
+ <uses-permission android:name="android.permission.GET_TASKS" />
+
+ <!-- Are you calling me? -->
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+
+# Added by lib2
+ <!-- Lib2 wants to know it all. -->
+ <uses-permission android:name="android.permission.READ_LOGS"/>
+
+</manifest>
+
+
+@errors
+
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/30_uses_sdk_ok.xml b/manifmerger/tests/src/com/android/manifmerger/data/30_uses_sdk_ok.xml
new file mode 100755
index 0000000..2d3670d
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/30_uses_sdk_ok.xml
@@ -0,0 +1,80 @@
+#
+# Test uses-sdk: add a uses-sdk from an app that doesn't define one.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk
+ android:minSdkVersion="11"
+ android:targetSdkVersion="14"
+ />
+
+ <application />
+
+</manifest>
+
+
+@lib1
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- This app requires cupcake. -->
+ <uses-sdk android:minSdkVersion="3" />
+
+</manifest>
+
+
+@lib2
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- This only defines a max-sdk, and we purposely ignore this attribute.
+ It doesn't get merged and doesn't generate a conflict either.
+ -->
+ <uses-sdk
+ android:maxSdkVersion="5"
+ />
+
+</manifest>
+
+
+@lib3
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Lib3 redefines the same requirements as lib1.
+ -->
+ <uses-sdk
+ android:minSdkVersion="3"
+ android:targetSdkVersion="11"
+ />
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk
+ android:minSdkVersion="11"
+ android:targetSdkVersion="14"
+ />
+
+ <application />
+
+</manifest>
+
+
+@errors
+
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/32_uses_sdk_minsdk_ok.xml b/manifmerger/tests/src/com/android/manifmerger/data/32_uses_sdk_minsdk_ok.xml
new file mode 100755
index 0000000..ffe7353
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/32_uses_sdk_minsdk_ok.xml
@@ -0,0 +1,64 @@
+#
+# Test uses-sdk: it's ok for a library to have a smaller minSdkVersion than the main manifest.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="14" />
+
+ <application />
+
+</manifest>
+
+
+@lib1
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- it's ok for a library to have a smaller minSdkVersion than the main manifest. -->
+ <uses-sdk android:minSdkVersion="4" />
+
+</manifest>
+
+
+@lib2
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <uses-sdk android:minSdkVersion="10" />
+
+</manifest>
+
+
+@lib3
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <uses-sdk android:minSdkVersion="11" />
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="14" />
+
+ <application />
+
+</manifest>
+
+
+@errors
+
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/33_uses_sdk_minsdk_conflict.xml b/manifmerger/tests/src/com/android/manifmerger/data/33_uses_sdk_minsdk_conflict.xml
new file mode 100755
index 0000000..0d0ab87
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/33_uses_sdk_minsdk_conflict.xml
@@ -0,0 +1,110 @@
+#
+# Test uses-sdk: it's an error for a library to require a minSdkVersion higher than the
+# one defined in the main manifest.
+#
+# Also a uses-sdk with a lack of minSdkVersion is equivalent to using version=1.
+#
+
+@fails
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- This is the same as writing android:minSdkVersion="1" -->
+ <uses-sdk android:targetSdkVersion="14" />
+
+ <application />
+
+</manifest>
+
+
+@lib1
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- The app can cope with API 1 but this library can only cope with API 4. -->
+ <uses-sdk android:minSdkVersion="4" />
+
+</manifest>
+
+
+@lib2
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <uses-sdk android:minSdkVersion="10" />
+
+</manifest>
+
+
+@lib3
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <uses-sdk android:minSdkVersion="11" />
+
+</manifest>
+
+
+@lib4_parsingError
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Parsing errors -->
+ <uses-sdk android:minSdkVersion="abcd" />
+
+</manifest>
+
+
+@lib5_parsingError
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Parsing errors -->
+ <uses-sdk android:minSdkVersion="123456789123456789" />
+
+</manifest>
+
+
+@lib6_parsingError
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Parsing errors -->
+ <uses-sdk android:minSdkVersion="0xFFFFFFFFFFFFFFFF" />
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- This is the same as writing android:minSdkVersion="1" -->
+ <uses-sdk android:targetSdkVersion="14" />
+
+ <application />
+
+</manifest>
+
+
+@errors
+
+E [ManifestMergerTest1_lib1.xml:4, ManifestMergerTest0_main.xml:4] Main manifest has <uses-sdk android:minSdkVersion='1'> but library uses minSdkVersion='4'
+Note: main manifest lacks a <uses-sdk android:minSdkVersion> declaration, which defaults to value 1.
+E [ManifestMergerTest2_lib2.xml:3, ManifestMergerTest0_main.xml:4] Main manifest has <uses-sdk android:minSdkVersion='1'> but library uses minSdkVersion='10'
+Note: main manifest lacks a <uses-sdk android:minSdkVersion> declaration, which defaults to value 1.
+E [ManifestMergerTest3_lib3.xml:3, ManifestMergerTest0_main.xml:4] Main manifest has <uses-sdk android:minSdkVersion='1'> but library uses minSdkVersion='11'
+Note: main manifest lacks a <uses-sdk android:minSdkVersion> declaration, which defaults to value 1.
+E [ManifestMergerTest4_lib4_parsingError.xml:4] Failed to parse <uses-sdk minSdkVersion='abcd'>: must be an integer number.
+E [ManifestMergerTest5_lib5_parsingError.xml:4] Failed to parse <uses-sdk minSdkVersion='123456789123456789'>: must be an integer number.
+E [ManifestMergerTest6_lib6_parsingError.xml:4] Failed to parse <uses-sdk minSdkVersion='0xFFFFFFFFFFFFFFFF'>: must be an integer number.
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/36_uses_sdk_targetsdk_warning.xml b/manifmerger/tests/src/com/android/manifmerger/data/36_uses_sdk_targetsdk_warning.xml
new file mode 100755
index 0000000..2e87e19
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/36_uses_sdk_targetsdk_warning.xml
@@ -0,0 +1,73 @@
+
+# Test uses-sdk: there's a warning if the main manifest defines a targetSdkVersion that
+# .
+#
+
+@fails
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- This app requires cupcake and targets honeycomb's yummy Holo theme. -->
+ <uses-sdk
+ android:minSdkVersion="3"
+ android:targetSdkVersion="4"
+ />
+
+ <application />
+
+</manifest>
+
+
+@lib1
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- This app requires cupcake and targets honeycomb's yummy Holo theme. -->
+ <uses-sdk
+ android:minSdkVersion="3"
+ android:targetSdkVersion="11"
+ />
+
+</manifest>
+
+
+@lib2
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- This is not an error nor a warning. -->
+ <uses-sdk
+ android:minSdkVersion="3"
+ android:targetSdkVersion="4"
+ />
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- This app requires cupcake and targets honeycomb's yummy Holo theme. -->
+ <uses-sdk
+ android:minSdkVersion="3"
+ android:targetSdkVersion="4"
+ />
+
+ <application />
+
+</manifest>
+
+
+@errors
+
+W [ManifestMergerTest1_lib1.xml:4, ManifestMergerTest0_main.xml:4] Main manifest has <uses-sdk android:targetSdkVersion='4'> but library uses targetSdkVersion='11'
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/40_uses_feat_merge.xml b/manifmerger/tests/src/com/android/manifmerger/data/40_uses_feat_merge.xml
new file mode 100755
index 0000000..1e88473
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/40_uses_feat_merge.xml
@@ -0,0 +1,178 @@
+#
+# Test merge of uses-feature:
+# - Merge is OK if destination already has one with the same @name.
+# - required defaults to "true"
+# - when merging, a required=true (explicit or implicit) overwrites a required=false.
+#
+# Note: uses-feature with android:glEsVersion is dealt with in another test case.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- A feature that is implicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature0_DefaultTrue" />
+
+ <!-- A feature that is implicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature1_DefaultTrue" />
+
+ <!-- A feature that is explicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature2_RequiredTrue"
+ android:required="true" />
+
+ <!-- A feature that is explicitly marked as required=false -->
+ <uses-feature
+ android:name="com.example.SomeFeature3_RequiredFalse"
+ android:required="false" />
+
+ <!-- A feature that is explicitly marked as required=false -->
+ <uses-feature
+ android:name="com.example.SomeFeature4_RequiredFalse"
+ android:required="false" />
+
+ <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" >
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Same as 1 from main, marking it as required=false -->
+ <uses-feature
+ android:name="com.example.SomeFeature1_DefaultTrue"
+ android:required="false" />
+
+ <!-- Same as 3 from main -->
+ <uses-feature
+ android:name="com.example.SomeFeature3_RequiredFalse"
+ android:required="false" />
+
+ <!-- Same as 4 from main -->
+ <uses-feature
+ android:name="com.example.SomeFeature4_RequiredFalse"
+ android:required="false" />
+
+ <!-- Add a new feature that is implicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature5_RequiredTrue"
+ android:required="true" />
+
+ <!-- Add a new feature that is implicitly marked as required=false -->
+ <uses-feature
+ android:name="com.example.SomeFeature6_RequiredFalse"
+ android:required="false" />
+
+ <application
+ android:label="@string/lib_name1"
+ android:name="com.example.Library1" >
+
+ </application>
+</manifest>
+
+
+@lib2
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Overrides 3, changing it from required=false to true -->
+ <uses-feature
+ android:name="com.example.SomeFeature3_RequiredFalse"
+ android:required="true" />
+
+ <!-- Same as 4 from main -->
+ <uses-feature
+ android:name="com.example.SomeFeature4_RequiredFalse"
+ android:required="false" />
+
+ <!-- Overrides 6, but implicitly declaring required=True -->
+ <uses-feature
+ android:name="com.example.SomeFeature6_RequiredFalse" />
+
+ <application
+ android:label="@string/lib_name1"
+ android:name="com.example.Library1" >
+
+ </application>
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- A feature that is implicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature0_DefaultTrue" />
+
+ <!-- A feature that is implicitly marked as required=true -->
+# required=false from lib1 is ignored, it stays at the default
+ <uses-feature
+ android:name="com.example.SomeFeature1_DefaultTrue" />
+
+ <!-- A feature that is explicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature2_RequiredTrue"
+ android:required="true" />
+
+ <!-- A feature that is explicitly marked as required=false -->
+# lib1 keeps it required=false but lib2 makes it switch to required=true
+ <uses-feature
+ android:name="com.example.SomeFeature3_RequiredFalse"
+ android:required="true" />
+
+ <!-- A feature that is explicitly marked as required=false -->
+ <uses-feature
+ android:name="com.example.SomeFeature4_RequiredFalse"
+ android:required="false" />
+
+ <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" >
+
+ </application>
+
+# new from lib1
+ <!-- Add a new feature that is implicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature5_RequiredTrue"
+ android:required="true" />
+
+# new from lib1, but lib2 makes it switch to required=true
+ <!-- Add a new feature that is implicitly marked as required=false -->
+ <uses-feature
+ android:name="com.example.SomeFeature6_RequiredFalse"
+ android:required="true" />
+
+</manifest>
+
+
+@errors
+
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/41_uses_feat_errors.xml b/manifmerger/tests/src/com/android/manifmerger/data/41_uses_feat_errors.xml
new file mode 100755
index 0000000..85eaf1d
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/41_uses_feat_errors.xml
@@ -0,0 +1,205 @@
+#
+# Test merge of uses-feature:
+# - Merge is OK if destination already has one with the same @name.
+# - required defaults to "true"
+# - when merging, a required=true (explicit or implicit) overwrites a required=false.
+#
+# Note: uses-feature with android:glEsVersion is dealt with in another test case.
+#
+
+@fails
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- A feature that is implicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature0_DefaultTrue" />
+
+ <!-- A feature that is implicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature1_DefaultTrue" />
+
+ <!-- A feature that is explicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature2_RequiredTrue"
+ android:required="booh!" />
+
+ <!-- A feature that is explicitly marked as required=false -->
+ <uses-feature
+ android:name="com.example.SomeFeature3_RequiredFalse"
+ android:required="false" />
+
+ <!-- A feature that is explicitly marked as required=false. Duplicated. -->
+ <uses-feature
+ android:name="com.example.SomeFeature3_RequiredFalse"
+ android:required="false" />
+
+ <!-- A feature that is explicitly marked as required=false -->
+ <uses-feature
+ android:name="com.example.SomeFeature4_RequiredFalse"
+ android:required="false" />
+
+ <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" >
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Error: android:name attribute is missing. -->
+ <uses-feature />
+ <uses-feature android:required="false" />
+ <uses-feature android:required="true" />
+
+ <!-- Same as 2 from main. Warning/ignore because dest required isn't true/false. -->
+ <uses-feature
+ android:name="com.example.SomeFeature2_RequiredTrue"
+ android:required="true" />
+
+ <!-- Same as 3 from main. Warning because destination as a duplicate. -->
+ <uses-feature
+ android:name="com.example.SomeFeature3_RequiredFalse"
+ android:required="false" />
+
+ <!-- Same as 4 from main. Warning because required isn't true or false. -->
+ <uses-feature
+ android:name="com.example.SomeFeature4_RequiredFalse"
+ android:required="foo" />
+
+ <!-- Add a new feature that is implicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature5_RequiredTrue"
+ android:required="true" />
+
+ <!-- Add a new feature that is implicitly marked as required=false -->
+ <uses-feature
+ android:name="com.example.SomeFeature6_RequiredFalse"
+ android:required="false" />
+
+ <application
+ android:label="@string/lib_name1"
+ android:name="com.example.Library1" >
+
+ </application>
+</manifest>
+
+
+@lib2
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Overrides 3, changing it from required=false to true -->
+ <uses-feature
+ android:name="com.example.SomeFeature3_RequiredFalse"
+ android:required="true" />
+
+ <!-- Same as 4 from main -->
+ <uses-feature
+ android:name="com.example.SomeFeature4_RequiredFalse"
+ android:required="false" />
+
+ <!-- Overrides 6, but implicitly declaring required=True -->
+ <uses-feature
+ android:name="com.example.SomeFeature6_RequiredFalse" />
+
+ <application
+ android:label="@string/lib_name1"
+ android:name="com.example.Library1" >
+
+ </application>
+</manifest>
+
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <!-- A feature that is implicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature0_DefaultTrue" />
+
+ <!-- A feature that is implicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature1_DefaultTrue" />
+
+ <!-- A feature that is explicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature2_RequiredTrue"
+ android:required="booh!" />
+
+ <!-- A feature that is explicitly marked as required=false -->
+# lib1 keeps it required=false but lib2 makes it switch to required=true
+ <uses-feature
+ android:name="com.example.SomeFeature3_RequiredFalse"
+ android:required="true" />
+
+ <!-- A feature that is explicitly marked as required=false. Duplicated. -->
+# in case of duplicated name, they are all modified.
+ <uses-feature
+ android:name="com.example.SomeFeature3_RequiredFalse"
+ android:required="true" />
+
+ <!-- A feature that is explicitly marked as required=false -->
+ <uses-feature
+ android:name="com.example.SomeFeature4_RequiredFalse"
+ android:required="false" />
+
+ <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" >
+
+ </application>
+
+# new from lib1
+ <!-- Add a new feature that is implicitly marked as required=true -->
+ <uses-feature
+ android:name="com.example.SomeFeature5_RequiredTrue"
+ android:required="true" />
+
+# new from lib1, but lib2 makes it switch to required=true
+ <!-- Add a new feature that is implicitly marked as required=false -->
+ <uses-feature
+ android:name="com.example.SomeFeature6_RequiredFalse"
+ android:required="true" />
+
+</manifest>
+
+
+@errors
+
+E [ManifestMergerTest1_lib1.xml:4] Undefined 'name' attribute in /manifest/uses-feature.
+E [ManifestMergerTest1_lib1.xml:5] Undefined 'name' attribute in /manifest/uses-feature.
+E [ManifestMergerTest1_lib1.xml:6] Undefined 'name' attribute in /manifest/uses-feature.
+W [ManifestMergerTest0_main.xml:10] Invalid attribute 'required' in /manifest/uses-feature[@name=com.example.SomeFeature2_RequiredTrue] element:
+Expected 'true' or 'false' but found 'booh!'.
+W [ManifestMergerTest0_main.xml:13] has more than one /manifest/uses-feature[@name=com.example.SomeFeature3_RequiredFalse] element.
+W [ManifestMergerTest1_lib1.xml:15] Invalid attribute 'required' in /manifest/uses-feature[@name=com.example.SomeFeature4_RequiredFalse] element:
+Expected 'true' or 'false' but found 'foo'.
+W [ManifestMergerTest0_main.xml:13] has more than one /manifest/uses-feature[@name=com.example.SomeFeature3_RequiredFalse] element.
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/45_uses_feat_gles_once.xml b/manifmerger/tests/src/com/android/manifmerger/data/45_uses_feat_gles_once.xml
new file mode 100755
index 0000000..8928ae4
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/45_uses_feat_gles_once.xml
@@ -0,0 +1,124 @@
+#
+# Test merge of uses-feature with android:glEsVersion:
+# - Error if defined in lib+dest with dest < lib.
+# - Never automatically change dest.
+# - Default implied value is 1.0 (0x00010000).
+#
+# This tests a case that works. Also checks that glEsVersion attributes are stripped
+# when merging uses-feature with the name attribute.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-feature
+ android:name="com.example.SomeFeature0" />
+ <uses-feature
+ android:name="com.example.SomeFeature1"
+ android:required="false" />
+ <uses-feature android:glEsVersion="0x00020001" />
+
+ <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" >
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Add a new feature with a glEsVersion of 2.1 -->
+ <uses-feature
+ android:name="com.example.SomeFeature5"
+ android:required="false"
+ android:glEsVersion="0x00020001"
+ />
+
+ <!-- Add a glEsVersion of 2.0, which will be ignored -->
+ <uses-feature
+ android:glEsVersion="0x00020000"
+ />
+
+ <application
+ android:label="@string/lib_name1"
+ android:name="com.example.Library1" />
+
+</manifest>
+
+
+@lib2
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Add a new feature with a glEsVersion of 1.1 which will be ignored -->
+ <uses-feature
+ android:name="com.example.SomeFeature6"
+ android:required="false"
+ android:glEsVersion="0x00020001"
+ />
+
+ <!-- Add a glEsVersion of 1.0, which will be ignored -->
+ <uses-feature android:glEsVersion="0x00010000" />
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-feature
+ android:name="com.example.SomeFeature0" />
+ <uses-feature
+ android:name="com.example.SomeFeature1"
+ android:required="false" />
+ <uses-feature android:glEsVersion="0x00020001" />
+
+ <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" >
+
+ </application>
+
+ <!-- Add a new feature with a glEsVersion of 2.1 -->
+# lib1 adds this new node. Note how the glEsVersion=2.1 is stripped out.
+ <uses-feature
+ android:name="com.example.SomeFeature5"
+ android:required="false"
+ />
+
+ <!-- Add a new feature with a glEsVersion of 1.1 which will be ignored -->
+# lib2 adds this new node. Note how the glEsVersion=2.0 is stripped out.
+ <uses-feature
+ android:name="com.example.SomeFeature6"
+ android:required="false"
+ />
+
+</manifest>
+
+
+@errors
+
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/47_uses_feat_gles_conflict.xml b/manifmerger/tests/src/com/android/manifmerger/data/47_uses_feat_gles_conflict.xml
new file mode 100755
index 0000000..05f468f
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/47_uses_feat_gles_conflict.xml
@@ -0,0 +1,160 @@
+#
+# Test merge of uses-feature with android:glEsVersion:
+# - Error if defined in lib+dest with dest < lib.
+# - Never automatically change dest.
+# - Default implied value is 1.0 (0x00010000).
+#
+# This tests a case that doesn't works because the main manifest doesn't declare
+# the value and thus defaults to 1.0, so libraries with higher requirements will
+# conflict.
+#
+
+@fails
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-feature
+ android:name="com.example.SomeFeature0" />
+ <uses-feature
+ android:name="com.example.SomeFeature1"
+ android:required="false" />
+
+ <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" >
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Add a new feature with a glEsVersion of 2.1 -->
+ <uses-feature
+ android:name="com.example.SomeFeature5"
+ android:required="false"
+ android:glEsVersion="0x00020001"
+ />
+
+ <!-- Add a glEsVersion of 2.0, which will be ignored -->
+ <uses-feature
+ android:glEsVersion="0x00020000"
+ />
+
+ <application
+ android:label="@string/lib_name1"
+ android:name="com.example.Library1" />
+
+</manifest>
+
+
+@lib2
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Add a new feature with a glEsVersion of 1.1 which will be ignored -->
+ <uses-feature
+ android:name="com.example.SomeFeature6"
+ android:required="false"
+ android:glEsVersion="0x00020001"
+ />
+
+ <!-- Add a glEsVersion of 1.0, which will be ignored -->
+ <uses-feature
+ android:glEsVersion="0x00010000"
+ />
+
+ <!-- Test some invalid values. -->
+
+ <!-- 0 isn't a valid value and generates a warning stating it's ignored. -->
+ <uses-feature
+ android:glEsVersion="0"
+ />
+
+ <!-- 0.0xFFFF is 0.99... and generates a warning stating it's ignored.
+ The real minimal value is 1.0, not 0.99... -->
+ <uses-feature
+ android:glEsVersion="0x0000FFFF"
+ />
+
+ <!-- 0xFFFF.xFFFF is not invalid. It does correspond to 65535.9999847412109375
+ which is unlikely to be valid anyway. It's not ignored and should parse just fine.
+ -->
+ <uses-feature
+ android:glEsVersion="0xFFFFFFFF"
+ />
+
+ <!-- This value shouldn't parse correctly with a Long and will generate a parsing error.
+ -->
+ <uses-feature
+ android:glEsVersion="0xFFFFFFFFFFFFFFFF"
+ />
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-feature
+ android:name="com.example.SomeFeature0" />
+ <uses-feature
+ android:name="com.example.SomeFeature1"
+ android:required="false" />
+
+ <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" >
+
+ </application>
+
+ <!-- Add a new feature with a glEsVersion of 2.1 -->
+# lib1 adds this new node. Note how the glEsVersion=2.1 is stripped out.
+ <uses-feature
+ android:name="com.example.SomeFeature5"
+ android:required="false"
+ />
+
+ <!-- Add a new feature with a glEsVersion of 1.1 which will be ignored -->
+# lib2 adds this new node. Note how the glEsVersion=2.0 is stripped out.
+ <uses-feature
+ android:name="com.example.SomeFeature6"
+ android:required="false"
+ />
+
+</manifest>
+
+
+@errors
+
+W [ManifestMergerTest1_lib1.xml:4, ManifestMergerTest0_main.xml:1] Main manifest has <uses-feature android:glEsVersion='0x00010000'> but library uses glEsVersion='0x00020001'
+Note: main manifest lacks a <uses-feature android:glEsVersion> declaration, and thus defaults to glEsVersion=0x00010000.
+W [ManifestMergerTest2_lib2.xml:12] Ignoring <uses-feature android:glEsVersion='0'> because it's smaller than 1.0.
+W [ManifestMergerTest2_lib2.xml:15] Ignoring <uses-feature android:glEsVersion='0x0000FFFF'> because it's smaller than 1.0.
+E [ManifestMergerTest2_lib2.xml:21] Failed to parse <uses-feature android:glEsVersion='0xFFFFFFFFFFFFFFFF'>: must be an integer in the form 0x00020001.
+W [ManifestMergerTest2_lib2.xml:18, ManifestMergerTest0_main.xml:1] Main manifest has <uses-feature android:glEsVersion='0x00010000'> but library uses glEsVersion='0xffffffff'
+Note: main manifest lacks a <uses-feature android:glEsVersion> declaration, and thus defaults to glEsVersion=0x00010000.
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/50_uses_conf_warning.xml b/manifmerger/tests/src/com/android/manifmerger/data/50_uses_conf_warning.xml
new file mode 100755
index 0000000..4b79dc6
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/50_uses_conf_warning.xml
@@ -0,0 +1,158 @@
+#
+# Test uses-configuration:
+# - it's OK if a library defines one or multiple times an element already in the application.
+# - it's a warning if the library defines an element not in the application.
+# - this does not actually merge anything. The XML is not changed at all.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-configuration
+ android:reqFiveWayNav="true"
+ android:reqHardKeyboard="false"
+ android:reqKeyboardType="undefined"
+ android:reqNavigation="nonav"
+ android:reqTouchScreen="stylus"
+ />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <supports-gl-texture android:name="some.gl.texture1" />
+ <supports-gl-texture android:name="some.gl.texture2" />
+
+ <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.MainActivity"
+ 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>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- this is the same uses-conf than in the main. -->
+ <uses-configuration
+ android:reqFiveWayNav="true"
+ android:reqHardKeyboard="false"
+ android:reqKeyboardType="undefined"
+ android:reqNavigation="nonav"
+ android:reqTouchScreen="stylus"
+ />
+
+</manifest>
+
+
+@lib2
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- this is the not same uses-conf than in the main. -->
+ <uses-configuration
+ android:reqFiveWayNav="false"
+ android:reqNavigation="trackball"
+ android:reqTouchScreen="finger" />
+
+</manifest>
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-configuration
+ android:reqFiveWayNav="true"
+ android:reqHardKeyboard="false"
+ android:reqKeyboardType="undefined"
+ android:reqNavigation="nonav"
+ android:reqTouchScreen="stylus"
+ />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <supports-gl-texture android:name="some.gl.texture1" />
+ <supports-gl-texture android:name="some.gl.texture2" />
+
+ <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.MainActivity"
+ 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>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@errors
+
+W [ManifestMergerTest2_lib2.xml:4] /manifest/uses-configuration missing from ManifestMergerTest0_main.xml:
+<uses-configuration>
+ @android:reqFiveWayNav = false
+ @android:reqNavigation = trackball
+ @android:reqTouchScreen = finger
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/52_support_screens_warning.xml b/manifmerger/tests/src/com/android/manifmerger/data/52_support_screens_warning.xml
new file mode 100755
index 0000000..737144a
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/52_support_screens_warning.xml
@@ -0,0 +1,158 @@
+#
+# Test supports-screens:
+# - it's OK if a library defines one or multiple times an element already in the application.
+# - it's a warning if the library defines an element not in the application.
+# - this does not actually merge anything. The XML is not changed at all.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-configuration
+ android:reqFiveWayNav="true"
+ android:reqHardKeyboard="false"
+ android:reqKeyboardType="undefined"
+ android:reqNavigation="nonav"
+ android:reqTouchScreen="stylus"
+ />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <supports-gl-texture android:name="some.gl.texture1" />
+ <supports-gl-texture android:name="some.gl.texture2" />
+
+ <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.MainActivity"
+ 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>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- this is the same supports-screens than in the main. -->
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+</manifest>
+
+
+@lib2
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- this is the not same supports-screens than in the main. -->
+ <supports-screens
+ android:smallScreens="false"
+ android:resizeable="false"
+ />
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-configuration
+ android:reqFiveWayNav="true"
+ android:reqHardKeyboard="false"
+ android:reqKeyboardType="undefined"
+ android:reqNavigation="nonav"
+ android:reqTouchScreen="stylus"
+ />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <supports-gl-texture android:name="some.gl.texture1" />
+ <supports-gl-texture android:name="some.gl.texture2" />
+
+ <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.MainActivity"
+ 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>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@errors
+
+W [ManifestMergerTest2_lib2.xml:4] /manifest/supports-screens missing from ManifestMergerTest0_main.xml:
+<supports-screens>
+ @android:resizeable = false
+ @android:smallScreens = false
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/54_compat_screens_warning.xml b/manifmerger/tests/src/com/android/manifmerger/data/54_compat_screens_warning.xml
new file mode 100755
index 0000000..35085fd
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/54_compat_screens_warning.xml
@@ -0,0 +1,200 @@
+#
+# Test compatible-screens:
+# - it's OK if a library defines one or multiple times an element already in the application.
+# - it's a warning if the library defines an element not in the application.
+# - this does not actually merge anything. The XML is not changed at all.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-configuration
+ android:reqFiveWayNav="true"
+ android:reqHardKeyboard="false"
+ android:reqKeyboardType="undefined"
+ android:reqNavigation="nonav"
+ android:reqTouchScreen="stylus"
+ />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <supports-gl-texture android:name="some.gl.texture1" />
+ <supports-gl-texture android:name="some.gl.texture2" />
+
+ <compatible-screens>
+ <screen android:screenSize="small" android:screenDensity="ldpi" />
+ <screen android:screenSize="normal" android:screenDensity="xhdpi" />
+ </compatible-screens>
+
+ <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.MainActivity"
+ 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>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- this is the same compatible-screens than in the main. -->
+ <compatible-screens>
+ <screen android:screenSize="small" android:screenDensity="ldpi" />
+ <screen android:screenSize="normal" android:screenDensity="xhdpi" />
+ </compatible-screens>
+
+
+</manifest>
+
+
+@lib2
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- this is the not same compatible-screens than in the main. -->
+ <compatible-screens>
+ <screen android:screenSize="small" android:screenDensity="ldpi" />
+ <screen android:screenSize="normal" android:screenDensity="mdpi" />
+ </compatible-screens>
+
+ <compatible-screens>
+ <screen android:screenSize="small" android:screenDensity="ldpi" />
+ </compatible-screens>
+
+ <compatible-screens>
+ <screen android:screenSize="normal" android:screenDensity="ldpi" />
+ <screen android:screenSize="normal" android:screenDensity="mdpi" />
+ <screen android:screenSize="normal" android:screenDensity="hdpi" />
+ <screen android:screenSize="normal" android:screenDensity="xhdpi" />
+ </compatible-screens>
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-configuration
+ android:reqFiveWayNav="true"
+ android:reqHardKeyboard="false"
+ android:reqKeyboardType="undefined"
+ android:reqNavigation="nonav"
+ android:reqTouchScreen="stylus"
+ />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <supports-gl-texture android:name="some.gl.texture1" />
+ <supports-gl-texture android:name="some.gl.texture2" />
+
+ <compatible-screens>
+ <screen android:screenSize="small" android:screenDensity="ldpi" />
+ <screen android:screenSize="normal" android:screenDensity="xhdpi" />
+ </compatible-screens>
+
+ <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.MainActivity"
+ 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>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@errors
+
+W [ManifestMergerTest2_lib2.xml:4] /manifest/compatible-screens missing from ManifestMergerTest0_main.xml:
+<compatible-screens>
+ <screen>
+ @android:screenDensity = ldpi
+ @android:screenSize = small
+ <screen>
+ @android:screenDensity = mdpi
+ @android:screenSize = normal
+W [ManifestMergerTest2_lib2.xml:9] /manifest/compatible-screens missing from ManifestMergerTest0_main.xml:
+<compatible-screens>
+ <screen>
+ @android:screenDensity = ldpi
+ @android:screenSize = small
+W [ManifestMergerTest2_lib2.xml:13] /manifest/compatible-screens missing from ManifestMergerTest0_main.xml:
+<compatible-screens>
+ <screen>
+ @android:screenDensity = ldpi
+ @android:screenSize = normal
+ <screen>
+ @android:screenDensity = mdpi
+ @android:screenSize = normal
+ <screen>
+ @android:screenDensity = hdpi
+ @android:screenSize = normal
+ <screen>
+ @android:screenDensity = xhdpi
+ @android:screenSize = normal
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/56_support_gltext_warning.xml b/manifmerger/tests/src/com/android/manifmerger/data/56_support_gltext_warning.xml
new file mode 100755
index 0000000..9cc5089
--- /dev/null
+++ b/manifmerger/tests/src/com/android/manifmerger/data/56_support_gltext_warning.xml
@@ -0,0 +1,148 @@
+#
+# Test supports-gl-texture:
+# - it's OK if a library defines one or multiple times an element already in the application.
+# - it's a warning if the library defines an element not in the application.
+# - this does not actually merge anything. The XML is not changed at all.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-configuration
+ android:reqFiveWayNav="true"
+ android:reqHardKeyboard="false"
+ android:reqKeyboardType="undefined"
+ android:reqNavigation="nonav"
+ android:reqTouchScreen="stylus"
+ />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <supports-gl-texture android:name="some.gl.texture1" />
+ <supports-gl-texture android:name="some.gl.texture2" />
+
+ <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.MainActivity"
+ 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>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@lib1
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- this is the same supports-gl-texture than in the main. -->
+ <supports-gl-texture android:name="some.gl.texture1" />
+
+</manifest>
+
+
+@lib2
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- this is the not same supports-gl-texture than in the main. -->
+ <supports-gl-texture android:name="some.gl.texture3" />
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="11"/>
+
+ <supports-screens
+ android:largeScreens="true"
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:resizeable="true"
+ android:xlargeScreens="true"
+ />
+
+ <uses-configuration
+ android:reqFiveWayNav="true"
+ android:reqHardKeyboard="false"
+ android:reqKeyboardType="undefined"
+ android:reqNavigation="nonav"
+ android:reqTouchScreen="stylus"
+ />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <supports-gl-texture android:name="some.gl.texture1" />
+ <supports-gl-texture android:name="some.gl.texture2" />
+
+ <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.MainActivity"
+ 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>
+ </activity>
+
+ </application>
+
+</manifest>
+
+
+@errors
+
+W [ManifestMergerTest2_lib2.xml:4] /manifest/supports-gl-texture missing from ManifestMergerTest0_main.xml:
+<supports-gl-texture>
+ @android:name = some.gl.texture3