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/Android.mk b/tools/aapt2/Android.mk
index b52c530..1d4c3d2 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -23,6 +23,7 @@
 main := Main.cpp
 sources := \
 	compile/IdAssigner.cpp \
+	compile/InlineXmlFormatParser.cpp \
 	compile/Png.cpp \
 	compile/PseudolocaleGenerator.cpp \
 	compile/Pseudolocalizer.cpp \
@@ -31,6 +32,7 @@
 	flatten/Archive.cpp \
 	flatten/TableFlattener.cpp \
 	flatten/XmlFlattener.cpp \
+	io/File.cpp \
 	io/FileSystem.cpp \
 	io/ZipArchive.cpp \
 	link/AutoVersioner.cpp \
@@ -77,6 +79,7 @@
 
 testSources := \
 	compile/IdAssigner_test.cpp \
+	compile/InlineXmlFormatParser_test.cpp \
 	compile/PseudolocaleGenerator_test.cpp \
 	compile/Pseudolocalizer_test.cpp \
 	compile/XmlIdCollector_test.cpp \
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 19bd521..304e571 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -247,5 +247,59 @@
     }
 }
 
+namespace {
+
+class XmlPrinter : public xml::Visitor {
+public:
+    using xml::Visitor::visit;
+
+    void visit(xml::Element* el) override {
+        std::cerr << mPrefix;
+        std::cerr << "E: ";
+        if (!el->namespaceUri.empty()) {
+            std::cerr << el->namespaceUri << ":";
+        }
+        std::cerr << el->name << " (line=" << el->lineNumber << ")\n";
+
+        for (const xml::Attribute& attr : el->attributes) {
+            std::cerr << mPrefix << "  A: ";
+            if (!attr.namespaceUri.empty()) {
+                std::cerr << attr.namespaceUri << ":";
+            }
+            std::cerr << attr.name << "=" << attr.value << "\n";
+        }
+
+        const size_t previousSize = mPrefix.size();
+        mPrefix += "  ";
+        xml::Visitor::visit(el);
+        mPrefix.resize(previousSize);
+    }
+
+    void visit(xml::Namespace* ns) override {
+        std::cerr << mPrefix;
+        std::cerr << "N: " << ns->namespacePrefix << "=" << ns->namespaceUri
+                << " (line=" << ns->lineNumber << ")\n";
+
+        const size_t previousSize = mPrefix.size();
+        mPrefix += "  ";
+        xml::Visitor::visit(ns);
+        mPrefix.resize(previousSize);
+    }
+
+    void visit(xml::Text* text) override {
+        std::cerr << mPrefix;
+        std::cerr << "T: '" << text->text << "'\n";
+    }
+
+private:
+    std::string mPrefix;
+};
+
+} // namespace
+
+void Debug::dumpXml(xml::XmlResource* doc) {
+    XmlPrinter printer;
+    doc->root->accept(&printer);
+}
 
 } // namespace aapt
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index fbe6477..c0fcbf1 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -19,6 +19,7 @@
 
 #include "Resource.h"
 #include "ResourceTable.h"
+#include "xml/XmlDom.h"
 
 // Include for printf-like debugging.
 #include <iostream>
@@ -34,6 +35,7 @@
     static void printStyleGraph(ResourceTable* table,
                                 const ResourceName& targetStyle);
     static void dumpHex(const void* data, size_t len);
+    static void dumpXml(xml::XmlResource* doc);
 };
 
 } // namespace aapt
diff --git a/tools/aapt2/Format.proto b/tools/aapt2/Format.proto
index d05425c..0917129 100644
--- a/tools/aapt2/Format.proto
+++ b/tools/aapt2/Format.proto
@@ -34,7 +34,7 @@
 		optional string resource_name = 1;
 		optional uint32 line_no = 2;
 	}
-	
+
 	optional string resource_name = 1;
 	optional ConfigDescription config = 2;
 	optional string source_path = 3;
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index bcdf401..32e5cfd 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -925,35 +925,6 @@
             Reference(ResourceNameRef({}, ResourceType::kId, maybeName.value())), val.data };
 }
 
-static Maybe<Reference> parseXmlAttributeName(StringPiece str) {
-    str = util::trimWhitespace(str);
-    const char* start = str.data();
-    const char* const end = start + str.size();
-    const char* p = start;
-
-    Reference ref;
-    if (p != end && *p == '*') {
-        ref.privateReference = true;
-        start++;
-        p++;
-    }
-
-    StringPiece package;
-    StringPiece name;
-    while (p != end) {
-        if (*p == ':') {
-            package = StringPiece(start, p - start);
-            name = StringPiece(p + 1, end - (p + 1));
-            break;
-        }
-        p++;
-    }
-
-    ref.name = ResourceName(package.toString(), ResourceType::kAttr,
-                        name.empty() ? str.toString() : name.toString());
-    return Maybe<Reference>(std::move(ref));
-}
-
 bool ResourceParser::parseStyleItem(xml::XmlPullParser* parser, Style* style) {
     const Source source = mSource.withLine(parser->getLineNumber());
 
@@ -963,7 +934,7 @@
         return false;
     }
 
-    Maybe<Reference> maybeKey = parseXmlAttributeName(maybeName.value());
+    Maybe<Reference> maybeKey = ResourceUtils::parseXmlAttributeName(maybeName.value());
     if (!maybeKey) {
         mDiag->error(DiagMessage(source) << "invalid attribute name '" << maybeName.value() << "'");
         return false;
@@ -1226,7 +1197,7 @@
 
             // If this is a declaration, the package name may be in the name. Separate these out.
             // Eg. <attr name="android:text" />
-            Maybe<Reference> maybeRef = parseXmlAttributeName(maybeName.value());
+            Maybe<Reference> maybeRef = ResourceUtils::parseXmlAttributeName(maybeName.value());
             if (!maybeRef) {
                 mDiag->error(DiagMessage(itemSource) << "<attr> tag has invalid name '"
                              << maybeName.value() << "'");
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 21d2f64..bdc6a8c 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -327,7 +327,7 @@
     fileRef->setSource(source);
     fileRef->file = file;
     return addResourceImpl(name, ResourceId{}, config, StringPiece{}, std::move(fileRef),
-                           kValidNameChars, resolveValueCollision, diag);
+                           validChars, resolveValueCollision, diag);
 }
 
 bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index df60814..6c246d0 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -290,14 +290,6 @@
 private:
     ResourceTablePackage* findOrCreatePackage(const StringPiece& name);
 
-    bool addFileReferenceImpl(const ResourceNameRef& name,
-                              const ConfigDescription& config,
-                              const Source& source,
-                              const StringPiece& path,
-                              io::IFile* file,
-                              const char* validChars,
-                              IDiagnostics* diag);
-
     bool addResourceImpl(const ResourceNameRef& name,
                          const ResourceId& resId,
                          const ConfigDescription& config,
@@ -307,6 +299,14 @@
                          const CollisionResolverFunc& conflictResolver,
                          IDiagnostics* diag);
 
