AAPT2: Add Inline Complex XML support

See: https://developer.android.com/guide/topics/resources/complex-xml-resources.html

Change-Id: I8274c85e25cabf90423141c228697e873167d136
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index ea95dd1..c236394 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -48,10 +48,13 @@
 #include <google/protobuf/io/coded_stream.h>
 
 #include <fstream>
+#include <queue>
 #include <sys/stat.h>
 #include <unordered_map>
 #include <vector>
 
+using google::protobuf::io::CopyingOutputStreamAdaptor;
+
 namespace aapt {
 
 struct LinkOptions {
@@ -166,19 +169,7 @@
     }
 
     const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data());
-    size_t bufferSize = data->size();
-
-    // If the file ends with .flat, we must strip off the CompiledFileHeader from it.
-    if (util::stringEndsWith(file->getSource().path, ".flat")) {
-        CompiledFileInputStream inputStream(data->data(), data->size());
-        if (!inputStream.CompiledFile()) {
-            context->getDiagnostics()->error(DiagMessage(file->getSource())
-                                             << "invalid compiled file header");
-            return false;
-        }
-        buffer = reinterpret_cast<const uint8_t*>(inputStream.data());
-        bufferSize = inputStream.size();
-    }
+    const size_t bufferSize = data->size();
 
     if (context->verbose()) {
         context->getDiagnostics()->note(DiagMessage() << "writing " << outPath << " to archive");
@@ -255,42 +246,6 @@
     return xml::inflate(&fin, diag, Source(path));
 }
 
-static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(const Source& source,
-                                                                     const void* data, size_t len,
-                                                                     IDiagnostics* diag) {
-    CompiledFileInputStream inputStream(data, len);
-    if (!inputStream.CompiledFile()) {
-        diag->error(DiagMessage(source) << "invalid compiled file header");
-        return {};
-    }
-
-    const uint8_t* xmlData = reinterpret_cast<const uint8_t*>(inputStream.data());
-    const size_t xmlDataLen = inputStream.size();
-
-    std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source);
-    if (!xmlRes) {
-        return {};
-    }
-    return xmlRes;
-}
-
-static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
-                                                          const void* data, size_t len,
-                                                          IDiagnostics* diag) {
-    CompiledFileInputStream inputStream(data, len);
-    const pb::CompiledFile* pbFile = inputStream.CompiledFile();
-    if (!pbFile) {
-        diag->error(DiagMessage(source) << "invalid compiled file header");
-        return {};
-    }
-
-    std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source, diag);
-    if (!resFile) {
-        return {};
-    }
-    return resFile;
-}
-
 struct ResourceFileFlattenerOptions {
     bool noAutoVersion = false;
     bool noVersionVectors = false;
@@ -312,16 +267,26 @@
 
 private:
     struct FileOperation {
+        ConfigDescription config;
+
+        // The entry this file came from.
+        const ResourceEntry* entry;
+
+        // The file to copy as-is.
         io::IFile* fileToCopy;
+
+        // The XML to process and flatten.
         std::unique_ptr<xml::XmlResource> xmlToFlatten;
+
+        // The destination to write this file to.
         std::string dstPath;
         bool skipVersion = false;
     };
 
     uint32_t getCompressionFlags(const StringPiece& str);
 
-    bool linkAndVersionXmlFile(const ResourceEntry* entry, const ResourceFile& fileDesc,
-                               io::IFile* file, ResourceTable* table, FileOperation* outFileOp);
+    bool linkAndVersionXmlFile(ResourceTable* table, FileOperation* fileOp,
+                               std::queue<FileOperation>* outFileOpQueue);
 
     ResourceFileFlattenerOptions mOptions;
     IAaptContext* mContext;
@@ -341,52 +306,28 @@
     return ArchiveEntry::kCompress;
 }
 
