| /* | 
 |  * 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 |