AAPT2
First checking of AAPT2. The individual phases of AAPT2 work, but there
are some missing pieces.
For early testing we are missing:
- Need to properly mark file references and include them in package
- Need to package into zip
Final AAPT for apps we are missing:
- Need to crush PNGs
- Need to parse 9-patches
- Need to validate all of AndroidManifest.xml
- Need to write align method to align resource tables for splits.
Final AAPT for apps + system we are missing:
- Need to handle overlays
- Need to store comments for R file
- Need to handle --shared-lib (dynamic references too).
New AAPT features coming:
- Need to import compiled libraries
- Name mangling
- R file generation for library code
Change-Id: I95f8a63581b81a1f424ae6fb2c373c883b72c18d
diff --git a/tools/aapt2/XmlFlattener.cpp b/tools/aapt2/XmlFlattener.cpp
new file mode 100644
index 0000000..b6ca6d5
--- /dev/null
+++ b/tools/aapt2/XmlFlattener.cpp
@@ -0,0 +1,437 @@
+/*
+ * 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 "BigBuffer.h"
+#include "Logger.h"
+#include "Maybe.h"
+#include "Resolver.h"
+#include "Resource.h"
+#include "ResourceParser.h"
+#include "ResourceValues.h"
+#include "SdkConstants.h"
+#include "Source.h"
+#include "StringPool.h"
+#include "Util.h"
+#include "XmlFlattener.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <limits>
+#include <map>
+#include <string>
+#include <vector>
+
+namespace aapt {
+
+struct AttributeValueFlattener : ValueVisitor {
+ struct Args : ValueVisitorArgs {
+ Args(std::shared_ptr<Resolver> r, SourceLogger& s, android::Res_value& oV,
+ std::shared_ptr<XmlPullParser> p, bool& e, StringPool::Ref& rV,
+ std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>& sR) :
+ resolver(r), logger(s), outValue(oV), parser(p), error(e), rawValue(rV),
+ stringRefs(sR) {
+ }
+
+ std::shared_ptr<Resolver> resolver;
+ SourceLogger& logger;
+ android::Res_value& outValue;
+ std::shared_ptr<XmlPullParser> parser;
+ bool& error;
+ StringPool::Ref& rawValue;
+ std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>& stringRefs;
+ };
+
+ void visit(Reference& reference, ValueVisitorArgs& a) override {
+ Args& args = static_cast<Args&>(a);
+
+ Maybe<ResourceId> result = args.resolver->findId(reference.name);
+ if (!result || !result.value().isValid()) {
+ args.logger.error(args.parser->getLineNumber())
+ << "unresolved reference '"
+ << reference.name
+ << "'."
+ << std::endl;
+ args.error = true;
+ } else {
+ reference.id = result.value();
+ reference.flatten(args.outValue);
+ }
+ }
+
+ void visit(String& string, ValueVisitorArgs& a) override {
+ Args& args = static_cast<Args&>(a);
+
+ args.outValue.dataType = android::Res_value::TYPE_STRING;
+ args.stringRefs.emplace_back(args.rawValue,
+ reinterpret_cast<android::ResStringPool_ref*>(&args.outValue.data));
+ }
+
+ void visitItem(Item& item, ValueVisitorArgs& a) override {
+ Args& args = static_cast<Args&>(a);
+ item.flatten(args.outValue);
+ }
+};
+
+struct XmlAttribute {
+ uint32_t resourceId;
+ const XmlPullParser::Attribute* xmlAttr;
+ const Attribute* attr;
+ StringPool::Ref nameRef;
+};
+
+static bool lessAttributeId(const XmlAttribute& a, uint32_t id) {
+ return a.resourceId < id;
+}
+
+XmlFlattener::XmlFlattener(const std::shared_ptr<Resolver>& resolver) : mResolver(resolver) {
+}
+
+/**
+ * Reads events from the parser and writes to a BigBuffer. The binary XML file
+ * expects the StringPool to appear first, but we haven't collected the strings yet. We
+ * write to a temporary BigBuffer while parsing the input, adding strings we encounter
+ * to the StringPool. At the end, we write the StringPool to the given BigBuffer and
+ * then move the data from the temporary BigBuffer into the given one. This incurs no
+ * copies as the given BigBuffer simply takes ownership of the data.
+ */
+Maybe<size_t> XmlFlattener::flatten(const Source& source,
+ const std::shared_ptr<XmlPullParser>& parser,
+ BigBuffer* outBuffer, Options options) {
+ SourceLogger logger(source);
+ StringPool pool;
+ bool error = false;
+
+ size_t smallestStrippedAttributeSdk = std::numeric_limits<size_t>::max();
+
+ // Attribute names are stored without packages, but we use
+ // their StringPool index to lookup their resource IDs.
+ // This will cause collisions, so we can't dedupe
+ // attribute names from different packages. We use separate
+ // pools that we later combine.
+ std::map<std::u16string, StringPool> packagePools;
+
+ // Attribute resource IDs are stored in the same order
+ // as the attribute names appear in the StringPool.
+ // Since the StringPool contains more than just attribute
+ // names, to maintain a tight packing of resource IDs,
+ // we must ensure that attribute names appear first
+ // in our StringPool. For this, we assign a low priority
+ // (0xffffffff) to non-attribute strings. Attribute
+ // names will be stored along with a priority equal
+ // to their resource ID so that they are ordered.
+ StringPool::Context lowPriority { 0xffffffffu };
+
+ // Once we sort the StringPool, we can assign the updated indices
+ // to the correct data locations.
+ std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>> stringRefs;
+
+ // Since we don't know the size of the final StringPool, we write to this
+ // temporary BigBuffer, which we will append to outBuffer later.
+ BigBuffer out(1024);
+ while (XmlPullParser::isGoodEvent(parser->next())) {
+ XmlPullParser::Event event = parser->getEvent();
+ switch (event) {
+ case XmlPullParser::Event::kStartNamespace:
+ case XmlPullParser::Event::kEndNamespace: {
+ const size_t startIndex = out.size();
+ android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
+ if (event == XmlPullParser::Event::kStartNamespace) {
+ node->header.type = android::RES_XML_START_NAMESPACE_TYPE;
+ } else {
+ node->header.type = android::RES_XML_END_NAMESPACE_TYPE;
+ }
+
+ node->header.headerSize = sizeof(*node);
+ node->lineNumber = parser->getLineNumber();
+ node->comment.index = -1;
+
+ android::ResXMLTree_namespaceExt* ns =
+ out.nextBlock<android::ResXMLTree_namespaceExt>();
+ stringRefs.emplace_back(
+ pool.makeRef(parser->getNamespacePrefix(), lowPriority), &ns->prefix);
+ stringRefs.emplace_back(
+ pool.makeRef(parser->getNamespaceUri(), lowPriority), &ns->uri);
+
+ out.align4();
+ node->header.size = out.size() - startIndex;
+ break;
+ }
+
+ case XmlPullParser::Event::kStartElement: {
+ const size_t startIndex = out.size();
+ android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
+ node->header.type = android::RES_XML_START_ELEMENT_TYPE;
+ node->header.headerSize = sizeof(*node);
+ node->lineNumber = parser->getLineNumber();
+ node->comment.index = -1;
+
+ android::ResXMLTree_attrExt* elem = out.nextBlock<android::ResXMLTree_attrExt>();
+ stringRefs.emplace_back(
+ pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns);
+ stringRefs.emplace_back(
+ pool.makeRef(parser->getElementName(), lowPriority), &elem->name);
+ elem->attributeStart = sizeof(*elem);
+ elem->attributeSize = sizeof(android::ResXMLTree_attribute);
+
+ // The resource system expects attributes to be sorted by resource ID.
+ std::vector<XmlAttribute> sortedAttributes;
+ uint32_t nextAttributeId = 0;
+ const auto endAttrIter = parser->endAttributes();
+ for (auto attrIter = parser->beginAttributes();
+ attrIter != endAttrIter;
+ ++attrIter) {
+ uint32_t id;
+ StringPool::Ref nameRef;
+ const Attribute* attr = nullptr;
+ if (attrIter->namespaceUri.empty()) {
+ // Attributes that have no resource ID (because they don't belong to a
+ // package) should appear after those that do have resource IDs. Assign
+ // them some/ integer value that will appear after.
+ id = 0x80000000u | nextAttributeId++;
+ nameRef = pool.makeRef(attrIter->name, StringPool::Context{ id });
+ } else {
+ StringPiece16 package;
+ if (attrIter->namespaceUri == u"http://schemas.android.com/apk/res-auto") {
+ package = mResolver->getDefaultPackage();
+ } else {
+ // TODO(adamlesinski): Extract package from namespace.
+ // The package name appears like so:
+ // http://schemas.android.com/apk/res/<package name>
+ package = u"android";
+ }
+
+ // Find the Attribute object via our Resolver.
+ ResourceName attrName = {
+ package.toString(), ResourceType::kAttr, attrIter->name };
+ Maybe<Resolver::Entry> result = mResolver->findAttribute(attrName);
+ if (!result || !result.value().id.isValid()) {
+ logger.error(parser->getLineNumber())
+ << "unresolved attribute '"
+ << attrName
+ << "'."
+ << std::endl;
+ error = true;
+ continue;
+ }
+
+ if (!result.value().attr) {
+ logger.error(parser->getLineNumber())
+ << "not a valid attribute '"
+ << attrName
+ << "'."
+ << std::endl;
+ error = true;
+ continue;
+ }
+
+ if (options.maxSdkAttribute && package == u"android") {
+ size_t sdkVersion = findAttributeSdkLevel(attrIter->name);
+ if (sdkVersion > options.maxSdkAttribute.value()) {
+ // We will silently omit this attribute
+ smallestStrippedAttributeSdk =
+ std::min(smallestStrippedAttributeSdk, sdkVersion);
+ continue;
+ }
+ }
+
+ id = result.value().id.id;
+ attr = result.value().attr;
+
+ // Put the attribute name into a package specific pool, since we don't
+ // want to collapse names from different packages.
+ nameRef = packagePools[package.toString()].makeRef(
+ attrIter->name, StringPool::Context{ id });
+ }
+
+ // Insert the attribute into the sorted vector.
+ auto iter = std::lower_bound(sortedAttributes.begin(), sortedAttributes.end(),
+ id, lessAttributeId);
+ sortedAttributes.insert(iter, XmlAttribute{ id, &*attrIter, attr, nameRef });
+ }
+
+ if (error) {
+ break;
+ }
+
+ // Now that we have filtered out some attributes, get the final count.
+ elem->attributeCount = sortedAttributes.size();
+
+ // Flatten the sorted attributes.
+ for (auto entry : sortedAttributes) {
+ android::ResXMLTree_attribute* attr =
+ out.nextBlock<android::ResXMLTree_attribute>();
+ stringRefs.emplace_back(
+ pool.makeRef(entry.xmlAttr->namespaceUri, lowPriority), &attr->ns);
+ StringPool::Ref rawValueRef = pool.makeRef(entry.xmlAttr->value, lowPriority);
+ stringRefs.emplace_back(rawValueRef, &attr->rawValue);
+ stringRefs.emplace_back(entry.nameRef, &attr->name);
+
+ if (entry.attr) {
+ std::unique_ptr<Item> value = ResourceParser::parseItemForAttribute(
+ entry.xmlAttr->value, *entry.attr, mResolver->getDefaultPackage());
+ if (value) {
+ AttributeValueFlattener flattener;
+ value->accept(flattener, AttributeValueFlattener::Args{
+ mResolver,
+ logger,
+ attr->typedValue,
+ parser,
+ error,
+ rawValueRef,
+ stringRefs
+ });
+ } else if (!(entry.attr->typeMask & android::ResTable_map::TYPE_STRING)) {
+ logger.error(parser->getLineNumber())
+ << "'"
+ << *rawValueRef
+ << "' is not compatible with attribute "
+ << *entry.attr
+ << "."
+ << std::endl;
+ error = true;
+ } else {
+ attr->typedValue.dataType = android::Res_value::TYPE_STRING;
+ stringRefs.emplace_back(rawValueRef,
+ reinterpret_cast<android::ResStringPool_ref*>(
+ &attr->typedValue.data));
+ }
+ } else {
+ attr->typedValue.dataType = android::Res_value::TYPE_STRING;
+ stringRefs.emplace_back(rawValueRef,
+ reinterpret_cast<android::ResStringPool_ref*>(
+ &attr->typedValue.data));
+ }
+ attr->typedValue.size = sizeof(attr->typedValue);
+ }
+
+ out.align4();
+ node->header.size = out.size() - startIndex;
+ break;
+ }
+
+ case XmlPullParser::Event::kEndElement: {
+ const size_t startIndex = out.size();
+ android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
+ node->header.type = android::RES_XML_END_ELEMENT_TYPE;
+ node->header.headerSize = sizeof(*node);
+ node->lineNumber = parser->getLineNumber();
+ node->comment.index = -1;
+
+ android::ResXMLTree_endElementExt* elem =
+ out.nextBlock<android::ResXMLTree_endElementExt>();
+ stringRefs.emplace_back(
+ pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns);
+ stringRefs.emplace_back(
+ pool.makeRef(parser->getElementName(), lowPriority), &elem->name);
+
+ out.align4();
+ node->header.size = out.size() - startIndex;
+ break;
+ }
+
+ case XmlPullParser::Event::kText: {
+ StringPiece16 text = util::trimWhitespace(parser->getText());
+ if (text.empty()) {
+ break;
+ }
+
+ const size_t startIndex = out.size();
+ android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
+ node->header.type = android::RES_XML_CDATA_TYPE;
+ node->header.headerSize = sizeof(*node);
+ node->lineNumber = parser->getLineNumber();
+ node->comment.index = -1;
+
+ android::ResXMLTree_cdataExt* elem = out.nextBlock<android::ResXMLTree_cdataExt>();
+ stringRefs.emplace_back(pool.makeRef(text, lowPriority), &elem->data);
+
+ out.align4();
+ node->header.size = out.size() - startIndex;
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ }
+ out.align4();
+
+ if (error) {
+ return {};
+ }
+
+ if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
+ logger.error(parser->getLineNumber())
+ << parser->getLastError()
+ << std::endl;
+ return {};
+ }
+
+ // Merge the package pools into the main pool.
+ for (auto& packagePoolEntry : packagePools) {
+ pool.merge(std::move(packagePoolEntry.second));
+ }
+
+ // Sort so that attribute resource IDs show up first.
+ pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
+ return a.context.priority < b.context.priority;
+ });
+
+ // Now we flatten the string pool references into the correct places.
+ for (const auto& refEntry : stringRefs) {
+ refEntry.second->index = refEntry.first.getIndex();
+ }
+
+ // Write the XML header.
+ const size_t beforeXmlTreeIndex = outBuffer->size();
+ android::ResXMLTree_header* header = outBuffer->nextBlock<android::ResXMLTree_header>();
+ header->header.type = android::RES_XML_TYPE;
+ header->header.headerSize = sizeof(*header);
+
+ // Write the array of resource IDs, indexed by StringPool order.
+ const size_t beforeResIdMapIndex = outBuffer->size();
+ android::ResChunk_header* resIdMapChunk = outBuffer->nextBlock<android::ResChunk_header>();
+ resIdMapChunk->type = android::RES_XML_RESOURCE_MAP_TYPE;
+ resIdMapChunk->headerSize = sizeof(*resIdMapChunk);
+ for (const auto& str : pool) {
+ ResourceId id { str->context.priority };
+ if (!id.isValid()) {
+ // When we see the first non-resource ID,
+ // we're done.
+ break;
+ }
+
+ uint32_t* flatId = outBuffer->nextBlock<uint32_t>();
+ *flatId = id.id;
+ }
+ resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex;
+
+ // Flatten the StringPool.
+ StringPool::flattenUtf8(outBuffer, pool);
+
+ // Move the temporary BigBuffer into outBuffer->
+ outBuffer->appendBuffer(std::move(out));
+
+ header->header.size = outBuffer->size() - beforeXmlTreeIndex;
+
+ if (smallestStrippedAttributeSdk == std::numeric_limits<size_t>::max()) {
+ // Nothing was stripped
+ return 0u;
+ }
+ return smallestStrippedAttributeSdk;
+}
+
+} // namespace aapt