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/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
index 39e4489..e0f37ec 100644
--- a/tools/aapt2/compile/Compile.cpp
+++ b/tools/aapt2/compile/Compile.cpp
@@ -20,6 +20,7 @@
 #include "ResourceParser.h"
 #include "ResourceTable.h"
 #include "compile/IdAssigner.h"
+#include "compile/InlineXmlFormatParser.h"
 #include "compile/Png.h"
 #include "compile/PseudolocaleGenerator.h"
 #include "compile/XmlIdCollector.h"
@@ -39,6 +40,9 @@
 #include <fstream>
 #include <string>
 
+using google::protobuf::io::CopyingOutputStreamAdaptor;
+using google::protobuf::io::ZeroCopyOutputStream;
+
 namespace aapt {
 
 struct ResourcePathData {
@@ -238,13 +242,14 @@
         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 copyingAdaptor(writer);
 
-        if (!pbTable->SerializeToZeroCopyStream(&adaptor)) {
+        std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(&table);
+        if (!pbTable->SerializeToZeroCopyStream(&copyingAdaptor)) {
             context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
             return false;
         }
@@ -266,21 +271,23 @@
         return false;
     }
 
-    // Create the header.
-    std::unique_ptr<pb::CompiledFile> pbCompiledFile = serializeCompiledFileToPb(file);
-
+    // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry().
     {
-        // The stream must be destroyed before we finish the entry, or else
-        // some data won't be flushed.
         // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
         // interface.
-        google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);
-        CompiledFileOutputStream outputStream(&adaptor, pbCompiledFile.get());
-        for (const BigBuffer::Block& block : buffer) {
-            if (!outputStream.Write(block.buffer.get(), block.size)) {
-                diag->error(DiagMessage(outputPath) << "failed to write data");
-                return false;
-            }
+        CopyingOutputStreamAdaptor copyingAdaptor(writer);
+        CompiledFileOutputStream outputStream(&copyingAdaptor);
+
+        // Number of CompiledFiles.
+        outputStream.WriteLittleEndian32(1);
+
+        std::unique_ptr<pb::CompiledFile> compiledFile = serializeCompiledFileToPb(file);
+        outputStream.WriteCompiledFile(compiledFile.get());
+        outputStream.WriteData(&buffer);
+
+        if (outputStream.HadError()) {
+            diag->error(DiagMessage(outputPath) << "failed to write data");
+            return false;
         }
     }
 
@@ -300,17 +307,21 @@
         return false;
     }
 
-    // Create the header.
-    std::unique_ptr<pb::CompiledFile> pbCompiledFile = serializeCompiledFileToPb(file);
-
+    // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry().
     {
-        // The stream must be destroyed before we finish the entry, or else
-        // some data won't be flushed.
         // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
         // interface.
-        google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);
-        CompiledFileOutputStream outputStream(&adaptor, pbCompiledFile.get());
-        if (!outputStream.Write(map.getDataPtr(), map.getDataLength())) {
+        CopyingOutputStreamAdaptor copyingAdaptor(writer);
+        CompiledFileOutputStream outputStream(&copyingAdaptor);
+
+        // Number of CompiledFiles.
+        outputStream.WriteLittleEndian32(1);
+
+        std::unique_ptr<pb::CompiledFile> compiledFile = serializeCompiledFileToPb(file);
+        outputStream.WriteCompiledFile(compiledFile.get());
+        outputStream.WriteData(map.getDataPtr(), map.getDataLength());
+
+        if (outputStream.HadError()) {
             diag->error(DiagMessage(outputPath) << "failed to write data");
             return false;
         }
@@ -323,6 +334,28 @@
     return true;
 }
 
+static bool flattenXmlToOutStream(IAaptContext* context, const StringPiece& outputPath,
+                                  xml::XmlResource* xmlRes,
+                                  CompiledFileOutputStream* out) {
+    BigBuffer buffer(1024);
+    XmlFlattenerOptions xmlFlattenerOptions;
+    xmlFlattenerOptions.keepRawValues = true;
+    XmlFlattener flattener(&buffer, xmlFlattenerOptions);
+    if (!flattener.consume(context, xmlRes)) {
+        return false;
+    }
+
+    std::unique_ptr<pb::CompiledFile> pbCompiledFile = serializeCompiledFileToPb(xmlRes->file);
+    out->WriteCompiledFile(pbCompiledFile.get());
+    out->WriteData(&buffer);
+
+    if (out->HadError()) {
+        context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write data");
+        return false;
+    }
+    return true;
+}
+
 static bool compileXml(IAaptContext* context, const CompileOptions& options,
                        const ResourcePathData& pathData, IArchiveWriter* writer,
                        const std::string& outputPath) {
@@ -344,26 +377,55 @@
         return false;
     }
 
+    xmlRes->file.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
+    xmlRes->file.config = pathData.config;
+    xmlRes->file.source = pathData.source;
+
     // Collect IDs that are defined here.
     XmlIdCollector collector;
     if (!collector.consume(context, xmlRes.get())) {
         return false;
     }
 
-    xmlRes->file.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
-    xmlRes->file.config = pathData.config;
-    xmlRes->file.source = pathData.source;
-
-    BigBuffer buffer(1024);
-    XmlFlattenerOptions xmlFlattenerOptions;
-    xmlFlattenerOptions.keepRawValues = true;
-    XmlFlattener flattener(&buffer, xmlFlattenerOptions);
-    if (!flattener.consume(context, xmlRes.get())) {
+    // Look for and process any <aapt:attr> tags and create sub-documents.
+    InlineXmlFormatParser inlineXmlFormatParser;
+    if (!inlineXmlFormatParser.consume(context, xmlRes.get())) {
         return false;
     }
 
-    if (!writeHeaderAndBufferToWriter(outputPath, xmlRes->file, buffer, writer,
-                                      context->getDiagnostics())) {
+    // Start the entry so we can write the header.
+    if (!writer->startEntry(outputPath, 0)) {
+        context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open file");
+        return false;
+    }
+
+    // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry().
+    {
+        // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
+        // interface.
+        CopyingOutputStreamAdaptor copyingAdaptor(writer);
+        CompiledFileOutputStream outputStream(&copyingAdaptor);
+
+        std::vector<std::unique_ptr<xml::XmlResource>>& inlineDocuments =
+                inlineXmlFormatParser.getExtractedInlineXmlDocuments();
+
+        // Number of CompiledFiles.
+        outputStream.WriteLittleEndian32(1 + inlineDocuments.size());
+
+        if (!flattenXmlToOutStream(context, outputPath, xmlRes.get(), &outputStream)) {
+            return false;
+        }
+
+        for (auto& inlineXmlDoc : inlineDocuments) {
+            if (!flattenXmlToOutStream(context, outputPath, inlineXmlDoc.get(), &outputStream)) {
+                return false;
+            }
+        }
+    }
+
+    if (!writer->finishEntry()) {
+        context->getDiagnostics()->error(DiagMessage(outputPath)
+                                         << "failed to finish writing data");
         return false;
     }
     return true;