+    bool addFileReferenceImpl(const ResourceNameRef& name,
+                              const ConfigDescription& config,
+                              const Source& source,
+                              const StringPiece& path,
+                              io::IFile* file,
+                              const char* validChars,
+                              IDiagnostics* diag);
+
     bool setSymbolStateImpl(const ResourceNameRef& name,
                             const ResourceId& resId,
                             const Symbol& symbol,
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 11619fa..73a194e 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -268,6 +268,35 @@
     return result;
 }
 
+Maybe<Reference> parseXmlAttributeName(const StringPiece& str) {
+    StringPiece trimmedStr = util::trimWhitespace(str);
+    const char* start = trimmedStr.data();
+    const char* const end = start + trimmedStr.size();
+    const char* p = start;
+
+    Reference ref;
+    if (p != end && *p == '*') {
+        ref.privateReference = true;
+        start++;
+        p++;
+    }
+
+    StringPiece package;
+    StringPiece name;
+    while (p != end) {
+        if (*p == ':') {
+            package = StringPiece(start, p - start);
+            name = StringPiece(p + 1, end - (p + 1));
+            break;
+        }
+        p++;
+    }
+
+    ref.name = ResourceName(package.toString(), ResourceType::kAttr,
+                        name.empty() ? trimmedStr.toString() : name.toString());
+    return Maybe<Reference>(std::move(ref));
+}
+
 std::unique_ptr<Reference> tryParseReference(const StringPiece& str, bool* outCreate) {
     ResourceNameRef ref;
     bool privateRef = false;
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index 244047b..555203b 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -111,6 +111,14 @@
 Maybe<Reference> parseStyleParentReference(const StringPiece& str, std::string* outError);
 
 /*
+ * Returns a Reference if the string `str` was parsed as a valid XML attribute name.
+ * The valid format for an XML attribute name is:
+ *
+ * package:entry
+ */
+Maybe<Reference> parseXmlAttributeName(const StringPiece& str);
+
+/*
  * Returns a Reference object if the string was parsed as a resource or attribute reference,
  * ( @[+][package:]type/name | ?[package:]type/name ) setting outCreate to true if
  * the '+' was present in the string.
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;
diff --git a/tools/aapt2/compile/InlineXmlFormatParser.cpp b/tools/aapt2/compile/InlineXmlFormatParser.cpp
new file mode 100644
index 0000000..f965bff
--- /dev/null
+++ b/tools/aapt2/compile/InlineXmlFormatParser.cpp
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2016 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 "Debug.h"
+#include "ResourceUtils.h"
+#include "compile/InlineXmlFormatParser.h"
+#include "util/Util.h"
+#include "xml/XmlDom.h"
+#include "xml/XmlUtil.h"
+
+#include <android-base/macros.h>
+#include <sstream>
+#include <string>
+
+namespace aapt {
+
+namespace {
+
+/**
+ * XML Visitor that will find all <aapt:attr> elements for extraction.
+ */
+class Visitor : public xml::PackageAwareVisitor {
+public:
+    using xml::PackageAwareVisitor::visit;
+
+    struct InlineDeclaration {
+        xml::Element* el;
+        std::string attrNamespaceUri;
+        std::string attrName;
+    };
+
+    explicit Visitor(IAaptContext* context, xml::XmlResource* xmlResource) :
+            mContext(context), mXmlResource(xmlResource) {
+    }
+
+    void visit(xml::Element* el) override {
+        if (el->namespaceUri != xml::kSchemaAapt || el->name != "attr") {
+            xml::PackageAwareVisitor::visit(el);
+            return;
+        }
+
+        const Source& src = mXmlResource->file.source.withLine(el->lineNumber);
+
+        xml::Attribute* attr = el->findAttribute({}, "name");
+        if (!attr) {
+            mContext->getDiagnostics()->error(DiagMessage(src) << "missing 'name' attribute");
+            mError = true;
+            return;
+        }
+
+        Maybe<Reference> ref = ResourceUtils::parseXmlAttributeName(attr->value);
+        if (!ref) {
+            mContext->getDiagnostics()->error(DiagMessage(src) << "invalid XML attribute '"
+                                              << attr->value << "'");
+            mError = true;
+            return;
+        }
+
+        const ResourceName& name = ref.value().name.value();
+
+        // Use an empty string for the compilation package because we don't want to default to
+        // the local package if the user specified name="style" or something. This should just
+        // be the default namespace.
+        Maybe<xml::ExtractedPackage> maybePkg = transformPackageAlias(name.package, {});
+        if (!maybePkg) {
+            mContext->getDiagnostics()->error(DiagMessage(src) << "invalid namespace prefix '"
+                                              << name.package << "'");
+            mError = true;
+            return;
+        }
+
+        const xml::ExtractedPackage& pkg = maybePkg.value();
+        const bool privateNamespace = pkg.privateNamespace || ref.value().privateReference;
+
+        InlineDeclaration decl;
+        decl.el = el;
+        decl.attrName = name.entry;
+        if (!pkg.package.empty()) {
+            decl.attrNamespaceUri = xml::buildPackageNamespace(pkg.package, privateNamespace);
+        }
+
+        mInlineDeclarations.push_back(std::move(decl));
+    }
+
+    const std::vector<InlineDeclaration>& getInlineDeclarations() const {
+        return mInlineDeclarations;
+    }
+
+    bool hasError() const {
+        return mError;
+    }
+
+private:
+    DISALLOW_COPY_AND_ASSIGN(Visitor);
+
+    IAaptContext* mContext;
+    xml::XmlResource* mXmlResource;
+    std::vector<InlineDeclaration> mInlineDeclarations;
+    bool mError = false;
+};
+
+} // namespace
+
+bool InlineXmlFormatParser::consume(IAaptContext* context, xml::XmlResource* doc) {
+    Visitor visitor(context, doc);
+    doc->root->accept(&visitor);
+    if (visitor.hasError()) {
+        return false;
+    }
+
+    size_t nameSuffixCounter = 0;
+    for (const Visitor::InlineDeclaration& decl : visitor.getInlineDeclarations()) {
+        auto newDoc = util::make_unique<xml::XmlResource>();
+        newDoc->file.config = doc->file.config;
+        newDoc->file.source = doc->file.source.withLine(decl.el->lineNumber);
+        newDoc->file.name = doc->file.name;
+
+        // Modify the new entry name. We need to suffix the entry with a number to avoid
+        // local collisions, then mangle it with the empty package, such that it won't show up
+        // in R.java.
+
+        newDoc->file.name.entry = NameMangler::mangleEntry(
+                {}, newDoc->file.name.entry + "__" + std::to_string(nameSuffixCounter));
+
+        // Extracted elements must be the only child of <aapt:attr>.
+        // Make sure there is one root node in the children (ignore empty text).
+        for (auto& child : decl.el->children) {
+            const Source childSource = doc->file.source.withLine(child->lineNumber);
+            if (xml::Text* t = xml::nodeCast<xml::Text>(child.get())) {
+                if (!util::trimWhitespace(t->text).empty()) {
+                    context->getDiagnostics()->error(DiagMessage(childSource)
+                                                     << "can't extract text into its own resource");
+                    return false;
+                }
+            } else if (newDoc->root) {
+                context->getDiagnostics()->error(DiagMessage(childSource)
+                                                 << "inline XML resources must have a single root");
+                return false;
+            } else {
+                newDoc->root = std::move(child);
+                newDoc->root->parent = nullptr;
+            }
+        }
+
+        // Walk up and find the parent element.
+        xml::Node* node = decl.el;
+        xml::Element* parentEl = nullptr;
+        while (node->parent && (parentEl = xml::nodeCast<xml::Element>(node->parent)) == nullptr) {
+            node = node->parent;
+        }
+
+        if (!parentEl) {
+            context->getDiagnostics()->error(DiagMessage(newDoc->file.source)
+                                             << "no suitable parent for inheriting attribute");
+            return false;
+        }
+
+        // Add the inline attribute to the parent.
+        parentEl->attributes.push_back(xml::Attribute{
+                decl.attrNamespaceUri, decl.attrName, "@" + newDoc->file.name.toString() });
+
+        // Delete the subtree.
+        for (auto iter = parentEl->children.begin(); iter != parentEl->children.end(); ++iter) {
+            if (iter->get() == node) {
+                parentEl->children.erase(iter);
+                break;
+            }
+        }
+
+        mQueue.push_back(std::move(newDoc));
+
+        nameSuffixCounter++;
+    }
+    return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/compile/InlineXmlFormatParser.h b/tools/aapt2/compile/InlineXmlFormatParser.h
new file mode 100644
index 0000000..69065fd
--- /dev/null
+++ b/tools/aapt2/compile/InlineXmlFormatParser.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef AAPT_COMPILE_INLINEXMLFORMATPARSER_H
+#define AAPT_COMPILE_INLINEXMLFORMATPARSER_H
+
+#include "process/IResourceTableConsumer.h"
+
+#include <android-base/macros.h>
+#include <memory>
+#include <vector>
+
+namespace aapt {
+
+/**
+ * Extracts Inline XML definitions into their own xml::XmlResource objects.
+ *
+ * Inline XML looks like:
+ *
+ * <animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ *                  xmlns:aapt="http://schemas.android.com/aapt" >
+ *   <aapt:attr name="android:drawable" >
+ *     <vector
+ *       android:height="64dp"
+ *       android:width="64dp"
+ *       android:viewportHeight="600"
+ *       android:viewportWidth="600"/>
+ *   </aapt:attr>
+ * </animated-vector>
+ *
+ * The <vector> will be extracted into its own XML file and <animated-vector> will
+ * gain an attribute 'android:drawable' set to a reference to the extracted <vector> resource.
+ */
+class InlineXmlFormatParser : public IXmlResourceConsumer {
+public:
+    explicit InlineXmlFormatParser() = default;
+
+    bool consume(IAaptContext* context, xml::XmlResource* doc) override;
+
+    std::vector<std::unique_ptr<xml::XmlResource>>& getExtractedInlineXmlDocuments() {
+        return mQueue;
+    }
+
+private:
+    DISALLOW_COPY_AND_ASSIGN(InlineXmlFormatParser);
+
+    std::vector<std::unique_ptr<xml::XmlResource>> mQueue;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_COMPILE_INLINEXMLFORMATPARSER_H */
diff --git a/tools/aapt2/compile/InlineXmlFormatParser_test.cpp b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp
new file mode 100644
index 0000000..8d62210
--- /dev/null
+++ b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2016 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 "compile/InlineXmlFormatParser.h"
+#include "test/Test.h"
+
+namespace aapt {
+
+TEST(InlineXmlFormatParserTest, PassThrough) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
+      <View xmlns:android="http://schemas.android.com/apk/res/android">
+        <View android:text="hey">
+          <View android:id="hi" />
+        </View>
+      </View>)EOF");
+
+    InlineXmlFormatParser parser;
+    ASSERT_TRUE(parser.consume(context.get(), doc.get()));
+    EXPECT_EQ(0u, parser.getExtractedInlineXmlDocuments().size());
+}
+
+TEST(InlineXmlFormatParserTest, ExtractOneXmlResource) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
+      <View1 xmlns:android="http://schemas.android.com/apk/res/android"
+            xmlns:aapt="http://schemas.android.com/aapt">
+        <aapt:attr name="android:text">
+          <View2 android:text="hey">
+            <View3 android:id="hi" />
+          </View2>
+        </aapt:attr>
+      </View1>)EOF");
+
+    doc->file.name = test::parseNameOrDie("layout/main");
+
+    InlineXmlFormatParser parser;
+    ASSERT_TRUE(parser.consume(context.get(), doc.get()));
+
+    // One XML resource should have been extracted.
+    EXPECT_EQ(1u, parser.getExtractedInlineXmlDocuments().size());
+
+    xml::Element* el = xml::findRootElement(doc.get());
+    ASSERT_NE(nullptr, el);
+
+    EXPECT_EQ("View1", el->name);
+
+    // The <aapt:attr> tag should be extracted.
+    EXPECT_EQ(nullptr, el->findChild(xml::kSchemaAapt, "attr"));
+
+    // The 'android:text' attribute should be set with a reference.
+    xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "text");
+    ASSERT_NE(nullptr, attr);
+
+    ResourceNameRef nameRef;
+    ASSERT_TRUE(ResourceUtils::parseReference(attr->value, &nameRef));
+
+    xml::XmlResource* extractedDoc = parser.getExtractedInlineXmlDocuments()[0].get();
+    ASSERT_NE(nullptr, extractedDoc);
+
+    // Make sure the generated reference is correct.
+    EXPECT_EQ(nameRef.package, extractedDoc->file.name.package);
+    EXPECT_EQ(nameRef.type, extractedDoc->file.name.type);
+    EXPECT_EQ(nameRef.entry, extractedDoc->file.name.entry);
+
+    // Verify the structure of the extracted XML.
+    el = xml::findRootElement(extractedDoc);
+    ASSERT_NE(nullptr, el);
+    EXPECT_EQ("View2", el->name);
+    EXPECT_NE(nullptr, el->findChild({}, "View3"));
+}
+
+TEST(InlineXmlFormatParserTest, ExtractTwoXmlResources) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
+      <View1 xmlns:android="http://schemas.android.com/apk/res/android"
+            xmlns:aapt="http://schemas.android.com/aapt">
+        <aapt:attr name="android:text">
+          <View2 android:text="hey">
+            <View3 android:id="hi" />
+          </View2>
+        </aapt:attr>
+
+        <aapt:attr name="android:drawable">
+          <vector />
+        </aapt:attr>
+      </View1>)EOF");
+
+    doc->file.name = test::parseNameOrDie("layout/main");
+
+    InlineXmlFormatParser parser;
+    ASSERT_TRUE(parser.consume(context.get(), doc.get()));
+    ASSERT_EQ(2u, parser.getExtractedInlineXmlDocuments().size());
+
+    xml::Element* el = xml::findRootElement(doc.get());
+    ASSERT_NE(nullptr, el);
+
+    EXPECT_EQ("View1", el->name);
+
+    xml::Attribute* attrText = el->findAttribute(xml::kSchemaAndroid, "text");
+    ASSERT_NE(nullptr, attrText);
+
+    xml::Attribute* attrDrawable = el->findAttribute(xml::kSchemaAndroid, "drawable");
+    ASSERT_NE(nullptr, attrDrawable);
+
+    // The two extracted resources should have different names.
+    EXPECT_NE(attrText->value, attrDrawable->value);
+
+    // The child <aapt:attr> elements should be gone.
+    EXPECT_EQ(nullptr, el->findChild(xml::kSchemaAapt, "attr"));
+
+    xml::XmlResource* extractedDocText = parser.getExtractedInlineXmlDocuments()[0].get();
+    ASSERT_NE(nullptr, extractedDocText);
+    el = xml::findRootElement(extractedDocText);
+    ASSERT_NE(nullptr, el);
+    EXPECT_EQ("View2", el->name);
+
+    xml::XmlResource* extractedDocDrawable = parser.getExtractedInlineXmlDocuments()[1].get();
+    ASSERT_NE(nullptr, extractedDocDrawable);
+    el = xml::findRootElement(extractedDocDrawable);
+    ASSERT_NE(nullptr, el);
+    EXPECT_EQ("vector", el->name);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/dump/Dump.cpp b/tools/aapt2/dump/Dump.cpp
index 88c6f64..f61ec94 100644
--- a/tools/aapt2/dump/Dump.cpp
+++ b/tools/aapt2/dump/Dump.cpp
@@ -37,6 +37,7 @@
     std::unique_ptr<ResourceFile> file = deserializeCompiledFileFromPb(pbFile, source,
                                                                        context->getDiagnostics());
     if (!file) {
+        context->getDiagnostics()->warn(DiagMessage() << "failed to read compiled file");
         return;
     }
 
