diff options
author | Raphael <raphael@google.com> | 2012-01-11 12:01:57 -0800 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2012-01-11 12:01:57 -0800 |
commit | 2f370f84db49e23c4aa35a5949d7cefcb6c20ce4 (patch) | |
tree | f007e69927e1baebd7aa5eb5980086eb7edbebe8 /manifmerger | |
parent | 2dcaaaaafc501496d47948b8318060bbd17b60bb (diff) | |
parent | 2ccd27ce4e32da8832d2810f152b1752074bfd41 (diff) | |
download | sdk-2f370f84db49e23c4aa35a5949d7cefcb6c20ce4.zip sdk-2f370f84db49e23c4aa35a5949d7cefcb6c20ce4.tar.gz sdk-2f370f84db49e23c4aa35a5949d7cefcb6c20ce4.tar.bz2 |
Merge "Manifest Merger tool."
Diffstat (limited to 'manifmerger')
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<lib. Never automatically change dest minsdk. + * {@code @targetSdkVersion}: warning if dest<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<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<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<lib. Never automatically change dest minsdk. + * - {@code @targetSdkVersion}: warning if dest<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 < 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 |