-bool ResourceFileFlattener::linkAndVersionXmlFile(const ResourceEntry* entry,
-                                                  const ResourceFile& fileDesc,
-                                                  io::IFile* file,
-                                                  ResourceTable* table,
-                                                  FileOperation* outFileOp) {
-    const StringPiece srcPath = file->getSource().path;
+bool ResourceFileFlattener::linkAndVersionXmlFile(ResourceTable* table,
+                                                  FileOperation* fileOp,
+                                                  std::queue<FileOperation>* outFileOpQueue) {
+    xml::XmlResource* doc = fileOp->xmlToFlatten.get();
+    const Source& src = doc->file.source;
+
     if (mContext->verbose()) {
-        mContext->getDiagnostics()->note(DiagMessage() << "linking " << srcPath);
+        mContext->getDiagnostics()->note(DiagMessage() << "linking " << src.path);
     }
 
-    std::unique_ptr<io::IData> data = file->openAsData();
-    if (!data) {
-        mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file");
-        return false;
-    }
-
-    if (util::stringEndsWith(srcPath, ".flat")) {
-        outFileOp->xmlToFlatten = loadBinaryXmlSkipFileExport(file->getSource(),
-                                                              data->data(), data->size(),
-                                                              mContext->getDiagnostics());
-    } else {
-        outFileOp->xmlToFlatten = xml::inflate(data->data(), data->size(),
-                                               mContext->getDiagnostics(),
-                                               file->getSource());
-    }
-
-    if (!outFileOp->xmlToFlatten) {
-        return false;
-    }
-
-    // Copy the the file description header.
-    outFileOp->xmlToFlatten->file = fileDesc;
-
     XmlReferenceLinker xmlLinker;
-    if (!xmlLinker.consume(mContext, outFileOp->xmlToFlatten.get())) {
+    if (!xmlLinker.consume(mContext, doc)) {
         return false;
     }
 
-    if (mOptions.updateProguardSpec && !proguard::collectProguardRules(
-            outFileOp->xmlToFlatten->file.source, outFileOp->xmlToFlatten.get(), mKeepSet)) {
+    if (mOptions.updateProguardSpec && !proguard::collectProguardRules(src, doc, mKeepSet)) {
         return false;
     }
 
     if (mOptions.noXmlNamespaces) {
         XmlNamespaceRemover namespaceRemover;
-        if (!namespaceRemover.consume(mContext, outFileOp->xmlToFlatten.get())) {
+        if (!namespaceRemover.consume(mContext, doc)) {
             return false;
         }
     }
@@ -394,51 +335,58 @@
     if (!mOptions.noAutoVersion) {
         if (mOptions.noVersionVectors) {
             // Skip this if it is a vector or animated-vector.
-            xml::Element* el = xml::findRootElement(outFileOp->xmlToFlatten.get());
+            xml::Element* el = xml::findRootElement(doc);
             if (el && el->namespaceUri.empty()) {
                 if (el->name == "vector" || el->name == "animated-vector") {
                     // We are NOT going to version this file.
-                    outFileOp->skipVersion = true;
+                    fileOp->skipVersion = true;
                     return true;
                 }
             }
         }
 
+        const ConfigDescription& config = fileOp->config;
+
         // Find the first SDK level used that is higher than this defined config and
         // not superseded by a lower or equal SDK level resource.
         const int minSdkVersion = mContext->getMinSdkVersion();
         for (int sdkLevel : xmlLinker.getSdkLevels()) {
-            if (sdkLevel > minSdkVersion
-                    && sdkLevel > outFileOp->xmlToFlatten->file.config.sdkVersion) {
-                if (!shouldGenerateVersionedResource(entry, outFileOp->xmlToFlatten->file.config,
-                                                     sdkLevel)) {
+            if (sdkLevel > minSdkVersion && sdkLevel > config.sdkVersion) {
+                if (!shouldGenerateVersionedResource(fileOp->entry, config, sdkLevel)) {
                     // If we shouldn't generate a versioned resource, stop checking.
                     break;
                 }
 
-                ResourceFile versionedFileDesc = outFileOp->xmlToFlatten->file;
+                ResourceFile versionedFileDesc = doc->file;
                 versionedFileDesc.config.sdkVersion = (uint16_t) sdkLevel;
 
+                FileOperation newFileOp;
+                newFileOp.xmlToFlatten = util::make_unique<xml::XmlResource>(
+                        versionedFileDesc, doc->root->clone());
+                newFileOp.config = versionedFileDesc.config;
+                newFileOp.entry = fileOp->entry;
+                newFileOp.dstPath = ResourceUtils::buildResourceFileName(
+                        versionedFileDesc, mContext->getNameMangler());
+
                 if (mContext->verbose()) {
                     mContext->getDiagnostics()->note(DiagMessage(versionedFileDesc.source)
                                                      << "auto-versioning resource from config '"
-                                                     << outFileOp->xmlToFlatten->file.config
+                                                     << config
                                                      << "' -> '"
                                                      << versionedFileDesc.config << "'");
                 }
 
-                std::string genPath = ResourceUtils::buildResourceFileName(
-                        versionedFileDesc, mContext->getNameMangler());
-
                 bool added = table->addFileReferenceAllowMangled(versionedFileDesc.name,
                                                                  versionedFileDesc.config,
                                                                  versionedFileDesc.source,
-                                                                 genPath,
-                                                                 file,
+                                                                 newFileOp.dstPath,
+                                                                 nullptr,
                                                                  mContext->getDiagnostics());
                 if (!added) {
                     return false;
                 }
+
+                outFileOpQueue->push(std::move(newFileOp));
                 break;
             }
         }
@@ -458,12 +406,11 @@
         for (auto& type : pkg->types) {
             // Sort by config and name, so that we get better locality in the zip file.
             configSortedFiles.clear();
-            for (auto& entry : type->entries) {
-                // Iterate via indices because auto generated values can be inserted ahead of
-                // the value being processed.
-                for (size_t i = 0; i < entry->values.size(); i++) {
-                    ResourceConfigValue* configValue = entry->values[i].get();
+            std::queue<FileOperation> fileOperations;
 
+            // Populate the queue with all files in the ResourceTable.
+            for (auto& entry : type->entries) {
+                for (auto& configValue : entry->values) {
                     FileReference* fileRef = valueCast<FileReference>(configValue->value.get());
                     if (!fileRef) {
                         continue;
@@ -477,35 +424,67 @@
                     }
 
                     FileOperation fileOp;
+                    fileOp.entry = entry.get();
                     fileOp.dstPath = *fileRef->path;
+                    fileOp.config = configValue->config;
 
                     const StringPiece srcPath = file->getSource().path;
                     if (type->type != ResourceType::kRaw &&
                             (util::stringEndsWith(srcPath, ".xml.flat") ||
                             util::stringEndsWith(srcPath, ".xml"))) {
-                        ResourceFile fileDesc;
-                        fileDesc.config = configValue->config;
-                        fileDesc.name = ResourceName(pkg->name, type->type, entry->name);
-                        fileDesc.source = fileRef->getSource();
-                        if (!linkAndVersionXmlFile(entry.get(), fileDesc, file, table, &fileOp)) {
-                            error = true;
-                            continue;
+                        std::unique_ptr<io::IData> data = file->openAsData();
+                        if (!data) {
+                            mContext->getDiagnostics()->error(DiagMessage(file->getSource())
+                                                              << "failed to open file");
+                            return false;
                         }
 
+                        fileOp.xmlToFlatten = xml::inflate(data->data(), data->size(),
+                                                           mContext->getDiagnostics(),
+                                                           file->getSource());
+
+                        if (!fileOp.xmlToFlatten) {
+                            return false;
+                        }
+
+                        fileOp.xmlToFlatten->file.config = configValue->config;
+                        fileOp.xmlToFlatten->file.source = fileRef->getSource();
+                        fileOp.xmlToFlatten->file.name =
+                                ResourceName(pkg->name, type->type, entry->name);
+
+                        // Enqueue the XML files to be processed.
+                        fileOperations.push(std::move(fileOp));
                     } else {
                         fileOp.fileToCopy = file;
-                    }
 
-                    // NOTE(adamlesinski): Explicitly construct a StringPiece16 here, or else
-                    // we end up copying the string in the std::make_pair() method, then creating
-                    // a StringPiece16 from the copy, which would cause us to end up referencing
-                    // garbage in the map.
-                    const StringPiece entryName(entry->name);
-                    configSortedFiles[std::make_pair(configValue->config, entryName)] =
-                                      std::move(fileOp);
+                        // NOTE(adamlesinski): Explicitly construct a StringPiece here, or else
+                        // we end up copying the string in the std::make_pair() method, then
+                        // creating a StringPiece from the copy, which would cause us to end up
+                        // referencing garbage in the map.
+                        const StringPiece entryName(entry->name);
+                        configSortedFiles[std::make_pair(configValue->config, entryName)] =
+                                std::move(fileOp);
+                    }
                 }
             }
 
+            // Now process the XML queue
+            for (; !fileOperations.empty(); fileOperations.pop()) {
+                FileOperation& fileOp = fileOperations.front();
+
+                if (!linkAndVersionXmlFile(table, &fileOp, &fileOperations)) {
+                    error = true;
+                    continue;
+                }
+
+                // NOTE(adamlesinski): Explicitly construct a StringPiece here, or else
+                // we end up copying the string in the std::make_pair() method, then creating
+                // a StringPiece from the copy, which would cause us to end up referencing
+                // garbage in the map.
+                const StringPiece entryName(fileOp.entry->name);
+                configSortedFiles[std::make_pair(fileOp.config, entryName)] = std::move(fileOp);
+            }
+
             if (error) {
                 return false;
             }
@@ -518,10 +497,8 @@
                 if (fileOp.xmlToFlatten) {
                     Maybe<size_t> maxSdkLevel;
                     if (!mOptions.noAutoVersion && !fileOp.skipVersion) {
-                        maxSdkLevel =
-                                std::max<size_t>(
-                                        std::max<size_t>(config.sdkVersion, 1u),
-                                        mContext->getMinSdkVersion());
+                        maxSdkLevel = std::max<size_t>(std::max<size_t>(config.sdkVersion, 1u),
+                                                       mContext->getMinSdkVersion());
                     }
 
                     bool result = flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel,
@@ -866,13 +843,13 @@
             return false;
         }
 
-        std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table);
-
-        // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
-        // interface.
+        // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry().
         {
-            google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);
+            // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
+            // interface.
+            CopyingOutputStreamAdaptor adaptor(writer);
 
+            std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table);
             if (!pbTable->SerializeToZeroCopyStream(&adaptor)) {
                 mContext->getDiagnostics()->error(DiagMessage() << "failed to write");
                 return false;
@@ -1109,7 +1086,9 @@
 
     bool mergeCompiledFile(io::IFile* file, ResourceFile* fileDesc, bool override) {
         if (mContext->verbose()) {
-            mContext->getDiagnostics()->note(DiagMessage() << "merging compiled file "
+            mContext->getDiagnostics()->note(DiagMessage()
+                                             << "merging '" << fileDesc->name
+                                             << "' from compiled file "
                                              << file->getSource());
         }
 
@@ -1230,12 +1209,40 @@
                 return false;
             }
 
-            std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader(
-                    src, data->data(), data->size(), mContext->getDiagnostics());
-            if (resourceFile) {
-                return mergeCompiledFile(file, resourceFile.get(), override);
+            CompiledFileInputStream inputStream(data->data(), data->size());
+            uint32_t numFiles = 0;
+            if (!inputStream.ReadLittleEndian32(&numFiles)) {
+                mContext->getDiagnostics()->error(DiagMessage(src) << "failed read num files");
+                return false;
             }
-            return false;
+
+            for (uint32_t i = 0; i < numFiles; i++) {
+                pb::CompiledFile compiledFile;
+                if (!inputStream.ReadCompiledFile(&compiledFile)) {
+                    mContext->getDiagnostics()->error(DiagMessage(src)
+                                                      << "failed to read compiled file header");
+                    return false;
+                }
+
+                uint64_t offset, len;
+                if (!inputStream.ReadDataMetaData(&offset, &len)) {
+                    mContext->getDiagnostics()->error(DiagMessage(src)
+                                                      << "failed to read data meta data");
+                    return false;
+                }
+
+                std::unique_ptr<ResourceFile> resourceFile = deserializeCompiledFileFromPb(
+                        compiledFile, file->getSource(), mContext->getDiagnostics());
+                if (!resourceFile) {
+                    return false;
+                }
+
+                if (!mergeCompiledFile(file->createFileSegment(offset, len), resourceFile.get(),
+                                       override)) {
+                    return false;
+                }
+            }
+            return true;
         }
 
         // Ignore non .flat files. This could be classes.dex or something else that happens