@@ -112,9 +113,27 @@
         if (!table) {
             // Try as a compiled file.
             CompiledFileInputStream input(fileMap->getDataPtr(), fileMap->getDataLength());
-            if (const pb::CompiledFile* pbFile = input.CompiledFile()) {
-               dumpCompiledFile(*pbFile, input.data(), input.size(), Source(filePath), context);
-               return;
+
+            uint32_t numFiles = 0;
+            if (!input.ReadLittleEndian32(&numFiles)) {
+                return;
+            }
+
+            for (uint32_t i = 0; i < numFiles; i++) {
+                pb::CompiledFile compiledFile;
+                if (!input.ReadCompiledFile(&compiledFile)) {
+                    context->getDiagnostics()->warn(DiagMessage() << "failed to read compiled file");
+                    return;
+                }
+
+                uint64_t offset, len;
+                if (!input.ReadDataMetaData(&offset, &len)) {
+                    context->getDiagnostics()->warn(DiagMessage() << "failed to read meta data");
+                    return;
+                }
+
+                const void* data = static_cast<const uint8_t*>(fileMap->getDataPtr()) + offset;
+                dumpCompiledFile(compiledFile, data, len, Source(filePath), context);
             }
         }
     }
diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/flatten/Archive.h
index 34c10ad..96d8512 100644
--- a/tools/aapt2/flatten/Archive.h
+++ b/tools/aapt2/flatten/Archive.h
@@ -41,7 +41,8 @@
     size_t uncompressedSize;
 };
 
