diff options
author | Adam Lesinski <adamlesinski@google.com> | 2015-04-24 19:19:30 -0700 |
---|---|---|
committer | Adam Lesinski <adamlesinski@google.com> | 2015-05-04 16:43:24 -0700 |
commit | 24aad163bc88cb10d2275385e9afc3de7f342d65 (patch) | |
tree | 361fc0b3fbef5f68a16f357ae9d2bed5e93efbf5 | |
parent | ab2581398c812917145088590bd18eb83f3a2ea6 (diff) | |
download | frameworks_base-24aad163bc88cb10d2275385e9afc3de7f342d65.zip frameworks_base-24aad163bc88cb10d2275385e9afc3de7f342d65.tar.gz frameworks_base-24aad163bc88cb10d2275385e9afc3de7f342d65.tar.bz2 |
Add namespace handling in attribute values
Previously, you could only reference namespace prefixes in attribute names:
<View xmlns:appcompat="http://schemas.android.com/apk/res/android.support.v7.appcompat"
appcompat:name="hey"
...
Now you can also reference them in resource names within an attribute value:
...
android:text="@appcompat:string/confirm"
...
Which will be treated as "@android.support.v7.appcompat:string/confirm".
Change-Id: Ib076e867a990c80cf877a704eb77cd1ef0b23b52
42 files changed, 1063 insertions, 438 deletions
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index 05034c3..f3cf3c7 100644 --- a/tools/aapt2/Android.mk +++ b/tools/aapt2/Android.mk @@ -40,10 +40,10 @@ sources := \ ManifestValidator.cpp \ Png.cpp \ ResChunkPullParser.cpp \ - Resolver.cpp \ Resource.cpp \ ResourceParser.cpp \ ResourceTable.cpp \ + ResourceTableResolver.cpp \ ResourceValues.cpp \ SdkConstants.cpp \ StringPool.cpp \ diff --git a/tools/aapt2/BinaryResourceParser.cpp b/tools/aapt2/BinaryResourceParser.cpp index 326a2ac..bad5aa5 100644 --- a/tools/aapt2/BinaryResourceParser.cpp +++ b/tools/aapt2/BinaryResourceParser.cpp @@ -39,7 +39,7 @@ using namespace android; * given a mapping from resource ID to resource name. */ struct ReferenceIdToNameVisitor : ValueVisitor { - ReferenceIdToNameVisitor(const std::shared_ptr<Resolver>& resolver, + ReferenceIdToNameVisitor(const std::shared_ptr<IResolver>& resolver, std::map<ResourceId, ResourceName>* cache) : mResolver(resolver), mCache(cache) { } @@ -96,30 +96,25 @@ private: reference.name = cacheIter->second; reference.id = 0; } else { - const android::ResTable& table = mResolver->getResTable(); - android::ResTable::resource_name resourceName; - if (table.getResourceName(reference.id.id, false, &resourceName)) { - const ResourceType* type = parseResourceType(StringPiece16(resourceName.type, - resourceName.typeLen)); - assert(type); - reference.name.package.assign(resourceName.package, resourceName.packageLen); - reference.name.type = *type; - reference.name.entry.assign(resourceName.name, resourceName.nameLen); - reference.id = 0; + Maybe<ResourceName> result = mResolver->findName(reference.id); + if (result) { + reference.name = result.value(); // Add to cache. mCache->insert({reference.id, reference.name}); + + reference.id = 0; } } } - std::shared_ptr<Resolver> mResolver; + std::shared_ptr<IResolver> mResolver; std::map<ResourceId, ResourceName>* mCache; }; BinaryResourceParser::BinaryResourceParser(const std::shared_ptr<ResourceTable>& table, - const std::shared_ptr<Resolver>& resolver, + const std::shared_ptr<IResolver>& resolver, const Source& source, const void* data, size_t len) : diff --git a/tools/aapt2/BinaryResourceParser.h b/tools/aapt2/BinaryResourceParser.h index f95a0c8..2ac02c9 100644 --- a/tools/aapt2/BinaryResourceParser.h +++ b/tools/aapt2/BinaryResourceParser.h @@ -43,7 +43,7 @@ public: * add any resources parsed to `table`. `source` is for logging purposes. */ BinaryResourceParser(const std::shared_ptr<ResourceTable>& table, - const std::shared_ptr<Resolver>& resolver, + const std::shared_ptr<IResolver>& resolver, const Source& source, const void* data, size_t len); @@ -92,7 +92,7 @@ private: std::shared_ptr<ResourceTable> mTable; - std::shared_ptr<Resolver> mResolver; + std::shared_ptr<IResolver> mResolver; const Source mSource; diff --git a/tools/aapt2/BinaryXmlPullParser.cpp b/tools/aapt2/BinaryXmlPullParser.cpp index 7a07c06..476a215 100644 --- a/tools/aapt2/BinaryXmlPullParser.cpp +++ b/tools/aapt2/BinaryXmlPullParser.cpp @@ -15,6 +15,8 @@ */ #include "BinaryXmlPullParser.h" +#include "Maybe.h" +#include "Util.h" #include <androidfw/ResourceTypes.h> #include <memory> @@ -77,12 +79,31 @@ XmlPullParser::Event BinaryXmlPullParser::next() { mEvent = codeToEvent(code); switch (mEvent) { case Event::kStartNamespace: - case Event::kEndNamespace: + case Event::kEndNamespace: { data = mParser->getNamespacePrefix(&len); - mStr1.assign(data, len); + if (data) { + mStr1.assign(data, len); + } else { + mStr1.clear(); + } data = mParser->getNamespaceUri(&len); - mStr2.assign(data, len); + if (data) { + mStr2.assign(data, len); + } else { + mStr2.clear(); + } + + Maybe<std::u16string> result = util::extractPackageFromNamespace(mStr2); + if (result) { + if (mEvent == Event::kStartNamespace) { + mPackageAliases.emplace_back(mStr1, result.value()); + } else { + assert(mPackageAliases.back().second == result.value()); + mPackageAliases.pop_back(); + } + } break; + } case Event::kStartElement: copyAttributes(); @@ -90,14 +111,26 @@ XmlPullParser::Event BinaryXmlPullParser::next() { case Event::kEndElement: data = mParser->getElementNamespace(&len); - mStr1.assign(data, len); + if (data) { + mStr1.assign(data, len); + } else { + mStr1.clear(); + } data = mParser->getElementName(&len); - mStr2.assign(data, len); + if (data) { + mStr2.assign(data, len); + } else { + mStr2.clear(); + } break; case Event::kText: data = mParser->getText(&len); - mStr1.assign(data, len); + if (data) { + mStr1.assign(data, len); + } else { + mStr1.clear(); + } break; default: @@ -155,6 +188,22 @@ const std::u16string& BinaryXmlPullParser::getNamespaceUri() const { return sEmpty; } +bool BinaryXmlPullParser::applyPackageAlias(std::u16string* package, + const std::u16string& defaultPackage) const { + const auto endIter = mPackageAliases.rend(); + for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) { + if (iter->first == *package) { + if (iter->second.empty()) { + *package = defaultPackage; + } else { + *package = iter->second; + } + return true; + } + } + return false; +} + const std::u16string& BinaryXmlPullParser::getElementNamespace() const { if (!mHasComment && (mEvent == XmlPullParser::Event::kStartElement || mEvent == XmlPullParser::Event::kEndElement)) { @@ -191,11 +240,17 @@ void BinaryXmlPullParser::copyAttributes() { XmlPullParser::Attribute attr; size_t len; const char16_t* str = mParser->getAttributeNamespace(i, &len); - attr.namespaceUri.assign(str, len); + if (str) { + attr.namespaceUri.assign(str, len); + } str = mParser->getAttributeName(i, &len); - attr.name.assign(str, len); + if (str) { + attr.name.assign(str, len); + } str = mParser->getAttributeStringValue(i, &len); - attr.value.assign(str, len); + if (str) { + attr.value.assign(str, len); + } mAttributes.push_back(std::move(attr)); } } diff --git a/tools/aapt2/BinaryXmlPullParser.h b/tools/aapt2/BinaryXmlPullParser.h index 2d4256a..16fc8b7 100644 --- a/tools/aapt2/BinaryXmlPullParser.h +++ b/tools/aapt2/BinaryXmlPullParser.h @@ -34,25 +34,27 @@ public: BinaryXmlPullParser(const std::shared_ptr<android::ResXMLTree>& parser); BinaryXmlPullParser(const BinaryXmlPullParser& rhs) = delete; - Event getEvent() const; - const std::string& getLastError() const; - Event next(); + Event getEvent() const override; + const std::string& getLastError() const override; + Event next() override; - const std::u16string& getComment() const; - size_t getLineNumber() const; - size_t getDepth() const; + const std::u16string& getComment() const override; + size_t getLineNumber() const override; + size_t getDepth() const override; - const std::u16string& getText() const; + const std::u16string& getText() const override; - const std::u16string& getNamespacePrefix() const; - const std::u16string& getNamespaceUri() const; + const std::u16string& getNamespacePrefix() const override; + const std::u16string& getNamespaceUri() const override; + bool applyPackageAlias(std::u16string* package, const std::u16string& defaultpackage) + const override; - const std::u16string& getElementNamespace() const; - const std::u16string& getElementName() const; + const std::u16string& getElementNamespace() const override; + const std::u16string& getElementName() const override; - const_iterator beginAttributes() const; - const_iterator endAttributes() const; - size_t getAttributeCount() const; + const_iterator beginAttributes() const override; + const_iterator endAttributes() const override; + size_t getAttributeCount() const override; private: void copyAttributes(); @@ -66,6 +68,7 @@ private: const std::u16string sEmpty; const std::string sEmpty8; size_t mDepth; + std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases; }; } // namespace aapt diff --git a/tools/aapt2/BindingXmlPullParser.cpp b/tools/aapt2/BindingXmlPullParser.cpp index 58b96e8..4b7a656 100644 --- a/tools/aapt2/BindingXmlPullParser.cpp +++ b/tools/aapt2/BindingXmlPullParser.cpp @@ -252,6 +252,11 @@ const std::u16string& BindingXmlPullParser::getNamespaceUri() const { return mParser->getNamespaceUri(); } +bool BindingXmlPullParser::applyPackageAlias(std::u16string* package, + const std::u16string& defaultPackage) const { + return mParser->applyPackageAlias(package, defaultPackage); +} + const std::u16string& BindingXmlPullParser::getElementNamespace() const { return mParser->getElementNamespace(); } diff --git a/tools/aapt2/BindingXmlPullParser.h b/tools/aapt2/BindingXmlPullParser.h index c892b09..cfb16ef 100644 --- a/tools/aapt2/BindingXmlPullParser.h +++ b/tools/aapt2/BindingXmlPullParser.h @@ -42,6 +42,8 @@ public: const std::u16string& getNamespacePrefix() const override; const std::u16string& getNamespaceUri() const override; + bool applyPackageAlias(std::u16string* package, const std::u16string& defaultPackage) + const override; const std::u16string& getElementNamespace() const override; const std::u16string& getElementName() const override; diff --git a/tools/aapt2/Flag.cpp b/tools/aapt2/Flag.cpp index 3b2ff51..0f63c2c 100644 --- a/tools/aapt2/Flag.cpp +++ b/tools/aapt2/Flag.cpp @@ -65,7 +65,7 @@ void parse(int argc, char** argv, const StringPiece& command) { for (int i = 0; i < argc; i++) { const StringPiece arg(argv[i]); if (*arg.data() != '-') { - sArgs.emplace_back(arg.toString()); + sArgs.push_back(arg.toString()); continue; } diff --git a/tools/aapt2/JavaClassGenerator_test.cpp b/tools/aapt2/JavaClassGenerator_test.cpp index 96bb10b..57b600a 100644 --- a/tools/aapt2/JavaClassGenerator_test.cpp +++ b/tools/aapt2/JavaClassGenerator_test.cpp @@ -84,6 +84,7 @@ TEST_F(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) { output.find("public static final int hey_dude_cool_attr = 0;")); } +/* TEST_F(JavaClassGeneratorTest, EmitPackageMangledSymbols) { ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"foo" }, ResourceId{ 0x01, 0x02, 0x0000 })); @@ -111,6 +112,6 @@ TEST_F(JavaClassGeneratorTest, EmitPackageMangledSymbols) { output = out.str(); EXPECT_NE(std::string::npos, output.find("int test =")); EXPECT_EQ(std::string::npos, output.find("int foo =")); -} +}*/ } // namespace aapt diff --git a/tools/aapt2/Linker.cpp b/tools/aapt2/Linker.cpp index 4346c8b..42ea0f1 100644 --- a/tools/aapt2/Linker.cpp +++ b/tools/aapt2/Linker.cpp @@ -16,6 +16,8 @@ #include "Linker.h" #include "Logger.h" +#include "NameMangler.h" +#include "Resolver.h" #include "ResourceParser.h" #include "ResourceTable.h" #include "ResourceValues.h" @@ -38,7 +40,7 @@ namespace aapt { Linker::Args::Args(const ResourceNameRef& r, const SourceLine& s) : referrer(r), source(s) { } -Linker::Linker(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver) : +Linker::Linker(std::shared_ptr<ResourceTable> table, std::shared_ptr<IResolver> resolver) : mTable(table), mResolver(resolver), mError(false) { } @@ -100,11 +102,15 @@ bool Linker::linkAndValidate() { } entry->entryId = nextIndex++; + std::u16string unmangledPackage = mTable->getPackage(); + std::u16string unmangledName = entry->name; + NameMangler::unmangle(&unmangledName, &unmangledPackage); + // Update callers of this resource with the right ID. auto callersIter = mGraph.find(ResourceNameRef{ - mTable->getPackage(), + unmangledPackage, type->type, - entry->name + unmangledName }); if (callersIter != std::end(mGraph)) { @@ -175,13 +181,14 @@ void Linker::processAttributeValue(const ResourceNameRef& name, const SourceLine // we called through the original value. auto onCreateReference = [&](const ResourceName& name) { - mTable->addResource(name, ConfigDescription{}, - source, util::make_unique<Id>()); + // We should never get here. All references would have been + // parsed in the parser phase. + assert(false); + //mTable->addResource(name, ConfigDescription{}, source, util::make_unique<Id>()); }; - convertedValue = ResourceParser::parseItemForAttribute( - *str.value, attr, mResolver->getDefaultPackage(), - onCreateReference); + convertedValue = ResourceParser::parseItemForAttribute(*str.value, attr, + onCreateReference); if (!convertedValue && attr.typeMask & android::ResTable_map::TYPE_STRING) { // Last effort is to parse as a string. util::StringBuilder builder; @@ -225,13 +232,13 @@ void Linker::visit(Style& style, ValueVisitorArgs& a) { } for (Style::Entry& styleEntry : style.entries) { - Maybe<Resolver::Entry> result = mResolver->findAttribute(styleEntry.key.name); + Maybe<IResolver::Entry> result = mResolver->findAttribute(styleEntry.key.name); if (!result || !result.value().attr) { addUnresolvedSymbol(styleEntry.key.name, args.source); continue; } - const Resolver::Entry& entry = result.value(); + const IResolver::Entry& entry = result.value(); if (entry.id.isValid()) { styleEntry.key.id = entry.id; } else { diff --git a/tools/aapt2/Linker.h b/tools/aapt2/Linker.h index 9b911b7..d34e487 100644 --- a/tools/aapt2/Linker.h +++ b/tools/aapt2/Linker.h @@ -52,9 +52,9 @@ class Linker : ValueVisitor { public: /** * Create a Linker for the given resource table with the sources available in - * Resolver. Resolver should contain the ResourceTable as a source too. + * IResolver. IResolver should contain the ResourceTable as a source too. */ - Linker(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver); + Linker(std::shared_ptr<ResourceTable> table, std::shared_ptr<IResolver> resolver); Linker(const Linker&) = delete; @@ -117,7 +117,7 @@ private: friend ::std::ostream& operator<<(::std::ostream&, const Node&); std::shared_ptr<ResourceTable> mTable; - std::shared_ptr<Resolver> mResolver; + std::shared_ptr<IResolver> mResolver; std::map<ResourceNameRef, std::vector<Node>> mGraph; std::map<ResourceName, std::vector<SourceLine>> mUnresolvedSymbols; bool mError; diff --git a/tools/aapt2/Linker_test.cpp b/tools/aapt2/Linker_test.cpp index 4d2d360..3c5b8b4 100644 --- a/tools/aapt2/Linker_test.cpp +++ b/tools/aapt2/Linker_test.cpp @@ -15,8 +15,8 @@ */ #include "Linker.h" -#include "Resolver.h" #include "ResourceTable.h" +#include "ResourceTableResolver.h" #include "ResourceValues.h" #include "Util.h" @@ -31,7 +31,7 @@ struct LinkerTest : public ::testing::Test { mTable = std::make_shared<ResourceTable>(); mTable->setPackage(u"android"); mTable->setPackageId(0x01); - mLinker = std::make_shared<Linker>(mTable, std::make_shared<Resolver>( + mLinker = std::make_shared<Linker>(mTable, std::make_shared<ResourceTableResolver>( mTable, std::make_shared<android::AssetManager>())); // Create a few attributes for use in the tests. diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp index be806c9..dedf8b4 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -29,6 +29,7 @@ #include "Png.h" #include "ResourceParser.h" #include "ResourceTable.h" +#include "ResourceTableResolver.h" #include "ResourceValues.h" #include "SdkConstants.h" #include "SourceXmlPullParser.h" @@ -271,6 +272,7 @@ struct LinkItem { ConfigDescription config; std::string originalPath; ZipFile* apk; + std::u16string originalPackage; }; template <typename TChar> @@ -371,6 +373,8 @@ bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable> XmlFlattener flattener(table, {}); XmlFlattener::Options xmlOptions; + xmlOptions.defaultPackage = table->getPackage(); + if (options.versionStylesAndLayouts) { // We strip attributes that do not belong in this version of the resource. // Non-version qualified resources have an implicit version 1 requirement. @@ -432,7 +436,7 @@ bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable> return true; } -bool linkXml(const AaptOptions& options, const std::shared_ptr<Resolver>& resolver, +bool linkXml(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver, const LinkItem& item, const void* data, size_t dataLen, ZipFile* outApk) { std::shared_ptr<android::ResXMLTree> tree = std::make_shared<android::ResXMLTree>(); if (tree->setTo(data, dataLen, false) != android::NO_ERROR) { @@ -443,7 +447,10 @@ bool linkXml(const AaptOptions& options, const std::shared_ptr<Resolver>& resolv BigBuffer outBuffer(1024); XmlFlattener flattener({}, resolver); - if (!flattener.flatten(item.source, xmlParser, &outBuffer, {})) { + + XmlFlattener::Options xmlOptions; + xmlOptions.defaultPackage = item.originalPackage; + if (!flattener.flatten(item.source, xmlParser, &outBuffer, xmlOptions)) { return false; } @@ -490,8 +497,8 @@ bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outA return true; } -bool compileManifest(const AaptOptions& options, const std::shared_ptr<Resolver>& resolver, - ZipFile* outApk) { +bool compileManifest(const AaptOptions& options, + const std::shared_ptr<ResourceTableResolver>& resolver, ZipFile* outApk) { if (options.verbose) { Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl; } @@ -506,7 +513,9 @@ bool compileManifest(const AaptOptions& options, const std::shared_ptr<Resolver> std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in); XmlFlattener flattener({}, resolver); - if (!flattener.flatten(options.manifest, xmlParser, &outBuffer, {})) { + XmlFlattener::Options xmlOptions; + xmlOptions.defaultPackage = options.appInfo.package; + if (!flattener.flatten(options.manifest, xmlParser, &outBuffer, xmlOptions)) { return false; } @@ -640,8 +649,12 @@ static void addApkFilesToLinkQueue(const std::u16string& package, const Source& for (auto& value : entry->values) { visitFunc<FileReference>(*value.value, [&](FileReference& ref) { std::string pathUtf8 = util::utf16ToUtf8(*ref.path); + Source newSource = source; + newSource.path += "/"; + newSource.path += pathUtf8; outLinkQueue->push(LinkItem{ - source, name, value.config, pathUtf8, apk.get() }); + newSource, name, value.config, pathUtf8, apk.get(), + table->getPackage() }); // Now rewrite the file path. if (mangle) { ref.path = table->getValueStringPool().makeRef(util::utf8ToUtf16( @@ -657,9 +670,20 @@ 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<Resolver>& resolver) { - std::map<std::shared_ptr<ResourceTable>, std::unique_ptr<ZipFile>> apkFiles; + const std::shared_ptr<ResourceTableResolver>& resolver) { + std::map<std::shared_ptr<ResourceTable>, StaticLibraryData> apkFiles; std::unordered_set<std::u16string> linkedPackages; // Populate the linkedPackages with our own. @@ -681,19 +705,18 @@ bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outT return false; } - void* uncompressedData = zipFile->uncompress(entry); + std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>( + zipFile->uncompress(entry)); assert(uncompressedData); - BinaryResourceParser parser(table, resolver, source, uncompressedData, + BinaryResourceParser parser(table, resolver, source, uncompressedData.get(), entry->getUncompressedLen()); if (!parser.parse()) { - free(uncompressedData); return false; } - free(uncompressedData); // Keep track of where this table came from. - apkFiles[table] = std::move(zipFile); + apkFiles[table] = StaticLibraryData{ source, std::move(zipFile) }; // Add the package to the set of linked packages. linkedPackages.insert(table->getPackage()); @@ -704,7 +727,8 @@ bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outT const std::shared_ptr<ResourceTable>& inTable = p.first; // Collect all FileReferences and add them to the queue for processing. - addApkFilesToLinkQueue(options.appInfo.package, Source{}, inTable, p.second, &linkQueue); + addApkFilesToLinkQueue(options.appInfo.package, p.second.source, inTable, p.second.apk, + &linkQueue); // Merge the tables. if (!outTable->merge(std::move(*inTable))) { @@ -746,6 +770,7 @@ bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outT for (; !linkQueue.empty(); linkQueue.pop()) { const LinkItem& item = linkQueue.front(); + assert(!item.originalPackage.empty()); ZipEntry* entry = item.apk->getEntryByName(item.originalPath.data()); if (!entry) { Logger::error(item.source) << "failed to find '" << item.originalPath << "'." @@ -811,6 +836,20 @@ bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outT } } + outTable->getValueStringPool().prune(); + outTable->getValueStringPool().sort( + [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { + if (a.context.priority < b.context.priority) { + return true; + } + + if (a.context.priority > b.context.priority) { + return false; + } + return a.value < b.value; + }); + + // Flatten the resource table. TableFlattener::Options flattenerOptions; flattenerOptions.useExtendedChunks = false; @@ -823,7 +862,7 @@ bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outT } bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table, - const std::shared_ptr<Resolver>& resolver) { + const std::shared_ptr<IResolver>& resolver) { std::queue<CompileItem> compileQueue; bool error = false; @@ -1073,7 +1112,8 @@ int main(int argc, char** argv) { } // Make the resolver that will cache IDs for us. - std::shared_ptr<Resolver> resolver = std::make_shared<Resolver>(table, libraries); + std::shared_ptr<ResourceTableResolver> resolver = std::make_shared<ResourceTableResolver>( + table, libraries); if (options.phase == AaptOptions::Phase::Compile) { if (!compile(options, table, resolver)) { diff --git a/tools/aapt2/ManifestValidator.cpp b/tools/aapt2/ManifestValidator.cpp index 7ec0bc7..123b9fa 100644 --- a/tools/aapt2/ManifestValidator.cpp +++ b/tools/aapt2/ManifestValidator.cpp @@ -190,18 +190,26 @@ bool ManifestValidator::validateManifest(const Source& source, android::ResXMLPa bool error = false; SourceLogger logger(source); - const size_t attrCount = parser->getAttributeCount(); - for (size_t i = 0; i < attrCount; i++) { - size_t len = 0; - StringPiece16 attrNamespace(parser->getAttributeNamespace(i, &len), len); - StringPiece16 attrName(parser->getAttributeName(i, &len), len); - if (attrNamespace.empty() && attrName == u"package") { - error |= !validateInlineAttribute(parser, i, logger, kPackageIdentSet); - } else if (attrNamespace == u"android") { - if (attrName == u"sharedUserId") { - error |= !validateInlineAttribute(parser, i, logger, kPackageIdentSet); - } - } + const StringPiece16 kAndroid = u"android"; + const StringPiece16 kPackage = u"package"; + const StringPiece16 kSharedUserId = u"sharedUserId"; + + ssize_t idx; + + idx = parser->indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()); + if (idx < 0) { + logger.error(parser->getLineNumber()) + << "missing package attribute." + << std::endl; + error = true; + } else { + error |= !validateInlineAttribute(parser, idx, logger, kPackageIdentSet); + } + + idx = parser->indexOfAttribute(kAndroid.data(), kAndroid.size(), + kSharedUserId.data(), kSharedUserId.size()); + if (idx >= 0) { + error |= !validateInlineAttribute(parser, idx, logger, kPackageIdentSet); } return !error; } diff --git a/tools/aapt2/Maybe.h b/tools/aapt2/Maybe.h index f6a396d..ff6625f 100644 --- a/tools/aapt2/Maybe.h +++ b/tools/aapt2/Maybe.h @@ -34,54 +34,68 @@ public: /** * Construct Nothing. */ - inline Maybe(); + Maybe(); - inline ~Maybe(); + ~Maybe(); + + Maybe(const Maybe& rhs); template <typename U> - inline Maybe(const Maybe<U>& rhs); + Maybe(const Maybe<U>& rhs); + + Maybe(Maybe&& rhs); template <typename U> - inline Maybe(Maybe<U>&& rhs); + Maybe(Maybe<U>&& rhs); + + Maybe& operator=(const Maybe& rhs); template <typename U> - inline Maybe& operator=(const Maybe<U>& rhs); + Maybe& operator=(const Maybe<U>& rhs); + + Maybe& operator=(Maybe&& rhs); template <typename U> - inline Maybe& operator=(Maybe<U>&& rhs); + Maybe& operator=(Maybe<U>&& rhs); /** * Construct a Maybe holding a value. */ - inline Maybe(const T& value); + Maybe(const T& value); /** * Construct a Maybe holding a value. */ - inline Maybe(T&& value); + Maybe(T&& value); /** * True if this holds a value, false if * it holds Nothing. */ - inline operator bool() const; + operator bool() const; /** * Gets the value if one exists, or else * panics. */ - inline T& value(); + T& value(); /** * Gets the value if one exists, or else * panics. */ - inline const T& value() const; + const T& value() const; private: template <typename U> friend class Maybe; + template <typename U> + Maybe& copy(const Maybe<U>& rhs); + + template <typename U> + Maybe& move(Maybe<U>&& rhs); + void destroy(); bool mNothing; @@ -102,6 +116,14 @@ Maybe<T>::~Maybe() { } template <typename T> +Maybe<T>::Maybe(const Maybe& rhs) +: mNothing(rhs.mNothing) { + if (!rhs.mNothing) { + new (&mStorage) T(reinterpret_cast<const T&>(rhs.mStorage)); + } +} + +template <typename T> template <typename U> Maybe<T>::Maybe(const Maybe<U>& rhs) : mNothing(rhs.mNothing) { @@ -111,6 +133,18 @@ Maybe<T>::Maybe(const Maybe<U>& rhs) } template <typename T> +Maybe<T>::Maybe(Maybe&& rhs) +: mNothing(rhs.mNothing) { + if (!rhs.mNothing) { + rhs.mNothing = true; + + // Move the value from rhs. + new (&mStorage) T(std::move(reinterpret_cast<T&>(rhs.mStorage))); + rhs.destroy(); + } +} + +template <typename T> template <typename U> Maybe<T>::Maybe(Maybe<U>&& rhs) : mNothing(rhs.mNothing) { @@ -119,16 +153,25 @@ Maybe<T>::Maybe(Maybe<U>&& rhs) // Move the value from rhs. new (&mStorage) T(std::move(reinterpret_cast<U&>(rhs.mStorage))); - - // Since the value in rhs is now Nothing, - // run the destructor for the value. rhs.destroy(); } } template <typename T> +inline Maybe<T>& Maybe<T>::operator=(const Maybe& rhs) { + // Delegate to the actual assignment. + return copy(rhs); +} + +template <typename T> +template <typename U> +inline Maybe<T>& Maybe<T>::operator=(const Maybe<U>& rhs) { + return copy(rhs); +} + +template <typename T> template <typename U> -Maybe<T>& Maybe<T>::operator=(const Maybe<U>& rhs) { +Maybe<T>& Maybe<T>::copy(const Maybe<U>& rhs) { if (mNothing && rhs.mNothing) { // Both are nothing, nothing to do. return *this; @@ -150,8 +193,20 @@ Maybe<T>& Maybe<T>::operator=(const Maybe<U>& rhs) { } template <typename T> +inline Maybe<T>& Maybe<T>::operator=(Maybe&& rhs) { + // Delegate to the actual assignment. + return move(std::forward<Maybe<T>>(rhs)); +} + +template <typename T> +template <typename U> +inline Maybe<T>& Maybe<T>::operator=(Maybe<U>&& rhs) { + return move(std::forward<Maybe<U>>(rhs)); +} + +template <typename T> template <typename U> -Maybe<T>& Maybe<T>::operator=(Maybe<U>&& rhs) { +Maybe<T>& Maybe<T>::move(Maybe<U>&& rhs) { if (mNothing && rhs.mNothing) { // Both are nothing, nothing to do. return *this; @@ -162,14 +217,15 @@ Maybe<T>& Maybe<T>::operator=(Maybe<U>&& rhs) { rhs.destroy(); } else if (mNothing) { // We are nothing but rhs is something. - mNothing = rhs.mNothing; + mNothing = false; + rhs.mNothing = true; // Move the value from rhs. new (&mStorage) T(std::move(reinterpret_cast<U&>(rhs.mStorage))); rhs.destroy(); } else { // We are something but rhs is nothing, so destroy our value. - mNothing = rhs.mNothing; + mNothing = true; destroy(); } return *this; diff --git a/tools/aapt2/Maybe_test.cpp b/tools/aapt2/Maybe_test.cpp index 348d7dd..71bbb94 100644 --- a/tools/aapt2/Maybe_test.cpp +++ b/tools/aapt2/Maybe_test.cpp @@ -23,20 +23,64 @@ namespace aapt { struct Dummy { Dummy() { - std::cerr << "Constructing Dummy " << (void *) this << std::endl; + data = new int; + *data = 1; + std::cerr << "Construct Dummy{0x" << (void *) this + << "} with data=0x" << (void*) data + << std::endl; } Dummy(const Dummy& rhs) { - std::cerr << "Copying Dummy " << (void *) this << " from " << (const void*) &rhs << std::endl; + data = nullptr; + if (rhs.data) { + data = new int; + *data = *rhs.data; + } + std::cerr << "CopyConstruct Dummy{0x" << (void *) this + << "} from Dummy{0x" << (const void*) &rhs + << "}" << std::endl; } Dummy(Dummy&& rhs) { - std::cerr << "Moving Dummy " << (void *) this << " from " << (void*) &rhs << std::endl; + data = rhs.data; + rhs.data = nullptr; + std::cerr << "MoveConstruct Dummy{0x" << (void *) this + << "} from Dummy{0x" << (const void*) &rhs + << "}" << std::endl; + } + + Dummy& operator=(const Dummy& rhs) { + delete data; + data = nullptr; + + if (rhs.data) { + data = new int; + *data = *rhs.data; + } + std::cerr << "CopyAssign Dummy{0x" << (void *) this + << "} from Dummy{0x" << (const void*) &rhs + << "}" << std::endl; + return *this; + } + + Dummy& operator=(Dummy&& rhs) { + delete data; + data = rhs.data; + rhs.data = nullptr; + std::cerr << "MoveAssign Dummy{0x" << (void *) this + << "} from Dummy{0x" << (const void*) &rhs + << "}" << std::endl; + return *this; } ~Dummy() { - std::cerr << "Destroying Dummy " << (void *) this << std::endl; + std::cerr << "Destruct Dummy{0x" << (void *) this + << "} with data=0x" << (void*) data + << std::endl; + delete data; } + + int* data; }; TEST(MaybeTest, MakeNothing) { @@ -66,4 +110,12 @@ TEST(MaybeTest, Lifecycle) { Maybe<Dummy> val2 = make_value(Dummy()); } +TEST(MaybeTest, MoveAssign) { + Maybe<Dummy> val; + { + Maybe<Dummy> val2 = Dummy(); + val = std::move(val2); + } +} + } // namespace aapt diff --git a/tools/aapt2/Resolver.h b/tools/aapt2/Resolver.h index cb2234d..2c9a8e5 100644 --- a/tools/aapt2/Resolver.h +++ b/tools/aapt2/Resolver.h @@ -19,31 +19,18 @@ #include "Maybe.h" #include "Resource.h" -#include "ResourceTable.h" #include "ResourceValues.h" -#include <androidfw/AssetManager.h> #include <androidfw/ResourceTypes.h> -#include <memory> -#include <vector> -#include <unordered_set> namespace aapt { /** * Resolves symbolic references (package:type/entry) into resource IDs/objects. - * Encapsulates the search of library sources as well as the local ResourceTable. */ -class Resolver { +class IResolver { public: - /** - * Creates a resolver with a local ResourceTable and an AssetManager - * loaded with library packages. - */ - Resolver(std::shared_ptr<const ResourceTable> table, - std::shared_ptr<const android::AssetManager> sources); - - Resolver(const Resolver&) = delete; // Not copyable. + virtual ~IResolver() {}; /** * Holds the result of a resource name lookup. @@ -68,44 +55,27 @@ public: * Return the package to use when none is specified. This * is the package name of the app being built. */ - const std::u16string& getDefaultPackage() const; + virtual const std::u16string& getDefaultPackage() const = 0; /** * Returns a ResourceID if the name is found. The ResourceID * may not be valid if the resource was not assigned an ID. */ - Maybe<ResourceId> findId(const ResourceName& name); + virtual Maybe<ResourceId> findId(const ResourceName& name) = 0; /** * Returns an Entry if the name is found. Entry::attr * may be nullptr if the resource is not an attribute. */ - Maybe<Entry> findAttribute(const ResourceName& name); - - const android::ResTable& getResTable() const; - -private: - struct CacheEntry { - ResourceId id; - std::unique_ptr<Attribute> attr; - }; - - const CacheEntry* buildCacheEntry(const ResourceName& name); + virtual Maybe<Entry> findAttribute(const ResourceName& name) = 0; - std::shared_ptr<const ResourceTable> mTable; - std::shared_ptr<const android::AssetManager> mSources; - std::map<ResourceName, CacheEntry> mCache; - std::unordered_set<std::u16string> mIncludedPackages; + /** + * Find a resource by ID. Resolvers may contain resources without + * resource IDs assigned to them. + */ + virtual Maybe<ResourceName> findName(ResourceId resId) = 0; }; -inline const std::u16string& Resolver::getDefaultPackage() const { - return mTable->getPackage(); -} - -inline const android::ResTable& Resolver::getResTable() const { - return mSources->getResources(false); -} - } // namespace aapt #endif // AAPT_RESOLVER_H diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index f928acd..31104fbe 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -130,6 +130,7 @@ struct ResourceId { uint8_t typeId() const; uint16_t entryId() const; bool operator<(const ResourceId& rhs) const; + bool operator==(const ResourceId& rhs) const; }; // @@ -178,6 +179,10 @@ inline bool ResourceId::operator<(const ResourceId& rhs) const { return id < rhs.id; } +inline bool ResourceId::operator==(const ResourceId& rhs) const { + return id == rhs.id; +} + inline ::std::ostream& operator<<(::std::ostream& out, const ResourceId& resId) { std::ios_base::fmtflags oldFlags = out.flags(); diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index 943892d..e7e824c 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -175,23 +175,16 @@ bool ResourceParser::parseStyleParentReference(const StringPiece16& str, Referen } std::unique_ptr<Reference> ResourceParser::tryParseReference(const StringPiece16& str, - const StringPiece16& defaultPackage, bool* outCreate) { ResourceNameRef ref; bool privateRef = false; if (tryParseReference(str, &ref, outCreate, &privateRef)) { - if (ref.package.empty()) { - ref.package = defaultPackage; - } std::unique_ptr<Reference> value = util::make_unique<Reference>(ref); value->privateReference = privateRef; return value; } if (tryParseAttributeReference(str, &ref)) { - if (ref.package.empty()) { - ref.package = defaultPackage; - } *outCreate = false; return util::make_unique<Reference>(ref, Reference::Type::kAttribute); } @@ -330,7 +323,7 @@ std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseBool(const StringPiece1 StringPiece16 trimmedStr(util::trimWhitespace(str)); uint32_t data = 0; if (trimmedStr == u"true" || trimmedStr == u"TRUE") { - data = 1; + data = 0xffffffffu; } else if (trimmedStr != u"false" && trimmedStr != u"FALSE") { return {}; } @@ -397,7 +390,7 @@ uint32_t ResourceParser::androidTypeToAttributeTypeMask(uint16_t type) { } std::unique_ptr<Item> ResourceParser::parseItemForAttribute( - const StringPiece16& value, uint32_t typeMask, const StringPiece16& defaultPackage, + const StringPiece16& value, uint32_t typeMask, std::function<void(const ResourceName&)> onCreateReference) { std::unique_ptr<BinaryPrimitive> nullOrEmpty = tryParseNullOrEmpty(value); if (nullOrEmpty) { @@ -405,7 +398,7 @@ std::unique_ptr<Item> ResourceParser::parseItemForAttribute( } bool create = false; - std::unique_ptr<Reference> reference = tryParseReference(value, defaultPackage, &create); + std::unique_ptr<Reference> reference = tryParseReference(value, &create); if (reference) { if (create && onCreateReference) { onCreateReference(reference->name); @@ -457,11 +450,10 @@ std::unique_ptr<Item> ResourceParser::parseItemForAttribute( * allows. */ std::unique_ptr<Item> ResourceParser::parseItemForAttribute( - const StringPiece16& str, const Attribute& attr, const StringPiece16& defaultPackage, + const StringPiece16& str, const Attribute& attr, std::function<void(const ResourceName&)> onCreateReference) { const uint32_t typeMask = attr.typeMask; - std::unique_ptr<Item> value = parseItemForAttribute(str, typeMask, defaultPackage, - onCreateReference); + std::unique_ptr<Item> value = parseItemForAttribute(str, typeMask, onCreateReference); if (value) { return value; } @@ -770,14 +762,25 @@ std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, uint32_t t } auto onCreateReference = [&](const ResourceName& name) { + // name.package can be empty here, as it will assume the package name of the table. mTable->addResource(name, {}, mSource.line(beginXmlLine), util::make_unique<Id>()); }; // Process the raw value. std::unique_ptr<Item> processedItem = parseItemForAttribute(rawValue, typeMask, - mTable->getPackage(), onCreateReference); if (processedItem) { + // Fix up the reference. + visitFunc<Reference>(*processedItem, [&](Reference& ref) { + if (!ref.name.package.empty()) { + // The package name was set, so lookup its alias. + parser->applyPackageAlias(&ref.name.package, mTable->getPackage()); + } else { + // The package name was left empty, so it assumes the default package + // without alias lookup. + ref.name.package = mTable->getPackage(); + } + }); return processedItem; } @@ -1093,7 +1096,7 @@ bool ResourceParser::parseEnumOrFlagItem(XmlPullParser* parser, const StringPiec return true; } -static bool parseXmlAttributeName(StringPiece16 str, ResourceNameRef* outRef) { +static bool parseXmlAttributeName(StringPiece16 str, ResourceName* outName) { str = util::trimWhitespace(str); const char16_t* const start = str.data(); const char16_t* const end = start + str.size(); @@ -1110,12 +1113,12 @@ static bool parseXmlAttributeName(StringPiece16 str, ResourceNameRef* outRef) { p++; } - outRef->package = package; - outRef->type = ResourceType::kAttr; + outName->package = package.toString(); + outName->type = ResourceType::kAttr; if (name.size() == 0) { - outRef->entry = str; + outName->entry = str.toString(); } else { - outRef->entry = name; + outName->entry = name.toString(); } return true; } @@ -1130,8 +1133,8 @@ bool ResourceParser::parseUntypedItem(XmlPullParser* parser, Style& style) { return false; } - ResourceNameRef keyRef; - if (!parseXmlAttributeName(nameAttrIter->value, &keyRef)) { + ResourceName key; + if (!parseXmlAttributeName(nameAttrIter->value, &key)) { mLogger.error(parser->getLineNumber()) << "invalid attribute name '" << nameAttrIter->value @@ -1140,14 +1143,15 @@ bool ResourceParser::parseUntypedItem(XmlPullParser* parser, Style& style) { return false; } - if (keyRef.package.empty()) { - keyRef.package = mTable->getPackage(); + if (!key.package.empty()) { + // We have a package name set, so lookup its alias. + parser->applyPackageAlias(&key.package, mTable->getPackage()); + } else { + // The package name was omitted, so use the default package name with + // no alias lookup. + key.package = mTable->getPackage(); } - // Create a copy instead of a reference because we - // are about to invalidate keyRef when advancing the parser. - ResourceName key = keyRef.toResourceName(); - std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString); if (!value) { return false; @@ -1170,7 +1174,11 @@ bool ResourceParser::parseStyle(XmlPullParser* parser, const ResourceNameRef& re return false; } - if (style->parent.name.package.empty()) { + if (!style->parent.name.package.empty()) { + // Try to interpret the package name as an alias. These take precedence. + parser->applyPackageAlias(&style->parent.name.package, mTable->getPackage()); + } else { + // If no package is specified, this can not be an alias and is the local package. style->parent.name.package = mTable->getPackage(); } } diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h index 52194bd..7618999 100644 --- a/tools/aapt2/ResourceParser.h +++ b/tools/aapt2/ResourceParser.h @@ -76,12 +76,10 @@ public: /* * Returns a Reference object if the string was parsed as a resource or attribute reference, - * ( @[+][package:]type/name | ?[package:]type/name ) - * assigning defaultPackage if the package was not present in the string, and setting - * outCreate to true if the '+' was present in the string. + * ( @[+][package:]type/name | ?[package:]type/name ) setting outCreate to true if + * the '+' was present in the string. */ static std::unique_ptr<Reference> tryParseReference(const StringPiece16& str, - const StringPiece16& defaultPackage, bool* outCreate); /* @@ -127,20 +125,18 @@ public: */ static std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute& enumAttr, const StringPiece16& str); - /* * Try to convert a string to an Item for the given attribute. The attribute will * restrict what values the string can be converted to. - * The defaultPackage is used when the string is a reference with no defined package. * The callback function onCreateReference is called when the parsed item is a * reference to an ID that must be created (@+id/foo). */ static std::unique_ptr<Item> parseItemForAttribute( - const StringPiece16& value, const Attribute& attr, const StringPiece16& defaultPackage, + const StringPiece16& value, const Attribute& attr, std::function<void(const ResourceName&)> onCreateReference = {}); static std::unique_ptr<Item> parseItemForAttribute( - const StringPiece16& value, uint32_t typeMask, const StringPiece16& defaultPackage, + const StringPiece16& value, uint32_t typeMask, std::function<void(const ResourceName&)> onCreateReference = {}); static uint32_t androidTypeToAttributeTypeMask(uint16_t type); diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index 63352de..00be3bd 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -125,11 +125,9 @@ struct ResourceParserTest : public ::testing::Test { mTable->setPackage(u"android"); } - ::testing::AssertionResult testParse(std::istream& in) { + ::testing::AssertionResult testParse(const StringPiece& str) { std::stringstream input(kXmlPreamble); - input << "<resources>" << std::endl - << in.rdbuf() << std::endl - << "</resources>" << std::endl; + input << "<resources>\n" << str << "\n</resources>" << std::endl; ResourceParser parser(mTable, Source{ "test" }, {}, std::make_shared<SourceXmlPullParser>(input)); if (parser.parse()) { @@ -174,7 +172,7 @@ TEST_F(ResourceParserTest, FailToParseWithNoRootResourcesElement) { } TEST_F(ResourceParserTest, ParseQuotedString) { - std::stringstream input("<string name=\"foo\"> \" hey there \" </string>"); + std::string input = "<string name=\"foo\"> \" hey there \" </string>"; ASSERT_TRUE(testParse(input)); const String* str = findResource<String>(ResourceName{ @@ -184,7 +182,7 @@ TEST_F(ResourceParserTest, ParseQuotedString) { } TEST_F(ResourceParserTest, ParseEscapedString) { - std::stringstream input("<string name=\"foo\">\\?123</string>"); + std::string input = "<string name=\"foo\">\\?123</string>"; ASSERT_TRUE(testParse(input)); const String* str = findResource<String>(ResourceName{ @@ -194,9 +192,8 @@ TEST_F(ResourceParserTest, ParseEscapedString) { } TEST_F(ResourceParserTest, ParseAttr) { - std::stringstream input; - input << "<attr name=\"foo\" format=\"string\"/>" << std::endl - << "<attr name=\"bar\"/>" << std::endl; + std::string input = "<attr name=\"foo\" format=\"string\"/>\n" + "<attr name=\"bar\"/>"; ASSERT_TRUE(testParse(input)); const Attribute* attr = findResource<Attribute>(ResourceName{ @@ -211,11 +208,10 @@ TEST_F(ResourceParserTest, ParseAttr) { } TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) { - std::stringstream input; - input << "<declare-styleable name=\"Styleable\">" << std::endl - << " <attr name=\"foo\" />" << std::endl - << "</declare-styleable>" << std::endl - << "<attr name=\"foo\" format=\"string\"/>" << std::endl; + std::string input = "<declare-styleable name=\"Styleable\">\n" + " <attr name=\"foo\" />\n" + "</declare-styleable>\n" + "<attr name=\"foo\" format=\"string\"/>"; ASSERT_TRUE(testParse(input)); const Attribute* attr = findResource<Attribute>(ResourceName{ @@ -225,14 +221,12 @@ TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) { } TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) { - std::stringstream input; - input << "<declare-styleable name=\"Theme\">" << std::endl - << " <attr name=\"foo\" />" << std::endl - << "</declare-styleable>" << std::endl - << "<declare-styleable name=\"Window\">" << std::endl - << " <attr name=\"foo\" format=\"boolean\"/>" << std::endl - << "</declare-styleable>" << std::endl; - + std::string input = "<declare-styleable name=\"Theme\">" + " <attr name=\"foo\" />\n" + "</declare-styleable>\n" + "<declare-styleable name=\"Window\">\n" + " <attr name=\"foo\" format=\"boolean\"/>\n" + "</declare-styleable>"; ASSERT_TRUE(testParse(input)); const Attribute* attr = findResource<Attribute>(ResourceName{ @@ -242,12 +236,11 @@ TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) { } TEST_F(ResourceParserTest, ParseEnumAttr) { - std::stringstream input; - input << "<attr name=\"foo\">" << std::endl - << " <enum name=\"bar\" value=\"0\"/>" << std::endl - << " <enum name=\"bat\" value=\"1\"/>" << std::endl - << " <enum name=\"baz\" value=\"2\"/>" << std::endl - << "</attr>" << std::endl; + std::string input = "<attr name=\"foo\">\n" + " <enum name=\"bar\" value=\"0\"/>\n" + " <enum name=\"bat\" value=\"1\"/>\n" + " <enum name=\"baz\" value=\"2\"/>\n" + "</attr>"; ASSERT_TRUE(testParse(input)); const Attribute* enumAttr = findResource<Attribute>(ResourceName{ @@ -267,12 +260,11 @@ TEST_F(ResourceParserTest, ParseEnumAttr) { } TEST_F(ResourceParserTest, ParseFlagAttr) { - std::stringstream input; - input << "<attr name=\"foo\">" << std::endl - << " <flag name=\"bar\" value=\"0\"/>" << std::endl - << " <flag name=\"bat\" value=\"1\"/>" << std::endl - << " <flag name=\"baz\" value=\"2\"/>" << std::endl - << "</attr>" << std::endl; + std::string input = "<attr name=\"foo\">\n" + " <flag name=\"bar\" value=\"0\"/>\n" + " <flag name=\"bat\" value=\"1\"/>\n" + " <flag name=\"baz\" value=\"2\"/>\n" + "</attr>"; ASSERT_TRUE(testParse(input)); const Attribute* flagAttr = findResource<Attribute>(ResourceName{ @@ -297,22 +289,20 @@ TEST_F(ResourceParserTest, ParseFlagAttr) { } TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) { - std::stringstream input; - input << "<attr name=\"foo\">" << std::endl - << " <enum name=\"bar\" value=\"0\"/>" << std::endl - << " <enum name=\"bat\" value=\"1\"/>" << std::endl - << " <enum name=\"bat\" value=\"2\"/>" << std::endl - << "</attr>" << std::endl; + std::string input = "<attr name=\"foo\">\n" + " <enum name=\"bar\" value=\"0\"/>\n" + " <enum name=\"bat\" value=\"1\"/>\n" + " <enum name=\"bat\" value=\"2\"/>\n" + "</attr>"; ASSERT_FALSE(testParse(input)); } TEST_F(ResourceParserTest, ParseStyle) { - std::stringstream input; - input << "<style name=\"foo\" parent=\"@style/fu\">" << std::endl - << " <item name=\"bar\">#ffffffff</item>" << std::endl - << " <item name=\"bat\">@string/hey</item>" << std::endl - << " <item name=\"baz\"><b>hey</b></item>" << std::endl - << "</style>" << std::endl; + std::string input = "<style name=\"foo\" parent=\"@style/fu\">\n" + " <item name=\"bar\">#ffffffff</item>\n" + " <item name=\"bat\">@string/hey</item>\n" + " <item name=\"baz\"><b>hey</b></item>\n" + "</style>"; ASSERT_TRUE(testParse(input)); const Style* style = findResource<Style>(ResourceName{ @@ -330,8 +320,7 @@ TEST_F(ResourceParserTest, ParseStyle) { } TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) { - std::stringstream input; - input << "<style name=\"foo\" parent=\"com.app:Theme\"/>" << std::endl; + std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>"; ASSERT_TRUE(testParse(input)); const Style* style = findResource<Style>( @@ -340,9 +329,34 @@ TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) { EXPECT_EQ(ResourceNameRef(u"com.app", ResourceType::kStyle, u"Theme"), style->parent.name); } +TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) { + std::string input = "<style xmlns:app=\"http://schemas.android.com/apk/res/android\"\n" + " name=\"foo\" parent=\"app:Theme\"/>"; + ASSERT_TRUE(testParse(input)); + + const Style* style = findResource<Style>(ResourceName{ + u"android", ResourceType::kStyle, u"foo" }); + ASSERT_NE(style, nullptr); + EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kStyle, u"Theme"), style->parent.name); +} + +TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) { + std::string input = + "<style xmlns:app=\"http://schemas.android.com/apk/res/android\" name=\"foo\">\n" + " <item name=\"app:bar\">0</item>\n" + "</style>"; + ASSERT_TRUE(testParse(input)); + + const Style* style = findResource<Style>(ResourceName{ + u"android", ResourceType::kStyle, u"foo" }); + ASSERT_NE(style, nullptr); + ASSERT_EQ(1u, style->entries.size()); + EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kAttr, u"bar"), + style->entries[0].key.name); +} + TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) { - std::stringstream input; - input << "<string name=\"foo\">@+id/bar</string>" << std::endl; + std::string input = "<string name=\"foo\">@+id/bar</string>"; ASSERT_TRUE(testParse(input)); const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"bar"}); @@ -350,11 +364,10 @@ TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) { } TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) { - std::stringstream input; - input << "<declare-styleable name=\"foo\">" << std::endl - << " <attr name=\"bar\" />" << std::endl - << " <attr name=\"bat\" format=\"string|reference\"/>" << std::endl - << "</declare-styleable>" << std::endl; + std::string input = "<declare-styleable name=\"foo\">\n" + " <attr name=\"bar\" />\n" + " <attr name=\"bat\" format=\"string|reference\"/>\n" + "</declare-styleable>"; ASSERT_TRUE(testParse(input)); const Attribute* attr = findResource<Attribute>(ResourceName{ @@ -376,12 +389,11 @@ TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) { } TEST_F(ResourceParserTest, ParseArray) { - std::stringstream input; - input << "<array name=\"foo\">" << std::endl - << " <item>@string/ref</item>" << std::endl - << " <item>hey</item>" << std::endl - << " <item>23</item>" << std::endl - << "</array>" << std::endl; + std::string input = "<array name=\"foo\">\n" + " <item>@string/ref</item>\n" + " <item>hey</item>\n" + " <item>23</item>\n" + "</array>"; ASSERT_TRUE(testParse(input)); const Array* array = findResource<Array>(ResourceName{ @@ -395,19 +407,16 @@ TEST_F(ResourceParserTest, ParseArray) { } TEST_F(ResourceParserTest, ParsePlural) { - std::stringstream input; - input << "<plurals name=\"foo\">" << std::endl - << " <item quantity=\"other\">apples</item>" << std::endl - << " <item quantity=\"one\">apple</item>" << std::endl - << "</plurals>" << std::endl - << std::endl; + std::string input = "<plurals name=\"foo\">\n" + " <item quantity=\"other\">apples</item>\n" + " <item quantity=\"one\">apple</item>\n" + "</plurals>"; ASSERT_TRUE(testParse(input)); } TEST_F(ResourceParserTest, ParseCommentsWithResource) { - std::stringstream input; - input << "<!-- This is a comment -->" << std::endl - << "<string name=\"foo\">Hi</string>" << std::endl; + std::string input = "<!-- This is a comment -->\n" + "<string name=\"foo\">Hi</string>"; ASSERT_TRUE(testParse(input)); const ResourceTableType* type; @@ -425,7 +434,7 @@ TEST_F(ResourceParserTest, ParseCommentsWithResource) { * (as an ID has no value). */ TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) { - std::stringstream input("<public type=\"id\" name=\"foo\"/>"); + std::string input = "<public type=\"id\" name=\"foo\"/>"; ASSERT_TRUE(testParse(input)); const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"foo" }); diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index 02be651..7f55395 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -318,12 +318,16 @@ bool ResourceTable::merge(ResourceTable&& other) { for (auto& otherType : other) { std::unique_ptr<ResourceTableType>& type = findOrCreateType(otherType->type); - if (type->publicStatus.isPublic && otherType->publicStatus.isPublic && - type->typeId != otherType->typeId) { - Logger::error() << "can not merge type '" << type->type << "': conflicting public IDs " - << "(" << type->typeId << " vs " << otherType->typeId << ")." - << std::endl; - return false; + if (otherType->publicStatus.isPublic) { + if (type->publicStatus.isPublic && type->typeId != otherType->typeId) { + Logger::error() << "can not merge type '" << type->type + << "': conflicting public IDs " + << "(" << type->typeId << " vs " << otherType->typeId << ")." + << std::endl; + return false; + } + type->publicStatus = std::move(otherType->publicStatus); + type->typeId = otherType->typeId; } for (auto& otherEntry : otherType->entries) { @@ -335,13 +339,16 @@ bool ResourceTable::merge(ResourceTable&& other) { } std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, *nameToAdd); - if (entry->publicStatus.isPublic && otherEntry->publicStatus.isPublic && - entry->entryId != otherEntry->entryId) { - Logger::error() << "can not merge entry '" << type->type << "/" << entry->name - << "': conflicting public IDs " - << "(" << entry->entryId << " vs " << entry->entryId << ")." - << std::endl; - return false; + if (otherEntry->publicStatus.isPublic) { + if (entry->publicStatus.isPublic && entry->entryId != otherEntry->entryId) { + Logger::error() << "can not merge entry '" << type->type << "/" << entry->name + << "': conflicting public IDs " + << "(" << entry->entryId << " vs " << entry->entryId << ")." + << std::endl; + return false; + } + entry->publicStatus = std::move(otherEntry->publicStatus); + entry->entryId = otherEntry->entryId; } for (ResourceConfigValue& otherValue : otherEntry->values) { diff --git a/tools/aapt2/Resolver.cpp b/tools/aapt2/ResourceTableResolver.cpp index ae006ab..0a9f521 100644 --- a/tools/aapt2/Resolver.cpp +++ b/tools/aapt2/ResourceTableResolver.cpp @@ -16,9 +16,9 @@ #include "Maybe.h" #include "NameMangler.h" -#include "Resolver.h" #include "Resource.h" #include "ResourceTable.h" +#include "ResourceTableResolver.h" #include "ResourceValues.h" #include "Util.h" @@ -29,8 +29,9 @@ namespace aapt { -Resolver::Resolver(std::shared_ptr<const ResourceTable> table, - std::shared_ptr<const android::AssetManager> sources) : +ResourceTableResolver::ResourceTableResolver( + std::shared_ptr<const ResourceTable> table, + std::shared_ptr<const android::AssetManager> sources) : mTable(table), mSources(sources) { const android::ResTable& resTable = mSources->getResources(false); const size_t packageCount = resTable.getBasePackageCount(); @@ -40,7 +41,7 @@ Resolver::Resolver(std::shared_ptr<const ResourceTable> table, } } -Maybe<ResourceId> Resolver::findId(const ResourceName& name) { +Maybe<ResourceId> ResourceTableResolver::findId(const ResourceName& name) { Maybe<Entry> result = findAttribute(name); if (result) { return result.value().id; @@ -48,7 +49,7 @@ Maybe<ResourceId> Resolver::findId(const ResourceName& name) { return {}; } -Maybe<Resolver::Entry> Resolver::findAttribute(const ResourceName& name) { +Maybe<IResolver::Entry> ResourceTableResolver::findAttribute(const ResourceName& name) { auto cacheIter = mCache.find(name); if (cacheIter != std::end(mCache)) { return Entry{ cacheIter->second.id, cacheIter->second.attr.get() }; @@ -97,12 +98,30 @@ Maybe<Resolver::Entry> Resolver::findAttribute(const ResourceName& name) { return {}; } +Maybe<ResourceName> ResourceTableResolver::findName(ResourceId resId) { + const android::ResTable& table = mSources->getResources(false); + + android::ResTable::resource_name resourceName; + if (!table.getResourceName(resId.id, false, &resourceName)) { + return {}; + } + + const ResourceType* type = parseResourceType(StringPiece16(resourceName.type, + resourceName.typeLen)); + assert(type); + return ResourceName{ + { resourceName.package, resourceName.packageLen }, + *type, + { resourceName.name, resourceName.nameLen } }; +} + /** * This is called when we need to lookup a resource name in the AssetManager. * Since the values in the AssetManager are not parsed like in a ResourceTable, * we must create Attribute objects here if we find them. */ -const Resolver::CacheEntry* Resolver::buildCacheEntry(const ResourceName& name) { +const ResourceTableResolver::CacheEntry* ResourceTableResolver::buildCacheEntry( + const ResourceName& name) { const android::ResTable& table = mSources->getResources(false); const StringPiece16 type16 = toString(name.type); diff --git a/tools/aapt2/ResourceTableResolver.h b/tools/aapt2/ResourceTableResolver.h new file mode 100644 index 0000000..83818c2 --- /dev/null +++ b/tools/aapt2/ResourceTableResolver.h @@ -0,0 +1,82 @@ +/* + * 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. + */ + +#ifndef AAPT_RESOURCE_TABLE_RESOLVER_H +#define AAPT_RESOURCE_TABLE_RESOLVER_H + +#include "Maybe.h" +#include "Resolver.h" +#include "Resource.h" +#include "ResourceTable.h" +#include "ResourceValues.h" + +#include <androidfw/AssetManager.h> +#include <androidfw/ResourceTypes.h> +#include <memory> +#include <vector> +#include <unordered_set> + +namespace aapt { + +/** + * Encapsulates the search of library sources as well as the local ResourceTable. + */ +class ResourceTableResolver : public IResolver { +public: + /** + * Creates a resolver with a local ResourceTable and an AssetManager + * loaded with library packages. + */ + ResourceTableResolver(std::shared_ptr<const ResourceTable> table, + std::shared_ptr<const android::AssetManager> sources); + + ResourceTableResolver(const ResourceTableResolver&) = delete; // Not copyable. + + virtual const std::u16string& getDefaultPackage() const override; + + virtual Maybe<ResourceId> findId(const ResourceName& name) override; + + virtual Maybe<Entry> findAttribute(const ResourceName& name) override; + + virtual Maybe<ResourceName> findName(ResourceId resId) override; + + const android::ResTable& getResTable() const; + +private: + struct CacheEntry { + ResourceId id; + std::unique_ptr<Attribute> attr; + }; + + const CacheEntry* buildCacheEntry(const ResourceName& name); + + std::shared_ptr<const ResourceTable> mTable; + std::shared_ptr<const android::AssetManager> mSources; + std::map<ResourceName, CacheEntry> mCache; + std::unordered_set<std::u16string> mIncludedPackages; +}; + +inline const std::u16string& ResourceTableResolver::getDefaultPackage() const { + return mTable->getPackage(); +} + +inline const android::ResTable& ResourceTableResolver::getResTable() const { + return mSources->getResources(false); +} + +} // namespace aapt + +#endif // AAPT_RESOURCE_TABLE_RESOLVER_H diff --git a/tools/aapt2/ScopedXmlPullParser.cpp b/tools/aapt2/ScopedXmlPullParser.cpp index d9ae72c..48da93e 100644 --- a/tools/aapt2/ScopedXmlPullParser.cpp +++ b/tools/aapt2/ScopedXmlPullParser.cpp @@ -76,6 +76,11 @@ const std::u16string& ScopedXmlPullParser::getNamespaceUri() const { return mParser->getNamespaceUri(); } +bool ScopedXmlPullParser::applyPackageAlias(std::u16string* package, + const std::u16string& defaultPackage) const { + return mParser->applyPackageAlias(package, defaultPackage); +} + const std::u16string& ScopedXmlPullParser::getElementNamespace() const { return mParser->getElementNamespace(); } diff --git a/tools/aapt2/ScopedXmlPullParser.h b/tools/aapt2/ScopedXmlPullParser.h index e660499..a040f60 100644 --- a/tools/aapt2/ScopedXmlPullParser.h +++ b/tools/aapt2/ScopedXmlPullParser.h @@ -52,25 +52,27 @@ public: ScopedXmlPullParser& operator=(const ScopedXmlPullParser&) = delete; ~ScopedXmlPullParser(); - Event getEvent() const; - const std::string& getLastError() const; - Event next(); + Event getEvent() const override; + const std::string& getLastError() const override; + Event next() override; - const std::u16string& getComment() const; - size_t getLineNumber() const; - size_t getDepth() const; + const std::u16string& getComment() const override; + size_t getLineNumber() const override; + size_t getDepth() const override; - const std::u16string& getText() const; + const std::u16string& getText() const override; - const std::u16string& getNamespacePrefix() const; - const std::u16string& getNamespaceUri() const; + const std::u16string& getNamespacePrefix() const override; + const std::u16string& getNamespaceUri() const override; + bool applyPackageAlias(std::u16string* package, const std::u16string& defaultPackage) + const override; - const std::u16string& getElementNamespace() const; - const std::u16string& getElementName() const; + const std::u16string& getElementNamespace() const override; + const std::u16string& getElementName() const override; - const_iterator beginAttributes() const; - const_iterator endAttributes() const; - size_t getAttributeCount() const; + const_iterator beginAttributes() const override; + const_iterator endAttributes() const override; + size_t getAttributeCount() const override; private: XmlPullParser* mParser; diff --git a/tools/aapt2/SourceXmlPullParser.cpp b/tools/aapt2/SourceXmlPullParser.cpp index cb6a3c0..8099044 100644 --- a/tools/aapt2/SourceXmlPullParser.cpp +++ b/tools/aapt2/SourceXmlPullParser.cpp @@ -17,6 +17,7 @@ #include <iostream> #include <string> +#include "Maybe.h" #include "SourceXmlPullParser.h" #include "Util.h" @@ -66,7 +67,25 @@ SourceXmlPullParser::Event SourceXmlPullParser::next() { } } - return getEvent(); + Event event = getEvent(); + + // Record namespace prefixes and package names so that we can do our own + // handling of references that use namespace aliases. + if (event == Event::kStartNamespace || event == Event::kEndNamespace) { + Maybe<std::u16string> result = util::extractPackageFromNamespace(getNamespaceUri()); + if (event == Event::kStartNamespace) { + if (result) { + mPackageAliases.emplace_back(getNamespacePrefix(), result.value()); + } + } else { + if (result) { + assert(mPackageAliases.back().second == result.value()); + mPackageAliases.pop_back(); + } + } + } + + return event; } SourceXmlPullParser::Event SourceXmlPullParser::getEvent() const { @@ -112,6 +131,22 @@ const std::u16string& SourceXmlPullParser::getNamespaceUri() const { return mEventQueue.front().data2; } +bool SourceXmlPullParser::applyPackageAlias(std::u16string* package, + const std::u16string& defaultPackage) const { + const auto endIter = mPackageAliases.rend(); + for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) { + if (iter->first == *package) { + if (iter->second.empty()) { + *package = defaultPackage; + } else { + *package = iter->second; + } + return true; + } + } + return false; +} + const std::u16string& SourceXmlPullParser::getElementNamespace() const { const Event currentEvent = getEvent(); if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) { diff --git a/tools/aapt2/SourceXmlPullParser.h b/tools/aapt2/SourceXmlPullParser.h index ba904ba..15936d6 100644 --- a/tools/aapt2/SourceXmlPullParser.h +++ b/tools/aapt2/SourceXmlPullParser.h @@ -17,6 +17,8 @@ #ifndef AAPT_SOURCE_XML_PULL_PARSER_H #define AAPT_SOURCE_XML_PULL_PARSER_H +#include "XmlPullParser.h" + #include <istream> #include <libexpat/expat.h> #include <queue> @@ -24,8 +26,6 @@ #include <string> #include <vector> -#include "XmlPullParser.h" - namespace aapt { class SourceXmlPullParser : public XmlPullParser { @@ -34,25 +34,28 @@ public: SourceXmlPullParser(const SourceXmlPullParser& rhs) = delete; ~SourceXmlPullParser(); - Event getEvent() const; - const std::string& getLastError() const; - Event next(); + Event getEvent() const override; + const std::string& getLastError() const override ; + Event next() override ; + + const std::u16string& getComment() const override; + size_t getLineNumber() const override; + size_t getDepth() const override; - const std::u16string& getComment() const; - size_t getLineNumber() const; - size_t getDepth() const; + const std::u16string& getText() const override; - const std::u16string& getText() const; + const std::u16string& getNamespacePrefix() const override; + const std::u16string& getNamespaceUri() const override; + bool applyPackageAlias(std::u16string* package, + const std::u16string& defaultPackage) const override; - const std::u16string& getNamespacePrefix() const; - const std::u16string& getNamespaceUri() const; - const std::u16string& getElementNamespace() const; - const std::u16string& getElementName() const; + const std::u16string& getElementNamespace() const override; + const std::u16string& getElementName() const override; - const_iterator beginAttributes() const; - const_iterator endAttributes() const; - size_t getAttributeCount() const; + const_iterator beginAttributes() const override; + const_iterator endAttributes() const override; + size_t getAttributeCount() const override; private: static void XMLCALL startNamespaceHandler(void* userData, const char* prefix, const char* uri); @@ -80,6 +83,7 @@ private: const std::u16string mEmpty; size_t mDepth; std::stack<std::u16string> mNamespaceUris; + std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases; }; } // namespace aapt diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp index b983a53..c19aa98 100644 --- a/tools/aapt2/StringPool.cpp +++ b/tools/aapt2/StringPool.cpp @@ -265,25 +265,38 @@ void StringPool::sort(const std::function<bool(const Entry&, const Entry&)>& cmp ); } -static uint8_t* encodeLength(uint8_t* data, size_t length) { - if (length > 0x7fu) { - *data++ = 0x80u | (0x000000ffu & (length >> 8)); +template <typename T> +static T* encodeLength(T* data, size_t length) { + static_assert(std::is_integral<T>::value, "wat."); + + constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1); + constexpr size_t kMaxSize = kMask - 1; + if (length > kMaxSize) { + *data++ = kMask | (kMaxSize & (length >> (sizeof(T) * 8))); } - *data++ = 0x000000ffu & length; + *data++ = length; return data; } -static size_t encodedLengthByteCount(size_t length) { - return length > 0x7fu ? 2 : 1; +template <typename T> +static size_t encodedLengthUnits(size_t length) { + static_assert(std::is_integral<T>::value, "wat."); + + constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1); + constexpr size_t kMaxSize = kMask - 1; + return length > kMaxSize ? 2 : 1; } -bool StringPool::flattenUtf8(BigBuffer* out, const StringPool& pool) { + +bool StringPool::flatten(BigBuffer* out, const StringPool& pool, bool utf8) { const size_t startIndex = out->size(); android::ResStringPool_header* header = out->nextBlock<android::ResStringPool_header>(); header->header.type = android::RES_STRING_POOL_TYPE; header->header.headerSize = sizeof(*header); header->stringCount = pool.size(); - header->flags |= android::ResStringPool_header::UTF8_FLAG; + if (utf8) { + header->flags |= android::ResStringPool_header::UTF8_FLAG; + } uint32_t* indices = pool.size() != 0 ? out->nextBlock<uint32_t>(pool.size()) : nullptr; @@ -300,25 +313,31 @@ bool StringPool::flattenUtf8(BigBuffer* out, const StringPool& pool) { *indices = out->size() - beforeStringsIndex; indices++; - std::string encoded = util::utf16ToUtf8(entry->value); + if (utf8) { + std::string encoded = util::utf16ToUtf8(entry->value); + + const size_t totalSize = encodedLengthUnits<char>(entry->value.size()) + + encodedLengthUnits<char>(encoded.length()) + + encoded.size() + 1; - const size_t stringByteLength = sizeof(char) * encoded.length(); - const size_t totalSize = encodedLengthByteCount(entry->value.size()) - + encodedLengthByteCount(encoded.length()) - + stringByteLength - + sizeof(char); + char* data = out->nextBlock<char>(totalSize); - uint8_t* data = out->nextBlock<uint8_t>(totalSize); + // First encode the actual UTF16 string length. + data = encodeLength(data, entry->value.size()); - // First encode the actual UTF16 string length. - data = encodeLength(data, entry->value.size()); + // Now encode the size of the converted UTF8 string. + data = encodeLength(data, encoded.length()); + strncpy(data, encoded.data(), encoded.size()); + } else { + const size_t totalSize = encodedLengthUnits<char16_t>(entry->value.size()) + + entry->value.size() + 1; - // Now encode the size of the converted UTF8 string. - data = encodeLength(data, encoded.length()); + char16_t* data = out->nextBlock<char16_t>(totalSize); - memcpy(data, encoded.data(), stringByteLength); - data += stringByteLength; - *data = 0; + // Encode the actual UTF16 string length. + data = encodeLength(data, entry->value.size()); + strncpy16(data, entry->value.data(), entry->value.size()); + } } out->align4(); @@ -364,4 +383,12 @@ bool StringPool::flattenUtf8(BigBuffer* out, const StringPool& pool) { return true; } +bool StringPool::flattenUtf8(BigBuffer* out, const StringPool& pool) { + return flatten(out, pool, true); +} + +bool StringPool::flattenUtf16(BigBuffer* out, const StringPool& pool) { + return flatten(out, pool, false); +} + } // namespace aapt diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h index 64772a4..14304a6 100644 --- a/tools/aapt2/StringPool.h +++ b/tools/aapt2/StringPool.h @@ -127,7 +127,7 @@ public: using const_iterator = std::vector<std::unique_ptr<Entry>>::const_iterator; static bool flattenUtf8(BigBuffer* out, const StringPool& pool); - static bool flatten(BigBuffer* out, const StringPool& pool); + static bool flattenUtf16(BigBuffer* out, const StringPool& pool); StringPool() = default; StringPool(const StringPool&) = delete; @@ -193,6 +193,8 @@ private: friend const_iterator begin(const StringPool& pool); friend const_iterator end(const StringPool& pool); + static bool flatten(BigBuffer* out, const StringPool& pool, bool utf8); + Ref makeRefImpl(const StringPiece16& str, const Context& context, bool unique); std::vector<std::unique_ptr<Entry>> mStrings; diff --git a/tools/aapt2/TableFlattener.cpp b/tools/aapt2/TableFlattener.cpp index 4aadadc..406e506 100644 --- a/tools/aapt2/TableFlattener.cpp +++ b/tools/aapt2/TableFlattener.cpp @@ -211,6 +211,7 @@ struct ValueFlattener : ConstValueVisitor { virtual void visitItem(const Item& item, ValueVisitorArgs&) override { result = item.flatten(*mOutValue); + mOutValue->res0 = 0; mOutValue->size = sizeof(*mOutValue); } @@ -508,9 +509,9 @@ bool TableFlattener::flatten(BigBuffer* out, const ResourceTable& table) { package->name[table.getPackage().length()] = 0; package->typeStrings = package->header.headerSize; - StringPool::flattenUtf8(out, typePool); + StringPool::flattenUtf16(out, typePool); package->keyStrings = out->size() - beforePackageIndex; - StringPool::flattenUtf8(out, keyPool); + StringPool::flattenUtf16(out, keyPool); if (symbolEntryData != nullptr) { for (size_t i = 0; i < symbolEntries.size(); i++) { diff --git a/tools/aapt2/Util.cpp b/tools/aapt2/Util.cpp index c2418eb..7adaf1e 100644 --- a/tools/aapt2/Util.cpp +++ b/tools/aapt2/Util.cpp @@ -28,6 +28,9 @@ namespace aapt { namespace util { +constexpr const char16_t* kSchemaAuto = u"http://schemas.android.com/apk/res-auto"; +constexpr const char16_t* kSchemaPrefix = u"http://schemas.android.com/apk/res/"; + static std::vector<std::string> splitAndTransform(const StringPiece& str, char sep, const std::function<char(char)>& f) { std::vector<std::string> parts; @@ -279,5 +282,17 @@ std::unique_ptr<uint8_t[]> copy(const BigBuffer& buffer) { return data; } +Maybe<std::u16string> extractPackageFromNamespace(const std::u16string& namespaceUri) { + if (stringStartsWith<char16_t>(namespaceUri, kSchemaPrefix)) { + StringPiece16 schemaPrefix = kSchemaPrefix; + StringPiece16 package = namespaceUri; + return package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size()) + .toString(); + } else if (namespaceUri == kSchemaAuto) { + return std::u16string(); + } + return {}; +} + } // namespace util } // namespace aapt diff --git a/tools/aapt2/Util.h b/tools/aapt2/Util.h index 9f9707c..6015d82 100644 --- a/tools/aapt2/Util.h +++ b/tools/aapt2/Util.h @@ -18,6 +18,7 @@ #define AAPT_UTIL_H #include "BigBuffer.h" +#include "Maybe.h" #include "StringPiece.h" #include <androidfw/ResourceTypes.h> @@ -277,6 +278,15 @@ inline Tokenizer<Char>::Tokenizer(BasicStringPiece<Char> str, Char sep) : mEnd(str, sep, BasicStringPiece<Char>(str.end(), 0)) { } +/** + * Returns a package name if the namespace URI is of the form: + * http://schemas.android.com/apk/res/<package> + * + * Special case: if namespaceUri is http://schemas.android.com/apk/res-auto, + * returns an empty package name. + */ +Maybe<std::u16string> extractPackageFromNamespace(const std::u16string& namespaceUri); + } // namespace util /** diff --git a/tools/aapt2/XliffXmlPullParser.cpp b/tools/aapt2/XliffXmlPullParser.cpp index f0950a3..31115f2 100644 --- a/tools/aapt2/XliffXmlPullParser.cpp +++ b/tools/aapt2/XliffXmlPullParser.cpp @@ -85,6 +85,11 @@ const std::u16string& XliffXmlPullParser::getNamespaceUri() const { return mParser->getNamespaceUri(); } +bool XliffXmlPullParser::applyPackageAlias(std::u16string* package, + const std::u16string& defaultPackage) const { + return mParser->applyPackageAlias(package, defaultPackage); +} + const std::u16string& XliffXmlPullParser::getElementNamespace() const { return mParser->getElementNamespace(); } diff --git a/tools/aapt2/XliffXmlPullParser.h b/tools/aapt2/XliffXmlPullParser.h index d4aa222..7791227 100644 --- a/tools/aapt2/XliffXmlPullParser.h +++ b/tools/aapt2/XliffXmlPullParser.h @@ -33,25 +33,27 @@ public: XliffXmlPullParser(const std::shared_ptr<XmlPullParser>& parser); XliffXmlPullParser(const XliffXmlPullParser& rhs) = delete; - Event getEvent() const; - const std::string& getLastError() const; - Event next(); + Event getEvent() const override; + const std::string& getLastError() const override; + Event next() override; - const std::u16string& getComment() const; - size_t getLineNumber() const; - size_t getDepth() const; + const std::u16string& getComment() const override; + size_t getLineNumber() const override; + size_t getDepth() const override; - const std::u16string& getText() const; + const std::u16string& getText() const override; - const std::u16string& getNamespacePrefix() const; - const std::u16string& getNamespaceUri() const; + const std::u16string& getNamespacePrefix() const override; + const std::u16string& getNamespaceUri() const override; + bool applyPackageAlias(std::u16string* package, const std::u16string& defaultPackage) + const override; - const std::u16string& getElementNamespace() const; - const std::u16string& getElementName() const; + const std::u16string& getElementNamespace() const override; + const std::u16string& getElementName() const override; - const_iterator beginAttributes() const; - const_iterator endAttributes() const; - size_t getAttributeCount() const; + const_iterator beginAttributes() const override; + const_iterator endAttributes() const override; + size_t getAttributeCount() const override; private: std::shared_ptr<XmlPullParser> mParser; diff --git a/tools/aapt2/XmlFlattener.cpp b/tools/aapt2/XmlFlattener.cpp index dd6f63a..650e624 100644 --- a/tools/aapt2/XmlFlattener.cpp +++ b/tools/aapt2/XmlFlattener.cpp @@ -36,56 +36,67 @@ namespace aapt { constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android"; -constexpr const char16_t* kSchemaAuto = u"http://schemas.android.com/apk/res-auto"; -constexpr const char16_t* kSchemaPrefix = u"http://schemas.android.com/apk/res/"; struct AttributeValueFlattener : ValueVisitor { - struct Args : ValueVisitorArgs { - Args(std::shared_ptr<Resolver> r, SourceLogger& s, android::Res_value& oV, - std::shared_ptr<XmlPullParser> p, bool& e, StringPool::Ref& rV, - std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>& sR) : - resolver(r), logger(s), outValue(oV), parser(p), error(e), rawValue(rV), - stringRefs(sR) { - } + AttributeValueFlattener( + std::shared_ptr<IResolver> resolver, SourceLogger* logger, + android::Res_value* outValue, std::shared_ptr<XmlPullParser> parser, bool* outError, + StringPool::Ref rawValue, std::u16string* defaultPackage, + std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>* outStringRefs) : + mResolver(resolver), mLogger(logger), mOutValue(outValue), mParser(parser), + mError(outError), mRawValue(rawValue), mDefaultPackage(defaultPackage), + mStringRefs(outStringRefs) { + } - std::shared_ptr<Resolver> resolver; - SourceLogger& logger; - android::Res_value& outValue; - std::shared_ptr<XmlPullParser> parser; - bool& error; - StringPool::Ref& rawValue; - std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>& stringRefs; - }; + void visit(Reference& reference, ValueVisitorArgs&) override { + // First see if we can convert the package name from a prefix to a real + // package name. + ResourceName aliasedName = reference.name; - void visit(Reference& reference, ValueVisitorArgs& a) override { - Args& args = static_cast<Args&>(a); + if (!reference.name.package.empty()) { + // Only if we specified a package do we look for its alias. + mParser->applyPackageAlias(&reference.name.package, *mDefaultPackage); + } else { + reference.name.package = *mDefaultPackage; + } - Maybe<ResourceId> result = args.resolver->findId(reference.name); + Maybe<ResourceId> result = mResolver->findId(reference.name); if (!result || !result.value().isValid()) { - args.logger.error(args.parser->getLineNumber()) + std::ostream& out = mLogger->error(mParser->getLineNumber()) << "unresolved reference '" - << reference.name - << "'." - << std::endl; - args.error = true; + << aliasedName + << "'"; + if (aliasedName != reference.name) { + out << " (aka '" << reference.name << "')"; + } + out << "'." << std::endl; + *mError = true; } else { reference.id = result.value(); - reference.flatten(args.outValue); + reference.flatten(*mOutValue); } } - void visit(String& string, ValueVisitorArgs& a) override { - Args& args = static_cast<Args&>(a); - - args.outValue.dataType = android::Res_value::TYPE_STRING; - args.stringRefs.emplace_back(args.rawValue, - reinterpret_cast<android::ResStringPool_ref*>(&args.outValue.data)); + void visit(String& string, ValueVisitorArgs&) override { + mOutValue->dataType = android::Res_value::TYPE_STRING; + mStringRefs->emplace_back( + mRawValue, + reinterpret_cast<android::ResStringPool_ref*>(mOutValue->data)); } - void visitItem(Item& item, ValueVisitorArgs& a) override { - Args& args = static_cast<Args&>(a); - item.flatten(args.outValue); + void visitItem(Item& item, ValueVisitorArgs&) override { + item.flatten(*mOutValue); } + +private: + std::shared_ptr<IResolver> mResolver; + SourceLogger* mLogger; + android::Res_value* mOutValue; + std::shared_ptr<XmlPullParser> mParser; + bool* mError; + StringPool::Ref mRawValue; + std::u16string* mDefaultPackage; + std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>* mStringRefs; }; struct XmlAttribute { @@ -100,7 +111,7 @@ static bool lessAttributeId(const XmlAttribute& a, uint32_t id) { } XmlFlattener::XmlFlattener(const std::shared_ptr<ResourceTable>& table, - const std::shared_ptr<Resolver>& resolver) : + const std::shared_ptr<IResolver>& resolver) : mTable(table), mResolver(resolver) { } @@ -184,8 +195,13 @@ Maybe<size_t> XmlFlattener::flatten(const Source& source, node->comment.index = -1; android::ResXMLTree_attrExt* elem = out.nextBlock<android::ResXMLTree_attrExt>(); - stringRefs.emplace_back( - pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns); + if (!parser->getElementNamespace().empty()) { + stringRefs.emplace_back( + pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns); + } else { + // The device doesn't think a string of size 0 is the same as null. + elem->ns.index = -1; + } stringRefs.emplace_back( pool.makeRef(parser->getElementName(), lowPriority), &elem->name); elem->attributeStart = sizeof(*elem); @@ -222,28 +238,25 @@ Maybe<size_t> XmlFlattener::flatten(const Source& source, } - StringPiece16 package; - if (util::stringStartsWith<char16_t>(attrIter->namespaceUri, kSchemaPrefix)) { - StringPiece16 schemaPrefix = kSchemaPrefix; - package = attrIter->namespaceUri; - package = package.substr(schemaPrefix.size(), - package.size() - schemaPrefix.size()); - } else if (attrIter->namespaceUri == kSchemaAuto && mResolver) { - package = mResolver->getDefaultPackage(); - } - - if (package.empty() || !mResolver) { + Maybe<std::u16string> package = util::extractPackageFromNamespace( + attrIter->namespaceUri); + if (!package || !mResolver) { // Attributes that have no resource ID (because they don't belong to a // package) should appear after those that do have resource IDs. Assign - // them some/ integer value that will appear after. + // them some integer value that will appear after. id = 0x80000000u | nextAttributeId++; nameRef = pool.makeRef(attrIter->name, StringPool::Context{ id }); } else { // Find the Attribute object via our Resolver. ResourceName attrName = { - package.toString(), ResourceType::kAttr, attrIter->name }; - Maybe<Resolver::Entry> result = mResolver->findAttribute(attrName); + package.value(), ResourceType::kAttr, attrIter->name }; + + if (attrName.package.empty()) { + attrName.package = options.defaultPackage; + } + + Maybe<IResolver::Entry> result = mResolver->findAttribute(attrName); if (!result || !result.value().id.isValid()) { logger.error(parser->getLineNumber()) << "unresolved attribute '" @@ -269,7 +282,7 @@ Maybe<size_t> XmlFlattener::flatten(const Source& source, // Put the attribute name into a package specific pool, since we don't // want to collapse names from different packages. - nameRef = packagePools[package.toString()].makeRef( + nameRef = packagePools[package.value()].makeRef( attrIter->name, StringPool::Context{ id }); } @@ -290,26 +303,32 @@ Maybe<size_t> XmlFlattener::flatten(const Source& source, for (auto entry : sortedAttributes) { android::ResXMLTree_attribute* attr = out.nextBlock<android::ResXMLTree_attribute>(); - stringRefs.emplace_back( - pool.makeRef(entry.xmlAttr->namespaceUri, lowPriority), &attr->ns); - StringPool::Ref rawValueRef = pool.makeRef(entry.xmlAttr->value, lowPriority); - stringRefs.emplace_back(rawValueRef, &attr->rawValue); + if (!entry.xmlAttr->namespaceUri.empty()) { + stringRefs.emplace_back( + pool.makeRef(entry.xmlAttr->namespaceUri, lowPriority), &attr->ns); + } else { + attr->ns.index = -1; + } + stringRefs.emplace_back(entry.nameRef, &attr->name); + attr->rawValue.index = -1; + + StringPool::Ref rawValueRef = pool.makeRef(entry.xmlAttr->value, lowPriority); if (entry.attr) { std::unique_ptr<Item> value = ResourceParser::parseItemForAttribute( - entry.xmlAttr->value, *entry.attr, mResolver->getDefaultPackage()); + entry.xmlAttr->value, *entry.attr); if (value) { - AttributeValueFlattener flattener; - value->accept(flattener, AttributeValueFlattener::Args{ + AttributeValueFlattener flattener( mResolver, - logger, - attr->typedValue, + &logger, + &attr->typedValue, parser, - error, + &error, rawValueRef, - stringRefs - }); + &options.defaultPackage, + &stringRefs); + value->accept(flattener, {}); } else if (!(entry.attr->typeMask & android::ResTable_map::TYPE_STRING)) { logger.error(parser->getLineNumber()) << "'" @@ -321,12 +340,14 @@ Maybe<size_t> XmlFlattener::flatten(const Source& source, error = true; } else { attr->typedValue.dataType = android::Res_value::TYPE_STRING; + stringRefs.emplace_back(rawValueRef, &attr->rawValue); stringRefs.emplace_back(rawValueRef, reinterpret_cast<android::ResStringPool_ref*>( &attr->typedValue.data)); } } else { attr->typedValue.dataType = android::Res_value::TYPE_STRING; + stringRefs.emplace_back(rawValueRef, &attr->rawValue); stringRefs.emplace_back(rawValueRef, reinterpret_cast<android::ResStringPool_ref*>( &attr->typedValue.data)); @@ -438,7 +459,7 @@ Maybe<size_t> XmlFlattener::flatten(const Source& source, resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex; // Flatten the StringPool. - StringPool::flattenUtf8(outBuffer, pool); + StringPool::flattenUtf16(outBuffer, pool); // Move the temporary BigBuffer into outBuffer-> outBuffer->appendBuffer(std::move(out)); diff --git a/tools/aapt2/XmlFlattener.h b/tools/aapt2/XmlFlattener.h index 540a5ef..60a500e 100644 --- a/tools/aapt2/XmlFlattener.h +++ b/tools/aapt2/XmlFlattener.h @@ -23,6 +23,8 @@ #include "Source.h" #include "XmlPullParser.h" +#include <string> + namespace aapt { /** @@ -34,6 +36,12 @@ class XmlFlattener { public: struct Options { /** + * The package to use when a reference has no package specified + * (or a namespace URI equals "http://schemas.android.com/apk/res-auto"). + */ + std::u16string defaultPackage; + + /** * If set, tells the XmlFlattener to strip out * attributes that have been introduced after * max SDK. @@ -46,7 +54,7 @@ public: * and attributes. */ XmlFlattener(const std::shared_ptr<ResourceTable>& table, - const std::shared_ptr<Resolver>& resolver); + const std::shared_ptr<IResolver>& resolver); XmlFlattener(const XmlFlattener&) = delete; // Not copyable. @@ -62,7 +70,7 @@ public: private: std::shared_ptr<ResourceTable> mTable; - std::shared_ptr<Resolver> mResolver; + std::shared_ptr<IResolver> mResolver; }; } // namespace aapt diff --git a/tools/aapt2/XmlFlattener_test.cpp b/tools/aapt2/XmlFlattener_test.cpp index a7d7ac6..d2139d0 100644 --- a/tools/aapt2/XmlFlattener_test.cpp +++ b/tools/aapt2/XmlFlattener_test.cpp @@ -33,30 +33,76 @@ namespace aapt { constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; -class XmlFlattenerTest : public ::testing::Test { -public: - virtual void SetUp() override { - std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>(); - table->setPackage(u"android"); - table->setPackageId(0x01); +struct MockResolver : public IResolver { + MockResolver(const StringPiece16& defaultPackage, + const std::map<ResourceName, ResourceId>& items) : + mPackage(defaultPackage.toString()), mAttr(false, ResTable_map::TYPE_ANY), + mItems(items) { + } - table->addResource(ResourceName{ {}, ResourceType::kAttr, u"id" }, - ResourceId{ 0x01010000 }, {}, {}, - util::make_unique<Attribute>(false, ResTable_map::TYPE_ANY)); + virtual const std::u16string& getDefaultPackage() const override { + return mPackage; + } - table->addResource(ResourceName{ {}, ResourceType::kId, u"test" }, - ResourceId{ 0x01020000 }, {}, {}, util::make_unique<Id>()); + virtual Maybe<ResourceId> findId(const ResourceName& name) override { + const auto iter = mItems.find(name); + if (iter != mItems.end()) { + return iter->second; + } + return {}; + } - mFlattener = std::make_shared<XmlFlattener>(nullptr, - std::make_shared<Resolver>(table, std::make_shared<AssetManager>())); + virtual Maybe<Entry> findAttribute(const ResourceName& name) override { + Maybe<ResourceId> result = findId(name); + if (result) { + if (name.type == ResourceType::kAttr) { + return Entry{ result.value(), &mAttr }; + } else { + return Entry{ result.value() }; + } + } + return {}; } - ::testing::AssertionResult testFlatten(std::istream& in, ResXMLTree* outTree) { + virtual Maybe<ResourceName> findName(ResourceId resId) override { + for (auto& p : mItems) { + if (p.second == resId) { + return p.first; + } + } + return {}; + } + + std::u16string mPackage; + Attribute mAttr; + std::map<ResourceName, ResourceId> mItems; +}; + +class XmlFlattenerTest : public ::testing::Test { +public: + virtual void SetUp() override { + std::shared_ptr<IResolver> resolver = std::make_shared<MockResolver>(u"android", + std::map<ResourceName, ResourceId>({ + { ResourceName{ u"android", ResourceType::kAttr, u"attr" }, + ResourceId{ 0x01010000u } }, + { ResourceName{ u"android", ResourceType::kId, u"id" }, + ResourceId{ 0x01020000u } }, + { ResourceName{ u"com.lib", ResourceType::kAttr, u"attr" }, + ResourceId{ 0x01010001u } }, + { ResourceName{ u"com.lib", ResourceType::kId, u"id" }, + ResourceId{ 0x01020001u } }})); + + mFlattener = std::make_shared<XmlFlattener>(nullptr, resolver); + } + + ::testing::AssertionResult testFlatten(const std::string& in, ResXMLTree* outTree) { std::stringstream input(kXmlPreamble); - input << in.rdbuf() << std::endl; + input << in << std::endl; std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(input); BigBuffer outBuffer(1024); - if (!mFlattener->flatten(Source{ "test" }, xmlParser, &outBuffer, {})) { + XmlFlattener::Options xmlOptions; + xmlOptions.defaultPackage = u"android"; + if (!mFlattener->flatten(Source{ "test" }, xmlParser, &outBuffer, xmlOptions)) { return ::testing::AssertionFailure(); } @@ -71,11 +117,22 @@ public: }; TEST_F(XmlFlattenerTest, ParseSimpleView) { - std::stringstream input; - input << "<View xmlns:android=\"http://schemas.android.com/apk/res/android\"" << std::endl - << " android:id=\"@id/test\">" << std::endl - << "</View>" << std::endl; + std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + " android:attr=\"@id/id\">\n" + "</View>"; + ResXMLTree tree; + ASSERT_TRUE(testFlatten(input, &tree)); + + while (tree.next() != ResXMLTree::END_DOCUMENT) { + ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); + } +} +TEST_F(XmlFlattenerTest, ParseViewWithPackageAlias) { + std::string input = "<View xmlns:ns1=\"http://schemas.android.com/apk/res/android\"\n" + " xmlns:ns2=\"http://schemas.android.com/apk/res/android\"\n" + " ns1:attr=\"@ns2:id/id\">\n" + "</View>"; ResXMLTree tree; ASSERT_TRUE(testFlatten(input, &tree)); @@ -84,4 +141,100 @@ TEST_F(XmlFlattenerTest, ParseSimpleView) { } } +::testing::AssertionResult attributeNameAndValueEquals(ResXMLTree* tree, size_t index, + ResourceId nameId, ResourceId valueId) { + if (index >= tree->getAttributeCount()) { + return ::testing::AssertionFailure() << "index " << index << " is out of bounds (" + << tree->getAttributeCount() << ")"; + } + + if (tree->getAttributeNameResID(index) != nameId.id) { + return ::testing::AssertionFailure() + << "attribute at index " << index << " has ID " + << ResourceId{ (uint32_t) tree->getAttributeNameResID(index) } + << ". Expected ID " << nameId; + } + + if (tree->getAttributeDataType(index) != Res_value::TYPE_REFERENCE) { + return ::testing::AssertionFailure() << "attribute at index " << index << " has value of " + << "type " << std::hex + << tree->getAttributeDataType(index) << std::dec + << ". Expected reference (" << std::hex + << Res_value::TYPE_REFERENCE << std::dec << ")"; + } + + if ((uint32_t) tree->getAttributeData(index) != valueId.id) { + return ::testing::AssertionFailure() + << "attribute at index " << index << " has value " << "with ID " + << ResourceId{ (uint32_t) tree->getAttributeData(index) } + << ". Expected ID " << valueId; + } + return ::testing::AssertionSuccess(); +} + +TEST_F(XmlFlattenerTest, ParseViewWithShadowedPackageAlias) { + std::string input = "<View xmlns:app=\"http://schemas.android.com/apk/res/android\"\n" + " app:attr=\"@app:id/id\">\n" + " <View xmlns:app=\"http://schemas.android.com/apk/res/com.lib\"\n" + " app:attr=\"@app:id/id\"/>\n" + "</View>"; + ResXMLTree tree; + ASSERT_TRUE(testFlatten(input, &tree)); + + while (tree.next() != ResXMLTree::START_TAG) { + ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); + ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT); + } + + ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010000u }, + ResourceId{ 0x01020000u })); + + while (tree.next() != ResXMLTree::START_TAG) { + ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); + ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT); + } + + ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010001u }, + ResourceId{ 0x01020001u })); +} + +TEST_F(XmlFlattenerTest, ParseViewWithLocalPackageAndAliasOfTheSameName) { + std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/com.lib\"\n" + " android:attr=\"@id/id\"/>"; + ResXMLTree tree; + ASSERT_TRUE(testFlatten(input, &tree)); + + while (tree.next() != ResXMLTree::START_TAG) { + ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); + ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT); + } + + // We expect the 'android:attr' to be converted to 'com.lib:attr' due to the namespace + // assignment. + // However, we didn't give '@id/id' a package, so it should use the default package + // 'android', and not be converted from 'android' to 'com.lib'. + ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010001u }, + ResourceId{ 0x01020000u })); +} + +/* + * The device ResXMLParser in libandroidfw differentiates between empty namespace and null + * namespace. + */ +TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) { + std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + " package=\"android\"/>"; + + ResXMLTree tree; + ASSERT_TRUE(testFlatten(input, &tree)); + + while (tree.next() != ResXMLTree::START_TAG) { + ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); + ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT); + } + + const StringPiece16 kPackage = u"package"; + EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0); +} + } // namespace aapt diff --git a/tools/aapt2/XmlPullParser.h b/tools/aapt2/XmlPullParser.h index 753405c..accfd30 100644 --- a/tools/aapt2/XmlPullParser.h +++ b/tools/aapt2/XmlPullParser.h @@ -72,15 +72,26 @@ public: */ virtual const std::u16string& getText() const = 0; - /** - * Namespace prefix is available for StartNamespace and EndNamespace. - */ + // + // Namespace prefix and URI are available for StartNamespace and EndNamespace. + // + virtual const std::u16string& getNamespacePrefix() const = 0; + virtual const std::u16string& getNamespaceUri() const = 0; - /** - * Namespace URI is available for StartNamespace. + /* + * Uses the current stack of namespaces to resolve the package. Eg: + * xmlns:app = "http://schemas.android.com/apk/res/com.android.app" + * ... + * android:text="@app:string/message" + * + * In this case, 'app' will be converted to 'com.android.app'. + * + * If xmlns:app="http://schemas.android.com/apk/res-auto", then + * 'package' will be set to 'defaultPackage'. */ - virtual const std::u16string& getNamespaceUri() const = 0; + virtual bool applyPackageAlias(std::u16string* package, + const std::u16string& defaultPackage) const = 0; // // These are available for StartElement and EndElement. diff --git a/tools/aapt2/data/lib/res/values/styles.xml b/tools/aapt2/data/lib/res/values/styles.xml index adb5c4f..4ce6333 100644 --- a/tools/aapt2/data/lib/res/values/styles.xml +++ b/tools/aapt2/data/lib/res/values/styles.xml @@ -3,4 +3,6 @@ <style name="Platform.AppCompat" parent="@android:style/Theme"> <item name="android:windowNoTitle">true</item> </style> + + <bool name="allow">true</bool> </resources> diff --git a/tools/aapt2/data/res/layout/main.xml b/tools/aapt2/data/res/layout/main.xml index 5160570..77ccedb 100644 --- a/tools/aapt2/data/res/layout/main.xml +++ b/tools/aapt2/data/res/layout/main.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:support="http://schemas.android.com/apk/res/android.appcompat" android:id="@+id/view" android:layout_width="match_parent" android:layout_height="wrap_content"> @@ -11,7 +12,7 @@ android:layout_width="1dp" android:text="@{user.name}" android:layout_height="match_parent" - app:layout_width="false" + app:layout_width="@support:bool/allow" app:flags="complex|weak" android:colorAccent="#ffffff"/> </LinearLayout> diff --git a/tools/aapt2/data/res/values/styles.xml b/tools/aapt2/data/res/values/styles.xml index c5dd276..d0b19a3 100644 --- a/tools/aapt2/data/res/values/styles.xml +++ b/tools/aapt2/data/res/values/styles.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<resources> +<resources xmlns:lib="http://schemas.android.com/apk/res/android.appcompat"> <style name="App" parent="android.appcompat:Platform.AppCompat"> <item name="android:background">@color/primary</item> <item name="android:colorPrimary">@color/primary</item> @@ -9,6 +9,7 @@ <attr name="custom" format="reference" /> <style name="Pop"> <item name="custom">@drawable/image</item> + <item name="android:focusable">@lib:bool/allow</item> </style> <string name="yo">@string/wow</string> |