diff options
Diffstat (limited to 'tools/aapt2')
49 files changed, 1325 insertions, 538 deletions
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index 05034c3..5ef4311 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 \ @@ -90,7 +90,8 @@ hostStaticLibs := \ libcutils \ libexpat \ libziparchive-host \ - libpng + libpng \ + libbase ifneq ($(strip $(USE_MINGW)),) hostStaticLibs += libz diff --git a/tools/aapt2/BinaryResourceParser.cpp b/tools/aapt2/BinaryResourceParser.cpp index 326a2ac..d16f63b 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) : @@ -401,6 +396,12 @@ bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) { } break; + case RES_TABLE_PUBLIC_TYPE: + if (!parsePublic(parser.getChunk())) { + return false; + } + break; + default: Logger::warn(mSource) << "unexpected chunk of type " @@ -434,6 +435,55 @@ bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) { return true; } +bool BinaryResourceParser::parsePublic(const ResChunk_header* chunk) { + const Public_header* header = convertTo<Public_header>(chunk); + + if (header->typeId == 0) { + Logger::error(mSource) + << "invalid type ID " << header->typeId << std::endl; + return false; + } + + const ResourceType* parsedType = parseResourceType(util::getString(mTypePool, + header->typeId - 1)); + if (!parsedType) { + Logger::error(mSource) + << "invalid type " << util::getString(mTypePool, header->typeId - 1) << std::endl; + return false; + } + + const uintptr_t chunkEnd = reinterpret_cast<uintptr_t>(chunk) + chunk->size; + const Public_entry* entry = reinterpret_cast<const Public_entry*>( + getChunkData(header->header)); + for (uint32_t i = 0; i < header->count; i++) { + if (reinterpret_cast<uintptr_t>(entry) + sizeof(*entry) > chunkEnd) { + Logger::error(mSource) + << "Public_entry extends beyond chunk." + << std::endl; + return false; + } + + const ResourceId resId = { mTable->getPackageId(), header->typeId, entry->entryId }; + const ResourceName name = { + mTable->getPackage(), + *parsedType, + util::getString(mKeyPool, entry->key.index).toString() }; + + SourceLine source; + if (mSourcePool.getError() == NO_ERROR) { + source.path = util::utf16ToUtf8(util::getString(mSourcePool, entry->source.index)); + source.line = entry->sourceLine; + } + + if (!mTable->markPublic(name, resId, source)) { + return false; + } + + entry++; + } + return true; +} + bool BinaryResourceParser::parseTypeSpec(const ResChunk_header* chunk) { if (mTypePool.getError() != NO_ERROR) { Logger::error(mSource) @@ -641,10 +691,6 @@ std::unique_ptr<Item> BinaryResourceParser::parseValue(const ResourceNameRef& na return util::make_unique<BinaryPrimitive>(nullType); } - if (value->dataType == ExtendedTypes::TYPE_SENTINEL) { - return util::make_unique<Sentinel>(); - } - if (value->dataType == ExtendedTypes::TYPE_RAW_STRING) { return util::make_unique<RawString>( mTable->getValueStringPool().makeRef(util::getString(mValuePool, value->data), diff --git a/tools/aapt2/BinaryResourceParser.h b/tools/aapt2/BinaryResourceParser.h index f95a0c8..32876cd 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); @@ -66,6 +66,7 @@ private: bool idToName(Reference* reference); bool parsePackage(const android::ResChunk_header* chunk); + bool parsePublic(const android::ResChunk_header* chunk); bool parseTypeSpec(const android::ResChunk_header* chunk); bool parseType(const android::ResChunk_header* chunk); @@ -92,7 +93,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.cpp b/tools/aapt2/JavaClassGenerator.cpp index 3f92f18..2bb0e65 100644 --- a/tools/aapt2/JavaClassGenerator.cpp +++ b/tools/aapt2/JavaClassGenerator.cpp @@ -94,12 +94,12 @@ void JavaClassGenerator::visit(const Styleable& styleable, ValueVisitorArgs& a) std::u16string* entryName = static_cast<GenArgs&>(a).entryName; // This must be sorted by resource ID. - std::vector<std::pair<ResourceId, StringPiece16>> sortedAttributes; + std::vector<std::pair<ResourceId, ResourceNameRef>> sortedAttributes; sortedAttributes.reserve(styleable.entries.size()); for (const auto& attr : styleable.entries) { assert(attr.id.isValid() && "no ID set for Styleable entry"); assert(attr.name.isValid() && "no name set for Styleable entry"); - sortedAttributes.emplace_back(attr.id, attr.name.entry); + sortedAttributes.emplace_back(attr.id, attr.name); } std::sort(sortedAttributes.begin(), sortedAttributes.end()); @@ -124,8 +124,15 @@ void JavaClassGenerator::visit(const Styleable& styleable, ValueVisitorArgs& a) for (size_t i = 0; i < attrCount; i++) { *out << " " << "public static" << finalModifier - << " int " << transform(*entryName) << "_" << transform(sortedAttributes[i].second) - << " = " << i << ";" << std::endl; + << " int " << transform(*entryName); + + // We may reference IDs from other packages, so prefix the entry name with + // the package. + const ResourceNameRef& itemName = sortedAttributes[i].second; + if (itemName.package != mTable->getPackage()) { + *out << "_" << transform(itemName.package); + } + *out << "_" << transform(itemName.entry) << " = " << i << ";" << std::endl; } } diff --git a/tools/aapt2/JavaClassGenerator_test.cpp b/tools/aapt2/JavaClassGenerator_test.cpp index 96bb10b..d4341b6 100644 --- a/tools/aapt2/JavaClassGenerator_test.cpp +++ b/tools/aapt2/JavaClassGenerator_test.cpp @@ -16,8 +16,9 @@ #include "JavaClassGenerator.h" #include "Linker.h" -#include "Resolver.h" +#include "MockResolver.h" #include "ResourceTable.h" +#include "ResourceTableResolver.h" #include "ResourceValues.h" #include "Util.h" @@ -84,6 +85,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 })); @@ -93,9 +95,8 @@ TEST_F(JavaClassGeneratorTest, EmitPackageMangledSymbols) { SourceLine{ "lib.xml", 33 }, util::make_unique<Id>())); ASSERT_TRUE(mTable->merge(std::move(table))); - std::shared_ptr<Resolver> resolver = std::make_shared<Resolver>(mTable, - std::make_shared<const android::AssetManager>()); - Linker linker(mTable, resolver); + Linker linker(mTable, std::make_shared<MockResolver>(mTable, + std::map<ResourceName, ResourceId>())); ASSERT_TRUE(linker.linkAndValidate()); JavaClassGenerator generator(mTable, {}); @@ -113,4 +114,32 @@ TEST_F(JavaClassGeneratorTest, EmitPackageMangledSymbols) { EXPECT_EQ(std::string::npos, output.find("int foo =")); } +TEST_F(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) { + std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); + styleable->entries.emplace_back(ResourceNameRef{ mTable->getPackage(), + ResourceType::kAttr, + u"bar" }); + styleable->entries.emplace_back(ResourceNameRef{ u"com.lib", ResourceType::kAttr, u"bar" }); + ASSERT_TRUE(mTable->addResource(ResourceName{ {}, ResourceType::kStyleable, u"Foo" }, {}, {}, + std::move(styleable))); + + std::shared_ptr<IResolver> resolver = std::make_shared<MockResolver>(mTable, + std::map<ResourceName, ResourceId>({ + { ResourceName{ u"android", ResourceType::kAttr, u"bar" }, + ResourceId{ 0x01, 0x01, 0x0000 } }, + { ResourceName{ u"com.lib", ResourceType::kAttr, u"bar" }, + ResourceId{ 0x02, 0x01, 0x0000 } }})); + + Linker linker(mTable, resolver); + ASSERT_TRUE(linker.linkAndValidate()); + + JavaClassGenerator generator(mTable, {}); + + std::stringstream out; + EXPECT_TRUE(generator.generate(mTable->getPackage(), out)); + std::string output = out.str(); + EXPECT_NE(std::string::npos, output.find("int Foo_bar =")); + EXPECT_NE(std::string::npos, output.find("int Foo_com_lib_bar =")); +} + } // namespace aapt diff --git a/tools/aapt2/Linker.cpp b/tools/aapt2/Linker.cpp index 4346c8b..a8b7a14 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) { } @@ -65,13 +67,21 @@ bool Linker::linkAndValidate() { usedIds[type->typeId].insert(entry->entryId); } - for (auto& valueConfig : entry->values) { - // Dispatch to the right method of this linker - // based on the value's type. - valueConfig.value->accept(*this, Args{ - ResourceNameRef{ mTable->getPackage(), type->type, entry->name }, - valueConfig.source - }); + if (entry->publicStatus.isPublic && entry->values.empty()) { + // A public resource has no values. It will not be encoded + // properly without a symbol table. This is a unresolved symbol. + addUnresolvedSymbol(ResourceNameRef{ + mTable->getPackage(), type->type, entry->name }, + entry->publicStatus.source); + } else { + for (auto& valueConfig : entry->values) { + // Dispatch to the right method of this linker + // based on the value's type. + valueConfig.value->accept(*this, Args{ + ResourceNameRef{ mTable->getPackage(), type->type, entry->name }, + valueConfig.source + }); + } } } } @@ -100,11 +110,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 +189,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 +240,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 { @@ -263,11 +278,6 @@ void Linker::visit(Styleable& styleable, ValueVisitorArgs& a) { } } -void Linker::visit(Sentinel& sentinel, ValueVisitorArgs& a) { - Args& args = static_cast<Args&>(a); - addUnresolvedSymbol(args.referrer, args.source); -} - void Linker::visit(Array& array, ValueVisitorArgs& a) { Args& args = static_cast<Args&>(a); diff --git a/tools/aapt2/Linker.h b/tools/aapt2/Linker.h index 9b911b7..9db64ab 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; @@ -88,7 +88,6 @@ private: void visit(Attribute& attribute, ValueVisitorArgs& args) override; void visit(Styleable& styleable, ValueVisitorArgs& args) override; void visit(Style& style, ValueVisitorArgs& args) override; - void visit(Sentinel& sentinel, ValueVisitorArgs& args) override; void visit(Array& array, ValueVisitorArgs& args) override; void visit(Plural& plural, ValueVisitorArgs& args) override; @@ -117,7 +116,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..3377f07 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> @@ -321,9 +323,17 @@ struct AaptOptions { Compile, }; + enum class PackageType { + StandardApp, + StaticLibrary, + }; + // The phase to process. Phase phase; + // The type of package to produce. + PackageType packageType = PackageType::StandardApp; + // Details about the app. AppInfo appInfo; @@ -371,6 +381,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 +444,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 +455,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 +505,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 +521,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 +657,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 +678,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 +713,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 +735,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 +778,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 << "'." @@ -775,7 +808,11 @@ bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outT // Generate the Java class file. if (options.generateJavaClass) { - JavaClassGenerator generator(outTable, {}); + JavaClassGenerator::Options javaOptions; + if (options.packageType == AaptOptions::PackageType::StaticLibrary) { + javaOptions.useFinal = false; + } + JavaClassGenerator generator(outTable, javaOptions); for (const std::u16string& package : linkedPackages) { Source outPath = options.generateJavaClass.value(); @@ -811,9 +848,26 @@ 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; + if (options.packageType == AaptOptions::PackageType::StaticLibrary) { + flattenerOptions.useExtendedChunks = true; + } + if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) { return false; } @@ -823,7 +877,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; @@ -960,6 +1014,7 @@ static AaptOptions prepareArgs(int argc, char** argv) { printCommandsAndDie(); } + bool isStaticLib = false; if (options.phase == AaptOptions::Phase::Compile) { flag::requiredFlag("--package", "Android package name", [&options](const StringPiece& arg) { @@ -987,6 +1042,8 @@ static AaptOptions prepareArgs(int argc, char** argv) { [&options](const StringPiece& arg) { options.generateJavaClass = Source{ arg.toString() }; }); + flag::optionalSwitch("--static-lib", "generate a static Android library", true, + &isStaticLib); } // Common flags for all steps. @@ -1010,6 +1067,10 @@ static AaptOptions prepareArgs(int argc, char** argv) { flag::usageAndDie(fullCommand); } + if (isStaticLib) { + options.packageType = AaptOptions::PackageType::StaticLibrary; + } + // Copy all the remaining arguments. for (const std::string& arg : flag::getArgs()) { options.input.push_back(Source{ arg }); @@ -1073,7 +1134,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/MockResolver.h b/tools/aapt2/MockResolver.h new file mode 100644 index 0000000..48ff6a6 --- /dev/null +++ b/tools/aapt2/MockResolver.h @@ -0,0 +1,93 @@ +/* + * 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_MOCK_RESOLVER_H +#define AAPT_MOCK_RESOLVER_H + +#include "Maybe.h" +#include "Resolver.h" +#include "Resource.h" +#include "ResourceTable.h" +#include "ResourceTableResolver.h" +#include "ResourceValues.h" +#include "StringPiece.h" + +#include <map> +#include <string> + +namespace aapt { + +struct MockResolver : public IResolver { + MockResolver(const std::shared_ptr<ResourceTable>& table, + const std::map<ResourceName, ResourceId>& items) : + mResolver(std::make_shared<ResourceTableResolver>( + table, std::make_shared<const android::AssetManager>())), + mAttr(false, android::ResTable_map::TYPE_ANY), mItems(items) { + } + + virtual Maybe<ResourceId> findId(const ResourceName& name) override { + Maybe<ResourceId> result = mResolver->findId(name); + if (result) { + return result; + } + + const auto iter = mItems.find(name); + if (iter != mItems.end()) { + return iter->second; + } + return {}; + } + + virtual Maybe<Entry> findAttribute(const ResourceName& name) override { + Maybe<Entry> tableResult = mResolver->findAttribute(name); + if (tableResult) { + return tableResult; + } + + Maybe<ResourceId> result = findId(name); + if (result) { + if (name.type == ResourceType::kAttr) { + return Entry{ result.value(), &mAttr }; + } else { + return Entry{ result.value() }; + } + } + return {}; + } + + virtual Maybe<ResourceName> findName(ResourceId resId) override { + Maybe<ResourceName> result = mResolver->findName(resId); + if (result) { + return result; + } + + for (auto& p : mItems) { + if (p.second == resId) { + return p.first; + } + } + return {}; + } + +private: + std::shared_ptr<ResourceTableResolver> mResolver; + Attribute mAttr; + std::map<ResourceName, ResourceId> mItems; +}; + +} // namespace aapt + +#endif // AAPT_MOCK_RESOLVER_H diff --git a/tools/aapt2/Resolver.h b/tools/aapt2/Resolver.h index cb2234d..cb9318e 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. @@ -65,47 +52,24 @@ 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; - - /** * 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); + virtual Maybe<Entry> findAttribute(const ResourceName& name) = 0; - 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; + /** + * 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..fa9ac07 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -96,6 +96,8 @@ struct ResourceNameRef { ResourceNameRef(ResourceNameRef&&) = default; ResourceNameRef(const ResourceName& rhs); ResourceNameRef(const StringPiece16& p, ResourceType t, const StringPiece16& e); + ResourceNameRef& operator=(const ResourceNameRef& rhs) = default; + ResourceNameRef& operator=(ResourceNameRef&& rhs) = default; ResourceNameRef& operator=(const ResourceName& rhs); ResourceName toResourceName() const; @@ -130,6 +132,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 +181,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..9468860 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -299,16 +299,12 @@ bool ResourceTable::markPublic(const ResourceNameRef& name, const ResourceId res type->publicStatus.isPublic = true; entry->publicStatus.isPublic = true; + entry->publicStatus.source = source; if (resId.isValid()) { type->typeId = resId.typeId(); entry->entryId = resId.entryId(); } - - if (entry->values.empty()) { - entry->values.push_back(ResourceConfigValue{ {}, source, {}, - util::make_unique<Sentinel>() }); - } return true; } @@ -318,12 +314,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 +335,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/ResourceTable.h b/tools/aapt2/ResourceTable.h index 3591d11..94bacd8 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -35,6 +35,7 @@ namespace aapt { */ struct Public { bool isPublic = false; + SourceLine source; std::u16string comment; }; 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..c8e8ab7 --- /dev/null +++ b/tools/aapt2/ResourceTableResolver.h @@ -0,0 +1,76 @@ +/* + * 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 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 android::ResTable& ResourceTableResolver::getResTable() const { + return mSources->getResources(false); +} + +} // namespace aapt + +#endif // AAPT_RESOURCE_TABLE_RESOLVER_H diff --git a/tools/aapt2/ResourceTypeExtensions.h b/tools/aapt2/ResourceTypeExtensions.h index 60e225e..dcbe923 100644 --- a/tools/aapt2/ResourceTypeExtensions.h +++ b/tools/aapt2/ResourceTypeExtensions.h @@ -30,6 +30,8 @@ namespace aapt { * future collisions. */ enum { + RES_TABLE_PUBLIC_TYPE = 0x000d, + /** * A chunk that holds the string pool * for source entries (path/to/source:line). @@ -51,13 +53,6 @@ enum { struct ExtendedTypes { enum { /** - * A sentinel value used when a resource is defined as - * public but it has no defined value yet. If we don't - * flatten it with some value, we will lose its name. - */ - TYPE_SENTINEL = 0xff, - - /** * A raw string value that hasn't had its escape sequences * processed nor whitespace removed. */ @@ -65,6 +60,38 @@ struct ExtendedTypes { }; }; +struct Public_header { + android::ResChunk_header header; + + /** + * The ID of the type this structure refers to. + */ + uint8_t typeId; + + /** + * Reserved. Must be 0. + */ + uint8_t res0; + + /** + * Reserved. Must be 0. + */ + uint16_t res1; + + /** + * Number of public entries. + */ + uint32_t count; +}; + +struct Public_entry { + uint16_t entryId; + uint16_t res0; + android::ResStringPool_ref key; + android::ResStringPool_ref source; + uint32_t sourceLine; +}; + /** * A chunk with type RES_TABLE_SYMBOL_TABLE_TYPE. * Following the header are count number of SymbolTable_entry diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index 3a6d65d..2bf38e4 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -217,25 +217,6 @@ void BinaryPrimitive::print(std::ostream& out) const { } } -bool Sentinel::isWeak() const { - return true; -} - -bool Sentinel::flatten(android::Res_value& outValue) const { - outValue.dataType = ExtendedTypes::TYPE_SENTINEL; - outValue.data = 0; - return true; -} - -Sentinel* Sentinel::clone(StringPool* /*newPool*/) const { - return new Sentinel(); -} - -void Sentinel::print(std::ostream& out) const { - out << "(sentinel)"; - return; -} - Attribute::Attribute(bool w, uint32_t t) : weak(w), typeMask(t) { } diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index e3352f3..f8ece6f 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -206,18 +206,6 @@ struct BinaryPrimitive : public BaseItem<BinaryPrimitive> { void print(std::ostream& out) const override; }; -/** - * Sentinel value that should be ignored in the final output. - * Mainly used as a placeholder for public entries with no - * values defined yet. - */ -struct Sentinel : public BaseItem<Sentinel> { - bool isWeak() const override; - bool flatten(android::Res_value& outValue) const override; - Sentinel* clone(StringPool* newPool) const override; - void print(std::ostream& out) const override; -}; - struct Attribute : public BaseValue<Attribute> { struct Symbol { Reference symbol; @@ -332,10 +320,6 @@ struct ValueVisitor { visitItem(primitive, args); } - virtual void visit(Sentinel& sentinel, ValueVisitorArgs& args) { - visitItem(sentinel, args); - } - virtual void visit(Attribute& attr, ValueVisitorArgs& args) {} virtual void visit(Style& style, ValueVisitorArgs& args) {} virtual void visit(Array& array, ValueVisitorArgs& args) {} @@ -377,10 +361,6 @@ struct ConstValueVisitor { visitItem(primitive, args); } - virtual void visit(const Sentinel& sentinel, ValueVisitorArgs& args) { - visitItem(sentinel, args); - } - virtual void visit(const Attribute& attr, ValueVisitorArgs& args) {} virtual void visit(const Style& style, ValueVisitorArgs& args) {} virtual void visit(const Array& array, ValueVisitorArgs& args) {} 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..aa0f1d5 100644 --- a/tools/aapt2/TableFlattener.cpp +++ b/tools/aapt2/TableFlattener.cpp @@ -31,8 +31,8 @@ namespace aapt { struct FlatEntry { - const ResourceEntry& entry; - const Value& value; + const ResourceEntry* entry; + const Value* value; uint32_t entryKey; uint32_t sourcePathKey; uint32_t sourceLine; @@ -48,10 +48,10 @@ public: mMap = mOut->nextBlock<android::ResTable_map_entry>(); mMap->key.index = flatEntry.entryKey; mMap->flags = android::ResTable_entry::FLAG_COMPLEX; - if (flatEntry.entry.publicStatus.isPublic) { + if (flatEntry.entry->publicStatus.isPublic) { mMap->flags |= android::ResTable_entry::FLAG_PUBLIC; } - if (flatEntry.value.isWeak()) { + if (flatEntry.value->isWeak()) { mMap->flags |= android::ResTable_entry::FLAG_WEAK; } @@ -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); } @@ -228,14 +229,14 @@ TableFlattener::TableFlattener(Options options) bool TableFlattener::flattenValue(BigBuffer* out, const FlatEntry& flatEntry, SymbolEntryVector* symbols) { - if (flatEntry.value.isItem()) { + if (flatEntry.value->isItem()) { android::ResTable_entry* entry = out->nextBlock<android::ResTable_entry>(); - if (flatEntry.entry.publicStatus.isPublic) { + if (flatEntry.entry->publicStatus.isPublic) { entry->flags |= android::ResTable_entry::FLAG_PUBLIC; } - if (flatEntry.value.isWeak()) { + if (flatEntry.value->isWeak()) { entry->flags |= android::ResTable_entry::FLAG_WEAK; } @@ -251,14 +252,14 @@ bool TableFlattener::flattenValue(BigBuffer* out, const FlatEntry& flatEntry, entry->size += sizeof(*sourceBlock); } - const Item* item = static_cast<const Item*>(&flatEntry.value); + const Item* item = static_cast<const Item*>(flatEntry.value); ValueFlattener flattener(out, symbols); item->accept(flattener, {}); return flattener.result; } MapFlattener flattener(out, flatEntry, symbols); - flatEntry.value.accept(flattener, {}); + flatEntry.value->accept(flattener, {}); return true; } @@ -372,6 +373,15 @@ bool TableFlattener::flatten(BigBuffer* out, const ResourceTable& table) { } } + const size_t beforePublicHeader = typeBlock.size(); + Public_header* publicHeader = nullptr; + if (mOptions.useExtendedChunks) { + publicHeader = typeBlock.nextBlock<Public_header>(); + publicHeader->header.type = RES_TABLE_PUBLIC_TYPE; + publicHeader->header.headerSize = sizeof(*publicHeader); + publicHeader->typeId = type->typeId; + } + // The binary resource table lists resource entries for each configuration. // We store them inverted, where a resource entry lists the values for each // configuration available. Here we reverse this to match the binary table. @@ -386,18 +396,35 @@ bool TableFlattener::flatten(BigBuffer* out, const ResourceTable& table) { return false; } + if (publicHeader && entry->publicStatus.isPublic) { + // Write the public status of this entry. + Public_entry* publicEntry = typeBlock.nextBlock<Public_entry>(); + publicEntry->entryId = static_cast<uint32_t>(entry->entryId); + publicEntry->key.index = static_cast<uint32_t>(keyIndex); + publicEntry->source.index = static_cast<uint32_t>(sourcePool.makeRef( + util::utf8ToUtf16(entry->publicStatus.source.path)).getIndex()); + publicEntry->sourceLine = static_cast<uint32_t>(entry->publicStatus.source.line); + publicHeader->count += 1; + } + for (const auto& configValue : entry->values) { data[configValue.config].push_back(FlatEntry{ - *entry, - *configValue.value, + entry, + configValue.value.get(), static_cast<uint32_t>(keyIndex), static_cast<uint32_t>(sourcePool.makeRef(util::utf8ToUtf16( - configValue.source.path)).getIndex()), + configValue.source.path)).getIndex()), static_cast<uint32_t>(configValue.source.line) }); } } + if (publicHeader) { + typeBlock.align4(); + publicHeader->header.size = + static_cast<uint32_t>(typeBlock.size() - beforePublicHeader); + } + // Begin flattening a configuration for the current type. for (const auto& entry : data) { const size_t typeHeaderStart = typeBlock.size(); @@ -415,13 +442,13 @@ bool TableFlattener::flatten(BigBuffer* out, const ResourceTable& table) { const size_t entryStart = typeBlock.size(); for (const FlatEntry& flatEntry : entry.second) { - assert(flatEntry.entry.entryId < type->entries.size()); - indices[flatEntry.entry.entryId] = typeBlock.size() - entryStart; + assert(flatEntry.entry->entryId < type->entries.size()); + indices[flatEntry.entry->entryId] = typeBlock.size() - entryStart; if (!flattenValue(&typeBlock, flatEntry, &symbolEntries)) { Logger::error() << "failed to flatten resource '" << ResourceNameRef { - table.getPackage(), type->type, flatEntry.entry.name } + table.getPackage(), type->type, flatEntry.entry->name } << "' for configuration '" << entry.first << "'." @@ -508,9 +535,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..b45cd9b 100644 --- a/tools/aapt2/XmlFlattener_test.cpp +++ b/tools/aapt2/XmlFlattener_test.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "Resolver.h" +#include "MockResolver.h" #include "ResourceTable.h" #include "ResourceValues.h" #include "SourceXmlPullParser.h" @@ -36,27 +36,29 @@ constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?> 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); - - table->addResource(ResourceName{ {}, ResourceType::kAttr, u"id" }, - ResourceId{ 0x01010000 }, {}, {}, - util::make_unique<Attribute>(false, ResTable_map::TYPE_ANY)); - - table->addResource(ResourceName{ {}, ResourceType::kId, u"test" }, - ResourceId{ 0x01020000 }, {}, {}, util::make_unique<Id>()); - - mFlattener = std::make_shared<XmlFlattener>(nullptr, - std::make_shared<Resolver>(table, std::make_shared<AssetManager>())); + std::shared_ptr<IResolver> resolver = std::make_shared<MockResolver>( + std::make_shared<ResourceTable>(), + 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(std::istream& in, ResXMLTree* outTree) { + ::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 +73,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 +97,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/Makefile b/tools/aapt2/data/lib/Makefile index 8f56c54..372c225 100644 --- a/tools/aapt2/data/lib/Makefile +++ b/tools/aapt2/data/lib/Makefile @@ -56,7 +56,7 @@ $(foreach d,$(PRIVATE_RESOURCE_TYPES),$(eval $(call make-collect-rule,$d))) # Link: out/package-unaligned.apk <- out/values-v4.apk out/drawable-v4.apk $(PRIVATE_APK_UNALIGNED): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_LIBS) AndroidManifest.xml - $(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_LIBS)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) + $(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_LIBS)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) --static-lib # R.java: gen/com/android/app/R.java <- out/resources.arsc # No action since R.java is generated when out/resources.arsc is. 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> |