-struct IArchiveWriter : public google::protobuf::io::CopyingOutputStream {
+class IArchiveWriter : public google::protobuf::io::CopyingOutputStream {
+public:
     virtual ~IArchiveWriter() = default;
 
     virtual bool startEntry(const StringPiece& path, uint32_t flags) = 0;
diff --git a/tools/aapt2/integration-tests/AppOne/res/layout/special.xml b/tools/aapt2/integration-tests/AppOne/res/layout/special.xml
new file mode 100644
index 0000000..28c85ca
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/layout/special.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+    <include>
+        <aapt:attr name="layout" xmlns:aapt="http://schemas.android.com/aapt">
+            <RelativeLayout android:id="@+id/hello" />
+        </aapt:attr>
+    </include>
+</View>
diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h
index 467e604..34eed63 100644
--- a/tools/aapt2/io/Data.h
+++ b/tools/aapt2/io/Data.h
@@ -17,9 +17,9 @@
 #ifndef AAPT_IO_DATA_H
 #define AAPT_IO_DATA_H
 
-#include <utils/FileMap.h>
-
+#include <android-base/macros.h>
 #include <memory>
+#include <utils/FileMap.h>
 
 namespace aapt {
 namespace io {
@@ -35,6 +35,28 @@
     virtual size_t size() const = 0;
 };
 
+class DataSegment : public IData {
+public:
+    explicit DataSegment(std::unique_ptr<IData> data, size_t offset, size_t len) :
+            mData(std::move(data)), mOffset(offset), mLen(len) {
+    }
+
+    const void* data() const override {
+        return static_cast<const uint8_t*>(mData->data()) + mOffset;
+    }
+
+    size_t size() const override {
+        return mLen;
+    }
+
+private:
+    DISALLOW_COPY_AND_ASSIGN(DataSegment);
+
+    std::unique_ptr<IData> mData;
+    size_t mOffset;
+    size_t mLen;
+};
+
 /**
  * Implementation of IData that exposes a memory mapped file. The mmapped file is owned by this
  * object.
diff --git a/tools/aapt2/io/File.cpp b/tools/aapt2/io/File.cpp
new file mode 100644
index 0000000..739c0d2
--- /dev/null
+++ b/tools/aapt2/io/File.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2016 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 "io/File.h"
+
+#include <memory>
+
+namespace aapt {
+namespace io {
+
+IFile* IFile::createFileSegment(size_t offset, size_t len) {
+   FileSegment* fileSegment = new FileSegment(this, offset, len);
+   mSegments.push_back(std::unique_ptr<IFile>(fileSegment));
+   return fileSegment;
+}
+
+std::unique_ptr<IData> FileSegment::openAsData() {
+    std::unique_ptr<IData> data = mFile->openAsData();
+    if (!data) {
+        return {};
+    }
+
+    if (mOffset <= data->size() - mLen) {
+        return util::make_unique<DataSegment>(std::move(data), mOffset, mLen);
+    }
+    return {};
+}
+
+} // namespace io
+} // namespace aapt
diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h
index b4d4971..807981e 100644
--- a/tools/aapt2/io/File.h
+++ b/tools/aapt2/io/File.h
@@ -19,7 +19,10 @@
 
 #include "Source.h"
 #include "io/Data.h"
+#include "util/Util.h"
 
+#include <android-base/macros.h>
+#include <list>
 #include <memory>
 #include <vector>
 
@@ -50,6 +53,37 @@
      * a ZIP archive from the path to the containing ZIP archive.
      */
     virtual const Source& getSource() const = 0;
+
+    IFile* createFileSegment(size_t offset, size_t len);
+
+private:
+    // Any segments created from this IFile need to be owned by this IFile, so keep them
+    // in a list. This will never be read, so we prefer better insertion performance
+    // than cache locality, hence the list.
+    std::list<std::unique_ptr<IFile>> mSegments;
+};
+
+/**
+ * An IFile that wraps an underlying IFile but limits it to a subsection of that file.
+ */
+class FileSegment : public IFile {
+public:
+    explicit FileSegment(IFile* file, size_t offset, size_t len) :
+            mFile(file), mOffset(offset), mLen(len) {
+    }
+
+    std::unique_ptr<IData> openAsData() override;
+
+    const Source& getSource() const override {
+        return mFile->getSource();
+    }
+
+private:
+    DISALLOW_COPY_AND_ASSIGN(FileSegment);
+
+    IFile* mFile;
+    size_t mOffset;
+    size_t mLen;
 };
 
 class IFileCollectionIterator {
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
diff --git a/tools/aapt2/process/IResourceTableConsumer.h b/tools/aapt2/process/IResourceTableConsumer.h
index 69b7d92..a7e752a 100644
--- a/tools/aapt2/process/IResourceTableConsumer.h
+++ b/tools/aapt2/process/IResourceTableConsumer.h
@@ -51,7 +51,7 @@
 };
 
 namespace xml {
-struct XmlResource;
+class XmlResource;
 }
 
 struct IXmlResourceConsumer {
diff --git a/tools/aapt2/proto/ProtoSerialize.h b/tools/aapt2/proto/ProtoSerialize.h
index 6e224ab..cc7c251 100644
--- a/tools/aapt2/proto/ProtoSerialize.h
+++ b/tools/aapt2/proto/ProtoSerialize.h
@@ -28,6 +28,40 @@
 
 namespace aapt {
 
+class CompiledFileOutputStream {
+public:
+    explicit CompiledFileOutputStream(google::protobuf::io::ZeroCopyOutputStream* out);
+
+    void WriteLittleEndian32(uint32_t value);
+    void WriteCompiledFile(const pb::CompiledFile* compiledFile);
+    void WriteData(const BigBuffer* buffer);
+    void WriteData(const void* data, size_t len);
+    bool HadError();
+
+private:
+    DISALLOW_COPY_AND_ASSIGN(CompiledFileOutputStream);
+
+    void ensureAlignedWrite();
+
+    google::protobuf::io::CodedOutputStream mOut;
+};
+
+class CompiledFileInputStream {
+public:
+    explicit CompiledFileInputStream(const void* data, size_t size);
+
+    bool ReadLittleEndian32(uint32_t* outVal);
+    bool ReadCompiledFile(pb::CompiledFile* outVal);
+    bool ReadDataMetaData(uint64_t* outOffset, uint64_t* outLen);
+
+private:
+    DISALLOW_COPY_AND_ASSIGN(CompiledFileInputStream);
+
+    void ensureAlignedRead();
+
+    google::protobuf::io::CodedInputStream mIn;
+};
+
 std::unique_ptr<pb::ResourceTable> serializeTableToPb(ResourceTable* table);
 std::unique_ptr<ResourceTable> deserializeTableFromPb(const pb::ResourceTable& pbTable,
                                                       const Source& source,
@@ -38,40 +72,6 @@
                                                             const Source& source,
                                                             IDiagnostics* diag);
 
-class CompiledFileOutputStream : public google::protobuf::io::CopyingOutputStream {
-public:
-    CompiledFileOutputStream(google::protobuf::io::ZeroCopyOutputStream* out,
-                             pb::CompiledFile* pbFile);
-    bool Write(const void* data, int size) override;
-    bool Finish();
-
-private:
-    bool ensureFileWritten();
-
-    google::protobuf::io::CodedOutputStream mOut;
-    pb::CompiledFile* mPbFile;
-
-    DISALLOW_COPY_AND_ASSIGN(CompiledFileOutputStream);
-};
-
-class CompiledFileInputStream {
-public:
-    CompiledFileInputStream(const void* data, size_t size);
-
-    const pb::CompiledFile* CompiledFile();
-
-    const void* data();
-    size_t size();
-
-private:
-    google::protobuf::io::CodedInputStream mIn;
-    std::unique_ptr<pb::CompiledFile> mPbFile;
-    const uint8_t* mData;
-    size_t mSize;
-
-    DISALLOW_COPY_AND_ASSIGN(CompiledFileInputStream);
-};
-
 } // namespace aapt
 
 #endif /* AAPT_FLATTEN_TABLEPROTOSERIALIZER_H */
diff --git a/tools/aapt2/proto/TableProtoDeserializer.cpp b/tools/aapt2/proto/TableProtoDeserializer.cpp
index ca25c6a..595fa6f 100644
--- a/tools/aapt2/proto/TableProtoDeserializer.cpp
+++ b/tools/aapt2/proto/TableProtoDeserializer.cpp
@@ -468,52 +468,4 @@
     return file;
 }
 
-CompiledFileInputStream::CompiledFileInputStream(const void* data, size_t size) :
-        mIn(static_cast<const uint8_t*>(data), size), mPbFile(),
-        mData(static_cast<const uint8_t*>(data)), mSize(size) {
-}
-
-const pb::CompiledFile* CompiledFileInputStream::CompiledFile() {
-    if (!mPbFile) {
-        std::unique_ptr<pb::CompiledFile> pbFile = util::make_unique<pb::CompiledFile>();
-        uint64_t pbSize = 0u;
-        if (!mIn.ReadLittleEndian64(&pbSize)) {
-            return nullptr;
-        }
-        mIn.PushLimit(static_cast<int>(pbSize));
-        if (!pbFile->ParsePartialFromCodedStream(&mIn)) {
-            return nullptr;
-        }
-
-        const size_t padding = 4 - (pbSize & 0x03);
-        const size_t offset = sizeof(uint64_t) + pbSize + padding;
-        if (offset > mSize) {
-            return nullptr;
-        }
-
-        mData += offset;
-        mSize -= offset;
-        mPbFile = std::move(pbFile);
-    }
-    return mPbFile.get();
-}
-
-const void* CompiledFileInputStream::data() {
-    if (!mPbFile) {
-        if (!CompiledFile()) {
-            return nullptr;
-        }
-    }
-    return mData;
-}
-
-size_t CompiledFileInputStream::size() {
-    if (!mPbFile) {
-        if (!CompiledFile()) {
-            return 0;
-        }
-    }
-    return mSize;
-}
-
 } // namespace aapt
diff --git a/tools/aapt2/proto/TableProtoSerializer.cpp b/tools/aapt2/proto/TableProtoSerializer.cpp
index 425fca6..a5c2cbc 100644
--- a/tools/aapt2/proto/TableProtoSerializer.cpp
+++ b/tools/aapt2/proto/TableProtoSerializer.cpp
@@ -22,6 +22,10 @@
 #include "proto/ProtoSerialize.h"
 #include "util/BigBuffer.h"
 
+using google::protobuf::io::CodedOutputStream;
+using google::protobuf::io::CodedInputStream;
+using google::protobuf::io::ZeroCopyOutputStream;
+
 namespace aapt {
 
 namespace {
@@ -210,7 +214,7 @@
     });
     table->stringPool.prune();
 
-    std::unique_ptr<pb::ResourceTable> pbTable = util::make_unique<pb::ResourceTable>();
+    auto pbTable = util::make_unique<pb::ResourceTable>();
     serializeStringPoolToPb(table->stringPool, pbTable->mutable_string_pool());
 
     StringPool sourcePool, symbolPool;
@@ -274,7 +278,7 @@
 }
 
 std::unique_ptr<pb::CompiledFile> serializeCompiledFileToPb(const ResourceFile& file) {
-    std::unique_ptr<pb::CompiledFile> pbFile = util::make_unique<pb::CompiledFile>();
+    auto pbFile = util::make_unique<pb::CompiledFile>();
     pbFile->set_resource_name(file.name.toString());
     pbFile->set_source_path(file.source.path);
     serializeConfig(file.config, pbFile->mutable_config());
@@ -287,36 +291,112 @@
     return pbFile;
 }
 
