|
ABI |
Density |
API |
Prod/Internal |
x86 |
mdpi |
9 |
prod |
arm |
high |
14 |
internal |
mips |
xhigh |
15 |
|
xxhigh |
This leads to 3x4x3x2 possible combinations. However, for each assembly run, there can only be one flavor from each group, in the order the flavor group were declared in the original build.gradle leading to an ordered list of higher priority to lower priority potential manifest files to merge.
For instance, building the x86-high-15-prod variant will lookup the following manifest files to merge
-
x86/AndroidManifest.xml
-
high/AndroidManifest.xml
-
15/AndroidManifest.xml
-
internal/AndroidManifest.xml
Each file in this ordered list has a priority according to its rank in the list, and merging will use this priority to decide which XML element or attribute will overwrite a lower priority setting.
Therefore, the input of the merging tool will be as follows :
-
ordered list by priority of flavor/build type manifest files, these will be commonly referenced as the flavors manifests.
-
main manifest file
-
ordered list by declaration (or transitive dependency) of libraries manifest files.
-
Injectable values for placeholders and XML generation
Android Manifest file merging
Each element in a manifest file can be identified by its element type (e.g. activity, intent-filter) and an optional key value. Some elements like “activity” must have a key since there can be multiple present in a single AndroidManifest.xml. Other elements like “application” do not require a key since there can be only one.
The element type and key value pair represent the identity of a manifest element.
The merging activity is always between two identically typed elements, one coming from a higher priority manifest file, one coming from a lower priority manifest file. Each merging activity has a default behavior that will be described subsequently. Also each default merging activity on the node or on a specific attribute can potentially be overridden by including tools specific markers.
The merging process will also record all decisions taken along the way for each node, described in the "Logging" section.
Elements and Attributes merging process
Implicit declarations
A number of attributes can have default values (defined in the online docs as defaults). When a higher priority element does not define an attribute which has a default value of X, if a lower priority element defines that very attribute with the same value X, it will still be added to the merged element (with value X of course) since it represents a clear choice from the library to not consider this value as the default value but the right value for the feature (in case the default ever changes).
Most attributes with default values will be ignored by the manifest merger if a lower priority attribute defines a value; it will be merged since there is no conflict between a default value and an attribute set to any value. The attribute will be set to that set value in the resulting merged element.
However, there are a few exceptions listed below:
<uses-feature android:required> |
Defaults to true. Merging with other attributes will use a OR merging policy since if any library requires the feature, the resulting application will require the feature. |
<uses-library android:required> |
Same as uses-feature:required. |
<uses-sdk android:minSdkVersion> |
Defaults to 1. The higher priority document's version will be used but importing a library with a more recent version will generate an error. |
<uses-sdk android:maxSdkVersion> |
Same as uses-sdk:minSdkVersion |
<uses-sdk android:targetSdkVersion> |
Same as uses-sdk:minSdkVersion |
Automatic upgrades
When importing a library with a lower target SDK than the project’s, it may be necessary to explicitly grant permissions (and perhaps make other changes) for the library to function properly in the later runtime. This will be performed automatically by the manifest merger.
Placeholder support
When an attribute value contains a placeholder (see format below), the manifest merger will swap this placeholder value with an injected value. Injected values are specified in the build.gradle.
The syntax for placeholder values is ${name} since @ is reserved for links. After the last file merging occurred, and before the resulting merged android manifest file is written out, all values with a placeholder will be swapped with injected values. A build breakage will be generated if a variable name is unknown.
The placeholder string can have a prefix and/or a suffix allowing to partially replace the value.
Examples:
android:authority="${applicationId}.foo"
android:authority=”com.acme.${localApplicationId}”
android:authority=”com.acme.${localApplicationId}.foo”
An implicit placeholder ${applicationId} resolution will be automatically provided out of the box with the build.gradle applicationId value.
Examples :
<activity
android:name=".Main">
<intent-filter>
<action android:name="${applicationId}.foo">
</action>
</intent-filter>
</activity>
with the following gradle declaration :
android {
compileSdkVersion 19
buildToolsVersion "19.0.2"
productFlavors {
flavor1 {
applicationId = "com.android.tests.flavorlib.app.flavor1"
}
}
Once merged, the <action android:name> will be
<action android:name=“com.android.tests.flavorlib.app.flavor1.foo”>
For custom placeholders replacements, use the following DSL to configure the placeholders values :
android { defaultConfig { manifestPlaceholders = [ activityLabel:"defaultName"] } productFlavors { free { } pro { manifestPlaceholders = [ activityLabel:"proName" ] } }
Generic description of merging strategies
Merging XML can happen either at the node level or at the attribute level.
At the node level, the default merging policy is to merge attributes and sub-elements as long as there is no conflict. A conflict arises when two elements with the same identity have the same attribute with a different value.
For instance:
<activity
android:name=”com.foo.bar.ActivityOne”
android:theme=”@theme1”/>
merging with the following declaration will not generate a conflict:
<activity
android:name=”com.foo.bar.ActivityOne”
android:screenOrientation=”landscape/>
same for
<activity
android:name=”com.foo.bar.ActivityOne”
android:theme=”@theme1”/>
merging with the following declaration will not generate a conflict, since the “theme” attribute defined in both elements have the same value.
<activity
android:name=”com.foo.bar.ActivityOne”
android:theme=”@theme1”
android:screenOrientation=”landscape/>
however,
<activity
android:name=”com.foo.bar.ActivityOne”
android:theme=”@theme1”/>
merging with the following declaration will generate a conflict, since the “theme” attribute defined in both elements does not have the same value.
<activity
android:name=”com.foo.bar.ActivityOne”
android:theme=”@theme2”
android:screenOrientation=”landscape/>
Now, each element can have sub-elements and rules for matching those will follow the same general principle, sub-elements with the same identity will be matched together. If a sub-element exist in only one of the two parents, this is not a conflict.
Markers
A marker is a special attribute, in the tools namespace, used to express a specific decision for how to resolve conflicts.
All markers belong to the Android tools namespace, therefore you must declare the namespace in any AndroidManifest.xml containing at least one marker :
xmlns:tools="http://schemas.android.com/tools"
Example:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.tests.flavorlib.app"
xmlns:tools="http://schemas.android.com/tools">
<application
android:icon="@drawable/icon"
android:label="@string/app_name"
tools:replace=”icon, label”/>
</manifest>
When conflicts arise between elements to be merged, some markers must be explicitly added to guide the Manifest Merger. At the node level, the tools:node attribute should be used, at the attribute level, the tools:attr attribute should be used.
tools:node markers
A tools:node=”maker_value” attribute should be present on the node which is in conflict and need resolution.
<tools:node> attribute value |
manifest merger action |
<tools:node=”merge”> |
This is the implicit default mode for node merging, nodes will be merged as long as they do not conflict |
<tools:node=”replace”> |
Replace the lower priority declaration with the annotated one. |
<tools:node=”strict”> |
This will generate a build failure when another node with the same identity is present and is not strictly equal. |
<tools:node=”merge-only-attributes”> |
Only merge attributes from the lower priority declaration. |
<tools:node=”remove”> |
Remove the annotated element from the resulting XML. No lower priority declaration will be merged in irrespective of potential conflicts. |
<tools:node=”removeAll”> |
Remove all elements of the same node type (no key necessary). |
tools:attr markers
There can be be many attribute related markers on any particular element to resolve all attributes in conflict.
<tools:strict=”x, y, z”> |
Default implicit mode for attribute, generate an error when trying the merge lower priority attribute declaration with a different value. |
<tools:remove=”x, y, z”> |
Remove the x, y, z attributes from any lower priority declaration when merging in. |
<tools:replace=”x, y, z”> |
Replace the x, y, z attributes from any lower priority declaration with the provided value (must be present on the same node). |
Selector
Each tools:node or tools:attr declaration can be augmented by a tools:selector attribute which is contextual information on whether or not the merging strategy should be applied to the current lower priority XML description. For instance, this is useful when removing a permission only if coming for one particular library as opposed to any library:
<permission
android:name="permissionOne"
tools:node="remove"
tools:selector="com.example.lib1">
tools:overrideLibrary marker
tools:overrideLibrary="com.example.lib1, com.example.lib2"/>
package="com.example.lib1"> <uses-sdk android:minSdkVersion="4" /> </manifest>
Logging
Each operation/decision made during the manifest merger needs to be
-
recorded
-
formatted for machine capable parsing
-
ordered by node (since several decisions might have contributed to a node’s attributes)
The logging will not be organized as a linear set of events and decisions that ultimately yielded the output file. Instead, to make things easier for developers, the log file will be organized by top-level XML nodes encountered in the input files (whether or not it is present in the output file as we want to document node deletion as well).
Logging record:
A logging record is either a node record (describing all actions taken on that particular node) or a message record containing error messages and warnings.
logging-file = (logging-record)*
logging-record = node-record | message
Message
message=file:line_number:colum_number severity:\ndescription
description=(\tTEXT\n)*
Name |
Value |
file |
Input file generating the log entry |
line-number |
Input file line number generating the log entry |
column-number |
Input file column number generating the log entry. |
severity |
Error, Warning, Info |
description |
Log entry payload |
Examples
/Users/jedo/src/app/src/main/AndroidManifest.xml:3:9 Error:
Attribute activity@screenOrientation value=(portrait) from AndroidManifest.xml:3:9
is also present at flavorlib:lib1:unspecified:3:18 value=(landscape)
Suggestion: add 'tools:replace="icon"' to <activity> element at AndroidManifest.xml:1:5 to override
NodeRecord
Name |
Value |
node-type |
XML node type |
node-key |
node key attribute value |
record-type |
[ Action | Log ] * |
node-type#node-key\n
\t(node_action:Action)*
\t\t(attribute_action:Action)*
Action format
Name |
Value |
action-type |
added | rejected | implied |
target |
node | attribute |
target-name |
node key name or attribute name |
origin |
origin value location. |
Examples:
application
ADDED from AndroidManifest.xml:10:5
MERGED from flavorlib:lib2:unspecified:3:5
android:label
ADDED from AndroidManifest.xml:12:9
REJECTED from flavorlib:lib2:unspecified:3:55
android:icon
ADDED from AndroidManifest.xml:11:9
REJECTED from flavorlib:lib2:unspecified:3:18
receiver#com.example.WidgetReceiver
ADDED from ManifestMerger2Test0_main.xml:60:9
android:label ADDED from ManifestMerger2Test0_main.xml:61:13
android:icon ADDED from ManifestMerger2Test0_main.xml:62:13
android:name ADDED from ManifestMerger2Test0_main.xml:63:13
Build error
When a build error happens, the log for the particular node failing should be displayed followed by a descriptive error message for the user. For instance :
Higher priority declaration
<activity
android:name=”com.foo.bar.ActivityOne”
android:screenOrientation=”portrait”
android:theme=”@theme1”/>
with a lower priority declaration :
<activity
android:name=”com.foo.bar.ActivityOne”
android:screenOrientation=”landscape/>
will result in both the log file and the output (exact format for human and machine and IDE readable to be determined).
/Users/jedo/src/app/src/main/AndroidManifest.xml:3:9 Error:
Attribute activity@screenOrientation value=(portrait) from AndroidManifest.xml:3:9
is also present at flavorlib:lib1:unspecified:3:18 value=(landscape)
Suggestion: add 'tools:replace="icon"' to <activity> element at AndroidManifest.xml:1:5 to override
Blame
A “blame” type of output can be obtained to qualify each element and attribute resulting in the merged XML with some indication of where the element/attribute originated from.
Merging Policies
Each element type has a specific default merging policy attached to it. For example most elements types like activity or application have a default merging policy where all attributes and children are merged (assuming no conflict) into the resulting element. However other elements like the top level manifest will have a default merging policy of merging children only which mean that no attributes of the manifest element of a lower priority AndroidManifest.xml is eligible for merging.
Each element can also have a key associated or not. For instance, application have no key, there can only be one <application> elements per AndroidManifest.xml. Most keyed elements use the android:name attribute to express their key value, others
Merge
Non conflicting attributes are merged and children are merged according to their respective merging policy.
Merge Children Only
Attributes are not merged, only children are merged according to their respective merging policy.
Always
Elements are always kept “as is” and added to the common parent in the resulting merged file.
List of element merging policy and key
Node Type |
Merging Policy |
Key |
action |
merge |
android:name attribute |
activity |
merge |
android:name attribute |
application |
merge |
no key |
category |
merge |
android:name attribute |
data |
merge |
no key |
grant-uri-permission |
merge |
no key |
instrumentation |
merge |
android:name attribute |
intent-filter |
always |
sub-element actions and categories android:name attribute. Several declaration with the same key are allowed. |
manifest |
merge children only |
no key |
meta-data |
merge |
android:name attribute |
path-permission |
merge |
no key |
permission-group |
merge |
android:name attribute |
permission |
merge |
android:name attribute |
permission-tree |
merge |
android:name attribute |
provider |
merge |
android:name attribute |
receiver |
merge |
android:name attribute |
screen |
merge |
attribute screenSize |
service |
merge |
android:name attribute |
supports-gl-texture |
merge |
android:name attribute |
supports-screen |
merge |
no key |
uses-configuration |
merge |
no key |
uses-feature |
merge |
attribute name first and if not present attribute glEsVersion |
uses-library |
merge |
android:name attribute |
uses-permission |
merge |
android:name attribute |
uses-sdk |
merge |
no key |
custom elements |
merge |
no key |
Package name smart substitution
Some attributes are package dependent attributes which means the attribute supports smart substitution of partially fully qualified class names with package settings as provided by the manifest node's package attribute.
Each of the attribute described below can have a partial class name that is either starting with a dot or do not contain any dots.
Examples :
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.app1">
<application>
<activity android:name=".Main" />
</application>
</manifest>
will be expanded into :
<application>
<activity android:name="com.example.app1.Main" />
</application>
This is independent of any package setting in the build.gradle for if for example, you build.gradle contains :
android {
compileSdkVersion 19
buildToolsVersion "19.0.2"
productFlavors {
flavor1 {
applicationId = "com.android.tests.flavorlib.app.flavor1"
}
}
the resulting expansion will still be
<activity android:name=”com.example.app1.Main”>
If you need the injected value as the expanded attribute value use ${applicationId} placeholder, for example :
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.app1">
<application>
<activity android:name="${applicationId}.Main" />
</application>
</manifest>
List of package dependent attributes that can use this smart substitution facility :
Node Type |
Attribute local names |
activity |
name, parentActivityName |
activity-alias |
name, targetActivity |
application |
name, backupAgent |
instrumentation |
name |
provider |
name |
receiver |
name |
service |
name |
Implicit permissions addition
When importing a library with a targetSdkVersion lower than the importing application targetSdkVersion, some permissions maybe automatically added to the resulting merged manifest file. This is necessary since such libraries targeted runtimes where such permissions were implicitly granted. Therefore declaring such permission was not necessary. However in more recent Android releases, these permissions are not automatically granted. Therefore, a library targeting such old runtime without the permissions would not work properly once merged in the application targeting a more recent runtime. For instance, an application with a targetSdkVersion of 16 importing a library with a targetSdkVersion of 2 will get the WRITE_EXTERNAL_STORAGE permission added. See the table below for a complete list.
permission | reason |
WRITE_EXTERNAL_STORAGE | Added when importing a library with a targetSdkVersion < 4 |
READ_EXTERNAL_STORAGE | Added when importing a library that declared WRITE_EXTERNAL_STORAGE |
READ_PHONE_STATE | Added when importing a library with a targetSdkVersion < 4 |
READ_CALL_LOG | Added when importing a library with a targetSdkVersion < 16 and using READ_CONTACTS permission |
WRITE_CALL_LOG | Added when importing a library with a targetSdkVersion < 16 and using WRITE_CONTACTS permission |
Attributes markers examples
Override an attribute coming from a library
Using tools:replace=”x, y, z” will override x,y,z attributes from the imported library’s activity XML declarations.
Higher Priority declaration
<activity
android:name=”com.foo.bar.ActivityOne”
android:screenOrientation=”portrait”
android:theme=”@theme1”
tools:replace=”theme”/>
with a lower priority declaration :
<activity
android:name=”com.foo.bar.ActivityOne”
android:theme=”@olddogtheme”
android:windowSoftInputMode=”stateUnchanged”
android:exported=”true”>
will result in :
<activity
android:name=”com.foo.bar.ActivityOne”
android:screenOrientation=”portrait”
android:theme=”@theme1”
android:windowSoftInputMode=”stateUnchanged”
android:exported=”true”/>
Remove an attribute coming from the library.
Using tools:remove=”x, y, z” will remove the x, y, z attributes declaration from the resulting XML.
Higher priority declaration
<activity
android:name=”com.foo.bar.ActivityOne”
android:hardwareAccelerated=”true”
tools:remove=”android:theme,android:screenOrientation”/>
with a lower priority declaration :
<activity
android:name=”com.foo.bar.ActivityOne”
android:screenOrientation=”landscape”
android:theme=”@olddogtheme”
android:windowSoftInputMode=”stateUnchanged”
android:exported=”true”/>
will result in :
<activity
android:name=”com.foo.bar.ActivityOne”
android:hardwareAccelerated=”true”
android:windowSoftInputMode=”stateUnchanged”
android:exported=”true”/>
Enforcing an attribute value
Implicitly, all declared attributes are virtually annotated with a “strict” merging policy so if two elements eligible for merging each have the same attribute with a different value, this is a conflict that need to be explicitly addressed.
so, a higher priority declaration
<activity
android:name=”com.foo.bar.ActivityOne”
android:theme=”@newdogtheme”/>
with a lower priority declaration :
<activity
android:name=”com.foo.bar.ActivityOne”
android:theme=”@olddogtheme”/>
and a higher priority declaration
<activity
android:name=”com.foo.bar.ActivityOne”
android:theme=”@newdogtheme”
tools:strict=”theme”/>
with a lower priority declaration :
<activity
android:name=”com.foo.bar.ActivityOne”
android:theme=”@olddogtheme”/>
are strictly equivalent and will fail to merge correctly until a tools:replace=”theme” is added.
Mixed operations
If the user wants to remove some attributes and override others while conserving another set of original attributes, just add all markers side by side.
for example :
<activity
android:name=”com.foo.bar.ActivityOne”
android:windowSoftInputMode=”stateUnchanged”
android:theme=”@theme1”
tools:remove=”android:exported, android:screenOrientation”
tools:replace=”android:theme”/>
with a lower priority declaration :
<activity
android:name=”com.foo.bar.ActivityOne”
android:screenOrientation=”landscape”
android:theme=”@olddogtheme”
android:exported=”true”/>
will result in :
<activity
android:name=”com.foo.bar.ActivityOne”
android:theme=”@theme1”
android:windowSoftInputMode=”stateUnchanged”/>
Note that if the lower priority declaration contained android:windowSoftInputMode or any attribute that is not explicitly tagged for removal or replacement, a build error need to be generated.
Element markers examples
Element Removal
To remove an element from any library, declare in the higher priority document
<activity-alias
android:name=”foo.bar.alias”>
<meta-data
android:name=”zoo”
tools:node=”remove”/>
</activity-alias>
merged with :
<activity-alias
android:name=”foo.bar.alias”>
<meta-data
android:name=”zoo”
android:value=”@string/bear”/>
</activity-alias>
will yield :
<activity-alias
android:name=”foo.bar.alias”>
</activity-alias>
All Elements removal
To remove all elements of a certain type from any library, declare in the higher priority document
<activity-alias
android:name=”foo.bar.alias”>
<meta-data
tools:node=”removeAll”/>
</activity-alias>
merged with :
<activity-alias
android:name=”foo.bar.alias”>
<meta-data
android:name=”zoo”
android:value=”@string/bear”/>
<meta-data
android:name=”cage”
android:value=”@string/iron”/>
</activity-alias>
will yield :
<activity-alias
android:name=”foo.bar.alias”
</activity-alias>
Element substitution
<activity-alias
android:name=”foo.bar.alias”
tools:node=”replace”>
<meta-data
android:name=”zoo”/>
</activity-alias>
merged with:
<activity-alias
android:name=”foo.bar.alias”>
<meta-data
android:name=”cage”
android:value=”@string/iron”/>
</activity-alias>
will yield:
<activity-alias
android:name=”foo.bar.alias”>
<meta-data
android:name=”zoo”
tools:node=”remove”/>
</activity-alias>
Selector examples
Using the package name to select the library. Here we have 3 libraries merging into a main manifest file.
Main Manifest
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.main">
<permission
android:name="permissionOne"
tools:node="remove"
tools:selector="com.example.lib1">
</permission>
<permission
tools:node="removeAll"
tools:selector="com.example.lib3">
</permission>
<permission
android:name="permissionThree"
android:protectionLevel="signature"
tools:node="replace">
</permission>
</manifest>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.lib1">
<permission android:name="permissionOne"
android:protectionLevel="signature">
</permission>
<permission android:name="permissionTwo"
android:protectionLevel="signature">
</permission>
</manifest>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.lib2">
<permission android:name="permissionThree"
android:protectionLevel="normal">
</permission>
<permission android:name="permissionFour"
android:protectionLevel="normal">
</permission>
</manifest>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.lib2">
<permission android:name="permissionFive"
android:protectionLevel="normal">
</permission>
</manifest>
will yield:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.main" >
<permission
android:name="permissionThree"
android:protectionLevel="signature" >
</permission>
<permission
android:name="permissionTwo"
android:protectionLevel="signature" >
</permission>
<permission
android:name="permissionFour"
android:protectionLevel="normal" >
</permission>
</manifest>