diff options
Diffstat (limited to 'tools')
106 files changed, 3663 insertions, 1328 deletions
diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp index cb244eccfe21..641c34bd2dda 100644 --- a/tools/aapt/Package.cpp +++ b/tools/aapt/Package.cpp @@ -33,7 +33,7 @@ static const char* kNoCompressExt[] = { ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet", ".rtttl", ".imy", ".xmf", ".mp4", ".m4a", ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2", - ".amr", ".awb", ".wma", ".wmv" + ".amr", ".awb", ".wma", ".wmv", ".webm" }; /* fwd decls, so I can write this downward */ diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index e22e76de66c2..fb0fe38da1ff 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -1537,12 +1537,20 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil std::queue<CompileResourceWorkItem>& workQueue = table.getWorkQueue(); while (!workQueue.empty()) { CompileResourceWorkItem& workItem = workQueue.front(); - err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.file, &table, xmlFlags); + int xmlCompilationFlags = xmlFlags | XML_COMPILE_PARSE_VALUES + | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS; + if (!workItem.needsCompiling) { + xmlCompilationFlags &= ~XML_COMPILE_ASSIGN_ATTRIBUTE_IDS; + xmlCompilationFlags &= ~XML_COMPILE_PARSE_VALUES; + } + err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.xmlRoot, + workItem.file, &table, xmlCompilationFlags); + if (err == NO_ERROR) { assets->addResource(workItem.resPath.getPathLeaf(), - workItem.resPath, - workItem.file, - workItem.file->getResourceType()); + workItem.resPath, + workItem.file, + workItem.file->getResourceType()); } else { hasErrors = true; } @@ -1737,9 +1745,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil manifestFile->getGroupEntry(), manifestFile->getResourceType()); err = compileXmlFile(bundle, assets, String16(), manifestFile, - outManifestFile, &table, - XML_COMPILE_ASSIGN_ATTRIBUTE_IDS - | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES); + outManifestFile, &table, XML_COMPILE_STANDARD_RESOURCE & ~XML_COMPILE_STRIP_COMMENTS); if (err < NO_ERROR) { return err; } diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index d5a09d817b1e..0e470d924f5a 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -88,8 +88,11 @@ status_t compileXmlFile(const Bundle* bundle, root->setUTF8(true); } - bool hasErrors = false; + if (table->processBundleFormat(bundle, resourceName, target, root) != NO_ERROR) { + return UNKNOWN_ERROR; + } + bool hasErrors = false; if ((options&XML_COMPILE_ASSIGN_ATTRIBUTE_IDS) != 0) { status_t err = root->assignResourceIds(assets, table); if (err != NO_ERROR) { @@ -97,9 +100,11 @@ status_t compileXmlFile(const Bundle* bundle, } } - status_t err = root->parseValues(assets, table); - if (err != NO_ERROR) { - hasErrors = true; + if ((options&XML_COMPILE_PARSE_VALUES) != 0) { + status_t err = root->parseValues(assets, table); + if (err != NO_ERROR) { + hasErrors = true; + } } if (hasErrors) { @@ -114,7 +119,7 @@ status_t compileXmlFile(const Bundle* bundle, printf("Input XML Resource:\n"); root->print(); } - err = root->flatten(target, + status_t err = root->flatten(target, (options&XML_COMPILE_STRIP_COMMENTS) != 0, (options&XML_COMPILE_STRIP_RAW_VALUES) != 0); if (err != NO_ERROR) { @@ -4755,9 +4760,9 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle, newConfig.sdkVersion = sdkVersionToGenerate; sp<AaptFile> newFile = new AaptFile(target->getSourceFile(), AaptGroupEntry(newConfig), target->getResourceType()); - String8 resPath = String8::format("res/%s/%s", + String8 resPath = String8::format("res/%s/%s.xml", newFile->getGroupEntry().toDirName(target->getResourceType()).string(), - target->getSourceFile().getPathLeaf().string()); + String8(resourceName).string()); resPath.convertToResPath(); // Add a resource table entry. @@ -4784,9 +4789,11 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle, item.resourceName = resourceName; item.resPath = resPath; item.file = newFile; + item.xmlRoot = newRoot; + item.needsCompiling = false; // This step occurs after we parse/assign, so we don't need + // to do it again. mWorkQueue.push(item); } - return NO_ERROR; } @@ -4825,3 +4832,226 @@ void ResourceTable::getDensityVaryingResources( } } } + +static String16 buildNamespace(const String16& package) { + return String16("http://schemas.android.com/apk/res/") + package; +} + +static sp<XMLNode> findOnlyChildElement(const sp<XMLNode>& parent) { + const Vector<sp<XMLNode> >& children = parent->getChildren(); + sp<XMLNode> onlyChild; + for (size_t i = 0; i < children.size(); i++) { + if (children[i]->getType() != XMLNode::TYPE_CDATA) { + if (onlyChild != NULL) { + return NULL; + } + onlyChild = children[i]; + } + } + return onlyChild; +} + +/** + * Detects use of the `bundle' format and extracts nested resources into their own top level + * resources. The bundle format looks like this: + * + * <!-- res/drawable/bundle.xml --> + * <animated-vector xmlns:aapt="http://schemas.android.com/aapt"> + * <aapt:attr name="android:drawable"> + * <vector android:width="60dp" + * android:height="60dp"> + * <path android:name="v" + * android:fillColor="#000000" + * android:pathData="M300,70 l 0,-70 70,..." /> + * </vector> + * </aapt:attr> + * </animated-vector> + * + * When AAPT sees the <aapt:attr> tag, it will extract its single element and its children + * into a new high-level resource, assigning it a name and ID. Then value of the `name` + * attribute must be a resource attribute. That resource attribute is inserted into the parent + * with the reference to the extracted resource as the value. + * + * <!-- res/drawable/bundle.xml --> + * <animated-vector android:drawable="@drawable/bundle_1.xml"> + * </animated-vector> + * + * <!-- res/drawable/bundle_1.xml --> + * <vector android:width="60dp" + * android:height="60dp"> + * <path android:name="v" + * android:fillColor="#000000" + * android:pathData="M300,70 l 0,-70 70,..." /> + * </vector> + */ +status_t ResourceTable::processBundleFormat(const Bundle* bundle, + const String16& resourceName, + const sp<AaptFile>& target, + const sp<XMLNode>& root) { + Vector<sp<XMLNode> > namespaces; + if (root->getType() == XMLNode::TYPE_NAMESPACE) { + namespaces.push(root); + } + return processBundleFormatImpl(bundle, resourceName, target, root, &namespaces); +} + +status_t ResourceTable::processBundleFormatImpl(const Bundle* bundle, + const String16& resourceName, + const sp<AaptFile>& target, + const sp<XMLNode>& parent, + Vector<sp<XMLNode> >* namespaces) { + const String16 kAaptNamespaceUri16("http://schemas.android.com/aapt"); + const String16 kName16("name"); + const String16 kAttr16("attr"); + const String16 kAssetPackage16(mAssets->getPackage()); + + Vector<sp<XMLNode> >& children = parent->getChildren(); + for (size_t i = 0; i < children.size(); i++) { + const sp<XMLNode>& child = children[i]; + + if (child->getType() == XMLNode::TYPE_CDATA) { + continue; + } else if (child->getType() == XMLNode::TYPE_NAMESPACE) { + namespaces->push(child); + } + + if (child->getElementNamespace() != kAaptNamespaceUri16 || + child->getElementName() != kAttr16) { + status_t result = processBundleFormatImpl(bundle, resourceName, target, child, + namespaces); + if (result != NO_ERROR) { + return result; + } + + if (child->getType() == XMLNode::TYPE_NAMESPACE) { + namespaces->pop(); + } + continue; + } + + // This is the <aapt:attr> tag. Look for the 'name' attribute. + SourcePos source(child->getFilename(), child->getStartLineNumber()); + + sp<XMLNode> nestedRoot = findOnlyChildElement(child); + if (nestedRoot == NULL) { + source.error("<%s:%s> must have exactly one child element", + String8(child->getElementNamespace()).string(), + String8(child->getElementName()).string()); + return UNKNOWN_ERROR; + } + + // Find the special attribute 'parent-attr'. This attribute's value contains + // the resource attribute for which this element should be assigned in the parent. + const XMLNode::attribute_entry* attr = child->getAttribute(String16(), kName16); + if (attr == NULL) { + source.error("inline resource definition must specify an attribute via 'name'"); + return UNKNOWN_ERROR; + } + + // Parse the attribute name. + const char* errorMsg = NULL; + String16 attrPackage, attrType, attrName; + bool result = ResTable::expandResourceRef(attr->string.string(), + attr->string.size(), + &attrPackage, &attrType, &attrName, + &kAttr16, &kAssetPackage16, + &errorMsg, NULL); + if (!result) { + source.error("invalid attribute name for 'name': %s", errorMsg); + return UNKNOWN_ERROR; + } + + if (attrType != kAttr16) { + // The value of the 'name' attribute must be an attribute reference. + source.error("value of 'name' must be an attribute reference."); + return UNKNOWN_ERROR; + } + + // Generate a name for this nested resource and try to add it to the table. + // We do this in a loop because the name may be taken, in which case we will + // increment a suffix until we succeed. + String8 nestedResourceName; + String8 nestedResourcePath; + int suffix = 1; + while (true) { + // This child element will be extracted into its own resource file. + // Generate a name and path for it from its parent. + nestedResourceName = String8::format("%s_%d", + String8(resourceName).string(), suffix++); + nestedResourcePath = String8::format("res/%s/%s.xml", + target->getGroupEntry().toDirName(target->getResourceType()) + .string(), + nestedResourceName.string()); + + // Lookup or create the entry for this name. + sp<Entry> entry = getEntry(kAssetPackage16, + String16(target->getResourceType()), + String16(nestedResourceName), + source, + false, + &target->getGroupEntry().toParams(), + true); + if (entry == NULL) { + return UNKNOWN_ERROR; + } + + if (entry->getType() == Entry::TYPE_UNKNOWN) { + // The value for this resource has never been set, + // meaning we're good! + entry->setItem(source, String16(nestedResourcePath)); + break; + } + + // We failed (name already exists), so try with a different name + // (increment the suffix). + } + + if (bundle->getVerbose()) { + source.printf("generating nested resource %s:%s/%s", + mAssets->getPackage().string(), target->getResourceType().string(), + nestedResourceName.string()); + } + + // Build the attribute reference and assign it to the parent. + String16 nestedResourceRef = String16(String8::format("@%s:%s/%s", + mAssets->getPackage().string(), target->getResourceType().string(), + nestedResourceName.string())); + + String16 attrNs = buildNamespace(attrPackage); + if (parent->getAttribute(attrNs, attrName) != NULL) { + SourcePos(parent->getFilename(), parent->getStartLineNumber()) + .error("parent of nested resource already defines attribute '%s:%s'", + String8(attrPackage).string(), String8(attrName).string()); + return UNKNOWN_ERROR; + } + + // Add the reference to the inline resource. + parent->addAttribute(attrNs, attrName, nestedResourceRef); + + // Remove the <aapt:attr> child element from here. + children.removeAt(i); + i--; + + // Append all namespace declarations that we've seen on this branch in the XML tree + // to this resource. + // We do this because the order of namespace declarations and prefix usage is determined + // by the developer and we do not want to override any decisions. Be conservative. + for (size_t nsIndex = namespaces->size(); nsIndex > 0; nsIndex--) { + const sp<XMLNode>& ns = namespaces->itemAt(nsIndex - 1); + sp<XMLNode> newNs = XMLNode::newNamespace(ns->getFilename(), ns->getNamespacePrefix(), + ns->getNamespaceUri()); + newNs->addChild(nestedRoot); + nestedRoot = newNs; + } + + // Schedule compilation of the nested resource. + CompileResourceWorkItem workItem; + workItem.resPath = nestedResourcePath; + workItem.resourceName = String16(nestedResourceName); + workItem.xmlRoot = nestedRoot; + workItem.file = new AaptFile(target->getSourceFile(), target->getGroupEntry(), + target->getResourceType()); + mWorkQueue.push(workItem); + } + return NO_ERROR; +} diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h index c4bdf09d8b19..4b7b3cdcef2b 100644 --- a/tools/aapt/ResourceTable.h +++ b/tools/aapt/ResourceTable.h @@ -23,13 +23,14 @@ class ResourceTable; enum { XML_COMPILE_STRIP_COMMENTS = 1<<0, XML_COMPILE_ASSIGN_ATTRIBUTE_IDS = 1<<1, - XML_COMPILE_COMPACT_WHITESPACE = 1<<2, - XML_COMPILE_STRIP_WHITESPACE = 1<<3, - XML_COMPILE_STRIP_RAW_VALUES = 1<<4, - XML_COMPILE_UTF8 = 1<<5, + XML_COMPILE_PARSE_VALUES = 1 << 2, + XML_COMPILE_COMPACT_WHITESPACE = 1<<3, + XML_COMPILE_STRIP_WHITESPACE = 1<<4, + XML_COMPILE_STRIP_RAW_VALUES = 1<<5, + XML_COMPILE_UTF8 = 1<<6, XML_COMPILE_STANDARD_RESOURCE = - XML_COMPILE_STRIP_COMMENTS | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS + XML_COMPILE_STRIP_COMMENTS | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS | XML_COMPILE_PARSE_VALUES | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES }; @@ -83,6 +84,8 @@ struct CompileResourceWorkItem { String16 resourceName; String8 resPath; sp<AaptFile> file; + sp<XMLNode> xmlRoot; + bool needsCompiling = true; }; class ResourceTable : public ResTable::Accessor @@ -206,6 +209,12 @@ public: const sp<AaptFile>& file, const sp<XMLNode>& root); + status_t processBundleFormat(const Bundle* bundle, + const String16& resourceName, + const sp<AaptFile>& file, + const sp<XMLNode>& parent); + + sp<AaptFile> flatten(Bundle* bundle, const sp<const ResourceFilter>& filter, const bool isBase); @@ -586,6 +595,11 @@ private: Res_value* outValue); int getPublicAttributeSdkLevel(uint32_t attrId) const; + status_t processBundleFormatImpl(const Bundle* bundle, + const String16& resourceName, + const sp<AaptFile>& file, + const sp<XMLNode>& parent, + Vector<sp<XMLNode> >* namespaces); String16 mAssetsPackage; PackageType mPackageType; diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp index dc08eb806356..5b215daeb494 100644 --- a/tools/aapt/XMLNode.cpp +++ b/tools/aapt/XMLNode.cpp @@ -693,6 +693,12 @@ const Vector<sp<XMLNode> >& XMLNode::getChildren() const return mChildren; } + +Vector<sp<XMLNode> >& XMLNode::getChildren() +{ + return mChildren; +} + const String8& XMLNode::getFilename() const { return mFilename; @@ -717,6 +723,18 @@ const XMLNode::attribute_entry* XMLNode::getAttribute(const String16& ns, return NULL; } +bool XMLNode::removeAttribute(const String16& ns, const String16& name) +{ + for (size_t i = 0; i < mAttributes.size(); i++) { + const attribute_entry& ae(mAttributes.itemAt(i)); + if (ae.ns == ns && ae.name == name) { + removeAttribute(i); + return true; + } + } + return false; +} + XMLNode::attribute_entry* XMLNode::editAttribute(const String16& ns, const String16& name) { diff --git a/tools/aapt/XMLNode.h b/tools/aapt/XMLNode.h index b9e5cd574cdc..749bf9f59bf7 100644 --- a/tools/aapt/XMLNode.h +++ b/tools/aapt/XMLNode.h @@ -55,7 +55,7 @@ public: sp<XMLNode> newCData(const String8& filename) { return new XMLNode(filename); } - + enum type { TYPE_NAMESPACE, TYPE_ELEMENT, @@ -70,6 +70,7 @@ public: const String16& getElementNamespace() const; const String16& getElementName() const; const Vector<sp<XMLNode> >& getChildren() const; + Vector<sp<XMLNode> >& getChildren(); const String8& getFilename() const; @@ -97,6 +98,7 @@ public: const Vector<attribute_entry>& getAttributes() const; const attribute_entry* getAttribute(const String16& ns, const String16& name) const; + bool removeAttribute(const String16& ns, const String16& name); attribute_entry* editAttribute(const String16& ns, const String16& name); diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index ec29c3818bdc..d8e0aac678f0 100644 --- a/tools/aapt2/Android.mk +++ b/tools/aapt2/Android.mk @@ -58,8 +58,9 @@ sources := \ ResourceValues.cpp \ SdkConstants.cpp \ StringPool.cpp \ - XmlDom.cpp \ - XmlPullParser.cpp + xml/XmlDom.cpp \ + xml/XmlPullParser.cpp \ + xml/XmlUtil.cpp testSources := \ compile/IdAssigner_test.cpp \ @@ -90,8 +91,9 @@ testSources := \ ResourceUtils_test.cpp \ StringPool_test.cpp \ ValueVisitor_test.cpp \ - XmlDom_test.cpp \ - XmlPullParser_test.cpp + xml/XmlDom_test.cpp \ + xml/XmlPullParser_test.cpp \ + xml/XmlUtil_test.cpp toolSources := \ compile/Compile.cpp \ diff --git a/tools/aapt2/Diagnostics.h b/tools/aapt2/Diagnostics.h index 7ea26b3ed0df..ab4d284516e1 100644 --- a/tools/aapt2/Diagnostics.h +++ b/tools/aapt2/Diagnostics.h @@ -18,7 +18,6 @@ #define AAPT_DIAGNOSTICS_H #include "Source.h" - #include "util/StringPiece.h" #include "util/Util.h" diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index f2a1878d0dc5..c2ddb5c233c1 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -19,9 +19,8 @@ #include "ResourceUtils.h" #include "ResourceValues.h" #include "ValueVisitor.h" -#include "XmlPullParser.h" - #include "util/Util.h" +#include "xml/XmlPullParser.h" #include <sstream> @@ -29,26 +28,6 @@ namespace aapt { constexpr const char16_t* sXliffNamespaceUri = u"urn:oasis:names:tc:xliff:document:1.2"; -static Maybe<StringPiece16> findAttribute(XmlPullParser* parser, const StringPiece16& name) { - auto iter = parser->findAttribute(u"", name); - if (iter != parser->endAttributes()) { - return StringPiece16(util::trimWhitespace(iter->value)); - } - return {}; -} - -static Maybe<StringPiece16> findNonEmptyAttribute(XmlPullParser* parser, - const StringPiece16& name) { - auto iter = parser->findAttribute(u"", name); - if (iter != parser->endAttributes()) { - StringPiece16 trimmed = util::trimWhitespace(iter->value); - if (!trimmed.empty()) { - return trimmed; - } - } - return {}; -} - /** * Returns true if the element is <skip> or <eat-comment> and can be safely ignored. */ @@ -65,7 +44,7 @@ ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table, const S /** * Build a string from XML that converts nested elements into Span objects. */ -bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString, +bool ResourceParser::flattenXmlSubtree(xml::XmlPullParser* parser, std::u16string* outRawString, StyleString* outStyleString) { std::vector<Span> spanStack; @@ -74,9 +53,9 @@ bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* ou outStyleString->spans.clear(); util::StringBuilder builder; size_t depth = 1; - while (XmlPullParser::isGoodEvent(parser->next())) { - const XmlPullParser::Event event = parser->getEvent(); - if (event == XmlPullParser::Event::kEndElement) { + while (xml::XmlPullParser::isGoodEvent(parser->next())) { + const xml::XmlPullParser::Event event = parser->getEvent(); + if (event == xml::XmlPullParser::Event::kEndElement) { if (!parser->getElementNamespace().empty()) { // We already warned and skipped the start element, so just skip here too continue; @@ -91,11 +70,11 @@ bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* ou outStyleString->spans.push_back(spanStack.back()); spanStack.pop_back(); - } else if (event == XmlPullParser::Event::kText) { + } else if (event == xml::XmlPullParser::Event::kText) { outRawString->append(parser->getText()); builder.append(parser->getText()); - } else if (event == XmlPullParser::Event::kStartElement) { + } else if (event == xml::XmlPullParser::Event::kStartElement) { if (!parser->getElementNamespace().empty()) { if (parser->getElementNamespace() != sXliffNamespaceUri) { // Only warn if this isn't an xliff namespace. @@ -128,7 +107,7 @@ bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* ou spanStack.push_back(Span{ spanName, static_cast<uint32_t>(builder.str().size()) }); } - } else if (event == XmlPullParser::Event::kComment) { + } else if (event == xml::XmlPullParser::Event::kComment) { // Skip } else { assert(false); @@ -140,11 +119,11 @@ bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* ou return !error; } -bool ResourceParser::parse(XmlPullParser* parser) { +bool ResourceParser::parse(xml::XmlPullParser* parser) { bool error = false; const size_t depth = parser->getDepth(); - while (XmlPullParser::nextChildNode(parser, depth)) { - if (parser->getEvent() != XmlPullParser::Event::kStartElement) { + while (xml::XmlPullParser::nextChildNode(parser, depth)) { + if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { // Skip comments and text. continue; } @@ -159,7 +138,7 @@ bool ResourceParser::parse(XmlPullParser* parser) { break; }; - if (parser->getEvent() == XmlPullParser::Event::kBadDocument) { + if (parser->getEvent() == xml::XmlPullParser::Event::kBadDocument) { mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) << "xml parser error: " << parser->getLastError()); return false; @@ -167,10 +146,11 @@ bool ResourceParser::parse(XmlPullParser* parser) { return !error; } -static bool shouldStripResource(XmlPullParser* parser, const Maybe<std::u16string> productToMatch) { - assert(parser->getEvent() == XmlPullParser::Event::kStartElement); +static bool shouldStripResource(const xml::XmlPullParser* parser, + const Maybe<std::u16string> productToMatch) { + assert(parser->getEvent() == xml::XmlPullParser::Event::kStartElement); - if (Maybe<StringPiece16> maybeProduct = findNonEmptyAttribute(parser, u"product")) { + if (Maybe<StringPiece16> maybeProduct = xml::findNonEmptyAttribute(parser, u"product")) { if (!productToMatch) { if (maybeProduct.value() != u"default" && maybeProduct.value() != u"phone") { // We didn't specify a product and this is not a default product, so skip. @@ -229,20 +209,20 @@ static bool addResourcesToTable(ResourceTable* table, const ConfigDescription& c return !error; } -bool ResourceParser::parseResources(XmlPullParser* parser) { +bool ResourceParser::parseResources(xml::XmlPullParser* parser) { std::set<ResourceName> strippedResources; bool error = false; std::u16string comment; const size_t depth = parser->getDepth(); - while (XmlPullParser::nextChildNode(parser, depth)) { - const XmlPullParser::Event event = parser->getEvent(); - if (event == XmlPullParser::Event::kComment) { + while (xml::XmlPullParser::nextChildNode(parser, depth)) { + const xml::XmlPullParser::Event event = parser->getEvent(); + if (event == xml::XmlPullParser::Event::kComment) { comment = parser->getComment(); continue; } - if (event == XmlPullParser::Event::kText) { + if (event == xml::XmlPullParser::Event::kText) { if (!util::trimWhitespace(parser->getText()).empty()) { mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) << "plain text not allowed here"); @@ -251,7 +231,7 @@ bool ResourceParser::parseResources(XmlPullParser* parser) { continue; } - assert(event == XmlPullParser::Event::kStartElement); + assert(event == xml::XmlPullParser::Event::kStartElement); if (!parser->getElementNamespace().empty()) { // Skip unknown namespace. @@ -266,7 +246,7 @@ bool ResourceParser::parseResources(XmlPullParser* parser) { if (elementName == u"item") { // Items simply have their type encoded in the type attribute. - if (Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type")) { + if (Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type")) { elementName = maybeType.value().toString(); } else { mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) @@ -280,7 +260,7 @@ bool ResourceParser::parseResources(XmlPullParser* parser) { parsedResource.source = mSource.withLine(parser->getLineNumber()); parsedResource.comment = std::move(comment); - if (Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name")) { + if (Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name")) { parsedResource.name.entry = maybeName.value().toString(); } else if (elementName != u"public-group") { @@ -403,7 +383,7 @@ enum { * an Item. If allowRawValue is false, nullptr is returned in this * case. */ -std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, const uint32_t typeMask, +std::unique_ptr<Item> ResourceParser::parseXml(xml::XmlPullParser* parser, const uint32_t typeMask, const bool allowRawValue) { const size_t beginXmlLine = parser->getLineNumber(); @@ -432,10 +412,7 @@ std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, const uint if (processedItem) { // Fix up the reference. if (Reference* ref = valueCast<Reference>(processedItem.get())) { - if (Maybe<ResourceName> transformedName = - parser->transformPackage(ref->name.value(), u"")) { - ref->name = std::move(transformedName); - } + transformReferenceFromNamespace(parser, u"", ref); } return processedItem; } @@ -456,11 +433,11 @@ std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, const uint return {}; } -bool ResourceParser::parseString(XmlPullParser* parser, ParsedResource* outResource) { +bool ResourceParser::parseString(xml::XmlPullParser* parser, ParsedResource* outResource) { const Source source = mSource.withLine(parser->getLineNumber()); bool formatted = true; - if (Maybe<StringPiece16> formattedAttr = findAttribute(parser, u"formatted")) { + if (Maybe<StringPiece16> formattedAttr = xml::findAttribute(parser, u"formatted")) { if (!ResourceUtils::tryParseBool(formattedAttr.value(), &formatted)) { mDiag->error(DiagMessage(source) << "invalid value for 'formatted'. Must be a boolean"); return false; @@ -468,7 +445,7 @@ bool ResourceParser::parseString(XmlPullParser* parser, ParsedResource* outResou } bool translateable = mOptions.translatable; - if (Maybe<StringPiece16> translateableAttr = findAttribute(parser, u"translatable")) { + if (Maybe<StringPiece16> translateableAttr = xml::findAttribute(parser, u"translatable")) { if (!ResourceUtils::tryParseBool(translateableAttr.value(), &translateable)) { mDiag->error(DiagMessage(source) << "invalid value for 'translatable'. Must be a boolean"); @@ -495,7 +472,7 @@ bool ResourceParser::parseString(XmlPullParser* parser, ParsedResource* outResou return true; } -bool ResourceParser::parseColor(XmlPullParser* parser, ParsedResource* outResource) { +bool ResourceParser::parseColor(xml::XmlPullParser* parser, ParsedResource* outResource) { const Source source = mSource.withLine(parser->getLineNumber()); outResource->value = parseXml(parser, android::ResTable_map::TYPE_COLOR, kNoRawString); @@ -506,7 +483,7 @@ bool ResourceParser::parseColor(XmlPullParser* parser, ParsedResource* outResour return true; } -bool ResourceParser::parsePrimitive(XmlPullParser* parser, ParsedResource* outResource) { +bool ResourceParser::parsePrimitive(xml::XmlPullParser* parser, ParsedResource* outResource) { const Source source = mSource.withLine(parser->getLineNumber()); uint32_t typeMask = 0; @@ -540,10 +517,10 @@ bool ResourceParser::parsePrimitive(XmlPullParser* parser, ParsedResource* outRe return true; } -bool ResourceParser::parsePublic(XmlPullParser* parser, ParsedResource* outResource) { +bool ResourceParser::parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource) { const Source source = mSource.withLine(parser->getLineNumber()); - Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type"); + Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type"); if (!maybeType) { mDiag->error(DiagMessage(source) << "<public> must have a 'type' attribute"); return false; @@ -558,7 +535,7 @@ bool ResourceParser::parsePublic(XmlPullParser* parser, ParsedResource* outResou outResource->name.type = *parsedType; - if (Maybe<StringPiece16> maybeId = findNonEmptyAttribute(parser, u"id")) { + if (Maybe<StringPiece16> maybeId = xml::findNonEmptyAttribute(parser, u"id")) { android::Res_value val; bool result = android::ResTable::stringToInt(maybeId.value().data(), maybeId.value().size(), &val); @@ -580,10 +557,10 @@ bool ResourceParser::parsePublic(XmlPullParser* parser, ParsedResource* outResou return true; } -bool ResourceParser::parsePublicGroup(XmlPullParser* parser, ParsedResource* outResource) { +bool ResourceParser::parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource) { const Source source = mSource.withLine(parser->getLineNumber()); - Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type"); + Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type"); if (!maybeType) { mDiag->error(DiagMessage(source) << "<public-group> must have a 'type' attribute"); return false; @@ -596,7 +573,7 @@ bool ResourceParser::parsePublicGroup(XmlPullParser* parser, ParsedResource* out return false; } - Maybe<StringPiece16> maybeId = findNonEmptyAttribute(parser, u"first-id"); + Maybe<StringPiece16> maybeId = xml::findNonEmptyAttribute(parser, u"first-id"); if (!maybeId) { mDiag->error(DiagMessage(source) << "<public-group> must have a 'first-id' attribute"); return false; @@ -615,11 +592,11 @@ bool ResourceParser::parsePublicGroup(XmlPullParser* parser, ParsedResource* out std::u16string comment; bool error = false; const size_t depth = parser->getDepth(); - while (XmlPullParser::nextChildNode(parser, depth)) { - if (parser->getEvent() == XmlPullParser::Event::kComment) { + while (xml::XmlPullParser::nextChildNode(parser, depth)) { + if (parser->getEvent() == xml::XmlPullParser::Event::kComment) { comment = util::trimWhitespace(parser->getComment()).toString(); continue; - } else if (parser->getEvent() != XmlPullParser::Event::kStartElement) { + } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { // Skip text. continue; } @@ -628,20 +605,20 @@ bool ResourceParser::parsePublicGroup(XmlPullParser* parser, ParsedResource* out const std::u16string& elementNamespace = parser->getElementNamespace(); const std::u16string& elementName = parser->getElementName(); if (elementNamespace.empty() && elementName == u"public") { - Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name"); + Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name"); if (!maybeName) { mDiag->error(DiagMessage(itemSource) << "<public> must have a 'name' attribute"); error = true; continue; } - if (findNonEmptyAttribute(parser, u"id")) { + if (xml::findNonEmptyAttribute(parser, u"id")) { mDiag->error(DiagMessage(itemSource) << "'id' is ignored within <public-group>"); error = true; continue; } - if (findNonEmptyAttribute(parser, u"type")) { + if (xml::findNonEmptyAttribute(parser, u"type")) { mDiag->error(DiagMessage(itemSource) << "'type' is ignored within <public-group>"); error = true; continue; @@ -666,10 +643,10 @@ bool ResourceParser::parsePublicGroup(XmlPullParser* parser, ParsedResource* out return !error; } -bool ResourceParser::parseSymbol(XmlPullParser* parser, ParsedResource* outResource) { +bool ResourceParser::parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource) { const Source source = mSource.withLine(parser->getLineNumber()); - Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type"); + Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type"); if (!maybeType) { mDiag->error(DiagMessage(source) << "<" << parser->getElementName() << "> must have a " "'type' attribute"); @@ -715,17 +692,16 @@ static uint32_t parseFormatAttribute(const StringPiece16& str) { return mask; } - - -bool ResourceParser::parseAttr(XmlPullParser* parser, ParsedResource* outResource) { +bool ResourceParser::parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource) { outResource->source = mSource.withLine(parser->getLineNumber()); return parseAttrImpl(parser, outResource, false); } -bool ResourceParser::parseAttrImpl(XmlPullParser* parser, ParsedResource* outResource, bool weak) { +bool ResourceParser::parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource, + bool weak) { uint32_t typeMask = 0; - Maybe<StringPiece16> maybeFormat = findAttribute(parser, u"format"); + Maybe<StringPiece16> maybeFormat = xml::findAttribute(parser, u"format"); if (maybeFormat) { typeMask = parseFormatAttribute(maybeFormat.value()); if (typeMask == 0) { @@ -735,28 +711,62 @@ bool ResourceParser::parseAttrImpl(XmlPullParser* parser, ParsedResource* outRes } } - // If this is a declaration, the package name may be in the name. Separate these out. - // Eg. <attr name="android:text" /> - // No format attribute is allowed. - if (weak && !maybeFormat) { - StringPiece16 package, type, name; - ResourceUtils::extractResourceName(outResource->name.entry, &package, &type, &name); - if (type.empty() && !package.empty()) { - outResource->name.package = package.toString(); - outResource->name.entry = name.toString(); + Maybe<int32_t> maybeMin, maybeMax; + + if (Maybe<StringPiece16> maybeMinStr = xml::findAttribute(parser, u"min")) { + StringPiece16 minStr = util::trimWhitespace(maybeMinStr.value()); + if (!minStr.empty()) { + android::Res_value value; + if (android::ResTable::stringToInt(minStr.data(), minStr.size(), &value)) { + maybeMin = static_cast<int32_t>(value.data); + } + } + + if (!maybeMin) { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "invalid 'min' value '" << minStr << "'"); + return false; } } - std::vector<Attribute::Symbol> items; + if (Maybe<StringPiece16> maybeMaxStr = xml::findAttribute(parser, u"max")) { + StringPiece16 maxStr = util::trimWhitespace(maybeMaxStr.value()); + if (!maxStr.empty()) { + android::Res_value value; + if (android::ResTable::stringToInt(maxStr.data(), maxStr.size(), &value)) { + maybeMax = static_cast<int32_t>(value.data); + } + } + + if (!maybeMax) { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "invalid 'max' value '" << maxStr << "'"); + return false; + } + } + + if ((maybeMin || maybeMax) && (typeMask & android::ResTable_map::TYPE_INTEGER) == 0) { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "'min' and 'max' can only be used when format='integer'"); + return false; + } + + struct SymbolComparator { + bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) { + return a.symbol.name.value() < b.symbol.name.value(); + } + }; + + std::set<Attribute::Symbol, SymbolComparator> items; std::u16string comment; bool error = false; const size_t depth = parser->getDepth(); - while (XmlPullParser::nextChildNode(parser, depth)) { - if (parser->getEvent() == XmlPullParser::Event::kComment) { + while (xml::XmlPullParser::nextChildNode(parser, depth)) { + if (parser->getEvent() == xml::XmlPullParser::Event::kComment) { comment = util::trimWhitespace(parser->getComment()).toString(); continue; - } else if (parser->getEvent() != XmlPullParser::Event::kStartElement) { + } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { // Skip text. continue; } @@ -785,15 +795,27 @@ bool ResourceParser::parseAttrImpl(XmlPullParser* parser, ParsedResource* outRes } if (Maybe<Attribute::Symbol> s = parseEnumOrFlagItem(parser, elementName)) { + Attribute::Symbol& symbol = s.value(); ParsedResource childResource; - childResource.name = s.value().symbol.name.value(); + childResource.name = symbol.symbol.name.value(); childResource.source = itemSource; childResource.value = util::make_unique<Id>(); outResource->childResources.push_back(std::move(childResource)); - s.value().symbol.setComment(std::move(comment)); - s.value().symbol.setSource(itemSource); - items.push_back(std::move(s.value())); + symbol.symbol.setComment(std::move(comment)); + symbol.symbol.setSource(itemSource); + + auto insertResult = items.insert(std::move(symbol)); + if (!insertResult.second) { + const Attribute::Symbol& existingSymbol = *insertResult.first; + mDiag->error(DiagMessage(itemSource) + << "duplicate symbol '" << existingSymbol.symbol.name.value().entry + << "'"); + + mDiag->note(DiagMessage(existingSymbol.symbol.getSource()) + << "first defined here"); + error = true; + } } else { error = true; } @@ -810,23 +832,30 @@ bool ResourceParser::parseAttrImpl(XmlPullParser* parser, ParsedResource* outRes } std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak); - attr->symbols.swap(items); + attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end()); attr->typeMask = typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY); + if (maybeMin) { + attr->minInt = maybeMin.value(); + } + + if (maybeMax) { + attr->maxInt = maybeMax.value(); + } outResource->value = std::move(attr); return true; } -Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(XmlPullParser* parser, +Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(xml::XmlPullParser* parser, const StringPiece16& tag) { const Source source = mSource.withLine(parser->getLineNumber()); - Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name"); + Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name"); if (!maybeName) { mDiag->error(DiagMessage(source) << "no attribute 'name' found for tag <" << tag << ">"); return {}; } - Maybe<StringPiece16> maybeValue = findNonEmptyAttribute(parser, u"value"); + Maybe<StringPiece16> maybeValue = xml::findNonEmptyAttribute(parser, u"value"); if (!maybeValue) { mDiag->error(DiagMessage(source) << "no attribute 'value' found for tag <" << tag << ">"); return {}; @@ -845,12 +874,19 @@ Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(XmlPullParser* pars val.data }; } -static Maybe<ResourceName> parseXmlAttributeName(StringPiece16 str) { +static Maybe<Reference> parseXmlAttributeName(StringPiece16 str) { str = util::trimWhitespace(str); - const char16_t* const start = str.data(); + const char16_t* start = str.data(); const char16_t* const end = start + str.size(); const char16_t* p = start; + Reference ref; + if (p != end && *p == u'*') { + ref.privateReference = true; + start++; + p++; + } + StringPiece16 package; StringPiece16 name; while (p != end) { @@ -862,28 +898,28 @@ static Maybe<ResourceName> parseXmlAttributeName(StringPiece16 str) { p++; } - return ResourceName(package.toString(), ResourceType::kAttr, + ref.name = ResourceName(package.toString(), ResourceType::kAttr, name.empty() ? str.toString() : name.toString()); + return Maybe<Reference>(std::move(ref)); } -bool ResourceParser::parseStyleItem(XmlPullParser* parser, Style* style) { +bool ResourceParser::parseStyleItem(xml::XmlPullParser* parser, Style* style) { const Source source = mSource.withLine(parser->getLineNumber()); - Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name"); + Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name"); if (!maybeName) { mDiag->error(DiagMessage(source) << "<item> must have a 'name' attribute"); return false; } - Maybe<ResourceName> maybeKey = parseXmlAttributeName(maybeName.value()); + Maybe<Reference> maybeKey = parseXmlAttributeName(maybeName.value()); if (!maybeKey) { mDiag->error(DiagMessage(source) << "invalid attribute name '" << maybeName.value() << "'"); return false; } - if (Maybe<ResourceName> transformedName = parser->transformPackage(maybeKey.value(), u"")) { - maybeKey = std::move(transformedName); - } + transformReferenceFromNamespace(parser, u"", &maybeKey.value()); + maybeKey.value().setSource(source); std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString); if (!value) { @@ -891,15 +927,15 @@ bool ResourceParser::parseStyleItem(XmlPullParser* parser, Style* style) { return false; } - style->entries.push_back(Style::Entry{ Reference(maybeKey.value()), std::move(value) }); + style->entries.push_back(Style::Entry{ std::move(maybeKey.value()), std::move(value) }); return true; } -bool ResourceParser::parseStyle(XmlPullParser* parser, ParsedResource* outResource) { +bool ResourceParser::parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource) { const Source source = mSource.withLine(parser->getLineNumber()); std::unique_ptr<Style> style = util::make_unique<Style>(); - Maybe<StringPiece16> maybeParent = findAttribute(parser, u"parent"); + Maybe<StringPiece16> maybeParent = xml::findAttribute(parser, u"parent"); if (maybeParent) { // If the parent is empty, we don't have a parent, but we also don't infer either. if (!maybeParent.value().empty()) { @@ -910,10 +946,9 @@ bool ResourceParser::parseStyle(XmlPullParser* parser, ParsedResource* outResour return false; } - if (Maybe<ResourceName> transformedName = - parser->transformPackage(style->parent.value().name.value(), u"")) { - style->parent.value().name = std::move(transformedName); - } + // Transform the namespace prefix to the actual package name, and mark the reference as + // private if appropriate. + transformReferenceFromNamespace(parser, u"", &style->parent.value()); } } else { @@ -922,15 +957,15 @@ bool ResourceParser::parseStyle(XmlPullParser* parser, ParsedResource* outResour size_t pos = styleName.find_last_of(u'.'); if (pos != std::string::npos) { style->parentInferred = true; - style->parent = Reference( - ResourceName({}, ResourceType::kStyle, styleName.substr(0, pos))); + style->parent = Reference(ResourceName({}, ResourceType::kStyle, + styleName.substr(0, pos))); } } bool error = false; const size_t depth = parser->getDepth(); - while (XmlPullParser::nextChildNode(parser, depth)) { - if (parser->getEvent() != XmlPullParser::Event::kStartElement) { + while (xml::XmlPullParser::nextChildNode(parser, depth)) { + if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { // Skip text and comments. continue; } @@ -955,15 +990,15 @@ bool ResourceParser::parseStyle(XmlPullParser* parser, ParsedResource* outResour return true; } -bool ResourceParser::parseArray(XmlPullParser* parser, ParsedResource* outResource, +bool ResourceParser::parseArray(xml::XmlPullParser* parser, ParsedResource* outResource, uint32_t typeMask) { const Source source = mSource.withLine(parser->getLineNumber()); std::unique_ptr<Array> array = util::make_unique<Array>(); bool error = false; const size_t depth = parser->getDepth(); - while (XmlPullParser::nextChildNode(parser, depth)) { - if (parser->getEvent() != XmlPullParser::Event::kStartElement) { + while (xml::XmlPullParser::nextChildNode(parser, depth)) { + if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { // Skip text and comments. continue; } @@ -996,14 +1031,14 @@ bool ResourceParser::parseArray(XmlPullParser* parser, ParsedResource* outResour return true; } -bool ResourceParser::parsePlural(XmlPullParser* parser, ParsedResource* outResource) { +bool ResourceParser::parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource) { const Source source = mSource.withLine(parser->getLineNumber()); std::unique_ptr<Plural> plural = util::make_unique<Plural>(); bool error = false; const size_t depth = parser->getDepth(); - while (XmlPullParser::nextChildNode(parser, depth)) { - if (parser->getEvent() != XmlPullParser::Event::kStartElement) { + while (xml::XmlPullParser::nextChildNode(parser, depth)) { + if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { // Skip text and comments. continue; } @@ -1012,16 +1047,15 @@ bool ResourceParser::parsePlural(XmlPullParser* parser, ParsedResource* outResou const std::u16string& elementNamespace = parser->getElementNamespace(); const std::u16string& elementName = parser->getElementName(); if (elementNamespace.empty() && elementName == u"item") { - const auto endAttrIter = parser->endAttributes(); - auto attrIter = parser->findAttribute(u"", u"quantity"); - if (attrIter == endAttrIter || attrIter->value.empty()) { + Maybe<StringPiece16> maybeQuantity = xml::findNonEmptyAttribute(parser, u"quantity"); + if (!maybeQuantity) { mDiag->error(DiagMessage(itemSource) << "<item> in <plurals> requires attribute " << "'quantity'"); error = true; continue; } - StringPiece16 trimmedQuantity = util::trimWhitespace(attrIter->value); + StringPiece16 trimmedQuantity = util::trimWhitespace(maybeQuantity.value()); size_t index = 0; if (trimmedQuantity == u"zero") { index = Plural::Zero; @@ -1071,7 +1105,7 @@ bool ResourceParser::parsePlural(XmlPullParser* parser, ParsedResource* outResou return true; } -bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser, ParsedResource* outResource) { +bool ResourceParser::parseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* outResource) { const Source source = mSource.withLine(parser->getLineNumber()); std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); @@ -1081,11 +1115,11 @@ bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser, ParsedResource std::u16string comment; bool error = false; const size_t depth = parser->getDepth(); - while (XmlPullParser::nextChildNode(parser, depth)) { - if (parser->getEvent() == XmlPullParser::Event::kComment) { + while (xml::XmlPullParser::nextChildNode(parser, depth)) { + if (parser->getEvent() == xml::XmlPullParser::Event::kComment) { comment = util::trimWhitespace(parser->getComment()).toString(); continue; - } else if (parser->getEvent() != XmlPullParser::Event::kStartElement) { + } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { // Ignore text. continue; } @@ -1094,17 +1128,29 @@ bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser, ParsedResource const std::u16string& elementNamespace = parser->getElementNamespace(); const std::u16string& elementName = parser->getElementName(); if (elementNamespace.empty() && elementName == u"attr") { - const auto endAttrIter = parser->endAttributes(); - auto attrIter = parser->findAttribute(u"", u"name"); - if (attrIter == endAttrIter || attrIter->value.empty()) { + Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name"); + if (!maybeName) { mDiag->error(DiagMessage(itemSource) << "<attr> tag must have a 'name' attribute"); error = true; continue; } + // If this is a declaration, the package name may be in the name. Separate these out. + // Eg. <attr name="android:text" /> + Maybe<Reference> maybeRef = parseXmlAttributeName(maybeName.value()); + if (!maybeRef) { + mDiag->error(DiagMessage(itemSource) << "<attr> tag has invalid name '" + << maybeName.value() << "'"); + error = true; + continue; + } + + Reference& childRef = maybeRef.value(); + xml::transformReferenceFromNamespace(parser, u"", &childRef); + // Create the ParsedResource that will add the attribute to the table. ParsedResource childResource; - childResource.name = ResourceName({}, ResourceType::kAttr, attrIter->value); + childResource.name = childRef.name.value(); childResource.source = itemSource; childResource.comment = std::move(comment); @@ -1114,7 +1160,6 @@ bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser, ParsedResource } // Create the reference to this attribute. - Reference childRef(childResource.name); childRef.setComment(childResource.comment); childRef.setSource(itemSource); styleable->entries.push_back(std::move(childRef)); diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h index 18101ee111c9..1150758b930e 100644 --- a/tools/aapt2/ResourceParser.h +++ b/tools/aapt2/ResourceParser.h @@ -22,10 +22,9 @@ #include "ResourceTable.h" #include "ResourceValues.h" #include "StringPool.h" -#include "XmlPullParser.h" - #include "util/Maybe.h" #include "util/StringPiece.h" +#include "xml/XmlPullParser.h" #include <memory> @@ -57,7 +56,7 @@ public: ResourceParser(const ResourceParser&) = delete; // No copy. - bool parse(XmlPullParser* parser); + bool parse(xml::XmlPullParser* parser); private: /* @@ -66,7 +65,7 @@ private: * contains the escaped and whitespace trimmed text, while `outRawString` * contains the unescaped text. Returns true on success. */ - bool flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString, + bool flattenXmlSubtree(xml::XmlPullParser* parser, std::u16string* outRawString, StyleString* outStyleString); /* @@ -75,24 +74,25 @@ private: * If `allowRawValue` is true and the subtree can not be parsed as a regular Item, then a * RawString is returned. Otherwise this returns false; */ - std::unique_ptr<Item> parseXml(XmlPullParser* parser, const uint32_t typeMask, + std::unique_ptr<Item> parseXml(xml::XmlPullParser* parser, const uint32_t typeMask, const bool allowRawValue); - bool parseResources(XmlPullParser* parser); - bool parseString(XmlPullParser* parser, ParsedResource* outResource); - bool parseColor(XmlPullParser* parser, ParsedResource* outResource); - bool parsePrimitive(XmlPullParser* parser, ParsedResource* outResource); - bool parsePublic(XmlPullParser* parser, ParsedResource* outResource); - bool parsePublicGroup(XmlPullParser* parser, ParsedResource* outResource); - bool parseSymbol(XmlPullParser* parser, ParsedResource* outResource); - bool parseAttr(XmlPullParser* parser, ParsedResource* outResource); - bool parseAttrImpl(XmlPullParser* parser, ParsedResource* outResource, bool weak); - Maybe<Attribute::Symbol> parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag); - bool parseStyle(XmlPullParser* parser, ParsedResource* outResource); - bool parseStyleItem(XmlPullParser* parser, Style* style); - bool parseDeclareStyleable(XmlPullParser* parser, ParsedResource* outResource); - bool parseArray(XmlPullParser* parser, ParsedResource* outResource, uint32_t typeMask); - bool parsePlural(XmlPullParser* parser, ParsedResource* outResource); + bool parseResources(xml::XmlPullParser* parser); + bool parseString(xml::XmlPullParser* parser, ParsedResource* outResource); + bool parseColor(xml::XmlPullParser* parser, ParsedResource* outResource); + bool parsePrimitive(xml::XmlPullParser* parser, ParsedResource* outResource); + bool parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource); + bool parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource); + bool parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource); + bool parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource); + bool parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource, bool weak); + Maybe<Attribute::Symbol> parseEnumOrFlagItem(xml::XmlPullParser* parser, + const StringPiece16& tag); + bool parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource); + bool parseStyleItem(xml::XmlPullParser* parser, Style* style); + bool parseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* outResource); + bool parseArray(xml::XmlPullParser* parser, ParsedResource* outResource, uint32_t typeMask); + bool parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource); IDiagnostics* mDiag; ResourceTable* mTable; diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index b59eb95e7448..6e0812bf86e5 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -18,9 +18,8 @@ #include "ResourceTable.h" #include "ResourceUtils.h" #include "ResourceValues.h" -#include "XmlPullParser.h" - #include "test/Context.h" +#include "xml/XmlPullParser.h" #include <gtest/gtest.h> #include <sstream> @@ -36,7 +35,7 @@ TEST(ResourceParserSingleTest, FailToParseWithNoRootResourcesElement) { input << "<attr name=\"foo\"/>" << std::endl; ResourceTable table; ResourceParser parser(context->getDiagnostics(), &table, Source{ "test" }, {}); - XmlPullParser xmlParser(input); + xml::XmlPullParser xmlParser(input); ASSERT_FALSE(parser.parse(&xmlParser)); } @@ -56,7 +55,7 @@ struct ResourceParserTest : public ::testing::Test { parserOptions.product = product; ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{ "test" }, {}, parserOptions); - XmlPullParser xmlParser(input); + xml::XmlPullParser xmlParser(input); if (parser.parse(&xmlParser)) { return ::testing::AssertionSuccess(); } @@ -139,6 +138,22 @@ TEST_F(ResourceParserTest, ParseAttr) { EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask); } +TEST_F(ResourceParserTest, ParseAttrWithMinMax) { + std::string input = "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"integer\"/>"; + ASSERT_TRUE(testParse(input)); + + Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_INTEGER), attr->typeMask); + EXPECT_EQ(10, attr->minInt); + EXPECT_EQ(23, attr->maxInt); +} + +TEST_F(ResourceParserTest, FailParseAttrWithMinMaxButNotInteger) { + std::string input = "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"string\"/>"; + ASSERT_FALSE(testParse(input)); +} + TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) { std::string input = "<declare-styleable name=\"Styleable\">\n" " <attr name=\"foo\" />\n" @@ -360,6 +375,25 @@ TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) { EXPECT_EQ(test::parseNameOrDie(u"@attr/bat"), styleable->entries[1].name.value()); } +TEST_F(ResourceParserTest, ParsePrivateAttributesDeclareStyleable) { + std::string input = "<declare-styleable name=\"foo\" xmlns:privAndroid=\"http://schemas.android.com/apk/prv/res/android\">\n" + " <attr name=\"*android:bar\" />\n" + " <attr name=\"privAndroid:bat\" />\n" + "</declare-styleable>"; + ASSERT_TRUE(testParse(input)); + Styleable* styleable = test::getValue<Styleable>(&mTable, u"@styleable/foo"); + ASSERT_NE(nullptr, styleable); + ASSERT_EQ(2u, styleable->entries.size()); + + EXPECT_TRUE(styleable->entries[0].privateReference); + AAPT_ASSERT_TRUE(styleable->entries[0].name); + EXPECT_EQ(std::u16string(u"android"), styleable->entries[0].name.value().package); + + EXPECT_TRUE(styleable->entries[1].privateReference); + AAPT_ASSERT_TRUE(styleable->entries[1].name); + EXPECT_EQ(std::u16string(u"android"), styleable->entries[1].name.value().package); +} + TEST_F(ResourceParserTest, ParseArray) { std::string input = "<array name=\"foo\">\n" " <item>@string/ref</item>\n" diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index d3c3c1044ae7..ffe6595cf0b6 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -15,6 +15,7 @@ */ #include "ResourceUtils.h" +#include "flatten/ResourceTypeExtensions.h" #include "util/Util.h" #include <androidfw/ResourceTypes.h> @@ -23,22 +24,64 @@ namespace aapt { namespace ResourceUtils { -void extractResourceName(const StringPiece16& str, StringPiece16* outPackage, +bool extractResourceName(const StringPiece16& str, StringPiece16* outPackage, StringPiece16* outType, StringPiece16* outEntry) { + bool hasPackageSeparator = false; + bool hasTypeSeparator = false; const char16_t* start = str.data(); const char16_t* end = start + str.size(); const char16_t* current = start; while (current != end) { if (outType->size() == 0 && *current == u'/') { + hasTypeSeparator = true; outType->assign(start, current - start); start = current + 1; } else if (outPackage->size() == 0 && *current == u':') { + hasPackageSeparator = true; outPackage->assign(start, current - start); start = current + 1; } current++; } outEntry->assign(start, end - start); + + return !(hasPackageSeparator && outPackage->empty()) && !(hasTypeSeparator && outType->empty()); +} + +bool parseResourceName(const StringPiece16& str, ResourceNameRef* outRef, bool* outPrivate) { + size_t offset = 0; + bool priv = false; + if (str.data()[0] == u'*') { + priv = true; + offset = 1; + } + + StringPiece16 package; + StringPiece16 type; + StringPiece16 entry; + if (!extractResourceName(str.substr(offset, str.size() - offset), &package, &type, &entry)) { + return false; + } + + const ResourceType* parsedType = parseResourceType(type); + if (!parsedType) { + return false; + } + + if (entry.empty()) { + return false; + } + + if (outRef) { + outRef->package = package; + outRef->type = *parsedType; + outRef->entry = entry; + } + + if (outPrivate) { + *outPrivate = priv; + } + return true; } bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool* outCreate, @@ -55,33 +98,30 @@ bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool* if (trimmedStr.data()[1] == u'+') { create = true; offset += 1; - } else if (trimmedStr.data()[1] == u'*') { - priv = true; - offset += 1; } - StringPiece16 package; - StringPiece16 type; - StringPiece16 entry; - extractResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset), &package, &type, - &entry); - const ResourceType* parsedType = parseResourceType(type); - if (!parsedType) { + ResourceNameRef name; + if (!parseResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset), + &name, &priv)) { return false; } - if (create && *parsedType != ResourceType::kId) { + if (create && priv) { return false; } - if (outRef != nullptr) { - outRef->package = package; - outRef->type = *parsedType; - outRef->entry = entry; + if (create && name.type != ResourceType::kId) { + return false; } + + if (outRef) { + *outRef = name; + } + if (outCreate) { *outCreate = create; } + if (outPrivate) { *outPrivate = priv; } @@ -104,20 +144,33 @@ bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outRe StringPiece16 package; StringPiece16 type; StringPiece16 entry; - extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1), &package, &type, &entry); + if (!extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1), + &package, &type, &entry)) { + return false; + } if (!type.empty() && type != u"attr") { return false; } - outRef->package = package; - outRef->type = ResourceType::kAttr; - outRef->entry = entry; + if (entry.empty()) { + return false; + } + + if (outRef) { + outRef->package = package; + outRef->type = ResourceType::kAttr; + outRef->entry = entry; + } return true; } return false; } +bool isAttributeReference(const StringPiece16& str) { + return tryParseAttributeReference(str, nullptr); +} + /* * Style parent's are a bit different. We accept the following formats: * diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h index 851edc89fa90..f93a4c762706 100644 --- a/tools/aapt2/ResourceUtils.h +++ b/tools/aapt2/ResourceUtils.h @@ -34,10 +34,18 @@ namespace ResourceUtils { * * where the package can be empty. Validation must be performed on each * individual extracted piece to verify that the pieces are valid. + * Returns false if there was no package but a ':' was present. */ -void extractResourceName(const StringPiece16& str, StringPiece16* outPackage, +bool extractResourceName(const StringPiece16& str, StringPiece16* outPackage, StringPiece16* outType, StringPiece16* outEntry); +/** + * Returns true if the string was parsed as a resource name ([*][package:]type/name), with + * `outResource` set to the parsed resource name and `outPrivate` set to true if a '*' prefix + * was present. + */ +bool parseResourceName(const StringPiece16& str, ResourceNameRef* outResource, bool* outPrivate); + /* * Returns true if the string was parsed as a reference (@[+][package:]type/name), with * `outReference` set to the parsed reference. @@ -54,12 +62,17 @@ bool tryParseReference(const StringPiece16& str, ResourceNameRef* outReference, bool isReference(const StringPiece16& str); /* - * Returns true if the string was parsed as an attribute reference (?[package:]type/name), + * Returns true if the string was parsed as an attribute reference (?[package:][type/]name), * with `outReference` set to the parsed reference. */ bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outReference); /** + * Returns true if the string is in the form of an attribute reference(?[package:][type/]name). + */ +bool isAttributeReference(const StringPiece16& str); + +/** * Returns true if the value is a boolean, putting the result in `outValue`. */ bool tryParseBool(const StringPiece16& str, bool* outValue); diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp index 7de8f4130316..4bbfc32b9b37 100644 --- a/tools/aapt2/ResourceUtils_test.cpp +++ b/tools/aapt2/ResourceUtils_test.cpp @@ -16,15 +16,30 @@ #include "Resource.h" #include "ResourceUtils.h" - #include "test/Common.h" #include <gtest/gtest.h> namespace aapt { +TEST(ResourceUtilsTest, ParseResourceName) { + ResourceNameRef actual; + bool actualPriv = false; + EXPECT_TRUE(ResourceUtils::parseResourceName(u"android:color/foo", &actual, &actualPriv)); + EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kColor, u"foo"), actual); + EXPECT_FALSE(actualPriv); + + EXPECT_TRUE(ResourceUtils::parseResourceName(u"color/foo", &actual, &actualPriv)); + EXPECT_EQ(ResourceNameRef({}, ResourceType::kColor, u"foo"), actual); + EXPECT_FALSE(actualPriv); + + EXPECT_TRUE(ResourceUtils::parseResourceName(u"*android:color/foo", &actual, &actualPriv)); + EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kColor, u"foo"), actual); + EXPECT_TRUE(actualPriv); +} + TEST(ResourceUtilsTest, ParseReferenceWithNoPackage) { - ResourceNameRef expected = { {}, ResourceType::kColor, u"foo" }; + ResourceNameRef expected({}, ResourceType::kColor, u"foo"); ResourceNameRef actual; bool create = false; bool privateRef = false; @@ -35,7 +50,7 @@ TEST(ResourceUtilsTest, ParseReferenceWithNoPackage) { } TEST(ResourceUtilsTest, ParseReferenceWithPackage) { - ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" }; + ResourceNameRef expected(u"android", ResourceType::kColor, u"foo"); ResourceNameRef actual; bool create = false; bool privateRef = false; @@ -47,7 +62,7 @@ TEST(ResourceUtilsTest, ParseReferenceWithPackage) { } TEST(ResourceUtilsTest, ParseReferenceWithSurroundingWhitespace) { - ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" }; + ResourceNameRef expected(u"android", ResourceType::kColor, u"foo"); ResourceNameRef actual; bool create = false; bool privateRef = false; @@ -59,7 +74,7 @@ TEST(ResourceUtilsTest, ParseReferenceWithSurroundingWhitespace) { } TEST(ResourceUtilsTest, ParseAutoCreateIdReference) { - ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" }; + ResourceNameRef expected(u"android", ResourceType::kId, u"foo"); ResourceNameRef actual; bool create = false; bool privateRef = false; @@ -71,7 +86,7 @@ TEST(ResourceUtilsTest, ParseAutoCreateIdReference) { } TEST(ResourceUtilsTest, ParsePrivateReference) { - ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" }; + ResourceNameRef expected(u"android", ResourceType::kId, u"foo"); ResourceNameRef actual; bool create = false; bool privateRef = false; @@ -90,9 +105,29 @@ TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) { &privateRef)); } +TEST(ResourceUtilsTest, ParseAttributeReferences) { + EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?android")); + EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?android:foo")); + EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?attr/foo")); + EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?android:attr/foo")); +} + +TEST(ResourceUtilsTest, FailParseIncompleteReference) { + EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?style/foo")); + EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?android:style/foo")); + EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?android:")); + EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?android:attr/")); + EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:attr/")); + EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:attr/foo")); + EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:/")); + EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:/foo")); + EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?attr/")); + EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?/foo")); +} + TEST(ResourceUtilsTest, ParseStyleParentReference) { - const ResourceName kAndroidStyleFooName = { u"android", ResourceType::kStyle, u"foo" }; - const ResourceName kStyleFooName = { {}, ResourceType::kStyle, u"foo" }; + const ResourceName kAndroidStyleFooName(u"android", ResourceType::kStyle, u"foo"); + const ResourceName kStyleFooName({}, ResourceType::kStyle, u"foo"); std::string errStr; Maybe<Reference> ref = ResourceUtils::parseStyleParentReference(u"@android:style/foo", &errStr); diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index 8acff0d39019..04c375f5f974 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -15,9 +15,9 @@ */ #include "Resource.h" +#include "ResourceUtils.h" #include "ResourceValues.h" #include "ValueVisitor.h" - #include "util/Util.h" #include "flatten/ResourceTypeExtensions.h" @@ -71,27 +71,23 @@ Reference::Reference(const ResourceId& i, Type type) : id(i), referenceType(type } bool Reference::flatten(android::Res_value* outValue) const { - outValue->dataType = (referenceType == Reference::Type::kResource) - ? android::Res_value::TYPE_REFERENCE - : android::Res_value::TYPE_ATTRIBUTE; + outValue->dataType = (referenceType == Reference::Type::kResource) ? + android::Res_value::TYPE_REFERENCE : android::Res_value::TYPE_ATTRIBUTE; outValue->data = util::hostToDevice32(id ? id.value().id : 0); return true; } Reference* Reference::clone(StringPool* /*newPool*/) const { - Reference* ref = new Reference(); - ref->mComment = mComment; - ref->mSource = mSource; - ref->referenceType = referenceType; - ref->name = name; - ref->id = id; - return ref; + return new Reference(*this); } void Reference::print(std::ostream* out) const { *out << "(reference) "; if (referenceType == Reference::Type::kResource) { *out << "@"; + if (privateReference) { + *out << "*"; + } } else { *out << "?"; } @@ -116,10 +112,7 @@ bool Id::flatten(android::Res_value* out) const { } Id* Id::clone(StringPool* /*newPool*/) const { - Id* id = new Id(); - id->mComment = mComment; - id->mSource = mSource; - return id; + return new Id(*this); } void Id::print(std::ostream* out) const { @@ -214,10 +207,7 @@ bool BinaryPrimitive::flatten(android::Res_value* outValue) const { } BinaryPrimitive* BinaryPrimitive::clone(StringPool* /*newPool*/) const { - BinaryPrimitive* bp = new BinaryPrimitive(value); - bp->mComment = mComment; - bp->mSource = mSource; - return bp; + return new BinaryPrimitive(*this); } void BinaryPrimitive::print(std::ostream* out) const { @@ -226,7 +216,7 @@ void BinaryPrimitive::print(std::ostream* out) const { *out << "(null)"; break; case android::Res_value::TYPE_INT_DEC: - *out << "(integer) " << value.data; + *out << "(integer) " << static_cast<int32_t>(value.data); break; case android::Res_value::TYPE_INT_HEX: *out << "(integer) " << std::hex << value.data << std::dec; @@ -247,7 +237,10 @@ void BinaryPrimitive::print(std::ostream* out) const { } } -Attribute::Attribute(bool w, uint32_t t) : weak(w), typeMask(t) { +Attribute::Attribute(bool w, uint32_t t) : + weak(w), typeMask(t), + minInt(std::numeric_limits<int32_t>::min()), + maxInt(std::numeric_limits<int32_t>::max()) { } bool Attribute::isWeak() const { @@ -255,12 +248,7 @@ bool Attribute::isWeak() const { } Attribute* Attribute::clone(StringPool* /*newPool*/) const { - Attribute* attr = new Attribute(weak); - attr->mComment = mComment; - attr->mSource = mSource; - attr->typeMask = typeMask; - std::copy(symbols.begin(), symbols.end(), std::back_inserter(attr->symbols)); - return attr; + return new Attribute(*this); } void Attribute::printMask(std::ostream* out) const { @@ -376,6 +364,81 @@ void Attribute::print(std::ostream* out) const { } } +static void buildAttributeMismatchMessage(DiagMessage* msg, const Attribute* attr, + const Item* value) { + *msg << "expected"; + if (attr->typeMask & android::ResTable_map::TYPE_BOOLEAN) { + *msg << " boolean"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_COLOR) { + *msg << " color"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_DIMENSION) { + *msg << " dimension"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_ENUM) { + *msg << " enum"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_FLAGS) { + *msg << " flags"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_FLOAT) { + *msg << " float"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_FRACTION) { + *msg << " fraction"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_INTEGER) { + *msg << " integer"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_REFERENCE) { + *msg << " reference"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_STRING) { + *msg << " string"; + } + + *msg << " but got " << *value; +} + +bool Attribute::matches(const Item* item, DiagMessage* outMsg) const { + android::Res_value val = {}; + item->flatten(&val); + + // Always allow references. + const uint32_t mask = typeMask | android::ResTable_map::TYPE_REFERENCE; + if (!(mask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) { + if (outMsg) { + buildAttributeMismatchMessage(outMsg, this, item); + } + return false; + + } else if (ResourceUtils::androidTypeToAttributeTypeMask(val.dataType) & + android::ResTable_map::TYPE_INTEGER) { + if (static_cast<int32_t>(util::deviceToHost32(val.data)) < minInt) { + if (outMsg) { + *outMsg << *item << " is less than minimum integer " << minInt; + } + return false; + } else if (static_cast<int32_t>(util::deviceToHost32(val.data)) > maxInt) { + if (outMsg) { + *outMsg << *item << " is greater than maximum integer " << maxInt; + } + return false; + } + } + return true; +} + Style* Style::clone(StringPool* newPool) const { Style* style = new Style(); style->parent = parent; @@ -450,11 +513,7 @@ static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Ite } Styleable* Styleable::clone(StringPool* /*newPool*/) const { - Styleable* styleable = new Styleable(); - styleable->mComment = mComment; - styleable->mSource = mSource; - std::copy(entries.begin(), entries.end(), std::back_inserter(styleable->entries)); - return styleable; + return new Styleable(*this); } void Styleable::print(std::ostream* out) const { diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index 7ae346aab370..a03828206c91 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -17,9 +17,10 @@ #ifndef AAPT_RESOURCE_VALUES_H #define AAPT_RESOURCE_VALUES_H -#include "util/Maybe.h" +#include "Diagnostics.h" #include "Resource.h" #include "StringPool.h" +#include "util/Maybe.h" #include <array> #include <androidfw/ResourceTypes.h> @@ -233,8 +234,8 @@ struct Attribute : public BaseValue<Attribute> { bool weak; uint32_t typeMask; - uint32_t minInt; - uint32_t maxInt; + int32_t minInt; + int32_t maxInt; std::vector<Symbol> symbols; Attribute(bool w, uint32_t t = 0u); @@ -243,6 +244,7 @@ struct Attribute : public BaseValue<Attribute> { Attribute* clone(StringPool* newPool) const override; void printMask(std::ostream* out) const; void print(std::ostream* out) const override; + bool matches(const Item* item, DiagMessage* outMsg) const; }; struct Style : public BaseValue<Style> { diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h index 8af203cdad0e..319528e0ea1b 100644 --- a/tools/aapt2/Source.h +++ b/tools/aapt2/Source.h @@ -58,6 +58,10 @@ inline ::std::ostream& operator<<(::std::ostream& out, const Source& source) { return out; } +inline bool operator==(const Source& lhs, const Source& rhs) { + return lhs.path == rhs.path && lhs.line == rhs.line; +} + inline bool operator<(const Source& lhs, const Source& rhs) { int cmp = lhs.path.compare(rhs.path); if (cmp < 0) return true; diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp index 39088bcee140..90e35d52788c 100644 --- a/tools/aapt2/compile/Compile.cpp +++ b/tools/aapt2/compile/Compile.cpp @@ -19,19 +19,20 @@ #include "Flags.h" #include "ResourceParser.h" #include "ResourceTable.h" -#include "XmlDom.h" -#include "XmlPullParser.h" - #include "compile/IdAssigner.h" #include "compile/Png.h" #include "compile/XmlIdCollector.h" +#include "flatten/Archive.h" #include "flatten/FileExportWriter.h" #include "flatten/TableFlattener.h" #include "flatten/XmlFlattener.h" #include "util/Files.h" #include "util/Maybe.h" #include "util/Util.h" +#include "xml/XmlDom.h" +#include "xml/XmlPullParser.h" +#include <dirent.h> #include <fstream> #include <string> @@ -91,7 +92,7 @@ static Maybe<ResourcePathData> extractResourcePathData(const std::string& path, } return ResourcePathData{ - Source{ path }, + Source(path), util::utf8ToUtf16(dirStr), util::utf8ToUtf16(name), extension.toString(), @@ -102,25 +103,79 @@ static Maybe<ResourcePathData> extractResourcePathData(const std::string& path, struct CompileOptions { std::string outputPath; + Maybe<std::string> resDir; Maybe<std::u16string> product; bool verbose = false; }; -static std::string buildIntermediateFilename(const std::string outDir, - const ResourcePathData& data) { +static std::string buildIntermediateFilename(const ResourcePathData& data) { std::stringstream name; name << data.resourceDir; if (!data.configStr.empty()) { name << "-" << data.configStr; } name << "_" << data.name << "." << data.extension << ".flat"; - std::string outPath = outDir; - file::appendPath(&outPath, name.str()); - return outPath; + return name.str(); +} + +static bool isHidden(const StringPiece& filename) { + return util::stringStartsWith<char>(filename, "."); +} + +/** + * Walks the res directory structure, looking for resource files. + */ +static bool loadInputFilesFromDir(IAaptContext* context, const CompileOptions& options, + std::vector<ResourcePathData>* outPathData) { + const std::string& rootDir = options.resDir.value(); + std::unique_ptr<DIR, decltype(closedir)*> d(opendir(rootDir.data()), closedir); + if (!d) { + context->getDiagnostics()->error(DiagMessage() << strerror(errno)); + return false; + } + + while (struct dirent* entry = readdir(d.get())) { + if (isHidden(entry->d_name)) { + continue; + } + + std::string prefixPath = rootDir; + file::appendPath(&prefixPath, entry->d_name); + + if (file::getFileType(prefixPath) != file::FileType::kDirectory) { + continue; + } + + std::unique_ptr<DIR, decltype(closedir)*> subDir(opendir(prefixPath.data()), closedir); + if (!subDir) { + context->getDiagnostics()->error(DiagMessage() << strerror(errno)); + return false; + } + + while (struct dirent* leafEntry = readdir(subDir.get())) { + if (isHidden(leafEntry->d_name)) { + continue; + } + + std::string fullPath = prefixPath; + file::appendPath(&fullPath, leafEntry->d_name); + + std::string errStr; + Maybe<ResourcePathData> pathData = extractResourcePathData(fullPath, &errStr); + if (!pathData) { + context->getDiagnostics()->error(DiagMessage() << errStr); + return false; + } + + outPathData->push_back(std::move(pathData.value())); + } + } + return true; } static bool compileTable(IAaptContext* context, const CompileOptions& options, - const ResourcePathData& pathData, const std::string& outputPath) { + const ResourcePathData& pathData, IArchiveWriter* writer, + const std::string& outputPath) { ResourceTable table; { std::ifstream fin(pathData.source.path, std::ifstream::binary); @@ -131,7 +186,7 @@ static bool compileTable(IAaptContext* context, const CompileOptions& options, // Parse the values file from XML. - XmlPullParser xmlParser(fin); + xml::XmlPullParser xmlParser(fin); ResourceParserOptions parserOptions; parserOptions.product = options.product; @@ -151,6 +206,7 @@ static bool compileTable(IAaptContext* context, const CompileOptions& options, // Ensure we have the compilation package at least. table.createPackage(context->getCompilationPackage()); + // Assign an ID to any package that has resources. for (auto& pkg : table.packages) { if (!pkg->id) { // If no package ID was set while parsing (public identifiers), auto assign an ID. @@ -173,25 +229,26 @@ static bool compileTable(IAaptContext* context, const CompileOptions& options, return false; } - // Build the output filename. - std::ofstream fout(outputPath, std::ofstream::binary); - if (!fout) { - context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno)); + if (!writer->startEntry(outputPath, 0)) { + context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open"); return false; } - // Write it to disk. - if (!util::writeAll(fout, buffer)) { - context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno)); - return false; + if (writer->writeEntry(buffer)) { + if (writer->finishEntry()) { + return true; + } } - return true; + + context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write"); + return false; } static bool compileXml(IAaptContext* context, const CompileOptions& options, - const ResourcePathData& pathData, const std::string& outputPath) { + const ResourcePathData& pathData, IArchiveWriter* writer, + const std::string& outputPath) { - std::unique_ptr<XmlResource> xmlRes; + std::unique_ptr<xml::XmlResource> xmlRes; { std::ifstream fin(pathData.source.path, std::ifstream::binary); @@ -215,7 +272,7 @@ static bool compileXml(IAaptContext* context, const CompileOptions& options, return false; } - xmlRes->file.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name }; + xmlRes->file.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name); xmlRes->file.config = pathData.config; xmlRes->file.source = pathData.source; @@ -231,25 +288,27 @@ static bool compileXml(IAaptContext* context, const CompileOptions& options, fileExportWriter.finish(); - std::ofstream fout(outputPath, std::ofstream::binary); - if (!fout) { - context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno)); + if (!writer->startEntry(outputPath, 0)) { + context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open"); return false; } - // Write it to disk. - if (!util::writeAll(fout, buffer)) { - context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno)); - return false; + if (writer->writeEntry(buffer)) { + if (writer->finishEntry()) { + return true; + } } - return true; + + context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write"); + return false; } static bool compilePng(IAaptContext* context, const CompileOptions& options, - const ResourcePathData& pathData, const std::string& outputPath) { + const ResourcePathData& pathData, IArchiveWriter* writer, + const std::string& outputPath) { BigBuffer buffer(4096); ResourceFile resFile; - resFile.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name }; + resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name); resFile.config = pathData.config; resFile.source = pathData.source; @@ -270,24 +329,27 @@ static bool compilePng(IAaptContext* context, const CompileOptions& options, fileExportWriter.finish(); - std::ofstream fout(outputPath, std::ofstream::binary); - if (!fout) { - context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno)); + if (!writer->startEntry(outputPath, 0)) { + context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open"); return false; } - if (!util::writeAll(fout, buffer)) { - context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno)); - return false; + if (writer->writeEntry(buffer)) { + if (writer->finishEntry()) { + return true; + } } - return true; + + context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write"); + return false; } static bool compileFile(IAaptContext* context, const CompileOptions& options, - const ResourcePathData& pathData, const std::string& outputPath) { + const ResourcePathData& pathData, IArchiveWriter* writer, + const std::string& outputPath) { BigBuffer buffer(256); ResourceFile resFile; - resFile.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name }; + resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name); resFile.config = pathData.config; resFile.source = pathData.source; @@ -300,9 +362,8 @@ static bool compileFile(IAaptContext* context, const CompileOptions& options, return false; } - std::ofstream fout(outputPath, std::ofstream::binary); - if (!fout) { - context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno)); + if (!writer->startEntry(outputPath, 0)) { + context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open"); return false; } @@ -310,16 +371,17 @@ static bool compileFile(IAaptContext* context, const CompileOptions& options, // the buffer the entire file. fileExportWriter.getChunkHeader()->size = util::hostToDevice32(buffer.size() + f.value().getDataLength()); - if (!util::writeAll(fout, buffer)) { - context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno)); - return false; - } - if (!fout.write((const char*) f.value().getDataPtr(), f.value().getDataLength())) { - context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno)); - return false; + if (writer->writeEntry(buffer)) { + if (writer->writeEntry(f.value().getDataPtr(), f.value().getDataLength())) { + if (writer->finishEntry()) { + return true; + } + } } - return true; + + context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write"); + return false; } class CompileContext : public IAaptContext { @@ -360,6 +422,7 @@ int compile(const std::vector<StringPiece>& args) { Flags flags = Flags() .requiredFlag("-o", "Output path", &options.outputPath) .optionalFlag("--product", "Product type to compile", &product) + .optionalFlag("--dir", "Directory to scan for resources", &options.resDir) .optionalSwitch("-v", "Enables verbose logging", &options.verbose); if (!flags.parse("aapt2 compile", args, &std::cerr)) { return 1; @@ -370,19 +433,42 @@ int compile(const std::vector<StringPiece>& args) { } CompileContext context; + std::unique_ptr<IArchiveWriter> archiveWriter; std::vector<ResourcePathData> inputData; - inputData.reserve(flags.getArgs().size()); + if (options.resDir) { + if (!flags.getArgs().empty()) { + // Can't have both files and a resource directory. + context.getDiagnostics()->error(DiagMessage() << "files given but --dir specified"); + flags.usage("aapt2 compile", &std::cerr); + return 1; + } - // Collect data from the path for each input file. - for (const std::string& arg : flags.getArgs()) { - std::string errorStr; - if (Maybe<ResourcePathData> pathData = extractResourcePathData(arg, &errorStr)) { - inputData.push_back(std::move(pathData.value())); - } else { - context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg << ")"); + if (!loadInputFilesFromDir(&context, options, &inputData)) { return 1; } + + archiveWriter = createZipFileArchiveWriter(context.getDiagnostics(), options.outputPath); + + } else { + inputData.reserve(flags.getArgs().size()); + + // Collect data from the path for each input file. + for (const std::string& arg : flags.getArgs()) { + std::string errorStr; + if (Maybe<ResourcePathData> pathData = extractResourcePathData(arg, &errorStr)) { + inputData.push_back(std::move(pathData.value())); + } else { + context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg << ")"); + return 1; + } + } + + archiveWriter = createDirectoryArchiveWriter(context.getDiagnostics(), options.outputPath); + } + + if (!archiveWriter) { + return false; } bool error = false; @@ -395,32 +481,34 @@ int compile(const std::vector<StringPiece>& args) { // Overwrite the extension. pathData.extension = "arsc"; - const std::string outputFilename = buildIntermediateFilename( - options.outputPath, pathData); - if (!compileTable(&context, options, pathData, outputFilename)) { + const std::string outputFilename = buildIntermediateFilename(pathData); + if (!compileTable(&context, options, pathData, archiveWriter.get(), outputFilename)) { error = true; } } else { - const std::string outputFilename = buildIntermediateFilename(options.outputPath, - pathData); + const std::string outputFilename = buildIntermediateFilename(pathData); if (const ResourceType* type = parseResourceType(pathData.resourceDir)) { if (*type != ResourceType::kRaw) { if (pathData.extension == "xml") { - if (!compileXml(&context, options, pathData, outputFilename)) { + if (!compileXml(&context, options, pathData, archiveWriter.get(), + outputFilename)) { error = true; } } else if (pathData.extension == "png" || pathData.extension == "9.png") { - if (!compilePng(&context, options, pathData, outputFilename)) { + if (!compilePng(&context, options, pathData, archiveWriter.get(), + outputFilename)) { error = true; } } else { - if (!compileFile(&context, options, pathData, outputFilename)) { + if (!compileFile(&context, options, pathData, archiveWriter.get(), + outputFilename)) { error = true; } } } else { - if (!compileFile(&context, options, pathData, outputFilename)) { + if (!compileFile(&context, options, pathData, archiveWriter.get(), + outputFilename)) { error = true; } } diff --git a/tools/aapt2/compile/XmlIdCollector.cpp b/tools/aapt2/compile/XmlIdCollector.cpp index dfdf710967f9..f40689eaeb47 100644 --- a/tools/aapt2/compile/XmlIdCollector.cpp +++ b/tools/aapt2/compile/XmlIdCollector.cpp @@ -16,9 +16,8 @@ #include "ResourceUtils.h" #include "ResourceValues.h" -#include "XmlDom.h" - #include "compile/XmlIdCollector.h" +#include "xml/XmlDom.h" #include <algorithm> #include <vector> @@ -61,7 +60,7 @@ struct IdCollector : public xml::Visitor { } // namespace -bool XmlIdCollector::consume(IAaptContext* context, XmlResource* xmlRes) { +bool XmlIdCollector::consume(IAaptContext* context, xml::XmlResource* xmlRes) { xmlRes->file.exportedSymbols.clear(); IdCollector collector(&xmlRes->file.exportedSymbols); xmlRes->root->accept(&collector); diff --git a/tools/aapt2/compile/XmlIdCollector.h b/tools/aapt2/compile/XmlIdCollector.h index 96a58f22fab6..1b149449de2c 100644 --- a/tools/aapt2/compile/XmlIdCollector.h +++ b/tools/aapt2/compile/XmlIdCollector.h @@ -18,11 +18,12 @@ #define AAPT_XMLIDCOLLECTOR_H #include "process/IResourceTableConsumer.h" +#include "xml/XmlDom.h" namespace aapt { struct XmlIdCollector : public IXmlResourceConsumer { - bool consume(IAaptContext* context, XmlResource* xmlRes) override; + bool consume(IAaptContext* context, xml::XmlResource* xmlRes) override; }; } // namespace aapt diff --git a/tools/aapt2/compile/XmlIdCollector_test.cpp b/tools/aapt2/compile/XmlIdCollector_test.cpp index c703f451f05e..45b7af240abe 100644 --- a/tools/aapt2/compile/XmlIdCollector_test.cpp +++ b/tools/aapt2/compile/XmlIdCollector_test.cpp @@ -26,7 +26,7 @@ namespace aapt { TEST(XmlIdCollectorTest, CollectsIds) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/foo" text="@+id/bar"> @@ -50,7 +50,7 @@ TEST(XmlIdCollectorTest, CollectsIds) { TEST(XmlIdCollectorTest, DontCollectNonIds) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - std::unique_ptr<XmlResource> doc = test::buildXmlDom("<View foo=\"@+string/foo\"/>"); + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View foo=\"@+string/foo\"/>"); XmlIdCollector collector; ASSERT_TRUE(collector.consume(context.get(), doc.get())); diff --git a/tools/aapt2/flatten/Archive.cpp b/tools/aapt2/flatten/Archive.cpp index 6db13b86fbfa..3a244c05efec 100644 --- a/tools/aapt2/flatten/Archive.cpp +++ b/tools/aapt2/flatten/Archive.cpp @@ -18,7 +18,7 @@ #include "util/Files.h" #include "util/StringPiece.h" -#include <fstream> +#include <cstdio> #include <memory> #include <string> #include <vector> @@ -30,70 +30,85 @@ namespace { struct DirectoryWriter : public IArchiveWriter { std::string mOutDir; - std::vector<std::unique_ptr<ArchiveEntry>> mEntries; - - explicit DirectoryWriter(const StringPiece& outDir) : mOutDir(outDir.toString()) { + std::unique_ptr<FILE, decltype(fclose)*> mFile = { nullptr, fclose }; + + bool open(IDiagnostics* diag, const StringPiece& outDir) { + mOutDir = outDir.toString(); + file::FileType type = file::getFileType(mOutDir); + if (type == file::FileType::kNonexistant) { + diag->error(DiagMessage() << "directory " << mOutDir << " does not exist"); + return false; + } else if (type != file::FileType::kDirectory) { + diag->error(DiagMessage() << mOutDir << " is not a directory"); + return false; + } + return true; } - ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, - const BigBuffer& buffer) override { - std::string fullPath = mOutDir; - file::appendPath(&fullPath, path); - file::mkdirs(file::getStem(fullPath)); - - std::ofstream fout(fullPath, std::ofstream::binary); - if (!fout) { - return nullptr; - } - - if (!util::writeAll(fout, buffer)) { - return nullptr; + bool startEntry(const StringPiece& path, uint32_t flags) override { + if (mFile) { + return false; } - mEntries.push_back(util::make_unique<ArchiveEntry>(fullPath, flags, buffer.size())); - return mEntries.back().get(); - } - - ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, android::FileMap* fileMap, - size_t offset, size_t len) override { std::string fullPath = mOutDir; file::appendPath(&fullPath, path); file::mkdirs(file::getStem(fullPath)); - std::ofstream fout(fullPath, std::ofstream::binary); - if (!fout) { - return nullptr; + mFile = { fopen(fullPath.data(), "wb"), fclose }; + if (!mFile) { + return false; } + return true; + } - if (!fout.write((const char*) fileMap->getDataPtr() + offset, len)) { - return nullptr; + bool writeEntry(const BigBuffer& buffer) override { + if (!mFile) { + return false; } - mEntries.push_back(util::make_unique<ArchiveEntry>(fullPath, flags, len)); - return mEntries.back().get(); + for (const BigBuffer::Block& b : buffer) { + if (fwrite(b.buffer.get(), 1, b.size, mFile.get()) != b.size) { + mFile.reset(nullptr); + return false; + } + } + return true; } - virtual ~DirectoryWriter() { + bool writeEntry(const void* data, size_t len) override { + if (fwrite(data, 1, len, mFile.get()) != len) { + mFile.reset(nullptr); + return false; + } + return true; + } + bool finishEntry() override { + if (!mFile) { + return false; + } + mFile.reset(nullptr); + return true; } }; struct ZipFileWriter : public IArchiveWriter { - FILE* mFile; + std::unique_ptr<FILE, decltype(fclose)*> mFile = { nullptr, fclose }; std::unique_ptr<ZipWriter> mWriter; - std::vector<std::unique_ptr<ArchiveEntry>> mEntries; - explicit ZipFileWriter(const StringPiece& path) { - mFile = fopen(path.data(), "w+b"); - if (mFile) { - mWriter = util::make_unique<ZipWriter>(mFile); + bool open(IDiagnostics* diag, const StringPiece& path) { + mFile = { fopen(path.data(), "w+b"), fclose }; + if (!mFile) { + diag->error(DiagMessage() << "failed to open " << path << ": " << strerror(errno)); + return false; } + mWriter = util::make_unique<ZipWriter>(mFile.get()); + return true; } - ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, - const BigBuffer& buffer) override { + bool startEntry(const StringPiece& path, uint32_t flags) override { if (!mWriter) { - return nullptr; + return false; } size_t zipFlags = 0; @@ -107,75 +122,63 @@ struct ZipFileWriter : public IArchiveWriter { int32_t result = mWriter->StartEntry(path.data(), zipFlags); if (result != 0) { - return nullptr; - } - - for (const BigBuffer::Block& b : buffer) { - result = mWriter->WriteBytes(reinterpret_cast<const uint8_t*>(b.buffer.get()), b.size); - if (result != 0) { - return nullptr; - } - } - - result = mWriter->FinishEntry(); - if (result != 0) { - return nullptr; + return false; } - - mEntries.push_back(util::make_unique<ArchiveEntry>(path.toString(), flags, buffer.size())); - return mEntries.back().get(); + return true; } - ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, android::FileMap* fileMap, - size_t offset, size_t len) override { - if (!mWriter) { - return nullptr; - } - - size_t zipFlags = 0; - if (flags & ArchiveEntry::kCompress) { - zipFlags |= ZipWriter::kCompress; - } - - if (flags & ArchiveEntry::kAlign) { - zipFlags |= ZipWriter::kAlign32; - } - - int32_t result = mWriter->StartEntry(path.data(), zipFlags); + bool writeEntry(const void* data, size_t len) override { + int32_t result = mWriter->WriteBytes(data, len); if (result != 0) { - return nullptr; + return false; } + return true; + } - result = mWriter->WriteBytes((const char*) fileMap->getDataPtr() + offset, len); - if (result != 0) { - return nullptr; + bool writeEntry(const BigBuffer& buffer) override { + for (const BigBuffer::Block& b : buffer) { + int32_t result = mWriter->WriteBytes(b.buffer.get(), b.size); + if (result != 0) { + return false; + } } + return true; + } - result = mWriter->FinishEntry(); + bool finishEntry() override { + int32_t result = mWriter->FinishEntry(); if (result != 0) { - return nullptr; + return false; } - - mEntries.push_back(util::make_unique<ArchiveEntry>(path.toString(), flags, len)); - return mEntries.back().get(); + return true; } virtual ~ZipFileWriter() { if (mWriter) { mWriter->Finish(); - fclose(mFile); } } }; } // namespace -std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(const StringPiece& path) { - return util::make_unique<DirectoryWriter>(path); +std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(IDiagnostics* diag, + const StringPiece& path) { + + std::unique_ptr<DirectoryWriter> writer = util::make_unique<DirectoryWriter>(); + if (!writer->open(diag, path)) { + return {}; + } + return std::move(writer); } -std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(const StringPiece& path) { - return util::make_unique<ZipFileWriter>(path); +std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(IDiagnostics* diag, + const StringPiece& path) { + std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>(); + if (!writer->open(diag, path)) { + return {}; + } + return std::move(writer); } } // namespace aapt diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/flatten/Archive.h index c4ddeb3163c0..6da1d2ac5620 100644 --- a/tools/aapt2/flatten/Archive.h +++ b/tools/aapt2/flatten/Archive.h @@ -17,6 +17,7 @@ #ifndef AAPT_FLATTEN_ARCHIVE_H #define AAPT_FLATTEN_ARCHIVE_H +#include "Diagnostics.h" #include "util/BigBuffer.h" #include "util/Files.h" #include "util/StringPiece.h" @@ -42,15 +43,17 @@ struct ArchiveEntry { struct IArchiveWriter { virtual ~IArchiveWriter() = default; - virtual ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, - const BigBuffer& buffer) = 0; - virtual ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, - android::FileMap* fileMap, size_t offset, size_t len) = 0; + virtual bool startEntry(const StringPiece& path, uint32_t flags) = 0; + virtual bool writeEntry(const BigBuffer& buffer) = 0; + virtual bool writeEntry(const void* data, size_t len) = 0; + virtual bool finishEntry() = 0; }; -std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(const StringPiece& path); +std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(IDiagnostics* diag, + const StringPiece& path); -std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(const StringPiece& path); +std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(IDiagnostics* diag, + const StringPiece& path); } // namespace aapt diff --git a/tools/aapt2/flatten/ResourceTypeExtensions.h b/tools/aapt2/flatten/ResourceTypeExtensions.h index c1ff556ac0cf..02bff2c69362 100644 --- a/tools/aapt2/flatten/ResourceTypeExtensions.h +++ b/tools/aapt2/flatten/ResourceTypeExtensions.h @@ -62,7 +62,29 @@ struct ExtendedTypes { * A raw string value that hasn't had its escape sequences * processed nor whitespace removed. */ - TYPE_RAW_STRING = 0xfe + TYPE_RAW_STRING = 0xfe, + }; +}; + +/** + * New types for a ResTable_map. + */ +struct ExtendedResTableMapTypes { + enum { + /** + * Type that contains the source path of the next item in the map. + */ + ATTR_SOURCE_PATH = Res_MAKEINTERNAL(0xffff), + + /** + * Type that contains the source line of the next item in the map. + */ + ATTR_SOURCE_LINE = Res_MAKEINTERNAL(0xfffe), + + /** + * Type that contains the comment of the next item in the map. + */ + ATTR_COMMENT = Res_MAKEINTERNAL(0xfffd) }; }; diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp index 6b90fb276b6d..f42e6b7b126f 100644 --- a/tools/aapt2/flatten/TableFlattener.cpp +++ b/tools/aapt2/flatten/TableFlattener.cpp @@ -78,10 +78,18 @@ public: explicit SymbolWriter(StringPool* pool) : mPool(pool) { } - void addSymbol(const ResourceNameRef& name, size_t offset) { - symbols.push_back(Entry{ mPool->makeRef(name.package.toString() + u":" + - toString(name.type).toString() + u"/" + - name.entry.toString()), offset }); + void addSymbol(const Reference& ref, size_t offset) { + const ResourceName& name = ref.name.value(); + std::u16string fullName; + if (ref.privateReference) { + fullName += u"*"; + } + + if (!name.package.empty()) { + fullName += name.package + u":"; + } + fullName += toString(name.type).toString() + u"/" + name.entry; + symbols.push_back(Entry{ mPool->makeRef(fullName), offset }); } void shiftAllOffsets(size_t offset) { @@ -100,20 +108,27 @@ struct MapFlattenVisitor : public RawValueVisitor { SymbolWriter* mSymbols; FlatEntry* mEntry; BigBuffer* mBuffer; + StringPool* mSourcePool; + StringPool* mCommentPool; + bool mUseExtendedChunks; + size_t mEntryCount = 0; Maybe<uint32_t> mParentIdent; Maybe<ResourceNameRef> mParentName; - MapFlattenVisitor(SymbolWriter* symbols, FlatEntry* entry, BigBuffer* buffer) : - mSymbols(symbols), mEntry(entry), mBuffer(buffer) { + MapFlattenVisitor(SymbolWriter* symbols, FlatEntry* entry, BigBuffer* buffer, + StringPool* sourcePool, StringPool* commentPool, + bool useExtendedChunks) : + mSymbols(symbols), mEntry(entry), mBuffer(buffer), mSourcePool(sourcePool), + mCommentPool(commentPool), mUseExtendedChunks(useExtendedChunks) { } void flattenKey(Reference* key, ResTable_map* outEntry) { - if (!key->id) { + if (!key->id || (key->privateReference && mUseExtendedChunks)) { assert(key->name && "reference must have a name"); outEntry->name.ident = util::hostToDevice32(0); - mSymbols->addSymbol(key->name.value(), (mBuffer->size() - sizeof(ResTable_map)) + + mSymbols->addSymbol(*key, (mBuffer->size() - sizeof(ResTable_map)) + offsetof(ResTable_map, name)); } else { outEntry->name.ident = util::hostToDevice32(key->id.value().id); @@ -121,16 +136,21 @@ struct MapFlattenVisitor : public RawValueVisitor { } void flattenValue(Item* value, ResTable_map* outEntry) { + bool privateRef = false; if (Reference* ref = valueCast<Reference>(value)) { - if (!ref->id) { + privateRef = ref->privateReference && mUseExtendedChunks; + if (!ref->id || privateRef) { assert(ref->name && "reference must have a name"); - mSymbols->addSymbol(ref->name.value(), (mBuffer->size() - sizeof(ResTable_map)) + + mSymbols->addSymbol(*ref, (mBuffer->size() - sizeof(ResTable_map)) + offsetof(ResTable_map, value) + offsetof(Res_value, data)); } } bool result = value->flatten(&outEntry->value); + if (privateRef) { + outEntry->value.data = 0; + } assert(result && "flatten failed"); } @@ -142,6 +162,32 @@ struct MapFlattenVisitor : public RawValueVisitor { mEntryCount++; } + void flattenMetaData(Value* value) { + if (!mUseExtendedChunks) { + return; + } + + Reference key(ResourceId{ ExtendedResTableMapTypes::ATTR_SOURCE_PATH }); + StringPool::Ref sourcePathRef = mSourcePool->makeRef( + util::utf8ToUtf16(value->getSource().path)); + BinaryPrimitive val(Res_value::TYPE_INT_DEC, + static_cast<uint32_t>(sourcePathRef.getIndex())); + flattenEntry(&key, &val); + + if (value->getSource().line) { + key.id = ResourceId(ExtendedResTableMapTypes::ATTR_SOURCE_LINE); + val.value.data = static_cast<uint32_t>(value->getSource().line.value()); + flattenEntry(&key, &val); + } + + if (!value->getComment().empty()) { + key.id = ResourceId(ExtendedResTableMapTypes::ATTR_COMMENT); + StringPool::Ref commentRef = mCommentPool->makeRef(value->getComment()); + val.value.data = static_cast<uint32_t>(commentRef.getIndex()); + flattenEntry(&key, &val); + } + } + void visit(Attribute* attr) override { { Reference key(ResourceId{ ResTable_map::ATTR_TYPE }); @@ -149,6 +195,18 @@ struct MapFlattenVisitor : public RawValueVisitor { flattenEntry(&key, &val); } + if (attr->minInt != std::numeric_limits<int32_t>::min()) { + Reference key(ResourceId{ ResTable_map::ATTR_MIN }); + BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->minInt)); + flattenEntry(&key, &val); + } + + if (attr->maxInt != std::numeric_limits<int32_t>::max()) { + Reference key(ResourceId{ ResTable_map::ATTR_MAX }); + BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->maxInt)); + flattenEntry(&key, &val); + } + for (Attribute::Symbol& s : attr->symbols) { BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value); flattenEntry(&s.symbol, &val); @@ -169,7 +227,8 @@ struct MapFlattenVisitor : public RawValueVisitor { void visit(Style* style) override { if (style->parent) { - if (!style->parent.value().id) { + bool privateRef = style->parent.value().privateReference && mUseExtendedChunks; + if (!style->parent.value().id || privateRef) { assert(style->parent.value().name && "reference must have a name"); mParentName = style->parent.value().name; } else { @@ -182,6 +241,7 @@ struct MapFlattenVisitor : public RawValueVisitor { for (Style::Entry& entry : style->entries) { flattenEntry(&entry.key, entry.value.get()); + flattenMetaData(&entry.key); } } @@ -189,6 +249,7 @@ struct MapFlattenVisitor : public RawValueVisitor { for (auto& attrRef : styleable->entries) { BinaryPrimitive val(Res_value{}); flattenEntry(&attrRef, &val); + flattenMetaData(&attrRef); } } @@ -198,6 +259,7 @@ struct MapFlattenVisitor : public RawValueVisitor { flattenValue(item.get(), outEntry); outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value)); mEntryCount++; + flattenMetaData(item.get()); } } @@ -241,6 +303,7 @@ struct MapFlattenVisitor : public RawValueVisitor { Reference key(q); flattenEntry(&key, plural->values[i].get()); + flattenMetaData(plural->values[i].get()); } } }; @@ -339,21 +402,29 @@ private: bool flattenValue(FlatEntry* entry, BigBuffer* buffer) { if (Item* item = valueCast<Item>(entry->value)) { writeEntry<ResTable_entry, true>(entry, buffer); + bool privateRef = false; if (Reference* ref = valueCast<Reference>(entry->value)) { - if (!ref->id) { + // If there is no ID or the reference is private and we allow extended chunks, + // write out a 0 and mark the symbol table with the name of the reference. + privateRef = (ref->privateReference && mOptions.useExtendedChunks); + if (!ref->id || privateRef) { assert(ref->name && "reference must have at least a name"); - mSymbols->addSymbol(ref->name.value(), - buffer->size() + offsetof(Res_value, data)); + mSymbols->addSymbol(*ref, buffer->size() + offsetof(Res_value, data)); } } Res_value* outValue = buffer->nextBlock<Res_value>(); bool result = item->flatten(outValue); assert(result && "flatten failed"); + if (privateRef) { + // Force the value of 0 so we look up the symbol at unflatten time. + outValue->data = 0; + } outValue->size = util::hostToDevice16(sizeof(*outValue)); } else { const size_t beforeEntry = buffer->size(); ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext, false>(entry, buffer); - MapFlattenVisitor visitor(mSymbols, entry, buffer); + MapFlattenVisitor visitor(mSymbols, entry, buffer, mSourcePool, mSourcePool, + mOptions.useExtendedChunks); entry->value->accept(&visitor); outEntry->count = util::hostToDevice32(visitor.mEntryCount); if (visitor.mParentName) { diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp index 68a1f478c34d..7030603e5bbd 100644 --- a/tools/aapt2/flatten/TableFlattener_test.cpp +++ b/tools/aapt2/flatten/TableFlattener_test.cpp @@ -15,11 +15,11 @@ */ #include "flatten/TableFlattener.h" +#include "test/Builders.h" +#include "test/Context.h" #include "unflatten/BinaryResourceParser.h" #include "util/Util.h" -#include "test/Builders.h" -#include "test/Context.h" #include <gtest/gtest.h> @@ -262,4 +262,55 @@ TEST_F(TableFlattenerTest, FlattenUnlinkedTable) { EXPECT_EQ(ref->name.value(), test::parseNameOrDie(u"@com.app.test:color/green")); } +TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) { + Attribute attr(false); + attr.typeMask = android::ResTable_map::TYPE_INTEGER; + attr.minInt = 10; + attr.maxInt = 23; + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .setPackageId(u"android", 0x01) + .addValue(u"@android:attr/foo", ResourceId(0x01010000), + util::make_unique<Attribute>(attr)) + .build(); + + ResourceTable result; + ASSERT_TRUE(flatten(table.get(), &result)); + + Attribute* actualAttr = test::getValue<Attribute>(&result, u"@android:attr/foo"); + ASSERT_NE(nullptr, actualAttr); + EXPECT_EQ(attr.isWeak(), actualAttr->isWeak()); + EXPECT_EQ(attr.typeMask, actualAttr->typeMask); + EXPECT_EQ(attr.minInt, actualAttr->minInt); + EXPECT_EQ(attr.maxInt, actualAttr->maxInt); +} + +TEST_F(TableFlattenerTest, FlattenSourceAndCommentsForChildrenOfCompoundValues) { + Style style; + Reference key(test::parseNameOrDie(u"@android:attr/foo")); + key.id = ResourceId(0x01010000); + key.setSource(Source("test").withLine(2)); + key.setComment(StringPiece16(u"comment")); + style.entries.push_back(Style::Entry{ key, util::make_unique<Id>() }); + + test::ResourceTableBuilder builder = test::ResourceTableBuilder(); + std::unique_ptr<ResourceTable> table = builder + .setPackageId(u"android", 0x01) + .addValue(u"@android:attr/foo", ResourceId(0x01010000), + test::AttributeBuilder().build()) + .addValue(u"@android:style/foo", ResourceId(0x01020000), + std::unique_ptr<Style>(style.clone(builder.getStringPool()))) + .build(); + + ResourceTable result; + ASSERT_TRUE(flatten(table.get(), &result)); + + Style* actualStyle = test::getValue<Style>(&result, u"@android:style/foo"); + ASSERT_NE(nullptr, actualStyle); + ASSERT_EQ(1u, actualStyle->entries.size()); + + Reference* actualKey = &actualStyle->entries[0].key; + EXPECT_EQ(key.getSource(), actualKey->getSource()); + EXPECT_EQ(key.getComment(), actualKey->getComment()); +} + } // namespace aapt diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp index 4efb08bfe58d..8219462f8a73 100644 --- a/tools/aapt2/flatten/XmlFlattener.cpp +++ b/tools/aapt2/flatten/XmlFlattener.cpp @@ -15,15 +15,14 @@ */ #include "SdkConstants.h" -#include "XmlDom.h" - #include "flatten/ChunkWriter.h" #include "flatten/ResourceTypeExtensions.h" #include "flatten/XmlFlattener.h" +#include "xml/XmlDom.h" #include <androidfw/ResourceTypes.h> -#include <vector> #include <utils/misc.h> +#include <vector> using namespace android; @@ -306,7 +305,7 @@ bool XmlFlattener::flatten(IAaptContext* context, xml::Node* node) { return true; } -bool XmlFlattener::consume(IAaptContext* context, XmlResource* resource) { +bool XmlFlattener::consume(IAaptContext* context, xml::XmlResource* resource) { if (!resource->root) { return false; } diff --git a/tools/aapt2/flatten/XmlFlattener.h b/tools/aapt2/flatten/XmlFlattener.h index b1fb3a7cef27..a688ac965b0d 100644 --- a/tools/aapt2/flatten/XmlFlattener.h +++ b/tools/aapt2/flatten/XmlFlattener.h @@ -17,16 +17,12 @@ #ifndef AAPT_FLATTEN_XMLFLATTENER_H #define AAPT_FLATTEN_XMLFLATTENER_H -#include "util/BigBuffer.h" - #include "process/IResourceTableConsumer.h" +#include "util/BigBuffer.h" +#include "xml/XmlDom.h" namespace aapt { -namespace xml { -struct Node; -} - struct XmlFlattenerOptions { /** * Keep attribute raw string values along with typed values. @@ -45,7 +41,7 @@ public: mBuffer(buffer), mOptions(options) { } - bool consume(IAaptContext* context, XmlResource* resource) override; + bool consume(IAaptContext* context, xml::XmlResource* resource) override; private: BigBuffer* mBuffer; diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp index 318bcddf44ec..864887927328 100644 --- a/tools/aapt2/flatten/XmlFlattener_test.cpp +++ b/tools/aapt2/flatten/XmlFlattener_test.cpp @@ -16,11 +16,10 @@ #include "flatten/XmlFlattener.h" #include "link/Linkers.h" -#include "util/BigBuffer.h" -#include "util/Util.h" - #include "test/Builders.h" #include "test/Context.h" +#include "util/BigBuffer.h" +#include "util/Util.h" #include <androidfw/ResourceTypes.h> #include <gtest/gtest.h> @@ -45,7 +44,7 @@ public: .build(); } - ::testing::AssertionResult flatten(XmlResource* doc, android::ResXMLTree* outTree, + ::testing::AssertionResult flatten(xml::XmlResource* doc, android::ResXMLTree* outTree, XmlFlattenerOptions options = {}) { BigBuffer buffer(1024); XmlFlattener flattener(&buffer, options); @@ -65,7 +64,7 @@ protected: }; TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) { - std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( <View xmlns:test="http://com.test" attr="hey"> <Layout test:hello="hi" /> @@ -144,7 +143,7 @@ TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) { } TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripSdk21) { - std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/android" android:paddingStart="1dp" android:colorAccent="#ffffff"/>)EOF"); @@ -169,7 +168,7 @@ TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripSdk21) { } TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) { - std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/android" android:id="@id/id" class="str" @@ -192,7 +191,7 @@ TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) { * namespace. */ TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) { - std::unique_ptr<XmlResource> doc = test::buildXmlDom("<View package=\"android\"/>"); + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View package=\"android\"/>"); android::ResXMLTree tree; ASSERT_TRUE(flatten(doc.get(), &tree)); diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h new file mode 100644 index 000000000000..9081c55fc6e1 --- /dev/null +++ b/tools/aapt2/io/Data.h @@ -0,0 +1,85 @@ +/* + * 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_IO_DATA_H +#define AAPT_IO_DATA_H + +#include <utils/FileMap.h> + +#include <memory> + +namespace aapt { +namespace io { + +/** + * Interface for a block of contiguous memory. An instance of this interface owns the data. + */ +class IData { +public: + virtual ~IData() = default; + + virtual const void* data() const = 0; + virtual size_t size() const = 0; +}; + +/** + * Implementation of IData that exposes a memory mapped file. The mmapped file is owned by this + * object. + */ +class MmappedData : public IData { +public: + explicit MmappedData(android::FileMap&& map) : mMap(std::forward<android::FileMap>(map)) { + } + + const void* data() const override { + return mMap.getDataPtr(); + } + + size_t size() const override { + return mMap.getDataLength(); + } + +private: + android::FileMap mMap; +}; + +/** + * Implementation of IData that exposes a block of memory that was malloc'ed (new'ed). The + * memory is owned by this object. + */ +class MallocData : public IData { +public: + MallocData(std::unique_ptr<const uint8_t[]> data, size_t size) : + mData(std::move(data)), mSize(size) { + } + + const void* data() const override { + return mData.get(); + } + + size_t size() const override { + return mSize; + } + +private: + std::unique_ptr<const uint8_t[]> mData; + size_t mSize; +}; + +} // namespace io +} // namespace aapt + +#endif /* AAPT_IO_DATA_H */ diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h new file mode 100644 index 000000000000..9fca3980d964 --- /dev/null +++ b/tools/aapt2/io/File.h @@ -0,0 +1,72 @@ +/* + * 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_IO_FILE_H +#define AAPT_IO_FILE_H + +#include "Source.h" +#include "io/Data.h" + +#include <memory> +#include <vector> + +namespace aapt { +namespace io { + +/** + * Interface for a file, which could be a real file on the file system, or a file inside + * a ZIP archive. + */ +class IFile { +public: + virtual ~IFile() = default; + + /** + * Open the file and return it as a block of contiguous memory. How this occurs is + * implementation dependent. For example, if this is a file on the file system, it may + * simply mmap the contents. If this file represents a compressed file in a ZIP archive, + * it may need to inflate it to memory, incurring a copy. + * + * Returns nullptr on failure. + */ + virtual std::unique_ptr<IData> openAsData() = 0; + + /** + * Returns the source of this file. This is for presentation to the user and may not be a + * valid file system path (for example, it may contain a '@' sign to separate the files within + * a ZIP archive from the path to the containing ZIP archive. + */ + virtual const Source& getSource() const = 0; +}; + +/** + * Interface for a collection of files, all of which share a common source. That source may + * simply be the filesystem, or a ZIP archive. + */ +class IFileCollection { +public: + virtual ~IFileCollection() = default; + + using const_iterator = std::vector<std::unique_ptr<IFile>>::const_iterator; + + virtual const_iterator begin() const = 0; + virtual const_iterator end() const = 0; +}; + +} // namespace io +} // namespace aapt + +#endif /* AAPT_IO_FILE_H */ diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h new file mode 100644 index 000000000000..5dbefcc0f687 --- /dev/null +++ b/tools/aapt2/io/FileSystem.h @@ -0,0 +1,78 @@ +/* + * 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_IO_FILESYSTEM_H +#define AAPT_IO_FILESYSTEM_H + +#include "io/File.h" +#include "util/Files.h" + +namespace aapt { +namespace io { + +/** + * A regular file from the file system. Uses mmap to open the data. + */ +class RegularFile : public IFile { +public: + RegularFile(const Source& source) : mSource(source) { + } + + std::unique_ptr<IData> openAsData() override { + android::FileMap map; + if (Maybe<android::FileMap> map = file::mmapPath(mSource.path, nullptr)) { + return util::make_unique<MmappedData>(std::move(map.value())); + } + return {}; + } + + const Source& getSource() const override { + return mSource; + } + +private: + Source mSource; +}; + +/** + * An IFileCollection representing the file system. + */ +class FileCollection : public IFileCollection { +public: + /** + * Adds a file located at path. Returns the IFile representation of that file. + */ + IFile* insertFile(const StringPiece& path) { + mFiles.push_back(util::make_unique<RegularFile>(Source(path))); + return mFiles.back().get(); + } + + const_iterator begin() const override { + return mFiles.begin(); + } + + const_iterator end() const override { + return mFiles.end(); + } + +private: + std::vector<std::unique_ptr<IFile>> mFiles; +}; + +} // namespace io +} // namespace aapt + +#endif // AAPT_IO_FILESYSTEM_H diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h new file mode 100644 index 000000000000..98afc498708f --- /dev/null +++ b/tools/aapt2/io/ZipArchive.h @@ -0,0 +1,143 @@ +/* + * 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_IO_ZIPARCHIVE_H +#define AAPT_IO_ZIPARCHIVE_H + +#include "io/File.h" +#include "util/StringPiece.h" + +#include <utils/FileMap.h> +#include <ziparchive/zip_archive.h> + +namespace aapt { +namespace io { + +/** + * An IFile representing a file within a ZIP archive. If the file is compressed, it is uncompressed + * and copied into memory when opened. Otherwise it is mmapped from the ZIP archive. + */ +class ZipFile : public IFile { +public: + ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source) : + mZipHandle(handle), mZipEntry(entry), mSource(source) { + } + + std::unique_ptr<IData> openAsData() override { + if (mZipEntry.method == kCompressStored) { + int fd = GetFileDescriptor(mZipHandle); + + android::FileMap fileMap; + bool result = fileMap.create(nullptr, fd, mZipEntry.offset, + mZipEntry.uncompressed_length, true); + if (!result) { + return {}; + } + return util::make_unique<MmappedData>(std::move(fileMap)); + + } else { + std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>( + new uint8_t[mZipEntry.uncompressed_length]); + int32_t result = ExtractToMemory(mZipHandle, &mZipEntry, data.get(), + static_cast<uint32_t>(mZipEntry.uncompressed_length)); + if (result != 0) { + return {}; + } + return util::make_unique<MallocData>(std::move(data), mZipEntry.uncompressed_length); + } + } + + const Source& getSource() const override { + return mSource; + } + +private: + ZipArchiveHandle mZipHandle; + ZipEntry mZipEntry; + Source mSource; +}; + +/** + * An IFileCollection that represents a ZIP archive and the entries within it. + */ +class ZipFileCollection : public IFileCollection { +public: + static std::unique_ptr<ZipFileCollection> create(const StringPiece& path, + std::string* outError) { + std::unique_ptr<ZipFileCollection> collection = std::unique_ptr<ZipFileCollection>( + new ZipFileCollection()); + + int32_t result = OpenArchive(path.data(), &collection->mHandle); + if (result != 0) { + if (outError) *outError = ErrorCodeString(result); + return {}; + } + + ZipString suffix(".flat"); + void* cookie = nullptr; + result = StartIteration(collection->mHandle, &cookie, nullptr, &suffix); + if (result != 0) { + if (outError) *outError = ErrorCodeString(result); + return {}; + } + + using IterationEnder = std::unique_ptr<void, decltype(EndIteration)*>; + IterationEnder iterationEnder(cookie, EndIteration); + + ZipString zipEntryName; + ZipEntry zipData; + while ((result = Next(cookie, &zipData, &zipEntryName)) == 0) { + std::string nestedPath = path.toString(); + nestedPath += "@" + std::string(reinterpret_cast<const char*>(zipEntryName.name), + zipEntryName.name_length); + collection->mFiles.push_back(util::make_unique<ZipFile>(collection->mHandle, + zipData, + Source(nestedPath))); + } + + if (result != -1) { + if (outError) *outError = ErrorCodeString(result); + return {}; + } + return collection; + } + + const_iterator begin() const override { + return mFiles.begin(); + } + + const_iterator end() const override { + return mFiles.end(); + } + + ~ZipFileCollection() override { + if (mHandle) { + CloseArchive(mHandle); + } + } + +private: + ZipFileCollection() : mHandle(nullptr) { + } + + ZipArchiveHandle mHandle; + std::vector<std::unique_ptr<IFile>> mFiles; +}; + +} // namespace io +} // namespace aapt + +#endif /* AAPT_IO_ZIPARCHIVE_H */ diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp index d5a2b38bcbc4..da96b84fd4ea 100644 --- a/tools/aapt2/java/AnnotationProcessor_test.cpp +++ b/tools/aapt2/java/AnnotationProcessor_test.cpp @@ -17,12 +17,10 @@ #include "ResourceParser.h" #include "ResourceTable.h" #include "ResourceValues.h" -#include "XmlPullParser.h" - #include "java/AnnotationProcessor.h" - #include "test/Builders.h" #include "test/Context.h" +#include "xml/XmlPullParser.h" #include <gtest/gtest.h> @@ -42,7 +40,7 @@ struct AnnotationProcessorTest : public ::testing::Test { options); std::stringstream in; in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str; - XmlPullParser xmlParser(in); + xml::XmlPullParser xmlParser(in); if (parser.parse(&xmlParser)) { return ::testing::AssertionSuccess(); } diff --git a/tools/aapt2/java/ClassDefinitionWriter.h b/tools/aapt2/java/ClassDefinitionWriter.h index b8886f90c6c5..04e1274c3a97 100644 --- a/tools/aapt2/java/ClassDefinitionWriter.h +++ b/tools/aapt2/java/ClassDefinitionWriter.h @@ -17,6 +17,7 @@ #ifndef AAPT_JAVA_CLASSDEFINITION_H #define AAPT_JAVA_CLASSDEFINITION_H +#include "Resource.h" #include "java/AnnotationProcessor.h" #include "util/StringPiece.h" #include "util/Util.h" diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp index d963d8948f8d..a9b4c14337fe 100644 --- a/tools/aapt2/java/ManifestClassGenerator.cpp +++ b/tools/aapt2/java/ManifestClassGenerator.cpp @@ -15,12 +15,11 @@ */ #include "Source.h" -#include "XmlDom.h" - #include "java/AnnotationProcessor.h" #include "java/ClassDefinitionWriter.h" #include "java/ManifestClassGenerator.h" #include "util/Maybe.h" +#include "xml/XmlDom.h" #include <algorithm> @@ -80,7 +79,7 @@ static bool writeSymbol(IDiagnostics* diag, ClassDefinitionWriter* outClassDef, } bool ManifestClassGenerator::generate(IDiagnostics* diag, const StringPiece16& package, - XmlResource* res, std::ostream* out) { + xml::XmlResource* res, std::ostream* out) { xml::Element* el = xml::findRootElement(res->root.get()); if (!el) { return false; diff --git a/tools/aapt2/java/ManifestClassGenerator.h b/tools/aapt2/java/ManifestClassGenerator.h index 0f0998f8e2ba..226ed23b85f8 100644 --- a/tools/aapt2/java/ManifestClassGenerator.h +++ b/tools/aapt2/java/ManifestClassGenerator.h @@ -18,15 +18,15 @@ #define AAPT_JAVA_MANIFESTCLASSGENERATOR_H #include "Diagnostics.h" -#include "process/IResourceTableConsumer.h" #include "util/StringPiece.h" +#include "xml/XmlDom.h" #include <iostream> namespace aapt { struct ManifestClassGenerator { - bool generate(IDiagnostics* diag, const StringPiece16& package, XmlResource* res, + bool generate(IDiagnostics* diag, const StringPiece16& package, xml::XmlResource* res, std::ostream* out); }; diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp index 4081287a1852..fc57ae6fd8ff 100644 --- a/tools/aapt2/java/ManifestClassGenerator_test.cpp +++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp @@ -15,7 +15,6 @@ */ #include "java/ManifestClassGenerator.h" - #include "test/Builders.h" #include "test/Context.h" @@ -25,7 +24,7 @@ namespace aapt { TEST(ManifestClassGeneratorTest, NameIsProperlyGeneratedFromSymbol) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - std::unique_ptr<XmlResource> manifest = test::buildXmlDom(R"EOF( + std::unique_ptr<xml::XmlResource> manifest = test::buildXmlDom(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <permission android:name="android.permission.ACCESS_INTERNET" /> <permission android:name="android.DO_DANGEROUS_THINGS" /> @@ -75,7 +74,7 @@ TEST(ManifestClassGeneratorTest, NameIsProperlyGeneratedFromSymbol) { TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - std::unique_ptr<XmlResource> manifest = test::buildXmlDom(R"EOF( + std::unique_ptr<xml::XmlResource> manifest = test::buildXmlDom(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <!-- Required to access the internet. Added in API 1. --> diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp index 44314772fbd4..c0968545ad81 100644 --- a/tools/aapt2/java/ProguardRules.cpp +++ b/tools/aapt2/java/ProguardRules.cpp @@ -14,10 +14,9 @@ * limitations under the License. */ -#include "XmlDom.h" - #include "java/ProguardRules.h" #include "util/Util.h" +#include "xml/XmlDom.h" #include <memory> #include <string> @@ -40,11 +39,11 @@ public: virtual void visit(xml::Element* node) override { if (!node->namespaceUri.empty()) { - Maybe<std::u16string> maybePackage = util::extractPackageFromNamespace( + Maybe<xml::ExtractedPackage> maybePackage = xml::extractPackageFromNamespace( node->namespaceUri); if (maybePackage) { // This is a custom view, let's figure out the class name from this. - std::u16string package = maybePackage.value() + u"." + node->name; + std::u16string package = maybePackage.value().package + u"." + node->name; if (util::isJavaClassName(package)) { addClass(node->lineNumber, package); } @@ -185,7 +184,8 @@ struct ManifestVisitor : public BaseVisitor { std::u16string mPackage; }; -bool collectProguardRulesForManifest(const Source& source, XmlResource* res, KeepSet* keepSet) { +bool collectProguardRulesForManifest(const Source& source, xml::XmlResource* res, + KeepSet* keepSet) { ManifestVisitor visitor(source, keepSet); if (res->root) { res->root->accept(&visitor); @@ -194,7 +194,7 @@ bool collectProguardRulesForManifest(const Source& source, XmlResource* res, Kee return false; } -bool collectProguardRules(const Source& source, XmlResource* res, KeepSet* keepSet) { +bool collectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keepSet) { if (!res->root) { return false; } diff --git a/tools/aapt2/java/ProguardRules.h b/tools/aapt2/java/ProguardRules.h index be61eb9095c2..aafffd39d84e 100644 --- a/tools/aapt2/java/ProguardRules.h +++ b/tools/aapt2/java/ProguardRules.h @@ -19,8 +19,7 @@ #include "Resource.h" #include "Source.h" - -#include "process/IResourceTableConsumer.h" +#include "xml/XmlDom.h" #include <map> #include <ostream> @@ -47,8 +46,8 @@ private: std::map<std::u16string, std::set<Source>> mKeepMethodSet; }; -bool collectProguardRulesForManifest(const Source& source, XmlResource* res, KeepSet* keepSet); -bool collectProguardRules(const Source& source, XmlResource* res, KeepSet* keepSet); +bool collectProguardRulesForManifest(const Source& source, xml::XmlResource* res, KeepSet* keepSet); +bool collectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keepSet); bool writeKeepSet(std::ostream* out, const KeepSet& keepSet); diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp index 93f2dc6f04cd..33d9272b39fd 100644 --- a/tools/aapt2/link/Link.cpp +++ b/tools/aapt2/link/Link.cpp @@ -18,16 +18,17 @@ #include "Debug.h" #include "Flags.h" #include "NameMangler.h" -#include "XmlDom.h" - #include "compile/IdAssigner.h" #include "flatten/Archive.h" #include "flatten/TableFlattener.h" #include "flatten/XmlFlattener.h" +#include "io/FileSystem.h" +#include "io/ZipArchive.h" #include "java/JavaClassGenerator.h" #include "java/ManifestClassGenerator.h" #include "java/ProguardRules.h" #include "link/Linkers.h" +#include "link/ReferenceLinker.h" #include "link/ManifestFixer.h" #include "link/TableMerger.h" #include "process/IResourceTableConsumer.h" @@ -36,10 +37,10 @@ #include "unflatten/FileExportHeaderReader.h" #include "util/Files.h" #include "util/StringPiece.h" +#include "xml/XmlDom.h" #include <fstream> #include <sys/stat.h> -#include <utils/FileMap.h> #include <vector> namespace aapt { @@ -50,7 +51,7 @@ struct LinkOptions { std::vector<std::string> includePaths; std::vector<std::string> overlayFiles; Maybe<std::string> generateJavaClassPath; - std::vector<std::string> extraJavaPackages; + std::set<std::string> extraJavaPackages; Maybe<std::string> generateProguardRulesPath; bool noAutoVersion = false; bool staticLib = false; @@ -92,7 +93,15 @@ struct LinkContext : public IAaptContext { class LinkCommand { public: LinkCommand(const LinkOptions& options) : - mOptions(options), mContext(), mFinalTable() { + mOptions(options), mContext(), mFinalTable(), mFileCollection(nullptr) { + std::unique_ptr<io::FileCollection> fileCollection = + util::make_unique<io::FileCollection>(); + + // Get a pointer to the FileCollection for convenience, but it will be owned by the vector. + mFileCollection = fileCollection.get(); + + // Move it to the collection. + mCollections.push_back(std::move(fileCollection)); } std::string buildResourceFileName(const ResourceFile& resFile) { @@ -136,20 +145,9 @@ public: return builder.build(); } - /** - * Loads the resource table (not inside an apk) at the given path. - */ - std::unique_ptr<ResourceTable> loadTable(const std::string& input) { - std::string errorStr; - Maybe<android::FileMap> map = file::mmapPath(input, &errorStr); - if (!map) { - mContext.getDiagnostics()->error(DiagMessage(input) << errorStr); - return {}; - } - + std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len) { std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>(); - BinaryResourceParser parser(&mContext, table.get(), Source(input), - map.value().getDataPtr(), map.value().getDataLength()); + BinaryResourceParser parser(&mContext, table.get(), source, data, len); if (!parser.parse()) { return {}; } @@ -159,93 +157,82 @@ public: /** * Inflates an XML file from the source path. */ - std::unique_ptr<XmlResource> loadXml(const std::string& path) { + static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) { std::ifstream fin(path, std::ifstream::binary); if (!fin) { - mContext.getDiagnostics()->error(DiagMessage(path) << strerror(errno)); + diag->error(DiagMessage(path) << strerror(errno)); return {}; } - return xml::inflate(&fin, mContext.getDiagnostics(), Source(path)); + return xml::inflate(&fin, diag, Source(path)); } - /** - * Inflates a binary XML file from the source path. - */ - std::unique_ptr<XmlResource> loadBinaryXmlSkipFileExport(const std::string& path) { - // Read header for symbol info and export info. + static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport( + const Source& source, + const void* data, size_t len, + IDiagnostics* diag) { std::string errorStr; - Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr); - if (!maybeF) { - mContext.getDiagnostics()->error(DiagMessage(path) << errorStr); - return {}; - } - - ssize_t offset = getWrappedDataOffset(maybeF.value().getDataPtr(), - maybeF.value().getDataLength(), &errorStr); + ssize_t offset = getWrappedDataOffset(data, len, &errorStr); if (offset < 0) { - mContext.getDiagnostics()->error(DiagMessage(path) << errorStr); + diag->error(DiagMessage(source) << errorStr); return {}; } - std::unique_ptr<XmlResource> xmlRes = xml::inflate( - (const uint8_t*) maybeF.value().getDataPtr() + (size_t) offset, - maybeF.value().getDataLength() - offset, - mContext.getDiagnostics(), Source(path)); + std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate( + reinterpret_cast<const uint8_t*>(data) + static_cast<size_t>(offset), + len - static_cast<size_t>(offset), + diag, + source); if (!xmlRes) { return {}; } return xmlRes; } - Maybe<ResourceFile> loadFileExportHeader(const std::string& path) { - // Read header for symbol info and export info. + static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source, + const void* data, size_t len, + IDiagnostics* diag) { + std::unique_ptr<ResourceFile> resFile = util::make_unique<ResourceFile>(); std::string errorStr; - Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr); - if (!maybeF) { - mContext.getDiagnostics()->error(DiagMessage(path) << errorStr); - return {}; - } - - ResourceFile resFile; - ssize_t offset = unwrapFileExportHeader(maybeF.value().getDataPtr(), - maybeF.value().getDataLength(), - &resFile, &errorStr); + ssize_t offset = unwrapFileExportHeader(data, len, resFile.get(), &errorStr); if (offset < 0) { - mContext.getDiagnostics()->error(DiagMessage(path) << errorStr); + diag->error(DiagMessage(source) << errorStr); return {}; } - return std::move(resFile); + return resFile; } - bool copyFileToArchive(const std::string& path, const std::string& outPath, uint32_t flags, + bool copyFileToArchive(io::IFile* file, const std::string& outPath, uint32_t flags, IArchiveWriter* writer) { - std::string errorStr; - Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr); - if (!maybeF) { - mContext.getDiagnostics()->error(DiagMessage(path) << errorStr); + std::unique_ptr<io::IData> data = file->openAsData(); + if (!data) { + mContext.getDiagnostics()->error(DiagMessage(file->getSource()) + << "failed to open file"); return false; } - ssize_t offset = getWrappedDataOffset(maybeF.value().getDataPtr(), - maybeF.value().getDataLength(), - &errorStr); + std::string errorStr; + ssize_t offset = getWrappedDataOffset(data->data(), data->size(), &errorStr); if (offset < 0) { - mContext.getDiagnostics()->error(DiagMessage(path) << errorStr); + mContext.getDiagnostics()->error(DiagMessage(file->getSource()) << errorStr); return false; } - ArchiveEntry* entry = writer->writeEntry(outPath, flags, &maybeF.value(), - offset, maybeF.value().getDataLength() - offset); - if (!entry) { - mContext.getDiagnostics()->error( - DiagMessage(mOptions.outputPath) << "failed to write file " << outPath); - return false; + if (writer->startEntry(outPath, flags)) { + if (writer->writeEntry(reinterpret_cast<const uint8_t*>(data->data()) + offset, + data->size() - static_cast<size_t>(offset))) { + if (writer->finishEntry()) { + return true; + } + } } - return true; + + mContext.getDiagnostics()->error( + DiagMessage(mOptions.outputPath) << "failed to write file " << outPath); + return false; } - Maybe<AppInfo> extractAppInfoFromManifest(XmlResource* xmlRes) { + Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) { // Make sure the first element is <manifest> with package attribute. if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) { if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") { @@ -285,9 +272,9 @@ public: std::unique_ptr<IArchiveWriter> makeArchiveWriter() { if (mOptions.outputToDirectory) { - return createDirectoryArchiveWriter(mOptions.outputPath); + return createDirectoryArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath); } else { - return createZipFileArchiveWriter(mOptions.outputPath); + return createZipFileArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath); } } @@ -300,16 +287,20 @@ public: return false; } - ArchiveEntry* entry = writer->writeEntry("resources.arsc", ArchiveEntry::kAlign, buffer); - if (!entry) { - mContext.getDiagnostics()->error( - DiagMessage() << "failed to write resources.arsc to archive"); - return false; + if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) { + if (writer->writeEntry(buffer)) { + if (writer->finishEntry()) { + return true; + } + } } - return true; + + mContext.getDiagnostics()->error( + DiagMessage() << "failed to write resources.arsc to archive"); + return false; } - bool flattenXml(XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel, + bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel, IArchiveWriter* writer) { BigBuffer buffer(1024); XmlFlattenerOptions options = {}; @@ -320,13 +311,17 @@ public: return false; } - ArchiveEntry* entry = writer->writeEntry(path, ArchiveEntry::kCompress, buffer); - if (!entry) { - mContext.getDiagnostics()->error( - DiagMessage() << "failed to write " << path << " to archive"); - return false; + + if (writer->startEntry(path, ArchiveEntry::kCompress)) { + if (writer->writeEntry(buffer)) { + if (writer->finishEntry()) { + return true; + } + } } - return true; + mContext.getDiagnostics()->error( + DiagMessage() << "failed to write " << path << " to archive"); + return false; } bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate, @@ -354,7 +349,7 @@ public: return true; } - bool writeManifestJavaFile(XmlResource* manifestXml) { + bool writeManifestJavaFile(xml::XmlResource* manifestXml) { if (!mOptions.generateJavaClassPath) { return true; } @@ -412,34 +407,44 @@ public: return true; } - bool mergeResourceTable(const std::string& input, bool override) { + bool mergeResourceTable(io::IFile* file, bool override) { if (mOptions.verbose) { - mContext.getDiagnostics()->note(DiagMessage() << "linking " << input); + mContext.getDiagnostics()->note(DiagMessage() << "linking " << file->getSource()); + } + + std::unique_ptr<io::IData> data = file->openAsData(); + if (!data) { + mContext.getDiagnostics()->error(DiagMessage(file->getSource()) + << "failed to open file"); + return false; } - std::unique_ptr<ResourceTable> table = loadTable(input); + std::unique_ptr<ResourceTable> table = loadTable(file->getSource(), data->data(), + data->size()); if (!table) { return false; } - if (!mTableMerger->merge(Source(input), table.get(), override)) { + if (!mTableMerger->merge(file->getSource(), table.get(), override)) { return false; } return true; } - bool mergeCompiledFile(const std::string& input, ResourceFile&& file, bool override) { - if (file.name.package.empty()) { - file.name.package = mContext.getCompilationPackage().toString(); + bool mergeCompiledFile(io::IFile* file, std::unique_ptr<ResourceFile> fileDesc, bool override) { + // Apply the package name used for this compilation phase if none was specified. + if (fileDesc->name.package.empty()) { + fileDesc->name.package = mContext.getCompilationPackage().toString(); } - ResourceNameRef resName = file.name; - - Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(file.name); + // Mangle the name if necessary. + ResourceNameRef resName = fileDesc->name; + Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(fileDesc->name); if (mangledName) { resName = mangledName.value(); } + // If we are overriding resources, we supply a custom resolver function. std::function<int(Value*,Value*)> resolver; if (override) { resolver = [](Value* a, Value* b) -> int { @@ -456,14 +461,14 @@ public: } // Add this file to the table. - if (!mFinalTable.addFileReference(resName, file.config, file.source, - util::utf8ToUtf16(buildResourceFileName(file)), + if (!mFinalTable.addFileReference(resName, fileDesc->config, fileDesc->source, + util::utf8ToUtf16(buildResourceFileName(*fileDesc)), resolver, mContext.getDiagnostics())) { return false; } // Add the exports of this file to the table. - for (SourcedResourceName& exportedSymbol : file.exportedSymbols) { + for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) { if (exportedSymbol.name.package.empty()) { exportedSymbol.name.package = mContext.getCompilationPackage().toString(); } @@ -477,32 +482,78 @@ public: } std::unique_ptr<Id> id = util::make_unique<Id>(); - id->setSource(file.source.withLine(exportedSymbol.line)); + id->setSource(fileDesc->source.withLine(exportedSymbol.line)); bool result = mFinalTable.addResourceAllowMangled(resName, {}, std::move(id), - mContext.getDiagnostics()); + mContext.getDiagnostics()); if (!result) { return false; } } - mFilesToProcess[resName.toResourceName()] = FileToProcess{ Source(input), std::move(file) }; + // Now add this file for later processing. Once the table is assigned IDs, we can compile + // this file. + mFilesToProcess.insert(FileToProcess{ std::move(fileDesc), file }); return true; } - bool processFile(const std::string& input, bool override) { - if (util::stringEndsWith<char>(input, ".apk")) { - return mergeStaticLibrary(input); - } else if (util::stringEndsWith<char>(input, ".arsc.flat")) { - return mergeResourceTable(input, override); - } else if (Maybe<ResourceFile> maybeF = loadFileExportHeader(input)) { - return mergeCompiledFile(input, std::move(maybeF.value()), override); + /** + * Creates an io::IFileCollection from the ZIP archive and processes the files within. + */ + bool mergeArchive(const std::string& input, bool override) { + std::string errorStr; + std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create( + input, &errorStr); + if (!collection) { + mContext.getDiagnostics()->error(DiagMessage(input) << errorStr); + return false; + } + + bool error = false; + for (const std::unique_ptr<io::IFile>& file : *collection) { + if (!processFile(file.get(), override)) { + error = true; + } + } + + // Make sure to move the collection into the set of IFileCollections. + mCollections.push_back(std::move(collection)); + return !error; + } + + bool processFile(const std::string& path, bool override) { + if (util::stringEndsWith<char>(path, ".flata")) { + return mergeArchive(path, override); + } + + io::IFile* file = mFileCollection->insertFile(path); + return processFile(file, override); + } + + bool processFile(io::IFile* file, bool override) { + const Source& src = file->getSource(); + if (util::stringEndsWith<char>(src.path, ".arsc.flat")) { + return mergeResourceTable(file, override); + } else { + // Try opening the file and looking for an Export header. + std::unique_ptr<io::IData> data = file->openAsData(); + if (!data) { + mContext.getDiagnostics()->error(DiagMessage(src) << "failed to open"); + return false; + } + + std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader( + src, data->data(), data->size(), mContext.getDiagnostics()); + if (resourceFile) { + return mergeCompiledFile(file, std::move(resourceFile), override); + } } return false; } int run(const std::vector<std::string>& inputFiles) { // Load the AndroidManifest.xml - std::unique_ptr<XmlResource> manifestXml = loadXml(mOptions.manifestPath); + std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath, + mContext.getDiagnostics()); if (!manifestXml) { return 1; } @@ -545,25 +596,21 @@ public: << "with package ID " << std::hex << (int) mContext.mPackageId); } - bool error = false; for (const std::string& input : inputFiles) { if (!processFile(input, false)) { - error = true; + mContext.getDiagnostics()->error(DiagMessage() << "failed parsing input"); + return 1; } } for (const std::string& input : mOptions.overlayFiles) { if (!processFile(input, true)) { - error = true; + mContext.getDiagnostics()->error(DiagMessage() << "failed parsing overlays"); + return 1; } } - if (error) { - mContext.getDiagnostics()->error(DiagMessage() << "failed parsing input"); - return 1; - } - if (!verifyNoExternalPackages()) { return 1; } @@ -608,6 +655,7 @@ public: return 1; } + bool error = false; { ManifestFixerOptions manifestFixerOptions; manifestFixerOptions.minSdkVersionDefault = mOptions.minSdkVersionDefault; @@ -617,6 +665,11 @@ public: error = true; } + // AndroidManifest.xml has no resource name, but the CallSite is built from the name + // (aka, which package the AndroidManifest.xml is coming from). + // So we give it a package name so it can see local resources. + manifestXml->file.name.package = mContext.getCompilationPackage().toString(); + XmlReferenceLinker manifestLinker; if (manifestLinker.consume(&mContext, manifestXml.get())) { if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath), @@ -640,20 +693,36 @@ public: } } - for (auto& pair : mFilesToProcess) { - FileToProcess& file = pair.second; - if (file.file.name.type != ResourceType::kRaw && - util::stringEndsWith<char>(file.source.path, ".xml.flat")) { + if (error) { + mContext.getDiagnostics()->error(DiagMessage() << "failed processing manifest"); + return 1; + } + + for (const FileToProcess& file : mFilesToProcess) { + const StringPiece path = file.file->getSource().path; + + if (file.fileExport->name.type != ResourceType::kRaw && + util::stringEndsWith<char>(path, ".xml.flat")) { if (mOptions.verbose) { - mContext.getDiagnostics()->note(DiagMessage() << "linking " << file.source.path); + mContext.getDiagnostics()->note(DiagMessage() << "linking " << path); } - std::unique_ptr<XmlResource> xmlRes = loadBinaryXmlSkipFileExport(file.source.path); + std::unique_ptr<io::IData> data = file.file->openAsData(); + if (!data) { + mContext.getDiagnostics()->error(DiagMessage(file.file->getSource()) + << "failed to open file"); + return 1; + } + + std::unique_ptr<xml::XmlResource> xmlRes = loadBinaryXmlSkipFileExport( + file.file->getSource(), data->data(), data->size(), + mContext.getDiagnostics()); if (!xmlRes) { return 1; } - xmlRes->file = std::move(file.file); + // Move the file description over. + xmlRes->file = std::move(*file.fileExport); XmlReferenceLinker xmlLinker; if (xmlLinker.consume(&mContext, xmlRes.get())) { @@ -681,12 +750,13 @@ public: xmlRes->file.config, sdkLevel)) { xmlRes->file.config.sdkVersion = sdkLevel; - if (!mFinalTable.addFileReference(xmlRes->file.name, - xmlRes->file.config, - xmlRes->file.source, - util::utf8ToUtf16( - buildResourceFileName(xmlRes->file)), - mContext.getDiagnostics())) { + bool added = mFinalTable.addFileReference( + xmlRes->file.name, + xmlRes->file.config, + xmlRes->file.source, + util::utf8ToUtf16(buildResourceFileName(xmlRes->file)), + mContext.getDiagnostics()); + if (!added) { error = true; continue; } @@ -704,11 +774,10 @@ public: } } else { if (mOptions.verbose) { - mContext.getDiagnostics()->note(DiagMessage() << "copying " - << file.source.path); + mContext.getDiagnostics()->note(DiagMessage() << "copying " << path); } - if (!copyFileToArchive(file.source.path, buildResourceFileName(file.file), 0, + if (!copyFileToArchive(file.file, buildResourceFileName(*file.fileExport), 0, archiveWriter.get())) { error = true; } @@ -760,7 +829,7 @@ public: return 1; } - for (std::string& extraPackage : mOptions.extraJavaPackages) { + for (const std::string& extraPackage : mOptions.extraJavaPackages) { if (!writeJavaFile(&mFinalTable, actualPackage, util::utf8ToUtf16(extraPackage), options)) { return 1; @@ -794,17 +863,29 @@ private: ResourceTable mFinalTable; std::unique_ptr<TableMerger> mTableMerger; + io::FileCollection* mFileCollection; + std::vector<std::unique_ptr<io::IFileCollection>> mCollections; + struct FileToProcess { - Source source; - ResourceFile file; + std::unique_ptr<ResourceFile> fileExport; + io::IFile* file; + }; + + struct FileToProcessComparator { + bool operator()(const FileToProcess& a, const FileToProcess& b) { + return std::tie(a.fileExport->name, a.fileExport->config) < + std::tie(b.fileExport->name, b.fileExport->config); + } }; - std::map<ResourceName, FileToProcess> mFilesToProcess; + + std::set<FileToProcess, FileToProcessComparator> mFilesToProcess; }; int link(const std::vector<StringPiece>& args) { LinkOptions options; Maybe<std::string> privateSymbolsPackage; Maybe<std::string> minSdkVersion, targetSdkVersion; + std::vector<std::string> extraJavaPackages; Flags flags = Flags() .requiredFlag("-o", "Output path", &options.outputPath) .requiredFlag("--manifest", "Path to the Android manifest to build", @@ -833,7 +914,7 @@ int link(const std::vector<StringPiece>& args) { "If not specified, public and private symbols will use the application's " "package name", &privateSymbolsPackage) .optionalFlagList("--extra-packages", "Generate the same R.java but with different " - "package names", &options.extraJavaPackages) + "package names", &extraJavaPackages) .optionalSwitch("-v", "Enables verbose logging", &options.verbose); if (!flags.parse("aapt2 link", args, &std::cerr)) { @@ -852,6 +933,14 @@ int link(const std::vector<StringPiece>& args) { options.targetSdkVersionDefault = util::utf8ToUtf16(targetSdkVersion.value()); } + // Populate the set of extra packages for which to generate R.java. + for (std::string& extraPackage : extraJavaPackages) { + // A given package can actually be a colon separated list of packages. + for (StringPiece package : util::split(extraPackage, ':')) { + options.extraJavaPackages.insert(package.toString()); + } + } + LinkCommand cmd(options); return cmd.run(flags.getArgs()); } diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h index 7b3fc358fca4..4d3a483c6b82 100644 --- a/tools/aapt2/link/Linkers.h +++ b/tools/aapt2/link/Linkers.h @@ -17,7 +17,9 @@ #ifndef AAPT_LINKER_LINKERS_H #define AAPT_LINKER_LINKERS_H +#include "Resource.h" #include "process/IResourceTableConsumer.h" +#include "xml/XmlDom.h" #include <set> @@ -28,6 +30,14 @@ struct ResourceEntry; struct ConfigDescription; /** + * Defines the location in which a value exists. This determines visibility of other + * package's private symbols. + */ +struct CallSite { + ResourceNameRef resource; +}; + +/** * Determines whether a versioned resource should be created. If a versioned resource already * exists, it takes precedence. */ @@ -39,7 +49,7 @@ struct AutoVersioner : public IResourceTableConsumer { }; struct XmlAutoVersioner : public IXmlResourceConsumer { - bool consume(IAaptContext* context, XmlResource* resource) override; + bool consume(IAaptContext* context, xml::XmlResource* resource) override; }; /** @@ -69,15 +79,6 @@ struct PrivateAttributeMover : public IResourceTableConsumer { }; /** - * Resolves all references to resources in the ResourceTable and assigns them IDs. - * The ResourceTable must already have IDs assigned to each resource. - * Once the ResourceTable is processed by this linker, it is ready to be flattened. - */ -struct ReferenceLinker : public IResourceTableConsumer { - bool consume(IAaptContext* context, ResourceTable* table) override; -}; - -/** * Resolves attributes in the XmlResource and compiles string values to resource values. * Once an XmlResource is processed by this linker, it is ready to be flattened. */ @@ -86,7 +87,7 @@ private: std::set<int> mSdkLevelsFound; public: - bool consume(IAaptContext* context, XmlResource* resource) override; + bool consume(IAaptContext* context, xml::XmlResource* resource) override; /** * Once the XmlResource has been consumed, this returns the various SDK levels in which diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index 52d942670243..2034c5701492 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -15,10 +15,9 @@ */ #include "ResourceUtils.h" -#include "XmlDom.h" - #include "link/ManifestFixer.h" #include "util/Util.h" +#include "xml/XmlDom.h" namespace aapt { @@ -63,7 +62,7 @@ static bool fixUsesSdk(IAaptContext* context, const Source& source, xml::Element return true; } -bool ManifestFixer::consume(IAaptContext* context, XmlResource* doc) { +bool ManifestFixer::consume(IAaptContext* context, xml::XmlResource* doc) { xml::Element* root = xml::findRootElement(doc->root.get()); if (!root || !root->namespaceUri.empty() || root->name != u"manifest") { context->getDiagnostics()->error(DiagMessage(doc->file.source) diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h index 16e161dc40d8..a77e6d5f709c 100644 --- a/tools/aapt2/link/ManifestFixer.h +++ b/tools/aapt2/link/ManifestFixer.h @@ -18,6 +18,10 @@ #define AAPT_LINK_MANIFESTFIXER_H #include "process/IResourceTableConsumer.h" +#include "util/Maybe.h" +#include "xml/XmlDom.h" + +#include <string> namespace aapt { @@ -36,7 +40,7 @@ struct ManifestFixer : public IXmlResourceConsumer { ManifestFixer(const ManifestFixerOptions& options) : mOptions(options) { } - bool consume(IAaptContext* context, XmlResource* doc) override; + bool consume(IAaptContext* context, xml::XmlResource* doc) override; }; } // namespace aapt diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp index 5c5d8afa610d..f6bf895a7f57 100644 --- a/tools/aapt2/link/ManifestFixer_test.cpp +++ b/tools/aapt2/link/ManifestFixer_test.cpp @@ -15,7 +15,6 @@ */ #include "link/ManifestFixer.h" - #include "test/Builders.h" #include "test/Context.h" @@ -51,13 +50,13 @@ struct ManifestFixerTest : public ::testing::Test { .build(); } - std::unique_ptr<XmlResource> verify(const StringPiece& str) { + std::unique_ptr<xml::XmlResource> verify(const StringPiece& str) { return verifyWithOptions(str, {}); } - std::unique_ptr<XmlResource> verifyWithOptions(const StringPiece& str, - const ManifestFixerOptions& options) { - std::unique_ptr<XmlResource> doc = test::buildXmlDom(str); + std::unique_ptr<xml::XmlResource> verifyWithOptions(const StringPiece& str, + const ManifestFixerOptions& options) { + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(str); ManifestFixer fixer(options); if (fixer.consume(mContext.get(), doc.get())) { return doc; @@ -88,7 +87,7 @@ TEST_F(ManifestFixerTest, EnsureManifestHasPackage) { TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) { ManifestFixerOptions options = { std::u16string(u"8"), std::u16string(u"22") }; - std::unique_ptr<XmlResource> doc = verifyWithOptions(R"EOF( + std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="21" /> diff --git a/tools/aapt2/link/PrivateAttributeMover.cpp b/tools/aapt2/link/PrivateAttributeMover.cpp index 5a2f5f0771d2..3c8af4f81ffe 100644 --- a/tools/aapt2/link/PrivateAttributeMover.cpp +++ b/tools/aapt2/link/PrivateAttributeMover.cpp @@ -15,7 +15,6 @@ */ #include "ResourceTable.h" - #include "link/Linkers.h" #include <algorithm> diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp index 8c924b5b0f99..27435398c408 100644 --- a/tools/aapt2/link/ReferenceLinker.cpp +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -14,17 +14,18 @@ * limitations under the License. */ +#include "ReferenceLinker.h" + #include "Diagnostics.h" #include "ResourceTable.h" #include "ResourceUtils.h" #include "ResourceValues.h" -#include "util/Util.h" #include "ValueVisitor.h" - #include "link/Linkers.h" -#include "link/ReferenceLinkerVisitor.h" #include "process/IResourceTableConsumer.h" #include "process/SymbolTable.h" +#include "util/Util.h" +#include "xml/XmlUtil.h" #include <androidfw/ResourceTypes.h> #include <cassert> @@ -41,52 +42,15 @@ namespace { * * NOTE: All of the entries in the ResourceTable must be assigned IDs. */ -class StyleAndReferenceLinkerVisitor : public ValueVisitor { +class ReferenceLinkerVisitor : public ValueVisitor { private: - ReferenceLinkerVisitor mReferenceVisitor; IAaptContext* mContext; ISymbolTable* mSymbols; - IPackageDeclStack* mPackageDecls; + xml::IPackageDeclStack* mPackageDecls; StringPool* mStringPool; + CallSite* mCallSite; bool mError = false; - const ISymbolTable::Symbol* findAttributeSymbol(Reference* reference) { - assert(reference); - assert(reference->name || reference->id); - - if (reference->name) { - // Transform the package name if it is an alias. - Maybe<ResourceName> realName = mPackageDecls->transformPackage( - reference->name.value(), mContext->getCompilationPackage()); - - // Mangle the reference name if it should be mangled. - Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName( - realName ? realName.value() : reference->name.value()); - - const ISymbolTable::Symbol* s = nullptr; - if (mangledName) { - s = mSymbols->findByName(mangledName.value()); - } else if (realName) { - s = mSymbols->findByName(realName.value()); - } else { - s = mSymbols->findByName(reference->name.value()); - } - - if (s && s->attribute) { - return s; - } - } - - if (reference->id) { - if (const ISymbolTable::Symbol* s = mSymbols->findById(reference->id.value())) { - if (s->attribute) { - return s; - } - } - } - return nullptr; - } - /** * Transform a RawString value into a more specific, appropriate value, based on the * Attribute. If a non RawString value is passed in, this is an identity transform. @@ -94,8 +58,8 @@ private: std::unique_ptr<Item> parseValueWithAttribute(std::unique_ptr<Item> value, const Attribute* attr) { if (RawString* rawString = valueCast<RawString>(value.get())) { - std::unique_ptr<Item> transformed = ResourceUtils::parseItemForAttribute( - *rawString->value, attr); + std::unique_ptr<Item> transformed = + ResourceUtils::parseItemForAttribute(*rawString->value, attr); // If we could not parse as any specific type, try a basic STRING. if (!transformed && (attr->typeMask & android::ResTable_map::TYPE_STRING)) { @@ -114,63 +78,19 @@ private: return value; } - void buildAttributeMismatchMessage(DiagMessage* msg, const Attribute* attr, - const Item* value) { - *msg << "expected"; - if (attr->typeMask & android::ResTable_map::TYPE_BOOLEAN) { - *msg << " boolean"; - } - - if (attr->typeMask & android::ResTable_map::TYPE_COLOR) { - *msg << " color"; - } - - if (attr->typeMask & android::ResTable_map::TYPE_DIMENSION) { - *msg << " dimension"; - } - - if (attr->typeMask & android::ResTable_map::TYPE_ENUM) { - *msg << " enum"; - } - - if (attr->typeMask & android::ResTable_map::TYPE_FLAGS) { - *msg << " flags"; - } - - if (attr->typeMask & android::ResTable_map::TYPE_FLOAT) { - *msg << " float"; - } - - if (attr->typeMask & android::ResTable_map::TYPE_FRACTION) { - *msg << " fraction"; - } - - if (attr->typeMask & android::ResTable_map::TYPE_INTEGER) { - *msg << " integer"; - } - - if (attr->typeMask & android::ResTable_map::TYPE_REFERENCE) { - *msg << " reference"; - } - - if (attr->typeMask & android::ResTable_map::TYPE_STRING) { - *msg << " string"; - } - - *msg << " but got " << *value; - } - public: using ValueVisitor::visit; - StyleAndReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols, - StringPool* stringPool, IPackageDeclStack* decl) : - mReferenceVisitor(context, symbols, decl), mContext(context), mSymbols(symbols), - mPackageDecls(decl), mStringPool(stringPool) { + ReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols, StringPool* stringPool, + xml::IPackageDeclStack* decl,CallSite* callSite) : + mContext(context), mSymbols(symbols), mPackageDecls(decl), mStringPool(stringPool), + mCallSite(callSite) { } - void visit(Reference* reference) override { - mReferenceVisitor.visit(reference); + void visit(Reference* ref) override { + if (!ReferenceLinker::linkReference(ref, mContext, mSymbols, mPackageDecls, mCallSite)) { + mError = true; + } } /** @@ -184,58 +104,190 @@ public: } for (Style::Entry& entry : style->entries) { - if (const ISymbolTable::Symbol* s = findAttributeSymbol(&entry.key)) { + std::string errStr; + + // Transform the attribute reference so that it is using the fully qualified package + // name. This will also mark the reference as being able to see private resources if + // there was a '*' in the reference or if the package came from the private namespace. + Reference transformedReference = entry.key; + transformReferenceFromNamespace(mPackageDecls, mContext->getCompilationPackage(), + &transformedReference); + + // Find the attribute in the symbol table and check if it is visible from this callsite. + const ISymbolTable::Symbol* symbol = ReferenceLinker::resolveAttributeCheckVisibility( + transformedReference, mContext->getNameMangler(), mSymbols, mCallSite, &errStr); + if (symbol) { // Assign our style key the correct ID. - entry.key.id = s->id; + entry.key.id = symbol->id; // Try to convert the value to a more specific, typed value based on the // attribute it is set to. - entry.value = parseValueWithAttribute(std::move(entry.value), s->attribute.get()); + entry.value = parseValueWithAttribute(std::move(entry.value), + symbol->attribute.get()); // Link/resolve the final value (mostly if it's a reference). entry.value->accept(this); // Now verify that the type of this item is compatible with the attribute it - // is defined for. - android::Res_value val = {}; - entry.value->flatten(&val); - - // Always allow references. - const uint32_t typeMask = s->attribute->typeMask | - android::ResTable_map::TYPE_REFERENCE; - - if (!(typeMask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) { + // is defined for. We pass `nullptr` as the DiagMessage so that this check is + // fast and we avoid creating a DiagMessage when the match is successful. + if (!symbol->attribute->matches(entry.value.get(), nullptr)) { // The actual type of this item is incompatible with the attribute. - DiagMessage msg(style->getSource()); - buildAttributeMismatchMessage(&msg, s->attribute.get(), entry.value.get()); + DiagMessage msg(entry.key.getSource()); + + // Call the matches method again, this time with a DiagMessage so we fill + // in the actual error message. + symbol->attribute->matches(entry.value.get(), &msg); mContext->getDiagnostics()->error(msg); mError = true; } + } else { - DiagMessage msg(style->getSource()); + DiagMessage msg(entry.key.getSource()); msg << "style attribute '"; - if (entry.key.name) { - msg << entry.key.name.value().package << ":" << entry.key.name.value().entry; - } else { - msg << entry.key.id.value(); - } - msg << "' not found"; + ReferenceLinker::writeResourceName(&msg, entry.key, transformedReference); + msg << "' " << errStr; mContext->getDiagnostics()->error(msg); mError = true; } } } - inline bool hasError() { - return mError || mReferenceVisitor.hasError(); + bool hasError() { + return mError; } }; -struct EmptyDeclStack : public IPackageDeclStack { - Maybe<ResourceName> transformPackage(const ResourceName& name, - const StringPiece16& localPackage) const override { - if (name.package.empty()) { - return ResourceName{ localPackage.toString(), name.type, name.entry }; +} // namespace + +/** + * The symbol is visible if it is public, or if the reference to it is requesting private access + * or if the callsite comes from the same package. + */ +bool ReferenceLinker::isSymbolVisible(const ISymbolTable::Symbol& symbol, const Reference& ref, + const CallSite& callSite) { + if (!symbol.isPublic && !ref.privateReference) { + if (ref.name) { + return callSite.resource.package == ref.name.value().package; + } else if (ref.id) { + return ref.id.value().packageId() == symbol.id.packageId(); + } else { + return false; + } + } + return true; +} + +const ISymbolTable::Symbol* ReferenceLinker::resolveSymbol(const Reference& reference, + NameMangler* mangler, + ISymbolTable* symbols) { + if (reference.name) { + Maybe<ResourceName> mangled = mangler->mangleName(reference.name.value()); + return symbols->findByName(mangled ? mangled.value() : reference.name.value()); + } else if (reference.id) { + return symbols->findById(reference.id.value()); + } else { + return nullptr; + } +} + +const ISymbolTable::Symbol* ReferenceLinker::resolveSymbolCheckVisibility( + const Reference& reference, NameMangler* nameMangler, ISymbolTable* symbols, + CallSite* callSite, std::string* outError) { + const ISymbolTable::Symbol* symbol = resolveSymbol(reference, nameMangler, symbols); + if (!symbol) { + if (outError) *outError = "not found"; + return nullptr; + } + + if (!isSymbolVisible(*symbol, reference, *callSite)) { + if (outError) *outError = "is private"; + return nullptr; + } + return symbol; +} + +const ISymbolTable::Symbol* ReferenceLinker::resolveAttributeCheckVisibility( + const Reference& reference, NameMangler* nameMangler, ISymbolTable* symbols, + CallSite* callSite, std::string* outError) { + const ISymbolTable::Symbol* symbol = resolveSymbolCheckVisibility(reference, nameMangler, + symbols, callSite, + outError); + if (!symbol) { + return nullptr; + } + + if (!symbol->attribute) { + if (outError) *outError = "is not an attribute"; + return nullptr; + } + return symbol; +} + +Maybe<xml::AaptAttribute> ReferenceLinker::compileXmlAttribute(const Reference& reference, + NameMangler* nameMangler, + ISymbolTable* symbols, + CallSite* callSite, + std::string* outError) { + const ISymbolTable::Symbol* symbol = resolveSymbol(reference, nameMangler, symbols); + if (!symbol) { + return {}; + } + + if (!symbol->attribute) { + if (outError) *outError = "is not an attribute"; + return {}; + } + return xml::AaptAttribute{ symbol->id, *symbol->attribute }; +} + +void ReferenceLinker::writeResourceName(DiagMessage* outMsg, const Reference& orig, + const Reference& transformed) { + assert(outMsg); + + if (orig.name) { + *outMsg << orig.name.value(); + if (transformed.name.value() != orig.name.value()) { + *outMsg << " (aka " << transformed.name.value() << ")"; + } + } else { + *outMsg << orig.id.value(); + } +} + +bool ReferenceLinker::linkReference(Reference* reference, IAaptContext* context, + ISymbolTable* symbols, xml::IPackageDeclStack* decls, + CallSite* callSite) { + assert(reference); + assert(reference->name || reference->id); + + Reference transformedReference = *reference; + transformReferenceFromNamespace(decls, context->getCompilationPackage(), + &transformedReference); + + std::string errStr; + const ISymbolTable::Symbol* s = resolveSymbolCheckVisibility( + transformedReference, context->getNameMangler(), symbols, callSite, &errStr); + if (s) { + reference->id = s->id; + return true; + } + + DiagMessage errorMsg(reference->getSource()); + errorMsg << "resource "; + writeResourceName(&errorMsg, *reference, transformedReference); + errorMsg << " " << errStr; + context->getDiagnostics()->error(errorMsg); + return false; +} + +namespace { + +struct EmptyDeclStack : public xml::IPackageDeclStack { + Maybe<xml::ExtractedPackage> transformPackageAlias( + const StringPiece16& alias, const StringPiece16& localPackage) const override { + if (alias.empty()) { + return xml::ExtractedPackage{ localPackage.toString(), true /* private */ }; } return {}; } @@ -259,14 +311,16 @@ bool ReferenceLinker::consume(IAaptContext* context, ResourceTable* table) { error = true; } + CallSite callSite = { ResourceNameRef(package->name, type->type, entry->name) }; + ReferenceLinkerVisitor visitor(context, context->getExternalSymbols(), + &table->stringPool, &declStack, &callSite); + for (auto& configValue : entry->values) { - StyleAndReferenceLinkerVisitor visitor(context, - context->getExternalSymbols(), - &table->stringPool, &declStack); configValue.value->accept(&visitor); - if (visitor.hasError()) { - error = true; - } + } + + if (visitor.hasError()) { + error = true; } } } diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h new file mode 100644 index 000000000000..a0eb00c85b70 --- /dev/null +++ b/tools/aapt2/link/ReferenceLinker.h @@ -0,0 +1,106 @@ +/* + * 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_LINKER_REFERENCELINKER_H +#define AAPT_LINKER_REFERENCELINKER_H + +#include "Resource.h" +#include "ResourceValues.h" +#include "ValueVisitor.h" +#include "link/Linkers.h" +#include "process/IResourceTableConsumer.h" +#include "process/SymbolTable.h" +#include "xml/XmlDom.h" + +#include <cassert> + +namespace aapt { + +/** + * Resolves all references to resources in the ResourceTable and assigns them IDs. + * The ResourceTable must already have IDs assigned to each resource. + * Once the ResourceTable is processed by this linker, it is ready to be flattened. + */ +struct ReferenceLinker : public IResourceTableConsumer { + /** + * Returns true if the symbol is visible by the reference and from the callsite. + */ + static bool isSymbolVisible(const ISymbolTable::Symbol& symbol, const Reference& ref, + const CallSite& callSite); + + /** + * Performs name mangling and looks up the resource in the symbol table. Returns nullptr + * if the symbol was not found. + */ + static const ISymbolTable::Symbol* resolveSymbol(const Reference& reference, + NameMangler* mangler, ISymbolTable* symbols); + + /** + * Performs name mangling and looks up the resource in the symbol table. If the symbol is + * not visible by the reference at the callsite, nullptr is returned. outError holds + * the error message. + */ + static const ISymbolTable::Symbol* resolveSymbolCheckVisibility(const Reference& reference, + NameMangler* nameMangler, + ISymbolTable* symbols, + CallSite* callSite, + std::string* outError); + + /** + * Same as resolveSymbolCheckVisibility(), but also makes sure the symbol is an attribute. + * That is, the return value will have a non-null value for ISymbolTable::Symbol::attribute. + */ + static const ISymbolTable::Symbol* resolveAttributeCheckVisibility(const Reference& reference, + NameMangler* nameMangler, + ISymbolTable* symbols, + CallSite* callSite, + std::string* outError); + + /** + * Resolves the attribute reference and returns an xml::AaptAttribute if successful. + * If resolution fails, outError holds the error message. + */ + static Maybe<xml::AaptAttribute> compileXmlAttribute(const Reference& reference, + NameMangler* nameMangler, + ISymbolTable* symbols, + CallSite* callSite, + std::string* outError); + + /** + * Writes the resource name to the DiagMessage, using the "orig_name (aka <transformed_name>)" + * syntax. + */ + static void writeResourceName(DiagMessage* outMsg, const Reference& orig, + const Reference& transformed); + + /** + * Transforms the package name of the reference to the fully qualified package name using + * the xml::IPackageDeclStack, then mangles and looks up the symbol. If the symbol is visible + * to the reference at the callsite, the reference is updated with an ID. + * Returns false on failure, and an error message is logged to the IDiagnostics in the context. + */ + static bool linkReference(Reference* reference, IAaptContext* context, ISymbolTable* symbols, + xml::IPackageDeclStack* decls, CallSite* callSite); + + /** + * Links all references in the ResourceTable. + */ + bool consume(IAaptContext* context, ResourceTable* table) override; +}; + +} // namespace aapt + +#endif /* AAPT_LINKER_REFERENCELINKER_H */ diff --git a/tools/aapt2/link/ReferenceLinkerVisitor.h b/tools/aapt2/link/ReferenceLinkerVisitor.h deleted file mode 100644 index c70531ba8296..000000000000 --- a/tools/aapt2/link/ReferenceLinkerVisitor.h +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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_LINKER_REFERENCELINKERVISITOR_H -#define AAPT_LINKER_REFERENCELINKERVISITOR_H - -#include "Resource.h" -#include "ResourceValues.h" -#include "ValueVisitor.h" - -#include "process/IResourceTableConsumer.h" -#include "process/SymbolTable.h" - -#include <cassert> - -namespace aapt { - -/** - * The ReferenceLinkerVisitor will follow all references and make sure they point - * to resources that actually exist in the given ISymbolTable. - * Once the target resource has been found, the ID of the resource will be assigned - * to the reference object. - */ -class ReferenceLinkerVisitor : public ValueVisitor { - using ValueVisitor::visit; -private: - IAaptContext* mContext; - ISymbolTable* mSymbols; - IPackageDeclStack* mPackageDecls; - bool mError = false; - -public: - ReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols, IPackageDeclStack* decls) : - mContext(context), mSymbols(symbols), mPackageDecls(decls) { - } - - /** - * Lookup a reference and ensure it exists, either in our local table, or as an external - * symbol. Once found, assign the ID of the target resource to this reference object. - */ - void visit(Reference* reference) override { - assert(reference); - assert(reference->name || reference->id); - - // We prefer to lookup by name if the name is set. Otherwise it could be - // an out-of-date ID. - if (reference->name) { - // Transform the package name if it is an alias. - Maybe<ResourceName> realName = mPackageDecls->transformPackage( - reference->name.value(), mContext->getCompilationPackage()); - - // Mangle the reference name if it should be mangled. - Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName( - realName ? realName.value() : reference->name.value()); - - const ISymbolTable::Symbol* s = nullptr; - if (mangledName) { - s = mSymbols->findByName(mangledName.value()); - } else if (realName) { - s = mSymbols->findByName(realName.value()); - } else { - s = mSymbols->findByName(reference->name.value()); - } - - if (s) { - reference->id = s->id; - return; - } - - DiagMessage errorMsg; - errorMsg << "reference to " << reference->name.value(); - if (realName) { - errorMsg << " (aka " << realName.value() << ")"; - } - errorMsg << " was not found"; - mContext->getDiagnostics()->error(errorMsg); - mError = true; - return; - } - - if (!mSymbols->findById(reference->id.value())) { - mContext->getDiagnostics()->error(DiagMessage() - << "reference to " << reference->id.value() - << " was not found"); - mError = true; - } - } - - inline bool hasError() { - return mError; - } -}; - -} // namespace aapt - -#endif /* AAPT_LINKER_REFERENCELINKERVISITOR_H */ diff --git a/tools/aapt2/link/ReferenceLinker_test.cpp b/tools/aapt2/link/ReferenceLinker_test.cpp index 5e7641a4ec4f..8d324fe753a1 100644 --- a/tools/aapt2/link/ReferenceLinker_test.cpp +++ b/tools/aapt2/link/ReferenceLinker_test.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "link/Linkers.h" +#include "link/ReferenceLinker.h" #include "process/SymbolTable.h" #include "test/Builders.h" @@ -44,7 +44,7 @@ TEST(ReferenceLinkerTest, LinkSimpleReferences) { .setSymbolTable(JoinedSymbolTableBuilder() .addSymbolTable(util::make_unique<SymbolTableWrapper>(table.get())) .addSymbolTable(test::StaticSymbolTableBuilder() - .addSymbol(u"@android:string/ok", ResourceId(0x01040034)) + .addPublicSymbol(u"@android:string/ok", ResourceId(0x01040034)) .build()) .build()) .build(); @@ -92,12 +92,12 @@ TEST(ReferenceLinkerTest, LinkStyleAttributes) { .setPackageId(0x7f) .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" }) .setSymbolTable(test::StaticSymbolTableBuilder() - .addSymbol(u"@android:style/Theme.Material", ResourceId(0x01060000)) - .addSymbol(u"@android:attr/foo", ResourceId(0x01010001), + .addPublicSymbol(u"@android:style/Theme.Material", ResourceId(0x01060000)) + .addPublicSymbol(u"@android:attr/foo", ResourceId(0x01010001), test::AttributeBuilder() .setTypeMask(android::ResTable_map::TYPE_COLOR) .build()) - .addSymbol(u"@android:attr/bar", ResourceId(0x01010002), + .addPublicSymbol(u"@android:attr/bar", ResourceId(0x01010002), test::AttributeBuilder() .setTypeMask(android::ResTable_map::TYPE_FLAGS) .addItem(u"one", 0x01) @@ -132,7 +132,7 @@ TEST(ReferenceLinkerTest, LinkMangledReferencesAndAttributes) { .setPackageId(0x7f) .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test", { u"com.android.support" } }) .setSymbolTable(test::StaticSymbolTableBuilder() - .addSymbol(u"@com.app.test:attr/com.android.support$foo", + .addPublicSymbol(u"@com.app.test:attr/com.android.support$foo", ResourceId(0x7f010000), test::AttributeBuilder() .setTypeMask(android::ResTable_map::TYPE_COLOR).build()) .build()) @@ -156,4 +156,78 @@ TEST(ReferenceLinkerTest, LinkMangledReferencesAndAttributes) { EXPECT_EQ(style->entries.front().key.id.value(), ResourceId(0x7f010000)); } +TEST(ReferenceLinkerTest, FailToLinkPrivateSymbols) { + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .setPackageId(u"com.app.test", 0x7f) + .addReference(u"@com.app.test:string/foo", ResourceId(0x7f020000), + u"@android:string/hidden") + .build(); + + std::unique_ptr<IAaptContext> context = test::ContextBuilder() + .setCompilationPackage(u"com.app.test") + .setPackageId(0x7f) + .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" }) + .setSymbolTable(JoinedSymbolTableBuilder() + .addSymbolTable(util::make_unique<SymbolTableWrapper>(table.get())) + .addSymbolTable(test::StaticSymbolTableBuilder() + .addSymbol(u"@android:string/hidden", ResourceId(0x01040034)) + .build()) + .build()) + .build(); + + ReferenceLinker linker; + ASSERT_FALSE(linker.consume(context.get(), table.get())); +} + +TEST(ReferenceLinkerTest, FailToLinkPrivateMangledSymbols) { + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .setPackageId(u"com.app.test", 0x7f) + .addReference(u"@com.app.test:string/foo", ResourceId(0x7f020000), + u"@com.app.lib:string/hidden") + .build(); + + std::unique_ptr<IAaptContext> context = test::ContextBuilder() + .setCompilationPackage(u"com.app.test") + .setPackageId(0x7f) + .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test", { u"com.app.lib" } }) + .setSymbolTable(JoinedSymbolTableBuilder() + .addSymbolTable(util::make_unique<SymbolTableWrapper>(table.get())) + .addSymbolTable(test::StaticSymbolTableBuilder() + .addSymbol(u"@com.app.test:string/com.app.lib$hidden", + ResourceId(0x7f040034)) + .build()) + .build()) + .build(); + + ReferenceLinker linker; + ASSERT_FALSE(linker.consume(context.get(), table.get())); +} + +TEST(ReferenceLinkerTest, FailToLinkPrivateStyleAttributes) { + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .setPackageId(u"com.app.test", 0x7f) + .addValue(u"@com.app.test:style/Theme", test::StyleBuilder() + .addItem(u"@android:attr/hidden", ResourceUtils::tryParseColor(u"#ff00ff")) + .build()) + .build(); + + std::unique_ptr<IAaptContext> context = test::ContextBuilder() + .setCompilationPackage(u"com.app.test") + .setPackageId(0x7f) + .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" }) + .setSymbolTable(JoinedSymbolTableBuilder() + .addSymbolTable(util::make_unique<SymbolTableWrapper>(table.get())) + .addSymbolTable(test::StaticSymbolTableBuilder() + .addSymbol(u"@android:attr/hidden", ResourceId(0x01010001), + test::AttributeBuilder() + .setTypeMask(android::ResTable_map::TYPE_COLOR) + .build()) + .build()) + .build()) + .build(); + + ReferenceLinker linker; + ASSERT_FALSE(linker.consume(context.get(), table.get())); +} + } // namespace aapt diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp index 147b9bf16248..a26d7637ab3a 100644 --- a/tools/aapt2/link/XmlReferenceLinker.cpp +++ b/tools/aapt2/link/XmlReferenceLinker.cpp @@ -17,49 +17,91 @@ #include "Diagnostics.h" #include "ResourceUtils.h" #include "SdkConstants.h" -#include "XmlDom.h" - #include "link/Linkers.h" -#include "link/ReferenceLinkerVisitor.h" +#include "link/ReferenceLinker.h" #include "process/IResourceTableConsumer.h" #include "process/SymbolTable.h" #include "util/Util.h" +#include "xml/XmlDom.h" namespace aapt { namespace { -class XmlReferenceLinkerVisitor : public xml::PackageAwareVisitor { +/** + * Visits all references (including parents of styles, references in styles, arrays, etc) and + * links their symbolic name to their Resource ID, performing mangling and package aliasing + * as needed. + */ +class ReferenceVisitor : public ValueVisitor { +private: + IAaptContext* mContext; + ISymbolTable* mSymbols; + xml::IPackageDeclStack* mDecls; + CallSite* mCallSite; + bool mError; + +public: + using ValueVisitor::visit; + + ReferenceVisitor(IAaptContext* context, ISymbolTable* symbols, xml::IPackageDeclStack* decls, + CallSite* callSite) : + mContext(context), mSymbols(symbols), mDecls(decls), mCallSite(callSite), + mError(false) { + } + + void visit(Reference* ref) override { + if (!ReferenceLinker::linkReference(ref, mContext, mSymbols, mDecls, mCallSite)) { + mError = true; + } + } + + bool hasError() const { + return mError; + } +}; + +/** + * Visits each xml Element and compiles the attributes within. + */ +class XmlVisitor : public xml::PackageAwareVisitor { private: IAaptContext* mContext; ISymbolTable* mSymbols; + Source mSource; std::set<int>* mSdkLevelsFound; - ReferenceLinkerVisitor mReferenceLinkerVisitor; + CallSite* mCallSite; + ReferenceVisitor mReferenceVisitor; bool mError = false; public: using xml::PackageAwareVisitor::visit; - XmlReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols, - std::set<int>* sdkLevelsFound) : - mContext(context), mSymbols(symbols), mSdkLevelsFound(sdkLevelsFound), - mReferenceLinkerVisitor(context, symbols, this) { + XmlVisitor(IAaptContext* context, ISymbolTable* symbols, const Source& source, + std::set<int>* sdkLevelsFound, CallSite* callSite) : + mContext(context), mSymbols(symbols), mSource(source), mSdkLevelsFound(sdkLevelsFound), + mCallSite(callSite), mReferenceVisitor(context, symbols, this, callSite) { } void visit(xml::Element* el) override { + const Source source = mSource.withLine(el->lineNumber); for (xml::Attribute& attr : el->attributes) { - Maybe<std::u16string> maybePackage = - util::extractPackageFromNamespace(attr.namespaceUri); + Maybe<xml::ExtractedPackage> maybePackage = + xml::extractPackageFromNamespace(attr.namespaceUri); if (maybePackage) { // There is a valid package name for this attribute. We will look this up. - StringPiece16 package = maybePackage.value(); + StringPiece16 package = maybePackage.value().package; if (package.empty()) { // Empty package means the 'current' or 'local' package. package = mContext->getCompilationPackage(); } - attr.compiledAttribute = compileAttribute( - ResourceName{ package.toString(), ResourceType::kAttr, attr.name }); + Reference attrRef(ResourceNameRef(package, ResourceType::kAttr, attr.name)); + attrRef.privateReference = maybePackage.value().privateNamespace; + + std::string errStr; + attr.compiledAttribute = ReferenceLinker::compileXmlAttribute( + attrRef, mContext->getNameMangler(), mSymbols, mCallSite, &errStr); // Convert the string value into a compiled Value if this is a valid attribute. if (attr.compiledAttribute) { @@ -76,15 +118,16 @@ public: !(attribute->typeMask & android::ResTable_map::TYPE_STRING)) { // We won't be able to encode this as a string. mContext->getDiagnostics()->error( - DiagMessage() << "'" << attr.value << "' " - << "is incompatible with attribute " - << package << ":" << attr.name << " " << *attribute); + DiagMessage(source) << "'" << attr.value << "' " + << "is incompatible with attribute " + << package << ":" << attr.name << " " + << *attribute); mError = true; } } else { - mContext->getDiagnostics()->error( - DiagMessage() << "attribute '" << package << ":" << attr.name - << "' was not found"); + mContext->getDiagnostics()->error(DiagMessage(source) + << "attribute '" << package << ":" + << attr.name << "' " << errStr); mError = true; } @@ -95,7 +138,8 @@ public: if (attr.compiledValue) { // With a compiledValue, we must resolve the reference and assign it an ID. - attr.compiledValue->accept(&mReferenceLinkerVisitor); + attr.compiledValue->setSource(source); + attr.compiledValue->accept(&mReferenceVisitor); } } @@ -103,27 +147,18 @@ public: xml::PackageAwareVisitor::visit(el); } - Maybe<xml::AaptAttribute> compileAttribute(const ResourceName& name) { - Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(name); - if (const ISymbolTable::Symbol* symbol = mSymbols->findByName( - mangledName ? mangledName.value() : name)) { - if (symbol->attribute) { - return xml::AaptAttribute{ symbol->id, *symbol->attribute }; - } - } - return {}; - } - - inline bool hasError() { - return mError || mReferenceLinkerVisitor.hasError(); + bool hasError() { + return mError || mReferenceVisitor.hasError(); } }; } // namespace -bool XmlReferenceLinker::consume(IAaptContext* context, XmlResource* resource) { +bool XmlReferenceLinker::consume(IAaptContext* context, xml::XmlResource* resource) { mSdkLevelsFound.clear(); - XmlReferenceLinkerVisitor visitor(context, context->getExternalSymbols(), &mSdkLevelsFound); + CallSite callSite = { resource->file.name }; + XmlVisitor visitor(context, context->getExternalSymbols(), resource->file.source, + &mSdkLevelsFound, &callSite); if (resource->root) { resource->root->accept(&visitor); return !visitor.hasError(); diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp index 7f91ec348c9d..3bfaf91854bb 100644 --- a/tools/aapt2/link/XmlReferenceLinker_test.cpp +++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp @@ -31,37 +31,40 @@ public: .setNameManglerPolicy( NameManglerPolicy{ u"com.app.test", { u"com.android.support" } }) .setSymbolTable(test::StaticSymbolTableBuilder() - .addSymbol(u"@android:attr/layout_width", ResourceId(0x01010000), + .addPublicSymbol(u"@android:attr/layout_width", ResourceId(0x01010000), test::AttributeBuilder() .setTypeMask(android::ResTable_map::TYPE_ENUM | android::ResTable_map::TYPE_DIMENSION) .addItem(u"match_parent", 0xffffffff) .build()) - .addSymbol(u"@android:attr/background", ResourceId(0x01010001), + .addPublicSymbol(u"@android:attr/background", ResourceId(0x01010001), test::AttributeBuilder() .setTypeMask(android::ResTable_map::TYPE_COLOR).build()) - .addSymbol(u"@android:attr/attr", ResourceId(0x01010002), + .addPublicSymbol(u"@android:attr/attr", ResourceId(0x01010002), test::AttributeBuilder().build()) - .addSymbol(u"@android:attr/text", ResourceId(0x01010003), + .addPublicSymbol(u"@android:attr/text", ResourceId(0x01010003), test::AttributeBuilder() .setTypeMask(android::ResTable_map::TYPE_STRING) .build()) // Add one real symbol that was introduces in v21 - .addSymbol(u"@android:attr/colorAccent", ResourceId(0x01010435), + .addPublicSymbol(u"@android:attr/colorAccent", ResourceId(0x01010435), test::AttributeBuilder().build()) - .addSymbol(u"@android:id/id", ResourceId(0x01030000)) + // Private symbol. + .addSymbol(u"@android:color/hidden", ResourceId(0x01020001)) + + .addPublicSymbol(u"@android:id/id", ResourceId(0x01030000)) .addSymbol(u"@com.app.test:id/id", ResourceId(0x7f030000)) .addSymbol(u"@com.app.test:color/green", ResourceId(0x7f020000)) .addSymbol(u"@com.app.test:color/red", ResourceId(0x7f020001)) .addSymbol(u"@com.app.test:attr/colorAccent", ResourceId(0x7f010000), test::AttributeBuilder() .setTypeMask(android::ResTable_map::TYPE_COLOR).build()) - .addSymbol(u"@com.app.test:attr/com.android.support$colorAccent", + .addPublicSymbol(u"@com.app.test:attr/com.android.support$colorAccent", ResourceId(0x7f010001), test::AttributeBuilder() .setTypeMask(android::ResTable_map::TYPE_COLOR).build()) - .addSymbol(u"@com.app.test:attr/attr", ResourceId(0x7f010002), + .addPublicSymbol(u"@com.app.test:attr/attr", ResourceId(0x7f010002), test::AttributeBuilder().build()) .build()) .build(); @@ -71,23 +74,8 @@ protected: std::unique_ptr<IAaptContext> mContext; }; -static xml::Element* getRootElement(XmlResource* doc) { - xml::Node* node = doc->root.get(); - while (xml::nodeCast<xml::Namespace>(node)) { - if (node->children.empty()) { - return nullptr; - } - node = node->children.front().get(); - } - - if (xml::Element* el = xml::nodeCast<xml::Element>(node)) { - return el; - } - return nullptr; -} - TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) { - std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:background="@color/green" @@ -97,7 +85,7 @@ TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) { XmlReferenceLinker linker; ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); - xml::Element* viewEl = getRootElement(doc.get()); + xml::Element* viewEl = xml::findRootElement(doc.get()); ASSERT_NE(viewEl, nullptr); xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android", @@ -132,8 +120,26 @@ TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) { ASSERT_EQ(xmlAttr->compiledValue, nullptr); } +TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreNotLinked) { + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( + <View xmlns:android="http://schemas.android.com/apk/res/android" + android:colorAccent="@android:color/hidden" />)EOF"); + + XmlReferenceLinker linker; + ASSERT_FALSE(linker.consume(mContext.get(), doc.get())); +} + +TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreLinkedWhenReferenceHasStarPrefix) { + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( + <View xmlns:android="http://schemas.android.com/apk/res/android" + android:colorAccent="@*android:color/hidden" />)EOF"); + + XmlReferenceLinker linker; + ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); +} + TEST_F(XmlReferenceLinkerTest, SdkLevelsAreRecorded) { - std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/android" android:colorAccent="#ffffff" />)EOF"); @@ -143,14 +149,14 @@ TEST_F(XmlReferenceLinkerTest, SdkLevelsAreRecorded) { } TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) { - std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( <View xmlns:support="http://schemas.android.com/apk/res/com.android.support" support:colorAccent="#ff0000" />)EOF"); XmlReferenceLinker linker; ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); - xml::Element* viewEl = getRootElement(doc.get()); + xml::Element* viewEl = xml::findRootElement(doc.get()); ASSERT_NE(viewEl, nullptr); xml::Attribute* xmlAttr = viewEl->findAttribute( @@ -162,14 +168,14 @@ TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) { } TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) { - std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( <View xmlns:app="http://schemas.android.com/apk/res-auto" app:colorAccent="@app:color/red" />)EOF"); XmlReferenceLinker linker; ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); - xml::Element* viewEl = getRootElement(doc.get()); + xml::Element* viewEl = xml::findRootElement(doc.get()); ASSERT_NE(viewEl, nullptr); xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res-auto", @@ -185,7 +191,7 @@ TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) { } TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) { - std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( <View xmlns:app="http://schemas.android.com/apk/res/android" app:attr="@app:id/id"> <View xmlns:app="http://schemas.android.com/apk/res/com.app.test" @@ -195,7 +201,7 @@ TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) { XmlReferenceLinker linker; ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); - xml::Element* viewEl = getRootElement(doc.get()); + xml::Element* viewEl = xml::findRootElement(doc.get()); ASSERT_NE(viewEl, nullptr); // All attributes and references in this element should be referring to "android" (0x01). @@ -225,14 +231,14 @@ TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) { } TEST_F(XmlReferenceLinkerTest, LinkViewWithLocalPackageAndAliasOfTheSameName) { - std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/com.app.test" android:attr="@id/id"/>)EOF"); XmlReferenceLinker linker; ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); - xml::Element* viewEl = getRootElement(doc.get()); + xml::Element* viewEl = xml::findRootElement(doc.get()); ASSERT_NE(viewEl, nullptr); // All attributes and references in this element should be referring to "com.app.test" (0x7f). diff --git a/tools/aapt2/process/IResourceTableConsumer.h b/tools/aapt2/process/IResourceTableConsumer.h index 24ad05d14cec..a2528d2ac195 100644 --- a/tools/aapt2/process/IResourceTableConsumer.h +++ b/tools/aapt2/process/IResourceTableConsumer.h @@ -49,25 +49,13 @@ struct IResourceTableConsumer { }; namespace xml { -struct Node; +struct XmlResource; } -struct XmlResource { - ResourceFile file; - std::unique_ptr<xml::Node> root; -}; - struct IXmlResourceConsumer { virtual ~IXmlResourceConsumer() = default; - virtual bool consume(IAaptContext* context, XmlResource* resource) = 0; -}; - -struct IPackageDeclStack { - virtual ~IPackageDeclStack() = default; - - virtual Maybe<ResourceName> transformPackage(const ResourceName& name, - const StringPiece16& localPackage) const = 0; + virtual bool consume(IAaptContext* context, xml::XmlResource* resource) = 0; }; } // namespace aapt diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp index 9a8b263f5f97..6ad2f9c10d22 100644 --- a/tools/aapt2/process/SymbolTable.cpp +++ b/tools/aapt2/process/SymbolTable.cpp @@ -51,6 +51,7 @@ const ISymbolTable::Symbol* SymbolTableWrapper::findByName(const ResourceName& n std::shared_ptr<Symbol> symbol = std::make_shared<Symbol>(); symbol->id = ResourceId(sr.package->id.value(), sr.type->id.value(), sr.entry->id.value()); + symbol->isPublic = (sr.entry->symbolStatus.state == SymbolState::kPublic); if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) { const ConfigDescription kDefaultConfig; @@ -60,7 +61,7 @@ const ISymbolTable::Symbol* SymbolTableWrapper::findByName(const ResourceName& n if (iter != sr.entry->values.end() && iter->config == kDefaultConfig) { // This resource has an Attribute. if (Attribute* attr = valueCast<Attribute>(iter->value.get())) { - symbol->attribute = std::unique_ptr<Attribute>(attr->clone(nullptr)); + symbol->attribute = util::make_unique<Attribute>(*attr); } else { return {}; } @@ -76,17 +77,8 @@ const ISymbolTable::Symbol* SymbolTableWrapper::findByName(const ResourceName& n return symbol.get(); } - -static std::shared_ptr<ISymbolTable::Symbol> lookupIdInTable(const android::ResTable& table, - ResourceId id) { - android::Res_value val = {}; - ssize_t block = table.getResource(id.id, &val, true); - if (block >= 0) { - std::shared_ptr<ISymbolTable::Symbol> s = std::make_shared<ISymbolTable::Symbol>(); - s->id = id; - return s; - } - +static std::shared_ptr<ISymbolTable::Symbol> lookupAttributeInTable(const android::ResTable& table, + ResourceId id) { // Try as a bag. const android::ResTable::bag_entry* entry; ssize_t count = table.lockBag(id.id, &entry); @@ -110,29 +102,40 @@ static std::shared_ptr<ISymbolTable::Symbol> lookupIdInTable(const android::ResT if (s->attribute) { for (size_t i = 0; i < (size_t) count; i++) { - if (!Res_INTERNALID(entry[i].map.name.ident)) { - android::ResTable::resource_name entryName; - if (!table.getResourceName(entry[i].map.name.ident, false, &entryName)) { - table.unlockBag(entry); - return nullptr; + const android::ResTable_map& mapEntry = entry[i].map; + if (Res_INTERNALID(mapEntry.name.ident)) { + switch (mapEntry.name.ident) { + case android::ResTable_map::ATTR_MIN: + s->attribute->minInt = static_cast<int32_t>(mapEntry.value.data); + break; + case android::ResTable_map::ATTR_MAX: + s->attribute->maxInt = static_cast<int32_t>(mapEntry.value.data); + break; } + continue; + } - const ResourceType* parsedType = parseResourceType( - StringPiece16(entryName.type, entryName.typeLen)); - if (!parsedType) { - table.unlockBag(entry); - return nullptr; - } + android::ResTable::resource_name entryName; + if (!table.getResourceName(mapEntry.name.ident, false, &entryName)) { + table.unlockBag(entry); + return nullptr; + } - Attribute::Symbol symbol; - symbol.symbol.name = ResourceNameRef( - StringPiece16(entryName.package, entryName.packageLen), - *parsedType, - StringPiece16(entryName.name, entryName.nameLen)).toResourceName(); - symbol.symbol.id = ResourceId(entry[i].map.name.ident); - symbol.value = entry[i].map.value.data; - s->attribute->symbols.push_back(std::move(symbol)); + const ResourceType* parsedType = parseResourceType( + StringPiece16(entryName.type, entryName.typeLen)); + if (!parsedType) { + table.unlockBag(entry); + return nullptr; } + + Attribute::Symbol symbol; + symbol.symbol.name = ResourceName( + StringPiece16(entryName.package, entryName.packageLen), + *parsedType, + StringPiece16(entryName.name, entryName.nameLen)); + symbol.symbol.id = ResourceId(mapEntry.name.ident); + symbol.value = mapEntry.value.data; + s->attribute->symbols.push_back(std::move(symbol)); } } table.unlockBag(entry); @@ -148,15 +151,25 @@ const ISymbolTable::Symbol* AssetManagerSymbolTableBuilder::AssetManagerSymbolTa for (const auto& asset : mAssets) { const android::ResTable& table = asset->getResources(false); StringPiece16 typeStr = toString(name.type); + uint32_t typeSpecFlags = 0; ResourceId resId = table.identifierForName(name.entry.data(), name.entry.size(), typeStr.data(), typeStr.size(), - name.package.data(), name.package.size()); + name.package.data(), name.package.size(), + &typeSpecFlags); if (!resId.isValid()) { continue; } - std::shared_ptr<Symbol> s = lookupIdInTable(table, resId); + std::shared_ptr<Symbol> s; + if (name.type == ResourceType::kAttr) { + s = lookupAttributeInTable(table, resId); + } else { + s = std::make_shared<Symbol>(); + s->id = resId; + } + if (s) { + s->isPublic = (typeSpecFlags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0; mCache.put(name, s); return s.get(); } @@ -164,6 +177,44 @@ const ISymbolTable::Symbol* AssetManagerSymbolTableBuilder::AssetManagerSymbolTa return nullptr; } +static Maybe<ResourceName> getResourceName(const android::ResTable& table, ResourceId id) { + android::ResTable::resource_name resName; + if (!table.getResourceName(id.id, true, &resName)) { + return {}; + } + + ResourceName name; + if (resName.package) { + name.package = StringPiece16(resName.package, resName.packageLen).toString(); + } + + const ResourceType* type; + if (resName.type) { + type = parseResourceType(StringPiece16(resName.type, resName.typeLen)); + + } else if (resName.type8) { + type = parseResourceType(util::utf8ToUtf16(StringPiece(resName.type8, resName.typeLen))); + } else { + return {}; + } + + if (!type) { + return {}; + } + + name.type = *type; + + if (resName.name) { + name.entry = StringPiece16(resName.name, resName.nameLen).toString(); + } else if (resName.name8) { + name.entry = util::utf8ToUtf16(StringPiece(resName.name8, resName.nameLen)); + } else { + return {}; + } + + return name; +} + const ISymbolTable::Symbol* AssetManagerSymbolTableBuilder::AssetManagerSymbolTable::findById( ResourceId id) { if (const std::shared_ptr<Symbol>& s = mIdCache.get(id)) { @@ -173,8 +224,24 @@ const ISymbolTable::Symbol* AssetManagerSymbolTableBuilder::AssetManagerSymbolTa for (const auto& asset : mAssets) { const android::ResTable& table = asset->getResources(false); - std::shared_ptr<Symbol> s = lookupIdInTable(table, id); + Maybe<ResourceName> maybeName = getResourceName(table, id); + if (!maybeName) { + continue; + } + + uint32_t typeSpecFlags = 0; + table.getResourceFlags(id.id, &typeSpecFlags); + + std::shared_ptr<Symbol> s; + if (maybeName.value().type == ResourceType::kAttr) { + s = lookupAttributeInTable(table, id); + } else { + s = std::make_shared<Symbol>(); + s->id = id; + } + if (s) { + s->isPublic = (typeSpecFlags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0; mIdCache.put(id, s); return s.get(); } diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h index 89cd9725227a..f8e3d031fb67 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -19,10 +19,9 @@ #include "ResourceTable.h" #include "ResourceValues.h" -#include "XmlDom.h" -#include "util/Util.h" - #include "test/Common.h" +#include "util/Util.h" +#include "xml/XmlDom.h" #include <memory> @@ -37,6 +36,10 @@ private: public: ResourceTableBuilder() = default; + StringPool* getStringPool() { + return &mTable->stringPool; + } + ResourceTableBuilder& setPackageId(const StringPiece16& packageName, uint8_t id) { ResourceTablePackage* package = mTable->createPackage(packageName, id); assert(package); @@ -212,15 +215,22 @@ public: } }; -inline std::unique_ptr<XmlResource> buildXmlDom(const StringPiece& str) { +inline std::unique_ptr<xml::XmlResource> buildXmlDom(const StringPiece& str) { std::stringstream in; in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str; StdErrDiagnostics diag; - std::unique_ptr<XmlResource> doc = xml::inflate(&in, &diag, {}); + std::unique_ptr<xml::XmlResource> doc = xml::inflate(&in, &diag, {}); assert(doc); return doc; } +inline std::unique_ptr<xml::XmlResource> buildXmlDomForPackageName(IAaptContext* context, + const StringPiece& str) { + std::unique_ptr<xml::XmlResource> doc = buildXmlDom(str); + doc->file.name.package = context->getCompilationPackage().toString(); + return doc; +} + } // namespace test } // namespace aapt diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h index 4fa49186a09f..555a53959737 100644 --- a/tools/aapt2/test/Context.h +++ b/tools/aapt2/test/Context.h @@ -135,8 +135,19 @@ private: std::unique_ptr<SymbolTable> mSymbolTable = util::make_unique<SymbolTable>(); public: + StaticSymbolTableBuilder& addPublicSymbol(const StringPiece16& name, ResourceId id, + std::unique_ptr<Attribute> attr = {}) { + std::unique_ptr<ISymbolTable::Symbol> symbol = util::make_unique<ISymbolTable::Symbol>( + id, std::move(attr)); + symbol->isPublic = true; + mSymbolTable->mNameMap[parseNameOrDie(name)] = symbol.get(); + mSymbolTable->mIdMap[id] = symbol.get(); + mSymbolTable->mSymbols.push_back(std::move(symbol)); + return *this; + } + StaticSymbolTableBuilder& addSymbol(const StringPiece16& name, ResourceId id, - std::unique_ptr<Attribute> attr = {}) { + std::unique_ptr<Attribute> attr = {}) { std::unique_ptr<ISymbolTable::Symbol> symbol = util::make_unique<ISymbolTable::Symbol>( id, std::move(attr)); mSymbolTable->mNameMap[parseNameOrDie(name)] = symbol.get(); diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp index 0d17e8467d32..5cc7aa7cdc3e 100644 --- a/tools/aapt2/unflatten/BinaryResourceParser.cpp +++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp @@ -97,19 +97,19 @@ bool BinaryResourceParser::parse() { return !error; } -bool BinaryResourceParser::getSymbol(const void* data, ResourceNameRef* outSymbol) { +Maybe<Reference> BinaryResourceParser::getSymbol(const void* data) { if (!mSymbolEntries || mSymbolEntryCount == 0) { - return false; + return {}; } if ((uintptr_t) data < (uintptr_t) mData) { - return false; + return {}; } // We only support 32 bit offsets right now. const uintptr_t offset = (uintptr_t) data - (uintptr_t) mData; if (offset > std::numeric_limits<uint32_t>::max()) { - return false; + return {}; } for (size_t i = 0; i < mSymbolEntryCount; i++) { @@ -118,24 +118,23 @@ bool BinaryResourceParser::getSymbol(const void* data, ResourceNameRef* outSymbo const StringPiece16 str = util::getString( mSymbolPool, util::deviceToHost32(mSymbolEntries[i].name.index)); - StringPiece16 typeStr; - ResourceUtils::extractResourceName(str, &outSymbol->package, &typeStr, - &outSymbol->entry); - const ResourceType* type = parseResourceType(typeStr); - if (!type) { - return false; + ResourceNameRef nameRef; + bool privateRef = false; + if (!ResourceUtils::parseResourceName(str, &nameRef, &privateRef)) { + return {}; } - outSymbol->type = *type; - // Since we scan the symbol table in order, we can start looking for the // next symbol from this point. mSymbolEntryCount -= i + 1; mSymbolEntries += i + 1; - return true; + + Reference ref(nameRef); + ref.privateReference = privateRef; + return Maybe<Reference>(std::move(ref)); } } - return false; + return {}; } /** @@ -304,6 +303,11 @@ bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) { return false; } + // There can be multiple packages in a table, so + // clear the type and key pool in case they were set from a previous package. + mTypePool.uninit(); + mKeyPool.uninit(); + ResChunkPullParser parser(getChunkData(&packageHeader->header), getChunkDataLen(&packageHeader->header)); while (ResChunkPullParser::isGoodEvent(parser.next())) { @@ -561,15 +565,20 @@ bool BinaryResourceParser::parseType(const ResourceTablePackage* package, resourceValue = parseValue(name, config, value, entry->flags); } - assert(resourceValue && "failed to interpret valid resource"); + if (!resourceValue) { + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "failed to parse value for resource " << name + << " (" << resId << ") with configuration '" + << config << "'"); + return false; + } Source source = mSource; if (sourceBlock) { - size_t len; - const char* str = mSourcePool.string8At(util::deviceToHost32(sourceBlock->path.index), - &len); - if (str) { - source.path.assign(str, len); + StringPiece path = util::getString8(mSourcePool, + util::deviceToHost32(sourceBlock->path.index)); + if (!path.empty()) { + source.path = path.toString(); } source.line = util::deviceToHost32(sourceBlock->line); } @@ -652,7 +661,7 @@ std::unique_ptr<Item> BinaryResourceParser::parseValue(const ResourceNameRef& na if (value->dataType == Res_value::TYPE_REFERENCE || value->dataType == Res_value::TYPE_ATTRIBUTE) { const Reference::Type type = (value->dataType == Res_value::TYPE_REFERENCE) ? - Reference::Type::kResource : Reference::Type::kAttribute; + Reference::Type::kResource : Reference::Type::kAttribute; if (data != 0) { // This is a normal reference. @@ -660,9 +669,9 @@ std::unique_ptr<Item> BinaryResourceParser::parseValue(const ResourceNameRef& na } // This reference has an invalid ID. Check if it is an unresolved symbol. - ResourceNameRef symbol; - if (getSymbol(&value->data, &symbol)) { - return util::make_unique<Reference>(symbol, type); + if (Maybe<Reference> ref = getSymbol(&value->data)) { + ref.value().referenceType = type; + return util::make_unique<Reference>(std::move(ref.value())); } // This is not an unresolved symbol, so it must be the magic @null reference. @@ -710,26 +719,38 @@ std::unique_ptr<Style> BinaryResourceParser::parseStyle(const ResourceNameRef& n if (util::deviceToHost32(map->parent.ident) == 0) { // The parent is either not set or it is an unresolved symbol. // Check to see if it is a symbol. - ResourceNameRef symbol; - if (getSymbol(&map->parent.ident, &symbol)) { - style->parent = Reference(symbol.toResourceName()); - } + style->parent = getSymbol(&map->parent.ident); + } else { // The parent is a regular reference to a resource. style->parent = Reference(util::deviceToHost32(map->parent.ident)); } for (const ResTable_map& mapEntry : map) { + if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) { + if (style->entries.empty()) { + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "out-of-sequence meta data in style"); + return {}; + } + collectMetaData(mapEntry, &style->entries.back().key); + continue; + } + style->entries.emplace_back(); Style::Entry& styleEntry = style->entries.back(); if (util::deviceToHost32(mapEntry.name.ident) == 0) { // The map entry's key (attribute) is not set. This must be // a symbol reference, so resolve it. - ResourceNameRef symbol; - bool result = getSymbol(&mapEntry.name.ident, &symbol); - assert(result); - styleEntry.key.name = symbol.toResourceName(); + Maybe<Reference> symbol = getSymbol(&mapEntry.name.ident); + if (!symbol) { + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "unresolved style attribute"); + return {}; + } + styleEntry.key = std::move(symbol.value()); + } else { // The map entry's key (attribute) is a regular reference. styleEntry.key.id = ResourceId(util::deviceToHost32(mapEntry.name.ident)); @@ -737,7 +758,9 @@ std::unique_ptr<Style> BinaryResourceParser::parseStyle(const ResourceNameRef& n // Parse the attribute's value. styleEntry.value = parseValue(name, config, &mapEntry.value, 0); - assert(styleEntry.value); + if (!styleEntry.value) { + return {}; + } } return style; } @@ -757,21 +780,33 @@ std::unique_ptr<Attribute> BinaryResourceParser::parseAttr(const ResourceNameRef attr->typeMask = util::deviceToHost32(typeMaskIter->value.data); } - if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) { - for (const ResTable_map& mapEntry : map) { - if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) { - continue; + for (const ResTable_map& mapEntry : map) { + if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) { + switch (util::deviceToHost32(mapEntry.name.ident)) { + case ResTable_map::ATTR_MIN: + attr->minInt = static_cast<int32_t>(mapEntry.value.data); + break; + case ResTable_map::ATTR_MAX: + attr->maxInt = static_cast<int32_t>(mapEntry.value.data); + break; } + continue; + } + if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) { Attribute::Symbol symbol; symbol.value = util::deviceToHost32(mapEntry.value.data); if (util::deviceToHost32(mapEntry.name.ident) == 0) { // The map entry's key (id) is not set. This must be // a symbol reference, so resolve it. - ResourceNameRef symbolName; - bool result = getSymbol(&mapEntry.name.ident, &symbolName); - assert(result); - symbol.symbol.name = symbolName.toResourceName(); + Maybe<Reference> ref = getSymbol(&mapEntry.name.ident); + if (!ref) { + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "unresolved attribute symbol"); + return {}; + } + symbol.symbol = std::move(ref.value()); + } else { // The map entry's key (id) is a regular reference. symbol.symbol.id = ResourceId(util::deviceToHost32(mapEntry.name.ident)); @@ -781,15 +816,57 @@ std::unique_ptr<Attribute> BinaryResourceParser::parseAttr(const ResourceNameRef } } - // TODO(adamlesinski): Find min, max, i80n, etc attributes. + // TODO(adamlesinski): Find i80n, attributes. return attr; } +static bool isMetaDataEntry(const ResTable_map& mapEntry) { + switch (util::deviceToHost32(mapEntry.name.ident)) { + case ExtendedResTableMapTypes::ATTR_SOURCE_PATH: + case ExtendedResTableMapTypes::ATTR_SOURCE_LINE: + case ExtendedResTableMapTypes::ATTR_COMMENT: + return true; + } + return false; +} + +bool BinaryResourceParser::collectMetaData(const ResTable_map& mapEntry, Value* value) { + switch (util::deviceToHost32(mapEntry.name.ident)) { + case ExtendedResTableMapTypes::ATTR_SOURCE_PATH: + value->setSource(Source(util::getString8(mSourcePool, + util::deviceToHost32(mapEntry.value.data)))); + return true; + break; + + case ExtendedResTableMapTypes::ATTR_SOURCE_LINE: + value->setSource(value->getSource().withLine(util::deviceToHost32(mapEntry.value.data))); + return true; + break; + + case ExtendedResTableMapTypes::ATTR_COMMENT: + value->setComment(util::getString(mSourcePool, util::deviceToHost32(mapEntry.value.data))); + return true; + break; + } + return false; +} + std::unique_ptr<Array> BinaryResourceParser::parseArray(const ResourceNameRef& name, const ConfigDescription& config, const ResTable_map_entry* map) { std::unique_ptr<Array> array = util::make_unique<Array>(); + Source source; for (const ResTable_map& mapEntry : map) { + if (isMetaDataEntry(mapEntry)) { + if (array->items.empty()) { + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "out-of-sequence meta data in array"); + return {}; + } + collectMetaData(mapEntry, array->items.back().get()); + continue; + } + array->items.push_back(parseValue(name, config, &mapEntry.value, 0)); } return array; @@ -800,13 +877,27 @@ std::unique_ptr<Styleable> BinaryResourceParser::parseStyleable(const ResourceNa const ResTable_map_entry* map) { std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); for (const ResTable_map& mapEntry : map) { + if (isMetaDataEntry(mapEntry)) { + if (styleable->entries.empty()) { + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "out-of-sequence meta data in styleable"); + return {}; + } + collectMetaData(mapEntry, &styleable->entries.back()); + continue; + } + if (util::deviceToHost32(mapEntry.name.ident) == 0) { // The map entry's key (attribute) is not set. This must be // a symbol reference, so resolve it. - ResourceNameRef symbol; - bool result = getSymbol(&mapEntry.name.ident, &symbol); - assert(result); - styleable->entries.emplace_back(symbol); + Maybe<Reference> ref = getSymbol(&mapEntry.name.ident); + if (!ref) { + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "unresolved styleable symbol"); + return {}; + } + styleable->entries.emplace_back(std::move(ref.value())); + } else { // The map entry's key (attribute) is a regular reference. styleable->entries.emplace_back(util::deviceToHost32(mapEntry.name.ident)); @@ -819,26 +910,42 @@ std::unique_ptr<Plural> BinaryResourceParser::parsePlural(const ResourceNameRef& const ConfigDescription& config, const ResTable_map_entry* map) { std::unique_ptr<Plural> plural = util::make_unique<Plural>(); + Item* lastEntry = nullptr; for (const ResTable_map& mapEntry : map) { + if (isMetaDataEntry(mapEntry)) { + if (!lastEntry) { + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "out-of-sequence meta data in plural"); + return {}; + } + collectMetaData(mapEntry, lastEntry); + continue; + } + std::unique_ptr<Item> item = parseValue(name, config, &mapEntry.value, 0); + if (!item) { + return {}; + } + + lastEntry = item.get(); switch (util::deviceToHost32(mapEntry.name.ident)) { - case android::ResTable_map::ATTR_ZERO: + case ResTable_map::ATTR_ZERO: plural->values[Plural::Zero] = std::move(item); break; - case android::ResTable_map::ATTR_ONE: + case ResTable_map::ATTR_ONE: plural->values[Plural::One] = std::move(item); break; - case android::ResTable_map::ATTR_TWO: + case ResTable_map::ATTR_TWO: plural->values[Plural::Two] = std::move(item); break; - case android::ResTable_map::ATTR_FEW: + case ResTable_map::ATTR_FEW: plural->values[Plural::Few] = std::move(item); break; - case android::ResTable_map::ATTR_MANY: + case ResTable_map::ATTR_MANY: plural->values[Plural::Many] = std::move(item); break; - case android::ResTable_map::ATTR_OTHER: + case ResTable_map::ATTR_OTHER: plural->values[Plural::Other] = std::move(item); break; } diff --git a/tools/aapt2/unflatten/BinaryResourceParser.h b/tools/aapt2/unflatten/BinaryResourceParser.h index 02c4081cc0e2..0745a592c296 100644 --- a/tools/aapt2/unflatten/BinaryResourceParser.h +++ b/tools/aapt2/unflatten/BinaryResourceParser.h @@ -57,7 +57,7 @@ public: private: // Helper method to retrieve the symbol name for a given table offset specified // as a pointer. - bool getSymbol(const void* data, ResourceNameRef* outSymbol); + Maybe<Reference> getSymbol(const void* data); bool parseTable(const android::ResChunk_header* chunk); bool parseSymbolTable(const android::ResChunk_header* chunk); @@ -91,6 +91,13 @@ private: const ConfigDescription& config, const android::ResTable_map_entry* map); + /** + * If the mapEntry is a special type that denotes meta data (source, comment), then it is + * read and added to the Value. + * Returns true if the mapEntry was meta data. + */ + bool collectMetaData(const android::ResTable_map& mapEntry, Value* value); + IAaptContext* mContext; ResourceTable* mTable; diff --git a/tools/aapt2/util/Maybe.h b/tools/aapt2/util/Maybe.h index 1f7d5ce901b4..aa409ea62ade 100644 --- a/tools/aapt2/util/Maybe.h +++ b/tools/aapt2/util/Maybe.h @@ -275,6 +275,29 @@ inline Maybe<T> make_nothing() { return Maybe<T>(); } +/** + * Define the == operator between Maybe<T> and Maybe<U> if the operator T == U is defined. + * Otherwise this won't be defined and the compiler will yell at the callsite instead of inside + * Maybe.h. + */ +template <typename T, typename U> +auto operator==(const Maybe<T>& a, const Maybe<U>& b) +-> decltype(std::declval<T> == std::declval<U>) { + if (a && b) { + return a.value() == b.value(); + } + return false; +} + +/** + * Same as operator== but negated. + */ +template <typename T, typename U> +auto operator!=(const Maybe<T>& a, const Maybe<U>& b) +-> decltype(std::declval<T> == std::declval<U>) { + return !(a == b); +} + } // namespace aapt #endif // AAPT_MAYBE_H diff --git a/tools/aapt2/util/Maybe_test.cpp b/tools/aapt2/util/Maybe_test.cpp index d2c33cac7aa4..9cca40ea631f 100644 --- a/tools/aapt2/util/Maybe_test.cpp +++ b/tools/aapt2/util/Maybe_test.cpp @@ -119,4 +119,14 @@ TEST(MaybeTest, MoveAssign) { } } +TEST(MaybeTest, Equality) { + Maybe<int> a = 1; + Maybe<int> b = 1; + Maybe<int> c; + + EXPECT_EQ(a, b); + EXPECT_EQ(b, a); + EXPECT_NE(a, c); +} + } // namespace aapt diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp index 59b838587a6a..9ecc974d3a1c 100644 --- a/tools/aapt2/util/Util.cpp +++ b/tools/aapt2/util/Util.cpp @@ -28,9 +28,6 @@ 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; @@ -467,18 +464,6 @@ 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 {}; -} - bool extractResFilePathParts(const StringPiece16& path, StringPiece16* outPrefix, StringPiece16* outEntry, StringPiece16* outSuffix) { if (!stringStartsWith<char16_t>(path, u"res/")) { diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h index 80552a540ec3..0dacbd773488 100644 --- a/tools/aapt2/util/Util.h +++ b/tools/aapt2/util/Util.h @@ -158,6 +158,15 @@ inline StringPiece16 getString(const android::ResStringPool& pool, size_t idx) { return StringPiece16(); } +inline StringPiece getString8(const android::ResStringPool& pool, size_t idx) { + size_t len; + const char* str = pool.string8At(idx, &len); + if (str != nullptr) { + return StringPiece(str, len); + } + return StringPiece(); +} + /** * Checks that the Java string format contains no non-positional arguments (arguments without * explicitly specifying an index) when there are more than one argument. This is an error @@ -229,11 +238,12 @@ public: private: friend class Tokenizer<Char>; - iterator(BasicStringPiece<Char> s, Char sep, BasicStringPiece<Char> tok); + iterator(BasicStringPiece<Char> s, Char sep, BasicStringPiece<Char> tok, bool end); - BasicStringPiece<Char> str; - Char separator; - BasicStringPiece<Char> token; + BasicStringPiece<Char> mStr; + Char mSeparator; + BasicStringPiece<Char> mToken; + bool mEnd; }; Tokenizer(BasicStringPiece<Char> str, Char sep); @@ -252,36 +262,38 @@ inline Tokenizer<Char> tokenize(BasicStringPiece<Char> str, Char sep) { template <typename Char> typename Tokenizer<Char>::iterator& Tokenizer<Char>::iterator::operator++() { - const Char* start = token.end(); - const Char* end = str.end(); + const Char* start = mToken.end(); + const Char* end = mStr.end(); if (start == end) { - token.assign(token.end(), 0); + mEnd = true; + mToken.assign(mToken.end(), 0); return *this; } start += 1; const Char* current = start; while (current != end) { - if (*current == separator) { - token.assign(start, current - start); + if (*current == mSeparator) { + mToken.assign(start, current - start); return *this; } ++current; } - token.assign(start, end - start); + mToken.assign(start, end - start); return *this; } template <typename Char> inline BasicStringPiece<Char> Tokenizer<Char>::iterator::operator*() { - return token; + return mToken; } template <typename Char> inline bool Tokenizer<Char>::iterator::operator==(const iterator& rhs) const { // We check equality here a bit differently. // We need to know that the addresses are the same. - return token.begin() == rhs.token.begin() && token.end() == rhs.token.end(); + return mToken.begin() == rhs.mToken.begin() && mToken.end() == rhs.mToken.end() && + mEnd == rhs.mEnd; } template <typename Char> @@ -291,8 +303,8 @@ inline bool Tokenizer<Char>::iterator::operator!=(const iterator& rhs) const { template <typename Char> inline Tokenizer<Char>::iterator::iterator(BasicStringPiece<Char> s, Char sep, - BasicStringPiece<Char> tok) : - str(s), separator(sep), token(tok) { + BasicStringPiece<Char> tok, bool end) : + mStr(s), mSeparator(sep), mToken(tok), mEnd(end) { } template <typename Char> @@ -307,8 +319,8 @@ inline typename Tokenizer<Char>::iterator Tokenizer<Char>::end() { template <typename Char> inline Tokenizer<Char>::Tokenizer(BasicStringPiece<Char> str, Char sep) : - mBegin(++iterator(str, sep, BasicStringPiece<Char>(str.begin() - 1, 0))), - mEnd(str, sep, BasicStringPiece<Char>(str.end(), 0)) { + mBegin(++iterator(str, sep, BasicStringPiece<Char>(str.begin() - 1, 0), false)), + mEnd(str, sep, BasicStringPiece<Char>(str.end(), 0), true) { } inline uint16_t hostToDevice16(uint16_t value) { @@ -328,15 +340,6 @@ inline uint32_t deviceToHost32(uint32_t value) { } /** - * 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); - -/** * Given a path like: res/xml-sw600dp/foo.xml * * Extracts "res/xml-sw600dp/" into outPrefix. diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp index 9db9fb7f112a..9208e07e635b 100644 --- a/tools/aapt2/util/Util_test.cpp +++ b/tools/aapt2/util/Util_test.cpp @@ -101,6 +101,15 @@ TEST(UtilTest, TokenizeInput) { ASSERT_EQ(tokenizer.end(), iter); } +TEST(UtilTest, TokenizeEmptyString) { + auto tokenizer = util::tokenize(StringPiece16(u""), u'|'); + auto iter = tokenizer.begin(); + ASSERT_NE(tokenizer.end(), iter); + ASSERT_EQ(StringPiece16(), *iter); + ++iter; + ASSERT_EQ(tokenizer.end(), iter); +} + TEST(UtilTest, TokenizeAtEnd) { auto tokenizer = util::tokenize(StringPiece16(u"one."), u'.'); auto iter = tokenizer.begin(); diff --git a/tools/aapt2/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp index b769c7620658..d27b62fd99fb 100644 --- a/tools/aapt2/XmlDom.cpp +++ b/tools/aapt2/xml/XmlDom.cpp @@ -14,11 +14,12 @@ * limitations under the License. */ -#include "util/Util.h" #include "XmlDom.h" #include "XmlPullParser.h" +#include "util/Util.h" #include <cassert> +#include <expat.h> #include <memory> #include <stack> #include <string> @@ -317,6 +318,10 @@ std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnost return util::make_unique<XmlResource>(ResourceFile{}, std::move(root)); } +Element* findRootElement(XmlResource* doc) { + return findRootElement(doc->root.get()); +} + Element* findRootElement(Node* node) { if (!node) { return nullptr; @@ -397,5 +402,39 @@ std::vector<Element*> Element::getChildElements() { return elements; } +void PackageAwareVisitor::visit(Namespace* ns) { + bool added = false; + if (Maybe<ExtractedPackage> maybePackage = extractPackageFromNamespace(ns->namespaceUri)) { + ExtractedPackage& package = maybePackage.value(); + mPackageDecls.push_back(PackageDecl{ ns->namespacePrefix, std::move(package) }); + added = true; + } + + Visitor::visit(ns); + + if (added) { + mPackageDecls.pop_back(); + } +} + +Maybe<ExtractedPackage> PackageAwareVisitor::transformPackageAlias( + const StringPiece16& alias, const StringPiece16& localPackage) const { + if (alias.empty()) { + return ExtractedPackage{ localPackage.toString(), false /* private */ }; + } + + const auto rend = mPackageDecls.rend(); + for (auto iter = mPackageDecls.rbegin(); iter != rend; ++iter) { + if (alias == iter->prefix) { + if (iter->package.package.empty()) { + return ExtractedPackage{ localPackage.toString(), + iter->package.privateNamespace }; + } + return iter->package; + } + } + return {}; +} + } // namespace xml } // namespace aapt diff --git a/tools/aapt2/XmlDom.h b/tools/aapt2/xml/XmlDom.h index 9a46bcb9fc5c..033b0a4d031c 100644 --- a/tools/aapt2/XmlDom.h +++ b/tools/aapt2/xml/XmlDom.h @@ -22,11 +22,9 @@ #include "ResourceValues.h" #include "util/StringPiece.h" #include "util/Util.h" - -#include "process/IResourceTableConsumer.h" +#include "xml/XmlUtil.h" #include <istream> -#include <expat.h> #include <memory> #include <string> #include <vector> @@ -34,21 +32,9 @@ namespace aapt { namespace xml { -constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android"; - struct RawVisitor; /** - * The type of node. Can be used to downcast to the concrete XML node - * class. - */ -enum class NodeType { - kNamespace, - kElement, - kText, -}; - -/** * Base class for all XML nodes. */ struct Node { @@ -58,9 +44,10 @@ struct Node { std::u16string comment; std::vector<std::unique_ptr<Node>> children; + virtual ~Node() = default; + void addChild(std::unique_ptr<Node> child); virtual void accept(RawVisitor* visitor) = 0; - virtual ~Node() {} }; /** @@ -122,6 +109,14 @@ struct Text : public BaseNode<Text> { }; /** + * An XML resource with a source, name, and XML tree. + */ +struct XmlResource { + ResourceFile file; + std::unique_ptr<xml::Node> root; +}; + +/** * Inflates an XML DOM from a text stream, logging errors to the logger. * Returns the root node on success, or nullptr on failure. */ @@ -134,6 +129,7 @@ std::unique_ptr<XmlResource> inflate(std::istream* in, IDiagnostics* diag, const std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag, const Source& source); +Element* findRootElement(XmlResource* doc); Element* findRootElement(Node* node); /** @@ -180,7 +176,7 @@ class PackageAwareVisitor : public Visitor, public IPackageDeclStack { private: struct PackageDecl { std::u16string prefix; - std::u16string package; + ExtractedPackage package; }; std::vector<PackageDecl> mPackageDecls; @@ -188,41 +184,9 @@ private: public: using Visitor::visit; - void visit(Namespace* ns) override { - bool added = false; - { - Maybe<std::u16string> package = util::extractPackageFromNamespace(ns->namespaceUri); - if (package) { - mPackageDecls.push_back(PackageDecl{ ns->namespacePrefix, package.value() }); - added = true; - } - } - - Visitor::visit(ns); - - if (added) { - mPackageDecls.pop_back(); - } - } - - Maybe<ResourceName> transformPackage(const ResourceName& name, - const StringPiece16& localPackage) const override { - if (name.package.empty()) { - return ResourceName{ localPackage.toString(), name.type, name.entry }; - } - - const auto rend = mPackageDecls.rend(); - for (auto iter = mPackageDecls.rbegin(); iter != rend; ++iter) { - if (name.package == iter->prefix) { - if (iter->package.empty()) { - return ResourceName{ localPackage.toString(), name.type, name.entry }; - } else { - return ResourceName{ iter->package, name.type, name.entry }; - } - } - } - return {}; - } + void visit(Namespace* ns) override; + Maybe<ExtractedPackage> transformPackageAlias( + const StringPiece16& alias, const StringPiece16& localPackage) const override; }; // Implementations diff --git a/tools/aapt2/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp index a1b9ed067aaa..431ee2c8fb46 100644 --- a/tools/aapt2/XmlDom_test.cpp +++ b/tools/aapt2/xml/XmlDom_test.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "XmlDom.h" +#include "xml/XmlDom.h" #include <gtest/gtest.h> #include <sstream> @@ -38,7 +38,7 @@ TEST(XmlDomTest, Inflate) { const Source source = { "test.xml" }; StdErrDiagnostics diag; - std::unique_ptr<XmlResource> doc = xml::inflate(&in, &diag, source); + std::unique_ptr<xml::XmlResource> doc = xml::inflate(&in, &diag, source); ASSERT_NE(doc, nullptr); xml::Namespace* ns = xml::nodeCast<xml::Namespace>(doc->root.get()); diff --git a/tools/aapt2/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp index cff935c10aae..323ec05b5f2c 100644 --- a/tools/aapt2/XmlPullParser.cpp +++ b/tools/aapt2/xml/XmlPullParser.cpp @@ -16,12 +16,14 @@ #include "util/Maybe.h" #include "util/Util.h" -#include "XmlPullParser.h" +#include "xml/XmlPullParser.h" +#include "xml/XmlUtil.h" #include <iostream> #include <string> namespace aapt { +namespace xml { constexpr char kXmlNamespaceSep = 1; @@ -72,14 +74,14 @@ XmlPullParser::Event XmlPullParser::next() { // 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()); + Maybe<ExtractedPackage> result = extractPackageFromNamespace(getNamespaceUri()); if (event == Event::kStartNamespace) { if (result) { - mPackageAliases.emplace_back(getNamespacePrefix(), result.value()); + mPackageAliases.emplace_back( + PackageDecl{ getNamespacePrefix(), std::move(result.value()) }); } } else { if (result) { - assert(mPackageAliases.back().second == result.value()); mPackageAliases.pop_back(); } } @@ -131,20 +133,20 @@ const std::u16string& XmlPullParser::getNamespaceUri() const { return mEventQueue.front().data2; } -Maybe<ResourceName> XmlPullParser::transformPackage( - const ResourceName& name, const StringPiece16& localPackage) const { - if (name.package.empty()) { - return ResourceName{ localPackage.toString(), name.type, name.entry }; +Maybe<ExtractedPackage> XmlPullParser::transformPackageAlias( + const StringPiece16& alias, const StringPiece16& localPackage) const { + if (alias.empty()) { + return ExtractedPackage{ localPackage.toString(), false /* private */ }; } const auto endIter = mPackageAliases.rend(); for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) { - if (name.package == iter->first) { - if (iter->second.empty()) { - return ResourceName{ localPackage.toString(), name.type, name.entry }; - } else { - return ResourceName{ iter->second, name.type, name.entry }; + if (alias == iter->prefix) { + if (iter->package.package.empty()) { + return ExtractedPackage{ localPackage.toString(), + iter->package.privateNamespace }; } + return iter->package; } } return {}; @@ -283,4 +285,24 @@ void XMLCALL XmlPullParser::commentDataHandler(void* userData, const char* comme }); } +Maybe<StringPiece16> findAttribute(const XmlPullParser* parser, const StringPiece16& name) { + auto iter = parser->findAttribute(u"", name); + if (iter != parser->endAttributes()) { + return StringPiece16(util::trimWhitespace(iter->value)); + } + return {}; +} + +Maybe<StringPiece16> findNonEmptyAttribute(const XmlPullParser* parser, const StringPiece16& name) { + auto iter = parser->findAttribute(u"", name); + if (iter != parser->endAttributes()) { + StringPiece16 trimmed = util::trimWhitespace(iter->value); + if (!trimmed.empty()) { + return trimmed; + } + } + return {}; +} + +} // namespace xml } // namespace aapt diff --git a/tools/aapt2/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h index a0ce21dd3218..7e7070e5e5ea 100644 --- a/tools/aapt2/XmlPullParser.h +++ b/tools/aapt2/xml/XmlPullParser.h @@ -17,11 +17,11 @@ #ifndef AAPT_XML_PULL_PARSER_H #define AAPT_XML_PULL_PARSER_H -#include "util/Maybe.h" #include "Resource.h" -#include "util/StringPiece.h" - #include "process/IResourceTableConsumer.h" +#include "util/Maybe.h" +#include "util/StringPiece.h" +#include "xml/XmlUtil.h" #include <algorithm> #include <expat.h> @@ -33,6 +33,7 @@ #include <vector> namespace aapt { +namespace xml { class XmlPullParser : public IPackageDeclStack { public: @@ -60,7 +61,7 @@ public: static bool isGoodEvent(Event event); XmlPullParser(std::istream& in); - virtual ~XmlPullParser(); + ~XmlPullParser(); /** * Returns the current event that is being processed. @@ -95,6 +96,13 @@ public: const std::u16string& getNamespacePrefix() const; const std::u16string& getNamespaceUri() const; + // + // These are available for StartElement and EndElement. + // + + const std::u16string& getElementNamespace() const; + const std::u16string& getElementName() const; + /* * Uses the current stack of namespaces to resolve the package. Eg: * xmlns:app = "http://schemas.android.com/apk/res/com.android.app" @@ -106,17 +114,8 @@ public: * If xmlns:app="http://schemas.android.com/apk/res-auto", then * 'package' will be set to 'defaultPackage'. */ - // - - // - // These are available for StartElement and EndElement. - // - - const std::u16string& getElementNamespace() const; - const std::u16string& getElementName() const; - - Maybe<ResourceName> transformPackage(const ResourceName& name, - const StringPiece16& localPackage) const override; + Maybe<ExtractedPackage> transformPackageAlias( + const StringPiece16& alias, const StringPiece16& localPackage) const override; // // Remaining methods are for retrieving information about attributes @@ -169,9 +168,25 @@ private: const std::u16string mEmpty; size_t mDepth; std::stack<std::u16string> mNamespaceUris; - std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases; + + struct PackageDecl { + std::u16string prefix; + ExtractedPackage package; + }; + std::vector<PackageDecl> mPackageAliases; }; +/** + * Finds the attribute in the current element within the global namespace. + */ +Maybe<StringPiece16> findAttribute(const XmlPullParser* parser, const StringPiece16& name); + +/** + * Finds the attribute in the current element within the global namespace. The attribute's value + * must not be the empty string. + */ +Maybe<StringPiece16> findNonEmptyAttribute(const XmlPullParser* parser, const StringPiece16& name); + // // Implementation // @@ -277,6 +292,7 @@ inline XmlPullParser::const_iterator XmlPullParser::findAttribute(StringPiece16 return endIter; } +} // namespace xml } // namespace aapt #endif // AAPT_XML_PULL_PARSER_H diff --git a/tools/aapt2/XmlPullParser_test.cpp b/tools/aapt2/xml/XmlPullParser_test.cpp index 1c99a432f597..8fa2c6d274c8 100644 --- a/tools/aapt2/XmlPullParser_test.cpp +++ b/tools/aapt2/xml/XmlPullParser_test.cpp @@ -15,7 +15,7 @@ */ #include "util/StringPiece.h" -#include "XmlPullParser.h" +#include "xml/XmlPullParser.h" #include <gtest/gtest.h> #include <sstream> @@ -26,30 +26,30 @@ TEST(XmlPullParserTest, NextChildNodeTraversesCorrectly) { std::stringstream str; str << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" "<a><b><c xmlns:a=\"http://schema.org\"><d/></c><e/></b></a>"; - XmlPullParser parser(str); + xml::XmlPullParser parser(str); const size_t depthOuter = parser.getDepth(); - ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthOuter)); + ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthOuter)); - EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent()); + EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent()); EXPECT_EQ(StringPiece16(u"a"), StringPiece16(parser.getElementName())); const size_t depthA = parser.getDepth(); - ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthA)); - EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent()); + ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthA)); + EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent()); EXPECT_EQ(StringPiece16(u"b"), StringPiece16(parser.getElementName())); const size_t depthB = parser.getDepth(); - ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthB)); - EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent()); + ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthB)); + EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent()); EXPECT_EQ(StringPiece16(u"c"), StringPiece16(parser.getElementName())); - ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthB)); - EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent()); + ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthB)); + EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent()); EXPECT_EQ(StringPiece16(u"e"), StringPiece16(parser.getElementName())); - ASSERT_FALSE(XmlPullParser::nextChildNode(&parser, depthOuter)); - EXPECT_EQ(XmlPullParser::Event::kEndDocument, parser.getEvent()); + ASSERT_FALSE(xml::XmlPullParser::nextChildNode(&parser, depthOuter)); + EXPECT_EQ(xml::XmlPullParser::Event::kEndDocument, parser.getEvent()); } } // namespace aapt diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp new file mode 100644 index 000000000000..ab9f544d67ea --- /dev/null +++ b/tools/aapt2/xml/XmlUtil.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "util/Maybe.h" +#include "util/Util.h" +#include "xml/XmlUtil.h" + +#include <string> + +namespace aapt { +namespace xml { + +Maybe<ExtractedPackage> extractPackageFromNamespace(const std::u16string& namespaceUri) { + if (util::stringStartsWith<char16_t>(namespaceUri, kSchemaPublicPrefix)) { + StringPiece16 schemaPrefix = kSchemaPublicPrefix; + StringPiece16 package = namespaceUri; + package = package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size()); + if (package.empty()) { + return {}; + } + return ExtractedPackage{ package.toString(), false /* isPrivate */ }; + + } else if (util::stringStartsWith<char16_t>(namespaceUri, kSchemaPrivatePrefix)) { + StringPiece16 schemaPrefix = kSchemaPrivatePrefix; + StringPiece16 package = namespaceUri; + package = package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size()); + if (package.empty()) { + return {}; + } + return ExtractedPackage{ package.toString(), true /* isPrivate */ }; + + } else if (namespaceUri == kSchemaAuto) { + return ExtractedPackage{ std::u16string(), true /* isPrivate */ }; + } + return {}; +} + +void transformReferenceFromNamespace(IPackageDeclStack* declStack, + const StringPiece16& localPackage, Reference* inRef) { + if (inRef->name) { + if (Maybe<ExtractedPackage> transformedPackage = + declStack->transformPackageAlias(inRef->name.value().package, localPackage)) { + ExtractedPackage& extractedPackage = transformedPackage.value(); + inRef->name.value().package = std::move(extractedPackage.package); + + // If the reference was already private (with a * prefix) and the namespace is public, + // we keep the reference private. + inRef->privateReference |= extractedPackage.privateNamespace; + } + } +} + +} // namespace xml +} // namespace aapt diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h new file mode 100644 index 000000000000..98e5520a6ea2 --- /dev/null +++ b/tools/aapt2/xml/XmlUtil.h @@ -0,0 +1,85 @@ +/* + * 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_XML_XMLUTIL_H +#define AAPT_XML_XMLUTIL_H + +#include "ResourceValues.h" +#include "util/Maybe.h" + +#include <string> + +namespace aapt { +namespace xml { + +constexpr const char16_t* kSchemaAuto = u"http://schemas.android.com/apk/res-auto"; +constexpr const char16_t* kSchemaPublicPrefix = u"http://schemas.android.com/apk/res/"; +constexpr const char16_t* kSchemaPrivatePrefix = u"http://schemas.android.com/apk/prv/res/"; +constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android"; + +/** + * Result of extracting a package name from a namespace URI declaration. + */ +struct ExtractedPackage { + /** + * The name of the package. This can be the empty string, which means that the package + * should be assumed to be the package being compiled. + */ + std::u16string package; + + /** + * True if the package's private namespace was declared. This means that private resources + * are made visible. + */ + bool privateNamespace; +}; + +/** + * Returns an ExtractedPackage struct if the namespace URI is of the form: + * http://schemas.android.com/apk/res/<package> or + * http://schemas.android.com/apk/prv/res/<package> + * + * Special case: if namespaceUri is http://schemas.android.com/apk/res-auto, + * returns an empty package name. + */ +Maybe<ExtractedPackage> extractPackageFromNamespace(const std::u16string& namespaceUri); + +/** + * Interface representing a stack of XML namespace declarations. When looking up the package + * for a namespace prefix, the stack is checked from top to bottom. + */ +struct IPackageDeclStack { + virtual ~IPackageDeclStack() = default; + + /** + * Returns an ExtractedPackage struct if the alias given corresponds with a package declaration. + */ + virtual Maybe<ExtractedPackage> transformPackageAlias( + const StringPiece16& alias, const StringPiece16& localPackage) const = 0; +}; + +/** + * Helper function for transforming the original Reference inRef to a fully qualified reference + * via the IPackageDeclStack. This will also mark the Reference as private if the namespace of + * the package declaration was private. + */ +void transformReferenceFromNamespace(IPackageDeclStack* declStack, + const StringPiece16& localPackage, Reference* inRef); + +} // namespace xml +} // namespace aapt + +#endif /* AAPT_XML_XMLUTIL_H */ diff --git a/tools/aapt2/xml/XmlUtil_test.cpp b/tools/aapt2/xml/XmlUtil_test.cpp new file mode 100644 index 000000000000..7796b3ea7691 --- /dev/null +++ b/tools/aapt2/xml/XmlUtil_test.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "test/Common.h" +#include "xml/XmlUtil.h" + +#include <gtest/gtest.h> + +namespace aapt { + +TEST(XmlUtilTest, ExtractPackageFromNamespace) { + AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"com.android")); + AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"http://schemas.android.com/apk")); + AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res")); + AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res/")); + AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace( + u"http://schemas.android.com/apk/prv/res/")); + + Maybe<xml::ExtractedPackage> p = + xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res/a"); + AAPT_ASSERT_TRUE(p); + EXPECT_EQ(std::u16string(u"a"), p.value().package); + EXPECT_EQ(false, p.value().privateNamespace); + + p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/prv/res/android"); + AAPT_ASSERT_TRUE(p); + EXPECT_EQ(std::u16string(u"android"), p.value().package); + EXPECT_EQ(true, p.value().privateNamespace); + + p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/prv/res/com.test"); + AAPT_ASSERT_TRUE(p); + EXPECT_EQ(std::u16string(u"com.test"), p.value().package); + EXPECT_EQ(true, p.value().privateNamespace); + + p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res-auto"); + AAPT_ASSERT_TRUE(p); + EXPECT_EQ(std::u16string(), p.value().package); + EXPECT_EQ(true, p.value().privateNamespace); +} + +} // namespace aapt diff --git a/tools/layoutlib/.idea/encodings.xml b/tools/layoutlib/.idea/encodings.xml index e206d70d8595..f758959656c7 100644 --- a/tools/layoutlib/.idea/encodings.xml +++ b/tools/layoutlib/.idea/encodings.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <project version="4"> - <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" /> -</project> - + <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false"> + <file url="PROJECT" charset="UTF-8" /> + </component> +</project>
\ No newline at end of file diff --git a/tools/layoutlib/Android.mk b/tools/layoutlib/Android.mk index 61ddb04bc08c..53bfc1581e53 100644 --- a/tools/layoutlib/Android.mk +++ b/tools/layoutlib/Android.mk @@ -30,6 +30,9 @@ LOCAL_JAVACFLAGS := -source 6 -target 6 built_framework_dep := $(call java-lib-deps,framework) built_framework_classes := $(call java-lib-files,framework) +built_oj_dep := $(call java-lib-deps,core-oj) +built_oj_classes := $(call java-lib-files,core-oj) + built_core_dep := $(call java-lib-deps,core-libart) built_core_classes := $(call java-lib-files,core-libart) @@ -56,7 +59,8 @@ LOCAL_BUILT_MODULE_STEM := javalib.jar include $(BUILD_SYSTEM)/base_rules.mk ####################################### -$(LOCAL_BUILT_MODULE): $(built_core_dep) \ +$(LOCAL_BUILT_MODULE): $(built_oj_dep) \ + $(built_core_dep) \ $(built_framework_dep) \ $(built_ext_dep) \ $(built_ext_data) \ @@ -69,6 +73,7 @@ $(LOCAL_BUILT_MODULE): $(built_core_dep) \ $(hide) ls -l $(built_framework_classes) $(hide) java -ea -jar $(built_layoutlib_create_jar) \ $@ \ + $(built_oj_classes) \ $(built_core_classes) \ $(built_framework_classes) \ $(built_ext_classes) \ diff --git a/tools/layoutlib/bridge/src/android/animation/FakeAnimator.java b/tools/layoutlib/bridge/src/android/animation/FakeAnimator.java deleted file mode 100644 index 78aedc5a3102..000000000000 --- a/tools/layoutlib/bridge/src/android/animation/FakeAnimator.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2014 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. - */ - -package android.animation; - -/** - * A fake implementation of Animator which doesn't do anything. - */ -public class FakeAnimator extends Animator { - @Override - public long getStartDelay() { - return 0; - } - - @Override - public void setStartDelay(long startDelay) { - - } - - @Override - public Animator setDuration(long duration) { - return this; - } - - @Override - public long getDuration() { - return 0; - } - - @Override - public void setInterpolator(TimeInterpolator value) { - - } - - @Override - public boolean isRunning() { - return false; - } -} diff --git a/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java b/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java index 4603b6362b0e..54021c9f988d 100644 --- a/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java +++ b/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java @@ -16,9 +16,16 @@ package android.animation; +import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + /** * Delegate implementing the native methods of android.animation.PropertyValuesHolder * @@ -29,81 +36,161 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate; * around to map int to instance of the delegate. * * The main goal of this class' methods are to provide a native way to access setters and getters - * on some object. In this case we want to default to using Java reflection instead so the native - * methods do nothing. + * on some object. We override these methods to use reflection since the original reflection + * implementation of the PropertyValuesHolder won't be able to access protected methods. * */ -/*package*/ class PropertyValuesHolder_Delegate { +/*package*/ +@SuppressWarnings("unused") +class PropertyValuesHolder_Delegate { + // This code is copied from android.animation.PropertyValuesHolder and must be kept in sync + // We try several different types when searching for appropriate setter/getter functions. + // The caller may have supplied values in a type that does not match the setter/getter + // functions (such as the integers 0 and 1 to represent floating point values for alpha). + // Also, the use of generics in constructors means that we end up with the Object versions + // of primitive types (Float vs. float). But most likely, the setter/getter functions + // will take primitive types instead. + // So we supply an ordered array of other types to try before giving up. + private static Class[] FLOAT_VARIANTS = {float.class, Float.class, double.class, int.class, + Double.class, Integer.class}; + private static Class[] INTEGER_VARIANTS = {int.class, Integer.class, float.class, double.class, + Float.class, Double.class}; + + private static final Object sMethodIndexLock = new Object(); + private static final Map<Long, Method> ID_TO_METHOD = new HashMap<Long, Method>(); + private static final Map<String, Long> METHOD_NAME_TO_ID = new HashMap<String, Long>(); + private static long sNextId = 1; + + private static long registerMethod(Class<?> targetClass, String methodName, Class[] types, + int nArgs) { + // Encode the number of arguments in the method name + String methodIndexName = String.format("%1$s#%2$d", methodName, nArgs); + synchronized (sMethodIndexLock) { + Long methodId = METHOD_NAME_TO_ID.get(methodIndexName); + + if (methodId != null) { + // The method was already registered + return methodId; + } + + Class[] args = new Class[nArgs]; + Method method = null; + for (Class typeVariant : types) { + for (int i = 0; i < nArgs; i++) { + args[i] = typeVariant; + } + try { + method = targetClass.getDeclaredMethod(methodName, args); + } catch (NoSuchMethodException ignore) { + } + } + + if (method != null) { + methodId = sNextId++; + ID_TO_METHOD.put(methodId, method); + METHOD_NAME_TO_ID.put(methodIndexName, methodId); + + return methodId; + } + } + + // Method not found + return 0; + } + + private static void callMethod(Object target, long methodID, Object... args) { + Method method = ID_TO_METHOD.get(methodID); + assert method != null; + + try { + method.setAccessible(true); + method.invoke(target, args); + } catch (IllegalAccessException e) { + Bridge.getLog().error(null, "Unable to update property during animation", e, null); + } catch (InvocationTargetException e) { + Bridge.getLog().error(null, "Unable to update property during animation", e, null); + } + } @LayoutlibDelegate /*package*/ static long nGetIntMethod(Class<?> targetClass, String methodName) { - // return 0 to force PropertyValuesHolder to use Java reflection. - return 0; + return nGetMultipleIntMethod(targetClass, methodName, 1); } @LayoutlibDelegate /*package*/ static long nGetFloatMethod(Class<?> targetClass, String methodName) { - // return 0 to force PropertyValuesHolder to use Java reflection. - return 0; + return nGetMultipleFloatMethod(targetClass, methodName, 1); } @LayoutlibDelegate /*package*/ static long nGetMultipleIntMethod(Class<?> targetClass, String methodName, int numParams) { - // TODO: return the right thing. - return 0; + return registerMethod(targetClass, methodName, INTEGER_VARIANTS, numParams); } @LayoutlibDelegate /*package*/ static long nGetMultipleFloatMethod(Class<?> targetClass, String methodName, int numParams) { - // TODO: return the right thing. - return 0; + return registerMethod(targetClass, methodName, FLOAT_VARIANTS, numParams); } @LayoutlibDelegate /*package*/ static void nCallIntMethod(Object target, long methodID, int arg) { - // do nothing + callMethod(target, methodID, arg); } @LayoutlibDelegate /*package*/ static void nCallFloatMethod(Object target, long methodID, float arg) { - // do nothing + callMethod(target, methodID, arg); } @LayoutlibDelegate /*package*/ static void nCallTwoIntMethod(Object target, long methodID, int arg1, int arg2) { - // do nothing + callMethod(target, methodID, arg1, arg2); } @LayoutlibDelegate /*package*/ static void nCallFourIntMethod(Object target, long methodID, int arg1, int arg2, int arg3, int arg4) { - // do nothing + callMethod(target, methodID, arg1, arg2, arg3, arg4); } @LayoutlibDelegate /*package*/ static void nCallMultipleIntMethod(Object target, long methodID, int[] args) { - // do nothing + assert args != null; + + // Box parameters + Object[] params = new Object[args.length]; + for (int i = 0; i < args.length; i++) { + params[i] = args; + } + callMethod(target, methodID, params); } @LayoutlibDelegate /*package*/ static void nCallTwoFloatMethod(Object target, long methodID, float arg1, float arg2) { - // do nothing + callMethod(target, methodID, arg1, arg2); } @LayoutlibDelegate /*package*/ static void nCallFourFloatMethod(Object target, long methodID, float arg1, float arg2, float arg3, float arg4) { - // do nothing + callMethod(target, methodID, arg1, arg2, arg3, arg4); } @LayoutlibDelegate /*package*/ static void nCallMultipleFloatMethod(Object target, long methodID, float[] args) { - // do nothing + assert args != null; + + // Box parameters + Object[] params = new Object[args.length]; + for (int i = 0; i < args.length; i++) { + params[i] = args; + } + callMethod(target, methodID, params); } } diff --git a/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java index dd2978f5c414..3c71233b1df5 100644 --- a/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java @@ -44,7 +44,7 @@ public final class PathMeasure_Delegate { // ---- delegate data ---- // This governs how accurate the approximation of the Path is. - private static final float PRECISION = 0.002f; + private static final float PRECISION = 0.0002f; /** * Array containing the path points components. There are three components for each point: diff --git a/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java index 5f0d98b35431..9677aaf5d07a 100644 --- a/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java +++ b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java @@ -18,6 +18,7 @@ package android.os; import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; +import com.android.tools.layoutlib.java.System_Delegate; /** * Delegate implementing the native methods of android.os.SystemClock @@ -30,9 +31,6 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate; * */ public class SystemClock_Delegate { - private static long sBootTime = System.currentTimeMillis(); - private static long sBootTimeNano = System.nanoTime(); - /** * Returns milliseconds since boot, not counting time spent in deep sleep. * <b>Note:</b> This value may get reset occasionally (before it would @@ -42,7 +40,7 @@ public class SystemClock_Delegate { */ @LayoutlibDelegate /*package*/ static long uptimeMillis() { - return System.currentTimeMillis() - sBootTime; + return System_Delegate.currentTimeMillis() - System_Delegate.bootTimeMillis(); } /** @@ -52,7 +50,7 @@ public class SystemClock_Delegate { */ @LayoutlibDelegate /*package*/ static long elapsedRealtime() { - return System.currentTimeMillis() - sBootTime; + return System_Delegate.currentTimeMillis() - System_Delegate.bootTimeMillis(); } /** @@ -62,7 +60,7 @@ public class SystemClock_Delegate { */ @LayoutlibDelegate /*package*/ static long elapsedRealtimeNanos() { - return System.nanoTime() - sBootTimeNano; + return System_Delegate.nanoTime() - System_Delegate.bootTime(); } /** @@ -72,7 +70,7 @@ public class SystemClock_Delegate { */ @LayoutlibDelegate /*package*/ static long currentThreadTimeMillis() { - return System.currentTimeMillis(); + return System_Delegate.currentTimeMillis(); } /** @@ -84,7 +82,7 @@ public class SystemClock_Delegate { */ @LayoutlibDelegate /*package*/ static long currentThreadTimeMicro() { - return System.currentTimeMillis() * 1000; + return System_Delegate.currentTimeMillis() * 1000; } /** diff --git a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java index f75ee5030674..01af669e39d3 100644 --- a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java +++ b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java @@ -17,6 +17,8 @@ package android.view; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; +import java.util.concurrent.atomic.AtomicReference; + /** * Delegate used to provide new implementation of a select few methods of {@link Choreographer} * @@ -25,9 +27,41 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate; * */ public class Choreographer_Delegate { + static final AtomicReference<Choreographer> mInstance = new AtomicReference<Choreographer>(); + + @LayoutlibDelegate + public static Choreographer getInstance() { + if (mInstance.get() == null) { + mInstance.compareAndSet(null, Choreographer.getInstance_Original()); + } + + return mInstance.get(); + } @LayoutlibDelegate public static float getRefreshRate() { return 60.f; } + + @LayoutlibDelegate + static void scheduleVsyncLocked(Choreographer thisChoreographer) { + // do nothing + } + + public static void doFrame(long frameTimeNanos) { + Choreographer thisChoreographer = Choreographer.getInstance(); + + thisChoreographer.mLastFrameTimeNanos = frameTimeNanos; + + thisChoreographer.mFrameInfo.markInputHandlingStart(); + thisChoreographer.doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos); + + thisChoreographer.mFrameInfo.markAnimationsStart(); + thisChoreographer.doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); + + thisChoreographer.mFrameInfo.markPerformTraversalsStart(); + thisChoreographer.doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); + + thisChoreographer.doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); + } } diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java index 3c260a83ecde..eea254bb8fcf 100644 --- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java +++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java @@ -298,7 +298,7 @@ public class IWindowManagerImpl implements IWindowManager { @Override public Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, - int maxHeight) throws RemoteException { + int maxHeight, float frameScale) throws RemoteException { // TODO Auto-generated method stub return null; } @@ -541,4 +541,8 @@ public class IWindowManagerImpl implements IWindowManager { @Override public void endProlongedAnimations() { } + + @Override + public void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener) { + } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java index 48ca7d8d5fb6..683c4aabf6d9 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -183,7 +183,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { */ private static LayoutLog sCurrentLog = sDefaultLog; - private static final int LAST_SUPPORTED_FEATURE = Features.RECYCLER_VIEW_ADAPTER; + private static final int LAST_SUPPORTED_FEATURE = Features.CHOREOGRAPHER; @Override public int getApiLevel() { diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java index feb25905390c..2ac212c312c0 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java @@ -23,6 +23,7 @@ import com.android.ide.common.rendering.api.RenderSession; import com.android.ide.common.rendering.api.Result; import com.android.ide.common.rendering.api.ViewInfo; import com.android.layoutlib.bridge.impl.RenderSessionImpl; +import com.android.tools.layoutlib.java.System_Delegate; import android.view.View; import android.view.ViewGroup; @@ -191,6 +192,21 @@ public class BridgeRenderSession extends RenderSession { } @Override + public void setSystemTimeNanos(long nanos) { + System_Delegate.setNanosTime(nanos); + } + + @Override + public void setSystemBootTimeNanos(long nanos) { + System_Delegate.setBootTimeNanos(nanos); + } + + @Override + public void setElapsedFrameTimeNanos(long nanos) { + mSession.setElapsedFrameTimeNanos(nanos); + } + + @Override public void dispose() { } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index 0fcfa7888a21..ff15f3badb38 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -1438,6 +1438,14 @@ public final class BridgeContext extends Context { } @Override + public SharedPreferences getSharedPreferences(File arg0, int arg1) { + if (mSharedPreferences == null) { + mSharedPreferences = new BridgeSharedPreferences(); + } + return mSharedPreferences; + } + + @Override public Drawable getWallpaper() { // pass return null; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java index 8899e53648d2..01c3c500855d 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java @@ -184,8 +184,10 @@ public class BridgeIInputMethodManager implements IInputMethodManager { } @Override - public InputBindResult startInput(IInputMethodClient client, IInputContext inputContext, - EditorInfo attribute, int controlFlags) throws RemoteException { + public InputBindResult startInput( + /* @InputMethodClient.StartInputReason */ int startInputReason, + IInputMethodClient client, IInputContext inputContext, EditorInfo attribute, + int controlFlags) throws RemoteException { // TODO Auto-generated method stub return null; } @@ -226,9 +228,11 @@ public class BridgeIInputMethodManager implements IInputMethodManager { } @Override - public InputBindResult windowGainedFocus(IInputMethodClient client, IBinder windowToken, - int controlFlags, int softInputMode, int windowFlags, EditorInfo attribute, - IInputContext inputContext) throws RemoteException { + public InputBindResult windowGainedFocus( + /* @InputMethodClient.StartInputReason */ int startInputReason, + IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode, + int windowFlags, EditorInfo attribute, IInputContext inputContext) + throws RemoteException { // TODO Auto-generated method stub return null; } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java index 1ec054720547..5c73fb6a6fbe 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java @@ -167,6 +167,11 @@ public final class BridgeWindowSession implements IWindowSession { } @Override + public void cancelDragAndDrop(IBinder dragToken) throws RemoteException { + // pass for now + } + + @Override public void dragRecipientEntered(IWindow window) throws RemoteException { // pass for now } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java index 868c6d328e8e..cdcf0ea1ca76 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java @@ -16,10 +16,13 @@ package com.android.layoutlib.bridge.bars; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.LayoutlibCallback; import com.android.ide.common.rendering.api.RenderResources; import com.android.ide.common.rendering.api.ResourceValue; import com.android.ide.common.rendering.api.SessionParams; import com.android.ide.common.rendering.api.StyleResourceValue; +import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.impl.ResourceHelper; import com.android.resources.ResourceType; @@ -45,6 +48,8 @@ public class AppCompatActionBar extends BridgeActionBar { private Object mWindowDecorActionBar; private static final String WINDOW_ACTION_BAR_CLASS = "android.support.v7.internal.app.WindowDecorActionBar"; + // This is used on v23.1.1 and later. + private static final String WINDOW_ACTION_BAR_CLASS_NEW = "android.support.v7.app.WindowDecorActionBar"; private Class<?> mWindowActionBarClass; /** @@ -70,14 +75,25 @@ public class AppCompatActionBar extends BridgeActionBar { try { Class[] constructorParams = {View.class}; Object[] constructorArgs = {getDecorContent()}; - mWindowDecorActionBar = params.getLayoutlibCallback().loadView(WINDOW_ACTION_BAR_CLASS, - constructorParams, constructorArgs); + LayoutlibCallback callback = params.getLayoutlibCallback(); + + // Check if the old action bar class is present. + String actionBarClass = WINDOW_ACTION_BAR_CLASS; + try { + callback.findClass(actionBarClass); + } catch (ClassNotFoundException expected) { + // Failed to find the old class, use the newer one. + actionBarClass = WINDOW_ACTION_BAR_CLASS_NEW; + } + mWindowDecorActionBar = callback.loadView(actionBarClass, + constructorParams, constructorArgs); mWindowActionBarClass = mWindowDecorActionBar == null ? null : mWindowDecorActionBar.getClass(); setupActionBar(); } catch (Exception e) { - e.printStackTrace(); + Bridge.getLog().warning(LayoutLog.TAG_BROKEN, + "Failed to load AppCompat ActionBar with unknown error.", e); } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java index 42e55e2504de..a6e5fb841bff 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java @@ -33,7 +33,6 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.annotation.NonNull; -import android.annotation.Nullable; import android.content.res.ColorStateList; import android.graphics.Bitmap; import android.graphics.Bitmap_Delegate; @@ -228,18 +227,16 @@ abstract class CustomBar extends LinearLayout { * Find the background color for this bar from the theme attributes. Only relevant to StatusBar * and NavigationBar. * <p/> - * Returns null if not found. + * Returns 0 if not found. * * @param colorAttrName the attribute name for the background color * @param translucentAttrName the attribute name for the translucency property of the bar. * * @throws NumberFormatException if color resolved to an invalid string. */ - @Nullable - protected Integer getBarColor(@NonNull String colorAttrName, - @NonNull String translucentAttrName) { + protected int getBarColor(@NonNull String colorAttrName, @NonNull String translucentAttrName) { if (!Config.isGreaterOrEqual(mSimulatedPlatformVersion, LOLLIPOP)) { - return null; + return 0; } RenderResources renderResources = getContext().getRenderResources(); // First check if the bar is translucent. @@ -254,11 +251,10 @@ abstract class CustomBar extends LinearLayout { if (transparent) { return getColor(renderResources, colorAttrName); } - return null; + return 0; } - @Nullable - private static Integer getColor(RenderResources renderResources, String attr) { + private static int getColor(RenderResources renderResources, String attr) { // From ?attr/foo to @color/bar. This is most likely an ItemResourceValue. ResourceValue resource = renderResources.findItemInTheme(attr, true); // Form @color/bar to the #AARRGGBB @@ -279,7 +275,7 @@ abstract class CustomBar extends LinearLayout { } } } - return null; + return 0; } private ResourceValue getResourceValue(String reference) { diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java index d50ce23a0902..9c89bfe2a28f 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java @@ -65,8 +65,8 @@ public class NavigationBar extends CustomBar { super(context, orientation, getShortestWidth(context)>= 600 ? LAYOUT_600DP_XML : LAYOUT_XML, "navigation_bar.xml", simulatedPlatformVersion); - Integer color = getBarColor(ATTR_COLOR, ATTR_TRANSLUCENT); - setBackgroundColor(color == null ? 0xFF000000 : color); + int color = getBarColor(ATTR_COLOR, ATTR_TRANSLUCENT); + setBackgroundColor(color == 0 ? 0xFF000000 : color); // Cannot access the inside items through id because no R.id values have been // created for them. diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java index 95a5a58c535f..2dc7c65e2085 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java @@ -71,9 +71,8 @@ public class StatusBar extends CustomBar { // FIXME: use FILL_H? setGravity(Gravity.START | Gravity.TOP | Gravity.RIGHT); - Integer color = getBarColor(ATTR_COLOR, ATTR_TRANSLUCENT); - setBackgroundColor( - color == null ? Config.getStatusBarColor(simulatedPlatformVersion) : color); + int color = getBarColor(ATTR_COLOR, ATTR_TRANSLUCENT); + setBackgroundColor(color == 0 ? Config.getStatusBarColor(simulatedPlatformVersion) : color); // Cannot access the inside items through id because no R.id values have been // created for them. diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java index 2a4f58381aee..ec50cfe55651 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java @@ -46,6 +46,7 @@ import com.android.layoutlib.bridge.android.support.DesignLibUtil; import com.android.layoutlib.bridge.impl.binding.FakeAdapter; import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter; import com.android.resources.ResourceType; +import com.android.tools.layoutlib.java.System_Delegate; import com.android.util.Pair; import android.animation.AnimationThread; @@ -62,6 +63,7 @@ import android.graphics.Canvas; import android.preference.Preference_Delegate; import android.view.AttachInfo_Accessor; import android.view.BridgeInflater; +import android.view.Choreographer_Delegate; import android.view.IWindowManager; import android.view.IWindowManagerImpl; import android.view.Surface; @@ -120,6 +122,10 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { private int mMeasuredScreenWidth = -1; private int mMeasuredScreenHeight = -1; private boolean mIsAlphaChannelImage; + /** If >= 0, a frame will be executed */ + private long mElapsedFrameTimeNanos = -1; + /** True if one frame has been already executed to start the animations */ + private boolean mFirstFrameExecuted = false; // information being returned through the API private BufferedImage mImage; @@ -252,6 +258,14 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } /** + * Sets the time for which the next frame will be selected. The time is the elapsed time from + * the current system nanos time. You + */ + public void setElapsedFrameTimeNanos(long nanos) { + mElapsedFrameTimeNanos = nanos; + } + + /** * Renders the scene. * <p> * {@link #acquire(long)} must have been called before this. @@ -421,12 +435,23 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { gc.setComposite(AlphaComposite.Src); gc.setColor(new Color(0x00000000, true)); - gc.fillRect(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight); + gc.fillRect(0, 0, + mMeasuredScreenWidth, mMeasuredScreenHeight); // done gc.dispose(); } + if (mElapsedFrameTimeNanos >= 0) { + long initialTime = System_Delegate.nanoTime(); + if (!mFirstFrameExecuted) { + // The first frame will initialize the animations + Choreographer_Delegate.doFrame(initialTime); + mFirstFrameExecuted = true; + } + // Second frame will move the animations + Choreographer_Delegate.doFrame(initialTime + mElapsedFrameTimeNanos); + } mViewRoot.draw(mCanvas); } diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png Binary files differnew file mode 100644 index 000000000000..9f266278c352 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png Binary files differnew file mode 100644 index 000000000000..89009be843e7 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/indeterminate_progressbar.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/indeterminate_progressbar.xml new file mode 100644 index 000000000000..70d739692e29 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/indeterminate_progressbar.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:padding="16dp" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="fill_parent"> + + <ProgressBar + android:layout_height="fill_parent" + android:layout_width="fill_parent" /> + +</LinearLayout> + diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java index 9ebeebd49c82..2dca07cc4faf 100644 --- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java @@ -48,6 +48,8 @@ import java.io.IOException; import java.net.URL; import java.util.Arrays; import java.util.Comparator; +import java.util.Map; +import java.util.concurrent.TimeUnit; import static org.junit.Assert.fail; @@ -348,16 +350,46 @@ public class Main { renderAndVerify(params, "expand_horz_layout.png"); } + /** Test expand_layout.xml */ + @Test + public void testVectorAnimation() throws ClassNotFoundException { + // Create the layout pull parser. + LayoutPullParser parser = new LayoutPullParser(APP_TEST_RES + "/layout/" + + "indeterminate_progressbar.xml"); + // Create LayoutLibCallback. + LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger()); + layoutLibCallback.initResources(); + + SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5, + layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false, + RenderingMode.V_SCROLL, 22); + + renderAndVerify(params, "animated_vector.png", TimeUnit.SECONDS.toNanos(2)); + + parser = new LayoutPullParser(APP_TEST_RES + "/layout/" + + "indeterminate_progressbar.xml"); + params = getSessionParams(parser, ConfigGenerator.NEXUS_5, + layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false, + RenderingMode.V_SCROLL, 22); + renderAndVerify(params, "animated_vector_1.png", TimeUnit.SECONDS.toNanos(3)); + } + /** * Create a new rendering session and test that rendering given layout on nexus 5 * doesn't throw any exceptions and matches the provided image. + * <p/>If frameTimeNanos is >= 0 a frame will be executed during the rendering. The time + * indicates how far in the future is. */ - private void renderAndVerify(SessionParams params, String goldenFileName) + private void renderAndVerify(SessionParams params, String goldenFileName, long frameTimeNanos) throws ClassNotFoundException { // TODO: Set up action bar handler properly to test menu rendering. // Create session params. RenderSession session = sBridge.createSession(params); + if (frameTimeNanos != -1) { + session.setElapsedFrameTimeNanos(frameTimeNanos); + } + if (!session.getResult().isSuccess()) { getLogger().error(session.getResult().getException(), session.getResult().getErrorMessage()); @@ -380,6 +412,15 @@ public class Main { * Create a new rendering session and test that rendering given layout on nexus 5 * doesn't throw any exceptions and matches the provided image. */ + private void renderAndVerify(SessionParams params, String goldenFileName) + throws ClassNotFoundException { + renderAndVerify(params, goldenFileName, -1); + } + + /** + * Create a new rendering session and test that rendering given layout on nexus 5 + * doesn't throw any exceptions and matches the provided image. + */ private void renderAndVerify(String layoutFileName, String goldenFileName) throws ClassNotFoundException { // Create the layout pull parser. diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java index f6c2626e4271..8f0ad01c6dc3 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java @@ -77,6 +77,8 @@ public class AsmGenerator { /** Methods to inject. FQCN of class in which method should be injected => runnable that does * the injection. */ private final Map<String, ICreateInfo.InjectMethodRunnable> mInjectedMethodsMap; + /** A map { FQCN => set { field names } } which should be promoted to public visibility */ + private final Map<String, Set<String>> mPromotedFields; /** * Creates a new generator that can generate the output JAR with the stubbed classes. @@ -109,20 +111,8 @@ public class AsmGenerator { // Create the map/set of methods to change to delegates mDelegateMethods = new HashMap<String, Set<String>>(); - for (String signature : createInfo.getDelegateMethods()) { - int pos = signature.indexOf('#'); - if (pos <= 0 || pos >= signature.length() - 1) { - continue; - } - String className = binaryToInternalClassName(signature.substring(0, pos)); - String methodName = signature.substring(pos + 1); - Set<String> methods = mDelegateMethods.get(className); - if (methods == null) { - methods = new HashSet<String>(); - mDelegateMethods.put(className, methods); - } - methods.add(methodName); - } + addToMap(createInfo.getDelegateMethods(), mDelegateMethods); + for (String className : createInfo.getDelegateClassNatives()) { className = binaryToInternalClassName(className); Set<String> methods = mDelegateMethods.get(className); @@ -187,10 +177,34 @@ public class AsmGenerator { returnTypes.add(binaryToInternalClassName(className)); } + mPromotedFields = new HashMap<String, Set<String>>(); + addToMap(createInfo.getPromotedFields(), mPromotedFields); + mInjectedMethodsMap = createInfo.getInjectedMethodsMap(); } /** + * For each value in the array, split the value on '#' and add the parts to the map as key + * and value. + */ + private void addToMap(String[] entries, Map<String, Set<String>> map) { + for (String entry : entries) { + int pos = entry.indexOf('#'); + if (pos <= 0 || pos >= entry.length() - 1) { + return; + } + String className = binaryToInternalClassName(entry.substring(0, pos)); + String methodOrFieldName = entry.substring(pos + 1); + Set<String> set = map.get(className); + if (set == null) { + set = new HashSet<String>(); + map.put(className, set); + } + set.add(methodOrFieldName); + } + } + + /** * Returns the list of classes that have not been renamed yet. * <p/> * The names are "internal class names" rather than FQCN, i.e. they use "/" instead "." @@ -380,6 +394,10 @@ public class AsmGenerator { } } + Set<String> promoteFields = mPromotedFields.get(className); + if (promoteFields != null && !promoteFields.isEmpty()) { + cv = new PromoteFieldClassAdapter(cv, promoteFields); + } cr.accept(cv, 0); return cw.toByteArray(); } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java index e480eadc0cf6..b571c5a486e9 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -120,6 +120,11 @@ public final class CreateInfo implements ICreateInfo { } @Override + public String[] getPromotedFields() { + return PROMOTED_FIELDS; + } + + @Override public Map<String, InjectMethodRunnable> getInjectedMethodsMap() { return INJECTED_METHODS; } @@ -169,7 +174,9 @@ public final class CreateInfo implements ICreateInfo { "android.text.format.DateFormat#is24HourFormat", "android.text.Hyphenator#getSystemHyphenatorLocation", "android.util.Xml#newPullParser", + "android.view.Choreographer#getInstance", "android.view.Choreographer#getRefreshRate", + "android.view.Choreographer#scheduleVsyncLocked", "android.view.Display#updateDisplayInfoLocked", "android.view.Display#getWindowManager", "android.view.LayoutInflater#rInflate", @@ -290,6 +297,10 @@ public final class CreateInfo implements ICreateInfo { "org.kxml2.io.KXmlParser" }; + private final static String[] PROMOTED_FIELDS = new String[] { + "android.view.Choreographer#mLastFrameTimeNanos" + }; + /** * List of classes for which the methods returning them should be deleted. * The array contains a list of null terminated section starting with the name of the class diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java index ae4a57d8eea4..7ef75662aad4 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java @@ -17,6 +17,7 @@ package com.android.tools.layoutlib.create; import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @@ -40,6 +41,7 @@ public class DelegateClassAdapter extends ClassVisitor { private final String mClassName; private final Set<String> mDelegateMethods; private final Log mLog; + private boolean mIsStaticInnerClass; /** * Creates a new {@link DelegateClassAdapter} that can transform some methods @@ -62,16 +64,30 @@ public class DelegateClassAdapter extends ClassVisitor { mLog = log; mClassName = className; mDelegateMethods = delegateMethods; + // If this is an inner class, by default, we assume it's static. If it's not we will detect + // by looking at the fields (see visitField) + mIsStaticInnerClass = className.contains("$"); } //---------------------------------- // Methods from the ClassAdapter @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, + Object value) { + if (mIsStaticInnerClass && "this$0".equals(name)) { + // Having a "this$0" field, proves that this class is not a static inner class. + mIsStaticInnerClass = false; + } + + return super.visitField(access, name, desc, signature, value); + } + + @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - boolean isStatic = (access & Opcodes.ACC_STATIC) != 0; + boolean isStaticMethod = (access & Opcodes.ACC_STATIC) != 0; boolean isNative = (access & Opcodes.ACC_NATIVE) != 0; boolean useDelegate = (isNative && mDelegateMethods.contains(ALL_NATIVES)) || @@ -96,7 +112,8 @@ public class DelegateClassAdapter extends ClassVisitor { MethodVisitor mwDelegate = super.visitMethod(access, name, desc, signature, exceptions); DelegateMethodAdapter a = new DelegateMethodAdapter( - mLog, null, mwDelegate, mClassName, name, desc, isStatic); + mLog, null, mwDelegate, mClassName, name, desc, isStaticMethod, + mIsStaticInnerClass); // A native has no code to visit, so we need to generate it directly. a.generateDelegateCode(); @@ -120,6 +137,7 @@ public class DelegateClassAdapter extends ClassVisitor { desc, signature, exceptions); return new DelegateMethodAdapter( - mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStatic); + mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStaticMethod, + mIsStaticInnerClass); } } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java index 12690db547a9..cca9e574b7ea 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java @@ -85,6 +85,8 @@ class DelegateMethodAdapter extends MethodVisitor { private String mDesc; /** True if the original method is static. */ private final boolean mIsStatic; + /** True if the method is contained in a static inner class */ + private final boolean mIsStaticInnerClass; /** The internal class name (e.g. <code>com/android/SomeClass$InnerClass</code>.) */ private final String mClassName; /** The method name. */ @@ -120,7 +122,8 @@ class DelegateMethodAdapter extends MethodVisitor { String className, String methodName, String desc, - boolean isStatic) { + boolean isStatic, + boolean isStaticClass) { super(Opcodes.ASM4); mLog = log; mOrgWriter = mvOriginal; @@ -129,6 +132,7 @@ class DelegateMethodAdapter extends MethodVisitor { mMethodName = methodName; mDesc = desc; mIsStatic = isStatic; + mIsStaticInnerClass = isStaticClass; } /** @@ -206,7 +210,7 @@ class DelegateMethodAdapter extends MethodVisitor { // by the 'this' of any outer class, if any. if (!mIsStatic) { - if (outerType != null) { + if (outerType != null && !mIsStaticInnerClass) { // The first-level inner class has a package-protected member called 'this$0' // that points to the outer class. diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java index 54b1fe628769..6c62423a2a89 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java @@ -78,6 +78,13 @@ public interface ICreateInfo { Set<String> getExcludedClasses(); /** + * Returns a list of fields which should be promoted to public visibility. The array values + * are in the form of the binary FQCN of the class containing the field and the field name + * separated by a '#'. + */ + String[] getPromotedFields(); + + /** * Returns a map from binary FQCN className to {@link InjectMethodRunnable} which will be * called to inject methods into a class. * Can be empty but must not be null. diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java new file mode 100644 index 000000000000..e4b70da2504f --- /dev/null +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java @@ -0,0 +1,52 @@ +/* + * 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. + */ + +package com.android.tools.layoutlib.create; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; + +import java.util.Set; + +import static org.objectweb.asm.Opcodes.ACC_PRIVATE; +import static org.objectweb.asm.Opcodes.ACC_PROTECTED; +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import static org.objectweb.asm.Opcodes.ASM4; + +/** + * Promotes given fields to public visibility. + */ +public class PromoteFieldClassAdapter extends ClassVisitor { + + private final Set<String> mFieldNames; + private static final int ACC_NOT_PUBLIC = ~(ACC_PRIVATE | ACC_PROTECTED); + + public PromoteFieldClassAdapter(ClassVisitor cv, Set<String> fieldNames) { + super(ASM4, cv); + mFieldNames = fieldNames; + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, + Object value) { + if (mFieldNames.contains(name)) { + if ((access & ACC_PUBLIC) == 0) { + access = (access & ACC_NOT_PUBLIC) | ACC_PUBLIC; + } + } + return super.visitField(access, name, desc, signature, value); + } +} diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java index 0b85c48b4e68..5e47261ea89c 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java @@ -134,7 +134,33 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor { } }); - // Case 5: java.util.LinkedHashMap.eldest() + // Case 5: java.lang.System time calls + METHOD_REPLACERS.add(new MethodReplacer() { + @Override + public boolean isNeeded(String owner, String name, String desc, String sourceClass) { + return JAVA_LANG_SYSTEM.equals(owner) && name.equals("nanoTime"); + } + + @Override + public void replace(MethodInformation mi) { + mi.name = "nanoTime"; + mi.owner = Type.getInternalName(System_Delegate.class); + } + }); + METHOD_REPLACERS.add(new MethodReplacer() { + @Override + public boolean isNeeded(String owner, String name, String desc, String sourceClass) { + return JAVA_LANG_SYSTEM.equals(owner) && name.equals("currentTimeMillis"); + } + + @Override + public void replace(MethodInformation mi) { + mi.name = "currentTimeMillis"; + mi.owner = Type.getInternalName(System_Delegate.class); + } + }); + + // Case 6: java.util.LinkedHashMap.eldest() METHOD_REPLACERS.add(new MethodReplacer() { private final String VOID_TO_MAP_ENTRY = @@ -157,7 +183,7 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor { } }); - // Case 6: android.content.Context.getClassLoader() in LayoutInflater + // Case 7: android.content.Context.getClassLoader() in LayoutInflater METHOD_REPLACERS.add(new MethodReplacer() { // When LayoutInflater asks for a class loader, we must return the class loader that // cannot return app's custom views/classes. This is so that in case of any failure diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java index 613c8d96b862..be937445c33d 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java @@ -18,12 +18,22 @@ package com.android.tools.layoutlib.java; import com.android.tools.layoutlib.create.ReplaceMethodCallsAdapter; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + /** * Provides dummy implementation of methods that don't exist on the host VM. + * This also providers a time control that allows to set a specific system time. * * @see ReplaceMethodCallsAdapter */ +@SuppressWarnings("unused") public class System_Delegate { + // Current system time + private static AtomicLong mNanosTime = new AtomicLong(System.nanoTime()); + // Time that the system booted up in nanos + private static AtomicLong mBootNanosTime = new AtomicLong(System.nanoTime()); + public static void log(String message) { // ignore. } @@ -31,4 +41,28 @@ public class System_Delegate { public static void log(String message, Throwable th) { // ignore. } + + public static void setNanosTime(long nanos) { + mNanosTime.set(nanos); + } + + public static void setBootTimeNanos(long nanos) { + mBootNanosTime.set(nanos); + } + + public static long nanoTime() { + return mNanosTime.get(); + } + + public static long currentTimeMillis() { + return TimeUnit.NANOSECONDS.toMillis(mNanosTime.get()); + } + + public static long bootTime() { + return mBootNanosTime.get(); + } + + public static long bootTimeMillis() { + return TimeUnit.NANOSECONDS.toMillis(mBootNanosTime.get()); + } } diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java index 2c21470d6a2f..8a2235b8526c 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java @@ -138,6 +138,11 @@ public class AsmGeneratorTest { } @Override + public String[] getPromotedFields() { + return new String[0]; + } + + @Override public Map<String, InjectMethodRunnable> getInjectedMethodsMap() { return new HashMap<String, InjectMethodRunnable>(0); } @@ -213,6 +218,11 @@ public class AsmGeneratorTest { } @Override + public String[] getPromotedFields() { + return new String[0]; + } + + @Override public Map<String, InjectMethodRunnable> getInjectedMethodsMap() { return new HashMap<String, InjectMethodRunnable>(0); } @@ -296,6 +306,11 @@ public class AsmGeneratorTest { } @Override + public String[] getPromotedFields() { + return new String[0]; + } + + @Override public Map<String, InjectMethodRunnable> getInjectedMethodsMap() { return new HashMap<String, InjectMethodRunnable>(0); } @@ -374,6 +389,11 @@ public class AsmGeneratorTest { } @Override + public String[] getPromotedFields() { + return new String[0]; + } + + @Override public Map<String, InjectMethodRunnable> getInjectedMethodsMap() { HashMap<String, InjectMethodRunnable> map = new HashMap<String, InjectMethodRunnable>(1); diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java index 648cea430de2..e37a09b348b8 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java @@ -27,6 +27,7 @@ import static org.junit.Assert.fail; import com.android.tools.layoutlib.create.dataclass.ClassWithNative; import com.android.tools.layoutlib.create.dataclass.OuterClass; import com.android.tools.layoutlib.create.dataclass.OuterClass.InnerClass; +import com.android.tools.layoutlib.create.dataclass.OuterClass.StaticInnerClass; import org.junit.Before; import org.junit.Test; @@ -56,6 +57,8 @@ public class DelegateClassAdapterTest { private static final String OUTER_CLASS_NAME = OuterClass.class.getCanonicalName(); private static final String INNER_CLASS_NAME = OuterClass.class.getCanonicalName() + "$" + InnerClass.class.getSimpleName(); + private static final String STATIC_INNER_CLASS_NAME = + OuterClass.class.getCanonicalName() + "$" + StaticInnerClass.class.getSimpleName(); @Before public void setUp() throws Exception { @@ -294,6 +297,61 @@ public class DelegateClassAdapterTest { } } + @Test + public void testDelegateStaticInner() throws Throwable { + // We'll delegate the "get" method of both the inner and outer class. + HashSet<String> delegateMethods = new HashSet<String>(); + delegateMethods.add("get"); + + // Generate the delegate for the outer class. + ClassWriter cwOuter = new ClassWriter(0 /*flags*/); + String outerClassName = OUTER_CLASS_NAME.replace('.', '/'); + DelegateClassAdapter cvOuter = new DelegateClassAdapter( + mLog, cwOuter, outerClassName, delegateMethods); + ClassReader cr = new ClassReader(OUTER_CLASS_NAME); + cr.accept(cvOuter, 0 /* flags */); + + // Generate the delegate for the static inner class. + ClassWriter cwInner = new ClassWriter(0 /*flags*/); + String innerClassName = STATIC_INNER_CLASS_NAME.replace('.', '/'); + DelegateClassAdapter cvInner = new DelegateClassAdapter( + mLog, cwInner, innerClassName, delegateMethods); + cr = new ClassReader(STATIC_INNER_CLASS_NAME); + cr.accept(cvInner, 0 /* flags */); + + // Load the generated classes in a different class loader and try them + ClassLoader2 cl2 = null; + try { + cl2 = new ClassLoader2() { + @Override + public void testModifiedInstance() throws Exception { + + // Check the outer class + Class<?> outerClazz2 = loadClass(OUTER_CLASS_NAME); + Object o2 = outerClazz2.newInstance(); + assertNotNull(o2); + + // Check the inner class. Since it's not a static inner class, we need + // to use the hidden constructor that takes the outer class as first parameter. + Class<?> innerClazz2 = loadClass(STATIC_INNER_CLASS_NAME); + Constructor<?> innerCons = innerClazz2.getConstructor(); + Object i2 = innerCons.newInstance(); + assertNotNull(i2); + + // The original StaticInner.get returns 100+10+20, + // but the delegate makes it return 6+10+20 + assertEquals(6+10+20, callGet(i2, 10, 20)); + assertEquals(100+10+20, callGet_Original(i2, 10, 20)); + } + }; + cl2.add(OUTER_CLASS_NAME, cwOuter.toByteArray()); + cl2.add(STATIC_INNER_CLASS_NAME, cwInner.toByteArray()); + cl2.testModifiedInstance(); + } catch (Throwable t) { + throw dumpGeneratedClass(t, cl2); + } + } + //------- /** diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java index f083e76d995c..6dfb81662e40 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java @@ -45,6 +45,16 @@ public class OuterClass { } } + public static class StaticInnerClass { + public StaticInnerClass() { + } + + // StaticInnerClass.get returns 100 + a + b + public int get(int a, long b) { + return 100 + a + (int) b; + } + } + @SuppressWarnings("unused") private String privateMethod() { return "outerPrivateMethod"; diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java new file mode 100644 index 000000000000..a29439ee3fee --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java @@ -0,0 +1,30 @@ +/* + * 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. + */ + +package com.android.tools.layoutlib.create.dataclass; + +import com.android.tools.layoutlib.create.DelegateClassAdapterTest; +import com.android.tools.layoutlib.create.dataclass.OuterClass.StaticInnerClass; + +/** + * Used by {@link DelegateClassAdapterTest}. + */ +public class OuterClass_StaticInnerClass_Delegate { + // The delegate override of Inner.get return 6 + a + b + public static int get(StaticInnerClass inner, int a, long b) { + return 6 + a + (int) b; + } +} |