-CompiledFileOutputStream::CompiledFileOutputStream(google::protobuf::io::ZeroCopyOutputStream* out,
-                                                   pb::CompiledFile* pbFile) :
-        mOut(out), mPbFile(pbFile) {
+CompiledFileOutputStream::CompiledFileOutputStream(ZeroCopyOutputStream* out) : mOut(out) {
 }
 
-bool CompiledFileOutputStream::ensureFileWritten() {
-    if (mPbFile) {
-        const uint64_t pbSize = mPbFile->ByteSize();
-        mOut.WriteLittleEndian64(pbSize);
-        mPbFile->SerializeWithCachedSizes(&mOut);
-        const size_t padding = 4 - (pbSize & 0x03);
-        if (padding > 0) {
-            uint32_t zero = 0u;
-            mOut.WriteRaw(&zero, padding);
-        }
-        mPbFile = nullptr;
+void CompiledFileOutputStream::ensureAlignedWrite() {
+    const int padding = mOut.ByteCount() % 4;
+    if (padding > 0) {
+        uint32_t zero = 0u;
+        mOut.WriteRaw(&zero, padding);
     }
-    return !mOut.HadError();
 }
 
-bool CompiledFileOutputStream::Write(const void* data, int size) {
-    if (!ensureFileWritten()) {
+void CompiledFileOutputStream::WriteLittleEndian32(uint32_t val) {
+    ensureAlignedWrite();
+    mOut.WriteLittleEndian32(val);
+}
+
+void CompiledFileOutputStream::WriteCompiledFile(const pb::CompiledFile* compiledFile) {
+    ensureAlignedWrite();
+    mOut.WriteLittleEndian64(static_cast<uint64_t>(compiledFile->ByteSize()));
+    compiledFile->SerializeWithCachedSizes(&mOut);
+}
+
+void CompiledFileOutputStream::WriteData(const BigBuffer* buffer) {
+    ensureAlignedWrite();
+    mOut.WriteLittleEndian64(static_cast<uint64_t>(buffer->size()));
+    for (const BigBuffer::Block& block : *buffer) {
+        mOut.WriteRaw(block.buffer.get(), block.size);
+    }
+}
+
+void CompiledFileOutputStream::WriteData(const void* data, size_t len) {
+    ensureAlignedWrite();
+    mOut.WriteLittleEndian64(static_cast<uint64_t>(len));
+    mOut.WriteRaw(data, len);
+}
+
+bool CompiledFileOutputStream::HadError() {
+    return mOut.HadError();
+}
+
+CompiledFileInputStream::CompiledFileInputStream(const void* data, size_t size) :
+        mIn(static_cast<const uint8_t*>(data), size) {
+}
+
+void CompiledFileInputStream::ensureAlignedRead() {
+    const int padding = mIn.CurrentPosition() % 4;
+    if (padding > 0) {
+        // Reads are always 4 byte aligned.
+        mIn.Skip(padding);
+    }
+}
+
+bool CompiledFileInputStream::ReadLittleEndian32(uint32_t* outVal) {
+    ensureAlignedRead();
+    return mIn.ReadLittleEndian32(outVal);
+}
+
+bool CompiledFileInputStream::ReadCompiledFile(pb::CompiledFile* outVal) {
+    ensureAlignedRead();
+
+    uint64_t pbSize = 0u;
+    if (!mIn.ReadLittleEndian64(&pbSize)) {
         return false;
     }
-    mOut.WriteRaw(data, size);
-    return !mOut.HadError();
+
+    CodedInputStream::Limit l = mIn.PushLimit(static_cast<int>(pbSize));
+
+    // Check that we haven't tried to read past the end.
+    if (static_cast<uint64_t>(mIn.BytesUntilLimit()) != pbSize) {
+        mIn.PopLimit(l);
+        mIn.PushLimit(0);
+        return false;
+    }
+
+    if (!outVal->ParsePartialFromCodedStream(&mIn)) {
+        mIn.PopLimit(l);
+        mIn.PushLimit(0);
+        return false;
+    }
+
+    mIn.PopLimit(l);
+    return true;
 }
 
-bool CompiledFileOutputStream::Finish() {
-    return ensureFileWritten();
+bool CompiledFileInputStream::ReadDataMetaData(uint64_t* outOffset, uint64_t* outLen) {
+    ensureAlignedRead();
+
+    uint64_t pbSize = 0u;
+    if (!mIn.ReadLittleEndian64(&pbSize)) {
+        return false;
+    }
+
+    // Check that we aren't trying to read past the end.
+    if (pbSize > static_cast<uint64_t>(mIn.BytesUntilLimit())) {
+        mIn.PushLimit(0);
+        return false;
+    }
+
+    uint64_t offset = static_cast<uint64_t>(mIn.CurrentPosition());
+    if (!mIn.Skip(pbSize)) {
+        return false;
+    }
+
+    *outOffset = offset;
+    *outLen = pbSize;
+    return true;
 }
 
 } // namespace aapt
diff --git a/tools/aapt2/proto/TableProtoSerializer_test.cpp b/tools/aapt2/proto/TableProtoSerializer_test.cpp
index af1b011..2bd9767 100644
--- a/tools/aapt2/proto/TableProtoSerializer_test.cpp
+++ b/tools/aapt2/proto/TableProtoSerializer_test.cpp
@@ -18,6 +18,8 @@
 #include "proto/ProtoSerialize.h"
 #include "test/Test.h"
 
+using namespace google::protobuf::io;
+
 namespace aapt {
 
 TEST(TableProtoSerializer, SerializeSinglePackage) {
@@ -115,33 +117,66 @@
     f.source.path = "res/layout-hdpi-v9/main.xml";
     f.exportedSymbols.push_back(SourcedResourceName{ test::parseNameOrDie("id/unchecked"), 23u });
 
-    const std::string expectedData = "1234";
-
-    std::unique_ptr<pb::CompiledFile> pbFile = serializeCompiledFileToPb(f);
+    const std::string expectedData1 = "123";
+    const std::string expectedData2 = "1234";
 
     std::string outputStr;
     {
-        google::protobuf::io::StringOutputStream outStream(&outputStr);
-        CompiledFileOutputStream outFileStream(&outStream, pbFile.get());
+        std::unique_ptr<pb::CompiledFile> pbFile1 = serializeCompiledFileToPb(f);
 
-        ASSERT_TRUE(outFileStream.Write(expectedData.data(), expectedData.size()));
-        ASSERT_TRUE(outFileStream.Finish());
+        f.name.entry = "__" + f.name.entry + "$0";
+        std::unique_ptr<pb::CompiledFile> pbFile2 = serializeCompiledFileToPb(f);
+
+        StringOutputStream outStream(&outputStr);
+        CompiledFileOutputStream outFileStream(&outStream);
+        outFileStream.WriteLittleEndian32(2);
+        outFileStream.WriteCompiledFile(pbFile1.get());
+        outFileStream.WriteData(expectedData1.data(), expectedData1.size());
+        outFileStream.WriteCompiledFile(pbFile2.get());
+        outFileStream.WriteData(expectedData2.data(), expectedData2.size());
+        ASSERT_FALSE(outFileStream.HadError());
     }
 
     CompiledFileInputStream inFileStream(outputStr.data(), outputStr.size());
-    const pb::CompiledFile* newPbFile = inFileStream.CompiledFile();
-    ASSERT_NE(nullptr, newPbFile);
+    uint32_t numFiles = 0;
+    ASSERT_TRUE(inFileStream.ReadLittleEndian32(&numFiles));
+    ASSERT_EQ(2u, numFiles);
 
-    std::unique_ptr<ResourceFile> file = deserializeCompiledFileFromPb(*newPbFile, Source("test"),
+    // Read the first compiled file.
+
+    pb::CompiledFile newPbFile;
+    ASSERT_TRUE(inFileStream.ReadCompiledFile(&newPbFile));
+
+    std::unique_ptr<ResourceFile> file = deserializeCompiledFileFromPb(newPbFile, Source("test"),
                                                                        context->getDiagnostics());
     ASSERT_NE(nullptr, file);
 
-    std::string actualData((const char*)inFileStream.data(), inFileStream.size());
-    EXPECT_EQ(expectedData, actualData);
-    EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(inFileStream.data()) & 0x03);
+    uint64_t offset, len;
+    ASSERT_TRUE(inFileStream.ReadDataMetaData(&offset, &len));
+
+    std::string actualData(outputStr.data() + offset, len);
+    EXPECT_EQ(expectedData1, actualData);
+
+    // Expect the data to be aligned.
+    EXPECT_EQ(0u, offset & 0x03);
 
     ASSERT_EQ(1u, file->exportedSymbols.size());
     EXPECT_EQ(test::parseNameOrDie("id/unchecked"), file->exportedSymbols[0].name);
+
+    // Read the second compiled file.
+
+    ASSERT_TRUE(inFileStream.ReadCompiledFile(&newPbFile));
+
+    file = deserializeCompiledFileFromPb(newPbFile, Source("test"), context->getDiagnostics());
+    ASSERT_NE(nullptr, file);
+
+    ASSERT_TRUE(inFileStream.ReadDataMetaData(&offset, &len));
+
+    actualData = std::string(outputStr.data() + offset, len);
+    EXPECT_EQ(expectedData2, actualData);
+
+    // Expect the data to be aligned.
+    EXPECT_EQ(0u, offset & 0x03);
 }
 
 TEST(TableProtoSerializer, DeserializeCorruptHeaderSafely) {
@@ -152,19 +187,27 @@
 
     std::string outputStr;
     {
-        google::protobuf::io::StringOutputStream outStream(&outputStr);
-        CompiledFileOutputStream outFileStream(&outStream, pbFile.get());
-
-        ASSERT_TRUE(outFileStream.Write(expectedData.data(), expectedData.size()));
-        ASSERT_TRUE(outFileStream.Finish());
+        StringOutputStream outStream(&outputStr);
+        CompiledFileOutputStream outFileStream(&outStream);
+        outFileStream.WriteLittleEndian32(1);
+        outFileStream.WriteCompiledFile(pbFile.get());
+        outFileStream.WriteData(expectedData.data(), expectedData.size());
+        ASSERT_FALSE(outFileStream.HadError());
     }
 
-    outputStr[0] = 0xff;
+    outputStr[4] = 0xff;
 
     CompiledFileInputStream inFileStream(outputStr.data(), outputStr.size());
-    EXPECT_EQ(nullptr, inFileStream.CompiledFile());
-    EXPECT_EQ(nullptr, inFileStream.data());
-    EXPECT_EQ(0u, inFileStream.size());
+
+    uint32_t numFiles = 0;
+    EXPECT_TRUE(inFileStream.ReadLittleEndian32(&numFiles));
+    EXPECT_EQ(1u, numFiles);
+
+    pb::CompiledFile newPbFile;
+    EXPECT_FALSE(inFileStream.ReadCompiledFile(&newPbFile));
+
+    uint64_t offset, len;
+    EXPECT_FALSE(inFileStream.ReadDataMetaData(&offset, &len));
 }
 
 } // namespace aapt
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index 39bd5bf..28de78a 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -322,6 +322,21 @@
     return util::make_unique<XmlResource>(ResourceFile{}, std::move(root));
 }
 
+std::unique_ptr<Node> Namespace::clone() {
+    auto ns = util::make_unique<Namespace>();
+    ns->comment = comment;
+    ns->lineNumber = lineNumber;
+    ns->columnNumber = columnNumber;
+    ns->namespacePrefix = namespacePrefix;
+    ns->namespaceUri = namespaceUri;
+
+    ns->children.reserve(children.size());
+    for (const std::unique_ptr<xml::Node>& child : children) {
+        ns->addChild(child->clone());
+    }
+    return std::move(ns);
+}
+
 Element* findRootElement(XmlResource* doc) {
     return findRootElement(doc->root.get());
 }
@@ -406,6 +421,36 @@
     return elements;
 }
 
+std::unique_ptr<Node> Element::clone() {
+    auto el = util::make_unique<Element>();
+    el->comment = comment;
+    el->lineNumber = lineNumber;
+    el->columnNumber = columnNumber;
+    el->name = name;
+    el->namespaceUri = namespaceUri;
+
+    el->attributes.reserve(attributes.size());
+    for (xml::Attribute& attr : attributes) {
+        // Don't copy compiled values or attributes.
+        el->attributes.push_back(xml::Attribute{ attr.namespaceUri, attr.name, attr.value });
+    }
+
+    el->children.reserve(children.size());
+    for (const std::unique_ptr<xml::Node>& child : children) {
+        el->addChild(child->clone());
+    }
+    return std::move(el);
+}
+
+std::unique_ptr<Node> Text::clone() {
+    auto t = util::make_unique<Text>();
+    t->comment = comment;
+    t->lineNumber = lineNumber;
+    t->columnNumber = columnNumber;
+    t->text = text;
+    return std::move(t);
+}
+
 void PackageAwareVisitor::visit(Namespace* ns) {
    bool added = false;
    if (Maybe<ExtractedPackage> maybePackage = extractPackageFromNamespace(ns->namespaceUri)) {
diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h
index d083d82..e4f41b0 100644
--- a/tools/aapt2/xml/XmlDom.h
+++ b/tools/aapt2/xml/XmlDom.h
@@ -32,12 +32,13 @@
 namespace aapt {
 namespace xml {
 
-struct RawVisitor;
+class RawVisitor;
 
 /**
  * Base class for all XML nodes.
  */
-struct Node {
+class Node {
+public:
     Node* parent = nullptr;
     size_t lineNumber = 0;
     size_t columnNumber = 0;
@@ -48,6 +49,7 @@
 
     void addChild(std::unique_ptr<Node> child);
     virtual void accept(RawVisitor* visitor) = 0;
+    virtual std::unique_ptr<Node> clone() = 0;
 };
 
 /**
@@ -55,16 +57,20 @@
  * subclass of Node.
  */
 template <typename Derived>
-struct BaseNode : public Node {
+class BaseNode : public Node {
+public:
     virtual void accept(RawVisitor* visitor) override;
 };
 
 /**
  * A Namespace XML node. Can only have one child.
  */
-struct Namespace : public BaseNode<Namespace> {
+class Namespace : public BaseNode<Namespace> {
+public:
     std::string namespacePrefix;
     std::string namespaceUri;
+
+    std::unique_ptr<Node> clone() override;
 };
 
 struct AaptAttribute {
@@ -87,7 +93,8 @@
 /**
  * An Element XML node.
  */
-struct Element : public BaseNode<Element> {
+class Element : public BaseNode<Element> {
+public:
     std::string namespaceUri;
     std::string name;
     std::vector<Attribute> attributes;
@@ -99,19 +106,24 @@
                                          const StringPiece& attrName,
                                          const StringPiece& attrValue);
     std::vector<xml::Element*> getChildElements();
+    std::unique_ptr<Node> clone() override;
 };
 
 /**
  * A Text (CDATA) XML node. Can not have any children.
  */
-struct Text : public BaseNode<Text> {
+class Text : public BaseNode<Text> {
+public:
     std::string text;
+
+    std::unique_ptr<Node> clone() override;
 };
 
 /**
  * An XML resource with a source, name, and XML tree.
  */
-struct XmlResource {
+class XmlResource {
+public:
     ResourceFile file;
     std::unique_ptr<xml::Node> root;
 };
@@ -136,7 +148,8 @@
  * A visitor interface for the different XML Node subtypes. This will not traverse into
  * children. Use Visitor for that.
  */
-struct RawVisitor {
+class RawVisitor {
+public:
     virtual ~RawVisitor() = default;
 
     virtual void visit(Namespace* node) {}
@@ -147,7 +160,8 @@
 /**
  * Visitor whose default implementation visits the children nodes of any node.
  */
-struct Visitor : public RawVisitor {
+class Visitor : public RawVisitor {
+public:
     using RawVisitor::visit;
 
     void visit(Namespace* node) override {
@@ -173,6 +187,13 @@
  * An XML DOM visitor that will record the package name for a namespace prefix.
  */
 class PackageAwareVisitor : public Visitor, public IPackageDeclStack {
+public:
+    using Visitor::visit;
+
+    void visit(Namespace* ns) override;
+    Maybe<ExtractedPackage> transformPackageAlias(
+            const StringPiece& alias, const StringPiece& localPackage) const override;
+
 private:
     struct PackageDecl {
         std::string prefix;
@@ -180,13 +201,6 @@
     };
 
     std::vector<PackageDecl> mPackageDecls;
-
-public:
-    using Visitor::visit;
-
-    void visit(Namespace* ns) override;
-    Maybe<ExtractedPackage> transformPackageAlias(
-            const StringPiece& alias, const StringPiece& localPackage) const override;
 };
 
 // Implementations
@@ -197,7 +211,8 @@
 }
 
 template <typename T>
-struct NodeCastImpl : public RawVisitor {
+class NodeCastImpl : public RawVisitor {
+public:
     using RawVisitor::visit;
 
     T* value = nullptr;
diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp
index 0e9d005..b570fd7 100644
--- a/tools/aapt2/xml/XmlUtil.cpp
+++ b/tools/aapt2/xml/XmlUtil.cpp
@@ -23,8 +23,8 @@
 namespace aapt {
 namespace xml {
 
-std::string buildPackageNamespace(const StringPiece& package) {
-    std::string result = kSchemaPublicPrefix;
+std::string buildPackageNamespace(const StringPiece& package, bool privateReference) {
+    std::string result = privateReference ? kSchemaPrivatePrefix : kSchemaPublicPrefix;
     result.append(package.data(), package.size());
     return result;
 }
diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h
index b75d8ac..a6ad79d 100644
--- a/tools/aapt2/xml/XmlUtil.h
+++ b/tools/aapt2/xml/XmlUtil.h
@@ -30,6 +30,7 @@
 constexpr const char* kSchemaPrivatePrefix = "http://schemas.android.com/apk/prv/res/";
 constexpr const char* kSchemaAndroid = "http://schemas.android.com/apk/res/android";
 constexpr const char* kSchemaTools = "http://schemas.android.com/tools";
+constexpr const char* kSchemaAapt = "http://schemas.android.com/aapt";
 
 /**
  * Result of extracting a package name from a namespace URI declaration.
@@ -62,8 +63,12 @@
  * Returns an XML Android namespace for the given package of the form:
  *
  * http://schemas.android.com/apk/res/<package>
+ *
+ * If privateReference == true, the package will be of the form:
+ *
+ * http://schemas.android.com/apk/prv/res/<package>
  */
-std::string buildPackageNamespace(const StringPiece& package);
+std::string buildPackageNamespace(const StringPiece& package, bool privateReference=false);
 
 /**
  * Interface representing a stack of XML namespace declarations. When looking up the package