diff options
author | Adam Lesinski <adamlesinski@google.com> | 2015-05-20 15:24:01 -0700 |
---|---|---|
committer | Adam Lesinski <adamlesinski@google.com> | 2015-06-04 18:00:33 -0700 |
commit | 8c831ca87bb7c8699b2a5cb34b8d35deedf9ce4e (patch) | |
tree | 454f5342f3634e3cb2ea858fcf6226ec3b133911 /tools | |
parent | 581cc1ee59d01fe4b4a31618ab4aedfa639e42b0 (diff) | |
download | frameworks_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')
-rw-r--r-- | tools/aapt2/Android.mk | 4 | ||||
-rw-r--r-- | tools/aapt2/Main.cpp | 64 | ||||
-rw-r--r-- | tools/aapt2/ManifestMerger.cpp | 376 | ||||
-rw-r--r-- | tools/aapt2/ManifestMerger.h | 45 | ||||
-rw-r--r-- | tools/aapt2/ManifestMerger_test.cpp | 121 | ||||
-rw-r--r-- | tools/aapt2/data/lib/AndroidManifest.xml | 5 |
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> |