summaryrefslogtreecommitdiffstats
path: root/tools/aapt2
diff options
context:
space:
mode:
authorAdam Lesinski <adamlesinski@google.com>2015-05-20 15:24:01 -0700
committerAdam Lesinski <adamlesinski@google.com>2015-06-04 18:00:33 -0700
commit8c831ca87bb7c8699b2a5cb34b8d35deedf9ce4e (patch)
tree454f5342f3634e3cb2ea858fcf6226ec3b133911 /tools/aapt2
parent581cc1ee59d01fe4b4a31618ab4aedfa639e42b0 (diff)
downloadframeworks_base-8c831ca87bb7c8699b2a5cb34b8d35deedf9ce4e.zip
frameworks_base-8c831ca87bb7c8699b2a5cb34b8d35deedf9ce4e.tar.gz
frameworks_base-8c831ca87bb7c8699b2a5cb34b8d35deedf9ce4e.tar.bz2
AAPT2: Add manifest merging
Now that AAPT2 is library-aware, it needs to take care of all library related work, including merging manifests. The logic was taken from the current Java ManifestMerger. Change-Id: Id93f713f27ae8617922bf89e325e45be9f863c06
Diffstat (limited to 'tools/aapt2')
-rw-r--r--tools/aapt2/Android.mk4
-rw-r--r--tools/aapt2/Main.cpp64
-rw-r--r--tools/aapt2/ManifestMerger.cpp376
-rw-r--r--tools/aapt2/ManifestMerger.h45
-rw-r--r--tools/aapt2/ManifestMerger_test.cpp121
-rw-r--r--tools/aapt2/data/lib/AndroidManifest.xml5
6 files changed, 599 insertions, 16 deletions
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 8c128d3..d311cd9 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -36,6 +36,7 @@ sources := \
Linker.cpp \
Locale.cpp \
Logger.cpp \
+ ManifestMerger.cpp \
ManifestParser.cpp \
ManifestValidator.cpp \
Png.cpp \
@@ -65,6 +66,7 @@ testSources := \
JavaClassGenerator_test.cpp \
Linker_test.cpp \
Locale_test.cpp \
+ ManifestMerger_test.cpp \
ManifestParser_test.cpp \
Maybe_test.cpp \
NameMangler_test.cpp \
@@ -102,7 +104,7 @@ else
endif
cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG
-cppFlags := -std=c++11 -Wno-missing-field-initializers
+cppFlags := -std=c++11 -Wno-missing-field-initializers -Wno-unused-private-field
# ==========================================================
# Build the host static library: libaapt2
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 84957b4..de2dafc 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -23,6 +23,7 @@
#include "Flag.h"
#include "JavaClassGenerator.h"
#include "Linker.h"
+#include "ManifestMerger.h"
#include "ManifestParser.h"
#include "ManifestValidator.h"
#include "NameMangler.h"
@@ -56,6 +57,20 @@ constexpr const char* kAaptVersionStr = "2.0-alpha";
using namespace aapt;
/**
+ * Used with smart pointers to free malloc'ed memory.
+ */
+struct DeleteMalloc {
+ void operator()(void* ptr) {
+ free(ptr);
+ }
+};
+
+struct StaticLibraryData {
+ Source source;
+ std::unique_ptr<ZipFile> apk;
+};
+
+/**
* Collect files from 'root', filtering out any files that do not
* match the FileFilter 'filter'.
*/
@@ -493,6 +508,7 @@ bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outA
}
bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
+ const std::map<std::shared_ptr<ResourceTable>, StaticLibraryData>& libApks,
const android::ResTable& table, ZipFile* outApk) {
if (options.verbose) {
Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
@@ -510,9 +526,40 @@ bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver
return false;
}
+ ManifestMerger merger({});
+ if (!merger.setAppManifest(options.manifest, options.appInfo.package, std::move(root))) {
+ return false;
+ }
+
+ for (const auto& entry : libApks) {
+ ZipFile* libApk = entry.second.apk.get();
+ const std::u16string& libPackage = entry.first->getPackage();
+ const Source& libSource = entry.second.source;
+
+ ZipEntry* zipEntry = libApk->getEntryByName("AndroidManifest.xml");
+ if (!zipEntry) {
+ continue;
+ }
+
+ std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
+ libApk->uncompress(zipEntry));
+ assert(uncompressedData);
+
+ SourceLogger logger(libSource);
+ std::unique_ptr<xml::Node> libRoot = xml::inflate(uncompressedData.get(),
+ zipEntry->getUncompressedLen(), &logger);
+ if (!libRoot) {
+ return false;
+ }
+
+ if (!merger.mergeLibraryManifest(libSource, libPackage, std::move(libRoot))) {
+ return false;
+ }
+ }
+
BigBuffer outBuffer(1024);
- if (!xml::flattenAndLink(options.manifest, root.get(), options.appInfo.package, resolver, {},
- &outBuffer)) {
+ if (!xml::flattenAndLink(options.manifest, merger.getMergedXml(), options.appInfo.package,
+ resolver, {}, &outBuffer)) {
return false;
}
@@ -667,17 +714,6 @@ static void addApkFilesToLinkQueue(const std::u16string& package, const Source&
static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
ZipFile::kOpenReadWrite;
-struct DeleteMalloc {
- void operator()(void* ptr) {
- free(ptr);
- }
-};
-
-struct StaticLibraryData {
- Source source;
- std::unique_ptr<ZipFile> apk;
-};
-
bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable,
const std::shared_ptr<IResolver>& resolver) {
std::map<std::shared_ptr<ResourceTable>, StaticLibraryData> apkFiles;
@@ -770,7 +806,7 @@ bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outT
}
android::ResTable binTable;
- if (!compileManifest(options, resolver, binTable, &outApk)) {
+ if (!compileManifest(options, resolver, apkFiles, binTable, &outApk)) {
return false;
}
diff --git a/tools/aapt2/ManifestMerger.cpp b/tools/aapt2/ManifestMerger.cpp
new file mode 100644
index 0000000..71d3424
--- /dev/null
+++ b/tools/aapt2/ManifestMerger.cpp
@@ -0,0 +1,376 @@
+#include "ManifestMerger.h"
+#include "Maybe.h"
+#include "ResourceParser.h"
+#include "Source.h"
+#include "Util.h"
+#include "XmlPullParser.h"
+
+#include <iostream>
+#include <memory>
+#include <set>
+#include <string>
+
+namespace aapt {
+
+constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
+
+static xml::Element* findManifest(xml::Node* root) {
+ if (!root) {
+ return nullptr;
+ }
+
+ while (root->type == xml::NodeType::kNamespace) {
+ if (root->children.empty()) {
+ break;
+ }
+ root = root->children[0].get();
+ }
+
+ if (root && root->type == xml::NodeType::kElement) {
+ xml::Element* el = static_cast<xml::Element*>(root);
+ if (el->namespaceUri.empty() && el->name == u"manifest") {
+ return el;
+ }
+ }
+ return nullptr;
+}
+
+static xml::Element* findChildWithSameName(xml::Element* parent, xml::Element* src) {
+ xml::Attribute* attrKey = src->findAttribute(kSchemaAndroid, u"name");
+ if (!attrKey) {
+ return nullptr;
+ }
+ return parent->findChildWithAttribute(src->namespaceUri, src->name, attrKey);
+}
+
+static bool attrLess(const xml::Attribute& lhs, const xml::Attribute& rhs) {
+ return std::tie(lhs.namespaceUri, lhs.name, lhs.value)
+ < std::tie(rhs.namespaceUri, rhs.name, rhs.value);
+}
+
+static int compare(xml::Element* lhs, xml::Element* rhs) {
+ int diff = lhs->attributes.size() - rhs->attributes.size();
+ if (diff != 0) {
+ return diff;
+ }
+
+ std::set<xml::Attribute, decltype(&attrLess)> lhsAttrs(&attrLess);
+ lhsAttrs.insert(lhs->attributes.begin(), lhs->attributes.end());
+ for (auto& attr : rhs->attributes) {
+ if (lhsAttrs.erase(attr) == 0) {
+ // The rhs attribute is not in the left.
+ return -1;
+ }
+ }
+
+ if (!lhsAttrs.empty()) {
+ // The lhs has attributes not in the rhs.
+ return 1;
+ }
+ return 0;
+}
+
+ManifestMerger::ManifestMerger(const Options& options) :
+ mOptions(options), mAppLogger({}), mLogger({}) {
+}
+
+bool ManifestMerger::setAppManifest(const Source& source, const std::u16string& package,
+ std::unique_ptr<xml::Node> root) {
+
+ mAppLogger = SourceLogger{ source };
+ mRoot = std::move(root);
+ return true;
+}
+
+bool ManifestMerger::checkEqual(xml::Element* elA, xml::Element* elB) {
+ if (compare(elA, elB) != 0) {
+ mLogger.error(elB->lineNumber)
+ << "library tag '" << elB->name << "' conflicts with app tag."
+ << std::endl;
+ mAppLogger.note(elA->lineNumber)
+ << "app tag '" << elA->name << "' defined here."
+ << std::endl;
+ return false;
+ }
+
+ std::vector<xml::Element*> childrenA = elA->getChildElements();
+ std::vector<xml::Element*> childrenB = elB->getChildElements();
+
+ if (childrenA.size() != childrenB.size()) {
+ mLogger.error(elB->lineNumber)
+ << "library tag '" << elB->name << "' children conflict with app tag."
+ << std::endl;
+ mAppLogger.note(elA->lineNumber)
+ << "app tag '" << elA->name << "' defined here."
+ << std::endl;
+ return false;
+ }
+
+ auto cmp = [](xml::Element* lhs, xml::Element* rhs) -> bool {
+ return compare(lhs, rhs) < 0;
+ };
+
+ std::sort(childrenA.begin(), childrenA.end(), cmp);
+ std::sort(childrenB.begin(), childrenB.end(), cmp);
+
+ for (size_t i = 0; i < childrenA.size(); i++) {
+ if (!checkEqual(childrenA[i], childrenB[i])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool ManifestMerger::mergeNewOrEqual(xml::Element* parentA, xml::Element* elA, xml::Element* elB) {
+ if (!elA) {
+ parentA->addChild(elB->clone());
+ return true;
+ }
+ return checkEqual(elA, elB);
+}
+
+bool ManifestMerger::mergePreferRequired(xml::Element* parentA, xml::Element* elA,
+ xml::Element* elB) {
+ if (!elA) {
+ parentA->addChild(elB->clone());
+ return true;
+ }
+
+ xml::Attribute* reqA = elA->findAttribute(kSchemaAndroid, u"required");
+ xml::Attribute* reqB = elB->findAttribute(kSchemaAndroid, u"required");
+ bool requiredA = !reqA || (reqA->value != u"false" && reqA->value != u"FALSE");
+ bool requiredB = !reqB || (reqB->value != u"false" && reqB->value != u"FALSE");
+ if (!requiredA && requiredB) {
+ if (reqA) {
+ *reqA = xml::Attribute{ kSchemaAndroid, u"required", u"true" };
+ } else {
+ elA->attributes.push_back(xml::Attribute{ kSchemaAndroid, u"required", u"true" });
+ }
+ }
+ return true;
+}
+
+static int findIntegerValue(xml::Attribute* attr, int defaultValue) {
+ if (attr) {
+ std::unique_ptr<BinaryPrimitive> integer = ResourceParser::tryParseInt(attr->value);
+ if (integer) {
+ return integer->value.data;
+ }
+ }
+ return defaultValue;
+}
+
+bool ManifestMerger::mergeUsesSdk(xml::Element* elA, xml::Element* elB) {
+ bool error = false;
+ xml::Attribute* minAttrA = nullptr;
+ xml::Attribute* minAttrB = nullptr;
+ if (elA) {
+ minAttrA = elA->findAttribute(kSchemaAndroid, u"minSdkVersion");
+ }
+
+ if (elB) {
+ minAttrB = elB->findAttribute(kSchemaAndroid, u"minSdkVersion");
+ }
+
+ int minSdkA = findIntegerValue(minAttrA, 1);
+ int minSdkB = findIntegerValue(minAttrB, 1);
+
+ if (minSdkA < minSdkB) {
+ std::ostream* out;
+ if (minAttrA) {
+ out = &(mAppLogger.error(elA->lineNumber) << "app declares ");
+ } else if (elA) {
+ out = &(mAppLogger.error(elA->lineNumber) << "app has implied ");
+ } else {
+ out = &(mAppLogger.error() << "app has implied ");
+ }
+
+ *out << "minSdkVersion=" << minSdkA << " but library expects a higher SDK version."
+ << std::endl;
+
+ // elB is valid because minSdkB wouldn't be greater than minSdkA if it wasn't.
+ mLogger.note(elB->lineNumber)
+ << "library declares minSdkVersion=" << minSdkB << "."
+ << std::endl;
+ error = true;
+ }
+
+ xml::Attribute* targetAttrA = nullptr;
+ xml::Attribute* targetAttrB = nullptr;
+
+ if (elA) {
+ targetAttrA = elA->findAttribute(kSchemaAndroid, u"targetSdkVersion");
+ }
+
+ if (elB) {
+ targetAttrB = elB->findAttribute(kSchemaAndroid, u"targetSdkVersion");
+ }
+
+ int targetSdkA = findIntegerValue(targetAttrA, minSdkA);
+ int targetSdkB = findIntegerValue(targetAttrB, minSdkB);
+
+ if (targetSdkA < targetSdkB) {
+ std::ostream* out;
+ if (targetAttrA) {
+ out = &(mAppLogger.warn(elA->lineNumber) << "app declares ");
+ } else if (elA) {
+ out = &(mAppLogger.warn(elA->lineNumber) << "app has implied ");
+ } else {
+ out = &(mAppLogger.warn() << "app has implied ");
+ }
+
+ *out << "targetSdkVerion=" << targetSdkA << " but library expects target SDK "
+ << targetSdkB << "." << std::endl;
+
+ mLogger.note(elB->lineNumber)
+ << "library declares targetSdkVersion=" << targetSdkB << "."
+ << std::endl;
+ error = true;
+ }
+ return !error;
+}
+
+bool ManifestMerger::mergeApplication(xml::Element* applicationA, xml::Element* applicationB) {
+ if (!applicationA || !applicationB) {
+ return true;
+ }
+
+ bool error = false;
+
+ // First make sure that the names are identical.
+ xml::Attribute* nameA = applicationA->findAttribute(kSchemaAndroid, u"name");
+ xml::Attribute* nameB = applicationB->findAttribute(kSchemaAndroid, u"name");
+ if (nameB) {
+ if (!nameA) {
+ applicationA->attributes.push_back(*nameB);
+ } else if (nameA->value != nameB->value) {
+ mLogger.error(applicationB->lineNumber)
+ << "conflicting application name '"
+ << nameB->value
+ << "'." << std::endl;
+ mAppLogger.note(applicationA->lineNumber)
+ << "application defines application name '"
+ << nameA->value
+ << "'." << std::endl;
+ error = true;
+ }
+ }
+
+ // Now we descend into the activity/receiver/service/provider tags
+ for (xml::Element* elB : applicationB->getChildElements()) {
+ if (!elB->namespaceUri.empty()) {
+ continue;
+ }
+
+ if (elB->name == u"activity" || elB->name == u"activity-alias"
+ || elB->name == u"service" || elB->name == u"receiver"
+ || elB->name == u"provider" || elB->name == u"meta-data") {
+ xml::Element* elA = findChildWithSameName(applicationA, elB);
+ error |= !mergeNewOrEqual(applicationA, elA, elB);
+ } else if (elB->name == u"uses-library") {
+ xml::Element* elA = findChildWithSameName(applicationA, elB);
+ error |= !mergePreferRequired(applicationA, elA, elB);
+ }
+ }
+ return !error;
+}
+
+bool ManifestMerger::mergeLibraryManifest(const Source& source, const std::u16string& package,
+ std::unique_ptr<xml::Node> libRoot) {
+ mLogger = SourceLogger{ source };
+ xml::Element* manifestA = findManifest(mRoot.get());
+ xml::Element* manifestB = findManifest(libRoot.get());
+ if (!manifestA) {
+ mAppLogger.error() << "missing manifest tag." << std::endl;
+ return false;
+ }
+
+ if (!manifestB) {
+ mLogger.error() << "library missing manifest tag." << std::endl;
+ return false;
+ }
+
+ bool error = false;
+
+ // Do <application> first.
+ xml::Element* applicationA = manifestA->findChild({}, u"application");
+ xml::Element* applicationB = manifestB->findChild({}, u"application");
+ error |= !mergeApplication(applicationA, applicationB);
+
+ // Do <uses-sdk> next.
+ xml::Element* usesSdkA = manifestA->findChild({}, u"uses-sdk");
+ xml::Element* usesSdkB = manifestB->findChild({}, u"uses-sdk");
+ error |= !mergeUsesSdk(usesSdkA, usesSdkB);
+
+ for (xml::Element* elB : manifestB->getChildElements()) {
+ if (!elB->namespaceUri.empty()) {
+ continue;
+ }
+
+ if (elB->name == u"uses-permission" || elB->name == u"permission"
+ || elB->name == u"permission-group" || elB->name == u"permission-tree") {
+ xml::Element* elA = findChildWithSameName(manifestA, elB);
+ error |= !mergeNewOrEqual(manifestA, elA, elB);
+ } else if (elB->name == u"uses-feature") {
+ xml::Element* elA = findChildWithSameName(manifestA, elB);
+ error |= !mergePreferRequired(manifestA, elA, elB);
+ } else if (elB->name == u"uses-configuration" || elB->name == u"supports-screen"
+ || elB->name == u"compatible-screens" || elB->name == u"supports-gl-texture") {
+ xml::Element* elA = findChildWithSameName(manifestA, elB);
+ error |= !checkEqual(elA, elB);
+ }
+ }
+ return !error;
+}
+
+static void printMerged(xml::Node* node, int depth) {
+ std::string indent;
+ for (int i = 0; i < depth; i++) {
+ indent += " ";
+ }
+
+ switch (node->type) {
+ case xml::NodeType::kNamespace:
+ std::cerr << indent << "N: "
+ << "xmlns:" << static_cast<xml::Namespace*>(node)->namespacePrefix
+ << "=\"" << static_cast<xml::Namespace*>(node)->namespaceUri
+ << "\"\n";
+ break;
+
+ case xml::NodeType::kElement:
+ std::cerr << indent << "E: "
+ << static_cast<xml::Element*>(node)->namespaceUri
+ << ":" << static_cast<xml::Element*>(node)->name
+ << "\n";
+ for (const auto& attr : static_cast<xml::Element*>(node)->attributes) {
+ std::cerr << indent << " A: "
+ << attr.namespaceUri
+ << ":" << attr.name
+ << "=\"" << attr.value << "\"\n";
+ }
+ break;
+
+ case xml::NodeType::kText:
+ std::cerr << indent << "T: \"" << static_cast<xml::Text*>(node)->text << "\"\n";
+ break;
+ }
+
+ for (auto& child : node->children) {
+ printMerged(child.get(), depth + 1);
+ }
+}
+
+xml::Node* ManifestMerger::getMergedXml() {
+ return mRoot.get();
+}
+
+bool ManifestMerger::printMerged() {
+ if (!mRoot) {
+ return false;
+ }
+
+ ::aapt::printMerged(mRoot.get(), 0);
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ManifestMerger.h b/tools/aapt2/ManifestMerger.h
new file mode 100644
index 0000000..c6219db
--- /dev/null
+++ b/tools/aapt2/ManifestMerger.h
@@ -0,0 +1,45 @@
+#ifndef AAPT_MANIFEST_MERGER_H
+#define AAPT_MANIFEST_MERGER_H
+
+#include "Logger.h"
+#include "Source.h"
+#include "XmlDom.h"
+
+#include <memory>
+#include <string>
+
+namespace aapt {
+
+class ManifestMerger {
+public:
+ struct Options {
+ };
+
+ ManifestMerger(const Options& options);
+
+ bool setAppManifest(const Source& source, const std::u16string& package,
+ std::unique_ptr<xml::Node> root);
+
+ bool mergeLibraryManifest(const Source& source, const std::u16string& package,
+ std::unique_ptr<xml::Node> libRoot);
+
+ xml::Node* getMergedXml();
+
+ bool printMerged();
+
+private:
+ bool mergeNewOrEqual(xml::Element* parentA, xml::Element* elA, xml::Element* elB);
+ bool mergePreferRequired(xml::Element* parentA, xml::Element* elA, xml::Element* elB);
+ bool checkEqual(xml::Element* elA, xml::Element* elB);
+ bool mergeApplication(xml::Element* applicationA, xml::Element* applicationB);
+ bool mergeUsesSdk(xml::Element* elA, xml::Element* elB);
+
+ Options mOptions;
+ std::unique_ptr<xml::Node> mRoot;
+ SourceLogger mAppLogger;
+ SourceLogger mLogger;
+};
+
+} // namespace aapt
+
+#endif // AAPT_MANIFEST_MERGER_H
diff --git a/tools/aapt2/ManifestMerger_test.cpp b/tools/aapt2/ManifestMerger_test.cpp
new file mode 100644
index 0000000..6838253
--- /dev/null
+++ b/tools/aapt2/ManifestMerger_test.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#include "ManifestMerger.h"
+#include "SourceXmlPullParser.h"
+
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+
+namespace aapt {
+
+constexpr const char* kAppManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="21" />
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-feature android:name="android.hardware.GPS" android:required="false" />
+ <application android:name="com.android.library.Application">
+ <activity android:name="com.android.example.MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ <service android:name="com.android.library.Service">
+ <intent-filter>
+ <action android:name="com.android.library.intent.action.SYNC" />
+ </intent-filter>
+ </service>
+ </application>
+</manifest>
+)EOF";
+
+constexpr const char* kLibManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="21" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-feature android:name="android.hardware.GPS" />
+ <uses-permission android:name="android.permission.GPS" />
+ <application android:name="com.android.library.Application">
+ <service android:name="com.android.library.Service">
+ <intent-filter>
+ <action android:name="com.android.library.intent.action.SYNC" />
+ </intent-filter>
+ </service>
+ <provider android:name="com.android.library.DocumentProvider"
+ android:authorities="com.android.library.documents"
+ android:grantUriPermission="true"
+ android:exported="true"
+ android:permission="android.permission.MANAGE_DOCUMENTS"
+ android:enabled="@bool/atLeastKitKat">
+ <intent-filter>
+ <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
+ </intent-filter>
+ </provider>
+ </application>
+</manifest>
+)EOF";
+
+constexpr const char* kBadLibManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <uses-sdk android:minSdkVersion="17" android:targetSdkVersion="22" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-feature android:name="android.hardware.GPS" />
+ <uses-permission android:name="android.permission.GPS" />
+ <application android:name="com.android.library.Application2">
+ <service android:name="com.android.library.Service">
+ <intent-filter>
+ <action android:name="com.android.library.intent.action.SYNC_ACTION" />
+ </intent-filter>
+ </service>
+ </application>
+</manifest>
+)EOF";
+
+TEST(ManifestMergerTest, MergeManifestsSuccess) {
+ std::stringstream inA(kAppManifest);
+ std::stringstream inB(kLibManifest);
+
+ const Source sourceA = { "AndroidManifest.xml" };
+ const Source sourceB = { "lib.apk/AndroidManifest.xml" };
+ SourceLogger loggerA(sourceA);
+ SourceLogger loggerB(sourceB);
+
+ ManifestMerger merger({});
+ EXPECT_TRUE(merger.setAppManifest(sourceA, u"com.android.example",
+ xml::inflate(&inA, &loggerA)));
+ EXPECT_TRUE(merger.mergeLibraryManifest(sourceB, u"com.android.library",
+ xml::inflate(&inB, &loggerB)));
+}
+
+TEST(ManifestMergerTest, MergeManifestFail) {
+ std::stringstream inA(kAppManifest);
+ std::stringstream inB(kBadLibManifest);
+
+ const Source sourceA = { "AndroidManifest.xml" };
+ const Source sourceB = { "lib.apk/AndroidManifest.xml" };
+ SourceLogger loggerA(sourceA);
+ SourceLogger loggerB(sourceB);
+
+ ManifestMerger merger({});
+ EXPECT_TRUE(merger.setAppManifest(sourceA, u"com.android.example",
+ xml::inflate(&inA, &loggerA)));
+ EXPECT_FALSE(merger.mergeLibraryManifest(sourceB, u"com.android.library",
+ xml::inflate(&inB, &loggerB)));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/data/lib/AndroidManifest.xml b/tools/aapt2/data/lib/AndroidManifest.xml
index c1612e5..08b468e 100644
--- a/tools/aapt2/data/lib/AndroidManifest.xml
+++ b/tools/aapt2/data/lib/AndroidManifest.xml
@@ -1,3 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.appcompat"/>
+ package="android.appcompat">
+
+ <uses-feature android:name="bloooop" />
+</manifest>