We modify the XML of layouts and AndroidManifest enough that it warrants we operate on the tree in memory. These files are never very large so this should be fine. Change-Id: I5d597abdb3fca2a203cf7c0b40fcd926aecb3137
575 lines
22 KiB
C++
575 lines
22 KiB
C++
/*
|
|
* 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 {
|
|
namespace xml {
|
|
|
|
constexpr uint32_t kLowPriority = 0xffffffffu;
|
|
|
|
// A vector that maps String refs to their final destination in the out buffer.
|
|
using FlatStringRefList = std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>;
|
|
|
|
struct XmlFlattener : public Visitor {
|
|
XmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs,
|
|
const std::u16string& defaultPackage) :
|
|
mOut(outBuffer), mPool(pool), mStringRefs(stringRefs),
|
|
mDefaultPackage(defaultPackage) {
|
|
}
|
|
|
|
// No copying.
|
|
XmlFlattener(const XmlFlattener&) = delete;
|
|
XmlFlattener& operator=(const XmlFlattener&) = delete;
|
|
|
|
void writeNamespace(Namespace* node, uint16_t type) {
|
|
const size_t startIndex = mOut->size();
|
|
android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
|
|
android::ResXMLTree_namespaceExt* flatNs =
|
|
mOut->nextBlock<android::ResXMLTree_namespaceExt>();
|
|
mOut->align4();
|
|
|
|
flatNode->header = { type, sizeof(*flatNode), (uint32_t)(mOut->size() - startIndex) };
|
|
flatNode->lineNumber = node->lineNumber;
|
|
flatNode->comment.index = -1;
|
|
addString(node->namespacePrefix, kLowPriority, &flatNs->prefix);
|
|
addString(node->namespaceUri, kLowPriority, &flatNs->uri);
|
|
}
|
|
|
|
virtual void visit(Namespace* node) override {
|
|
// Extract the package/prefix from this namespace node.
|
|
Maybe<std::u16string> package = util::extractPackageFromNamespace(node->namespaceUri);
|
|
if (package) {
|
|
mPackageAliases.emplace_back(
|
|
node->namespacePrefix,
|
|
package.value().empty() ? mDefaultPackage : package.value());
|
|
}
|
|
|
|
writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE);
|
|
for (const auto& child : node->children) {
|
|
child->accept(this);
|
|
}
|
|
writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE);
|
|
|
|
if (package) {
|
|
mPackageAliases.pop_back();
|
|
}
|
|
}
|
|
|
|
virtual void visit(Text* node) override {
|
|
if (util::trimWhitespace(node->text).empty()) {
|
|
return;
|
|
}
|
|
|
|
const size_t startIndex = mOut->size();
|
|
android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
|
|
android::ResXMLTree_cdataExt* flatText = mOut->nextBlock<android::ResXMLTree_cdataExt>();
|
|
mOut->align4();
|
|
|
|
const uint16_t type = android::RES_XML_CDATA_TYPE;
|
|
flatNode->header = { type, sizeof(*flatNode), (uint32_t)(mOut->size() - startIndex) };
|
|
flatNode->lineNumber = node->lineNumber;
|
|
flatNode->comment.index = -1;
|
|
addString(node->text, kLowPriority, &flatText->data);
|
|
}
|
|
|
|
virtual void visit(Element* node) override {
|
|
const size_t startIndex = mOut->size();
|
|
android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
|
|
android::ResXMLTree_attrExt* flatElem = mOut->nextBlock<android::ResXMLTree_attrExt>();
|
|
|
|
const uint16_t type = android::RES_XML_START_ELEMENT_TYPE;
|
|
flatNode->header = { type, sizeof(*flatNode), 0 };
|
|
flatNode->lineNumber = node->lineNumber;
|
|
flatNode->comment.index = -1;
|
|
|
|
addString(node->namespaceUri, kLowPriority, &flatElem->ns);
|
|
addString(node->name, kLowPriority, &flatElem->name);
|
|
flatElem->attributeStart = sizeof(*flatElem);
|
|
flatElem->attributeSize = sizeof(android::ResXMLTree_attribute);
|
|
flatElem->attributeCount = node->attributes.size();
|
|
|
|
if (!writeAttributes(mOut, node, flatElem)) {
|
|
mError = true;
|
|
}
|
|
|
|
mOut->align4();
|
|
flatNode->header.size = (uint32_t)(mOut->size() - startIndex);
|
|
|
|
for (const auto& child : node->children) {
|
|
child->accept(this);
|
|
}
|
|
|
|
const size_t startEndIndex = mOut->size();
|
|
android::ResXMLTree_node* flatEndNode = mOut->nextBlock<android::ResXMLTree_node>();
|
|
android::ResXMLTree_endElementExt* flatEndElem =
|
|
mOut->nextBlock<android::ResXMLTree_endElementExt>();
|
|
mOut->align4();
|
|
|
|
const uint16_t endType = android::RES_XML_END_ELEMENT_TYPE;
|
|
flatEndNode->header = { endType, sizeof(*flatEndNode),
|
|
(uint32_t)(mOut->size() - startEndIndex) };
|
|
flatEndNode->lineNumber = node->lineNumber;
|
|
flatEndNode->comment.index = -1;
|
|
|
|
addString(node->namespaceUri, kLowPriority, &flatEndElem->ns);
|
|
addString(node->name, kLowPriority, &flatEndElem->name);
|
|
}
|
|
|
|
bool success() const {
|
|
return !mError;
|
|
}
|
|
|
|
protected:
|
|
void addString(const StringPiece16& str, uint32_t priority, android::ResStringPool_ref* dest) {
|
|
if (!str.empty()) {
|
|
mStringRefs->emplace_back(mPool->makeRef(str, StringPool::Context{ priority }), dest);
|
|
} else {
|
|
// The device doesn't think a string of size 0 is the same as null.
|
|
dest->index = -1;
|
|
}
|
|
}
|
|
|
|
void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
|
|
mStringRefs->emplace_back(ref, dest);
|
|
}
|
|
|
|
Maybe<std::u16string> getPackageAlias(const std::u16string& prefix) {
|
|
const auto endIter = mPackageAliases.rend();
|
|
for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) {
|
|
if (iter->first == prefix) {
|
|
return iter->second;
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
const std::u16string& getDefaultPackage() const {
|
|
return mDefaultPackage;
|
|
}
|
|
|
|
/**
|
|
* Subclasses override this to deal with attributes. Attributes can be flattened as
|
|
* raw values or as resources.
|
|
*/
|
|
virtual bool writeAttributes(BigBuffer* out, Element* node,
|
|
android::ResXMLTree_attrExt* flatElem) = 0;
|
|
|
|
private:
|
|
BigBuffer* mOut;
|
|
StringPool* mPool;
|
|
FlatStringRefList* mStringRefs;
|
|
std::u16string mDefaultPackage;
|
|
bool mError = false;
|
|
std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases;
|
|
};
|
|
|
|
/**
|
|
* Flattens XML, encoding the attributes as raw strings. This is used in the compile phase.
|
|
*/
|
|
struct CompileXmlFlattener : public XmlFlattener {
|
|
CompileXmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs,
|
|
const std::u16string& defaultPackage) :
|
|
XmlFlattener(outBuffer, pool, stringRefs, defaultPackage) {
|
|
}
|
|
|
|
virtual bool writeAttributes(BigBuffer* out, Element* node,
|
|
android::ResXMLTree_attrExt* flatElem) override {
|
|
flatElem->attributeCount = node->attributes.size();
|
|
if (node->attributes.empty()) {
|
|
return true;
|
|
}
|
|
|
|
android::ResXMLTree_attribute* flatAttrs = out->nextBlock<android::ResXMLTree_attribute>(
|
|
node->attributes.size());
|
|
for (const Attribute& attr : node->attributes) {
|
|
addString(attr.namespaceUri, kLowPriority, &flatAttrs->ns);
|
|
addString(attr.name, kLowPriority, &flatAttrs->name);
|
|
addString(attr.value, kLowPriority, &flatAttrs->rawValue);
|
|
flatAttrs++;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
struct AttributeToFlatten {
|
|
uint32_t resourceId = 0;
|
|
const Attribute* xmlAttr = nullptr;
|
|
const ::aapt::Attribute* resourceAttr = nullptr;
|
|
};
|
|
|
|
static bool lessAttributeId(const AttributeToFlatten& a, uint32_t id) {
|
|
return a.resourceId < id;
|
|
}
|
|
|
|
/**
|
|
* Flattens XML, encoding the attributes as resources.
|
|
*/
|
|
struct LinkedXmlFlattener : public XmlFlattener {
|
|
LinkedXmlFlattener(BigBuffer* outBuffer, StringPool* pool,
|
|
std::map<std::u16string, StringPool>* packagePools,
|
|
FlatStringRefList* stringRefs,
|
|
const std::u16string& defaultPackage,
|
|
const std::shared_ptr<IResolver>& resolver,
|
|
SourceLogger* logger,
|
|
const FlattenOptions& options) :
|
|
XmlFlattener(outBuffer, pool, stringRefs, defaultPackage), mResolver(resolver),
|
|
mLogger(logger), mPackagePools(packagePools), mOptions(options) {
|
|
}
|
|
|
|
virtual bool writeAttributes(BigBuffer* out, Element* node,
|
|
android::ResXMLTree_attrExt* flatElem) override {
|
|
bool error = false;
|
|
std::vector<AttributeToFlatten> sortedAttributes;
|
|
uint32_t nextAttributeId = 0x80000000u;
|
|
|
|
// Sort and filter attributes by their resource ID.
|
|
for (const Attribute& attr : node->attributes) {
|
|
AttributeToFlatten attrToFlatten;
|
|
attrToFlatten.xmlAttr = &attr;
|
|
|
|
Maybe<std::u16string> package = util::extractPackageFromNamespace(attr.namespaceUri);
|
|
if (package) {
|
|
// Find the Attribute object via our Resolver.
|
|
ResourceName attrName = { package.value(), ResourceType::kAttr, attr.name };
|
|
if (attrName.package.empty()) {
|
|
attrName.package = getDefaultPackage();
|
|
}
|
|
|
|
Maybe<IResolver::Entry> result = mResolver->findAttribute(attrName);
|
|
if (!result || !result.value().id.isValid() || !result.value().attr) {
|
|
error = true;
|
|
mLogger->error(node->lineNumber)
|
|
<< "unresolved attribute '" << attrName << "'."
|
|
<< std::endl;
|
|
} else {
|
|
attrToFlatten.resourceId = result.value().id.id;
|
|
attrToFlatten.resourceAttr = result.value().attr;
|
|
|
|
size_t sdk = findAttributeSdkLevel(attrToFlatten.resourceId);
|
|
if (mOptions.maxSdkAttribute && sdk > mOptions.maxSdkAttribute.value()) {
|
|
// We need to filter this attribute out.
|
|
mSmallestFilteredSdk = std::min(mSmallestFilteredSdk, sdk);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (attrToFlatten.resourceId == 0) {
|
|
// 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.
|
|
attrToFlatten.resourceId = nextAttributeId++;
|
|
}
|
|
|
|
// Insert the attribute into the sorted vector.
|
|
auto iter = std::lower_bound(sortedAttributes.begin(), sortedAttributes.end(),
|
|
attrToFlatten.resourceId, lessAttributeId);
|
|
sortedAttributes.insert(iter, std::move(attrToFlatten));
|
|
}
|
|
|
|
flatElem->attributeCount = sortedAttributes.size();
|
|
if (sortedAttributes.empty()) {
|
|
return true;
|
|
}
|
|
|
|
android::ResXMLTree_attribute* flatAttr = out->nextBlock<android::ResXMLTree_attribute>(
|
|
sortedAttributes.size());
|
|
|
|
// Now that we have sorted the attributes into their final encoded order, it's time
|
|
// to actually write them out.
|
|
uint16_t attributeIndex = 1;
|
|
for (const AttributeToFlatten& attrToFlatten : sortedAttributes) {
|
|
Maybe<std::u16string> package = util::extractPackageFromNamespace(
|
|
attrToFlatten.xmlAttr->namespaceUri);
|
|
|
|
// Assign the indices for specific attributes.
|
|
if (package && package.value() == u"android" && attrToFlatten.xmlAttr->name == u"id") {
|
|
flatElem->idIndex = attributeIndex;
|
|
} else if (attrToFlatten.xmlAttr->namespaceUri.empty()) {
|
|
if (attrToFlatten.xmlAttr->name == u"class") {
|
|
flatElem->classIndex = attributeIndex;
|
|
} else if (attrToFlatten.xmlAttr->name == u"style") {
|
|
flatElem->styleIndex = attributeIndex;
|
|
}
|
|
}
|
|
attributeIndex++;
|
|
|
|
// Add the namespaceUri and name to the list of StringRefs to encode.
|
|
addString(attrToFlatten.xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns);
|
|
flatAttr->rawValue.index = -1;
|
|
|
|
if (!attrToFlatten.resourceAttr) {
|
|
addString(attrToFlatten.xmlAttr->name, kLowPriority, &flatAttr->name);
|
|
} else {
|
|
// We've already extracted the package successfully before.
|
|
assert(package);
|
|
|
|
// 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.
|
|
//
|
|
// Lookup the StringPool for this package and make the reference there.
|
|
StringPool::Ref nameRef = (*mPackagePools)[package.value()].makeRef(
|
|
attrToFlatten.xmlAttr->name,
|
|
StringPool::Context{ attrToFlatten.resourceId });
|
|
|
|
// Add it to the list of strings to flatten.
|
|
addString(nameRef, &flatAttr->name);
|
|
|
|
if (mOptions.keepRawValues) {
|
|
// Keep raw values (this is for static libraries).
|
|
// TODO(with a smarter inflater for binary XML, we can do without this).
|
|
addString(attrToFlatten.xmlAttr->value, kLowPriority, &flatAttr->rawValue);
|
|
}
|
|
}
|
|
|
|
error |= !flattenItem(node, attrToFlatten.xmlAttr->value, attrToFlatten.resourceAttr,
|
|
flatAttr);
|
|
flatAttr->typedValue.size = sizeof(flatAttr->typedValue);
|
|
flatAttr++;
|
|
}
|
|
return !error;
|
|
}
|
|
|
|
Maybe<size_t> getSmallestFilteredSdk() const {
|
|
if (mSmallestFilteredSdk == std::numeric_limits<size_t>::max()) {
|
|
return {};
|
|
}
|
|
return mSmallestFilteredSdk;
|
|
}
|
|
|
|
private:
|
|
bool flattenItem(const Node* el, const std::u16string& value, const ::aapt::Attribute* attr,
|
|
android::ResXMLTree_attribute* flatAttr) {
|
|
std::unique_ptr<Item> item;
|
|
if (!attr) {
|
|
bool create = false;
|
|
item = ResourceParser::tryParseReference(value, &create);
|
|
if (!item) {
|
|
flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
|
|
addString(value, kLowPriority, &flatAttr->rawValue);
|
|
addString(value, kLowPriority, reinterpret_cast<android::ResStringPool_ref*>(
|
|
&flatAttr->typedValue.data));
|
|
return true;
|
|
}
|
|
} else {
|
|
item = ResourceParser::parseItemForAttribute(value, *attr);
|
|
if (!item) {
|
|
if (!(attr->typeMask & android::ResTable_map::TYPE_STRING)) {
|
|
mLogger->error(el->lineNumber)
|
|
<< "'"
|
|
<< value
|
|
<< "' is not compatible with attribute '"
|
|
<< *attr
|
|
<< "'."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
|
|
flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
|
|
addString(value, kLowPriority, &flatAttr->rawValue);
|
|
addString(value, kLowPriority, reinterpret_cast<android::ResStringPool_ref*>(
|
|
&flatAttr->typedValue.data));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
assert(item);
|
|
|
|
bool error = false;
|
|
|
|
// If this is a reference, resolve the name into an ID.
|
|
visitFunc<Reference>(*item, [&](Reference& reference) {
|
|
// First see if we can convert the package name from a prefix to a real
|
|
// package name.
|
|
ResourceName realName = reference.name;
|
|
if (!realName.package.empty()) {
|
|
Maybe<std::u16string> package = getPackageAlias(realName.package);
|
|
if (package) {
|
|
realName.package = package.value();
|
|
}
|
|
} else {
|
|
realName.package = getDefaultPackage();
|
|
}
|
|
|
|
Maybe<ResourceId> result = mResolver->findId(realName);
|
|
if (!result || !result.value().isValid()) {
|
|
std::ostream& out = mLogger->error(el->lineNumber)
|
|
<< "unresolved reference '"
|
|
<< reference.name
|
|
<< "'";
|
|
if (realName != reference.name) {
|
|
out << " (aka '" << realName << "')";
|
|
}
|
|
out << "'." << std::endl;
|
|
error = true;
|
|
} else {
|
|
reference.id = result.value();
|
|
}
|
|
});
|
|
|
|
if (error) {
|
|
return false;
|
|
}
|
|
|
|
item->flatten(flatAttr->typedValue);
|
|
return true;
|
|
}
|
|
|
|
std::shared_ptr<IResolver> mResolver;
|
|
SourceLogger* mLogger;
|
|
std::map<std::u16string, StringPool>* mPackagePools;
|
|
FlattenOptions mOptions;
|
|
size_t mSmallestFilteredSdk = std::numeric_limits<size_t>::max();
|
|
};
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
static void flattenXml(StringPool* pool, FlatStringRefList* stringRefs, BigBuffer* outBuffer,
|
|
BigBuffer&& xmlTreeBuffer) {
|
|
// Sort the string pool 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);
|
|
|
|
// Flatten the StringPool.
|
|
StringPool::flattenUtf16(outBuffer, *pool);
|
|
|
|
// 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.id == kLowPriority || !id.isValid()) {
|
|
// When we see the first non-resource ID,
|
|
// we're done.
|
|
break;
|
|
}
|
|
|
|
*outBuffer->nextBlock<uint32_t>() = id.id;
|
|
}
|
|
resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex;
|
|
|
|
// Move the temporary BigBuffer into outBuffer.
|
|
outBuffer->appendBuffer(std::move(xmlTreeBuffer));
|
|
header->header.size = outBuffer->size() - beforeXmlTreeIndex;
|
|
}
|
|
|
|
bool flatten(Node* root, const std::u16string& defaultPackage, BigBuffer* outBuffer) {
|
|
StringPool pool;
|
|
|
|
// This will hold the StringRefs and the location in which to write the index.
|
|
// Once we sort the StringPool, we can assign the updated indices
|
|
// to the correct data locations.
|
|
FlatStringRefList 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);
|
|
|
|
CompileXmlFlattener flattener(&out, &pool, &stringRefs, defaultPackage);
|
|
root->accept(&flattener);
|
|
|
|
if (!flattener.success()) {
|
|
return false;
|
|
}
|
|
|
|
flattenXml(&pool, &stringRefs, outBuffer, std::move(out));
|
|
return true;
|
|
};
|
|
|
|
Maybe<size_t> flattenAndLink(const Source& source, Node* root,
|
|
const std::u16string& defaultPackage,
|
|
const std::shared_ptr<IResolver>& resolver,
|
|
const FlattenOptions& options, BigBuffer* outBuffer) {
|
|
SourceLogger logger(source);
|
|
StringPool pool;
|
|
|
|
// 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;
|
|
|
|
FlatStringRefList 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);
|
|
|
|
LinkedXmlFlattener flattener(&out, &pool, &packagePools, &stringRefs, defaultPackage, resolver,
|
|
&logger, options);
|
|
root->accept(&flattener);
|
|
|
|
if (!flattener.success()) {
|
|
return {};
|
|
}
|
|
|
|
// Merge the package pools into the main pool.
|
|
for (auto& packagePoolEntry : packagePools) {
|
|
pool.merge(std::move(packagePoolEntry.second));
|
|
}
|
|
|
|
flattenXml(&pool, &stringRefs, outBuffer, std::move(out));
|
|
|
|
if (flattener.getSmallestFilteredSdk()) {
|
|
return flattener.getSmallestFilteredSdk();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
} // namespace xml
|
|
} // namespace aapt
|