AAPT2: Support static lib referencing static lib

When a static library A references static library B,
and app C references both A and B, we get the following symbol merging,
symbols from library B get imported twice.

We must only check that symbol references to library B are valid
when building library A. We should only merge all the symbols
when building final app C.

Change-Id: I23cba33b0901dcbb5328d9c9dfaa6a979c073c36
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 5ef4311..23d679d 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -30,6 +30,7 @@
 	BinaryXmlPullParser.cpp \
 	BindingXmlPullParser.cpp \
 	ConfigDescription.cpp \
+	Debug.cpp \
 	Files.cpp \
 	Flag.cpp \
 	JavaClassGenerator.cpp \
diff --git a/tools/aapt2/BinaryResourceParser.cpp b/tools/aapt2/BinaryResourceParser.cpp
index d16f63b..bbd21fb 100644
--- a/tools/aapt2/BinaryResourceParser.cpp
+++ b/tools/aapt2/BinaryResourceParser.cpp
@@ -475,10 +475,17 @@
             source.line = entry->sourceLine;
         }
 
-        if (!mTable->markPublic(name, resId, source)) {
+        if (!mTable->markPublicAllowMangled(name, resId, source)) {
             return false;
         }
 
+        // Add this resource name->id mapping to the index so
+        // that we can resolve all ID references to name references.
+        auto cacheIter = mIdIndex.find(resId);
+        if (cacheIter == mIdIndex.end()) {
+            mIdIndex.insert({ resId, name });
+        }
+
         entry++;
     }
     return true;
@@ -611,12 +618,12 @@
             source.line = sourceBlock->line;
         }
 
-        if (!mTable->addResource(name, config, source, std::move(resourceValue))) {
+        if (!mTable->addResourceAllowMangled(name, config, source, std::move(resourceValue))) {
             return false;
         }
 
         if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
-            if (!mTable->markPublic(name, resId, mSource.line(0))) {
+            if (!mTable->markPublicAllowMangled(name, resId, mSource.line(0))) {
                 return false;
             }
         }
@@ -635,6 +642,10 @@
                                                        const ConfigDescription& config,
                                                        const Res_value* value,
                                                        uint16_t flags) {
+    if (name.type == ResourceType::kId) {
+        return util::make_unique<Id>();
+    }
+
     if (value->dataType == Res_value::TYPE_STRING) {
         StringPiece16 str = util::getString(mValuePool, value->data);
 
@@ -697,13 +708,6 @@
                                                     StringPool::Context{ 1, config }));
     }
 
-    if (name.type == ResourceType::kId ||
-            (value->dataType == Res_value::TYPE_NULL &&
-            value->data == Res_value::DATA_NULL_UNDEFINED &&
-            (flags & ResTable_entry::FLAG_WEAK) != 0)) {
-        return util::make_unique<Id>();
-    }
-
     // Treat this as a raw binary primitive.
     return util::make_unique<BinaryPrimitive>(*value);
 }
@@ -789,10 +793,21 @@
                 continue;
             }
 
-            attr->symbols.push_back(Attribute::Symbol{
-                    Reference(mapEntry.name.ident),
-                    mapEntry.value.data
-            });
+            Attribute::Symbol symbol;
+            symbol.value = mapEntry.value.data;
+            if (mapEntry.name.ident == 0) {
+                // The map entry's key (id) is not set. This must be
+                // a symbol reference, so resolve it.
+                ResourceNameRef symbolName;
+                bool result = getSymbol(&mapEntry.name.ident, &symbolName);
+                assert(result);
+                symbol.symbol.name = symbolName.toResourceName();
+            } else {
+                // The map entry's key (id) is a regular reference.
+                symbol.symbol.id = mapEntry.name.ident;
+            }
+
+            attr->symbols.push_back(std::move(symbol));
         }
     }
 
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
new file mode 100644
index 0000000..a7b9bba
--- /dev/null
+++ b/tools/aapt2/Debug.cpp
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Debug.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "Util.h"
+
+#include <algorithm>
+#include <iostream>
+#include <map>
+#include <memory>
+#include <set>
+#include <vector>
+
+namespace aapt {
+
+struct PrintVisitor : ConstValueVisitor {
+    void visit(const Attribute& attr, ValueVisitorArgs&) override {
+        std::cout << "(attr) type=";
+        attr.printMask(std::cout);
+        static constexpr uint32_t kMask = android::ResTable_map::TYPE_ENUM |
+            android::ResTable_map::TYPE_FLAGS;
+        if (attr.typeMask & kMask) {
+            for (const auto& symbol : attr.symbols) {
+                std::cout << "\n        "
+                          << symbol.symbol.name.entry << " (" << symbol.symbol.id << ") = "
+                          << symbol.value;
+            }
+        }
+    }
+
+    void visit(const Style& style, ValueVisitorArgs&) override {
+        std::cout << "(style)";
+        if (style.parent.name.isValid() || style.parent.id.isValid()) {
+            std::cout << " parent=";
+            if (style.parent.name.isValid()) {
+                std::cout << style.parent.name << " ";
+            }
+
+            if (style.parent.id.isValid()) {
+                std::cout << style.parent.id;
+            }
+        }
+
+        for (const auto& entry : style.entries) {
+            std::cout << "\n        ";
+            if (entry.key.name.isValid()) {
+                std::cout << entry.key.name.package << ":" << entry.key.name.entry;
+            }
+
+            if (entry.key.id.isValid()) {
+                std::cout << "(" << entry.key.id << ")";
+            }
+
+            std::cout << "=" << *entry.value;
+        }
+    }
+
+    void visit(const Array& array, ValueVisitorArgs&) override {
+        array.print(std::cout);
+    }
+
+    void visit(const Plural& plural, ValueVisitorArgs&) override {
+        plural.print(std::cout);
+    }
+
+    void visit(const Styleable& styleable, ValueVisitorArgs&) override {
+        styleable.print(std::cout);
+    }
+
+    void visitItem(const Item& item, ValueVisitorArgs& args) override {
+        item.print(std::cout);
+    }
+};
+
+void Debug::printTable(const std::shared_ptr<ResourceTable>& table) {
+    std::cout << "Package name=" << table->getPackage();
+    if (table->getPackageId() != ResourceTable::kUnsetPackageId) {
+        std::cout << " id=" << std::hex << table->getPackageId() << std::dec;
+    }
+    std::cout << std::endl;
+
+    for (const auto& type : *table) {
+        std::cout << "  type " << type->type;
+        if (type->typeId != ResourceTableType::kUnsetTypeId) {
+            std::cout << " id=" << std::hex << type->typeId << std::dec;
+        }
+        std::cout << " entryCount=" << type->entries.size() << std::endl;
+
+        std::vector<const ResourceEntry*> sortedEntries;
+        for (const auto& entry : type->entries) {
+            auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(), entry.get(),
+                    [](const ResourceEntry* a, const ResourceEntry* b) -> bool {
+                        return a->entryId < b->entryId;
+                    });
+            sortedEntries.insert(iter, entry.get());
+        }
+
+        for (const ResourceEntry* entry : sortedEntries) {
+            ResourceId id = { table->getPackageId(), type->typeId, entry->entryId };
+            ResourceName name = { table->getPackage(), type->type, entry->name };
+            std::cout << "    spec resource " << id << " " << name;
+            if (entry->publicStatus.isPublic) {
+                std::cout << " PUBLIC";
+            }
+            std::cout << std::endl;
+
+            PrintVisitor visitor;
+            for (const auto& value : entry->values) {
+                std::cout << "      (" << value.config << ") ";
+                value.value->accept(visitor, {});
+                std::cout << std::endl;
+            }
+        }
+    }
+}
+
+static size_t getNodeIndex(const std::vector<ResourceName>& names, const ResourceName& name) {
+    auto iter = std::lower_bound(names.begin(), names.end(), name);
+    assert(iter != names.end() && *iter == name);
+    return std::distance(names.begin(), iter);
+}
+
+void Debug::printStyleGraph(const std::shared_ptr<ResourceTable>& table) {
+    std::vector<ResourceName> names;
+    std::map<ResourceName, std::set<ResourceName>> graph;
+
+    for (const auto& type : *table) {
+        for (const auto& entry : type->entries) {
+            ResourceName name = { table->getPackage(), type->type, entry->name };
+            for (const auto& value : entry->values) {
+                visitFunc<Style>(*value.value, [&](const Style& style) {
+                    if (style.parent.name.isValid()) {
+                        names.push_back(style.parent.name);
+                        names.push_back(name);
+                        graph[style.parent.name].insert(name);
+                    }
+                });
+            }
+        }
+    }
+
+    std::sort(names.begin(), names.end());
+    auto it1 = std::unique(names.begin(), names.end());
+    names.resize(std::distance(names.begin(), it1));
+
+    std::cout << "digraph styles {\n";
+
+    for (const auto& name : names) {
+        std::cout << "  node_" << getNodeIndex(names, name)
+                  << " [label=\"" << name.entry << "\"];\n";
+    }
+
+    for (const auto& entry : graph) {
+        const ResourceName& parent = entry.first;
+        size_t parentNodeIndex = getNodeIndex(names, parent);
+
+        for (const auto& childName : entry.second) {
+            std::cout << "node_" << getNodeIndex(names, childName) << " -> "
+                      << "node_" << parentNodeIndex << ";\n";
+        }
+    }
+
+    std::cout << "}" << std::endl;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
new file mode 100644
index 0000000..84ae40f
--- /dev/null
+++ b/tools/aapt2/Debug.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_DEBUG_H
+#define AAPT_DEBUG_H
+
+#include "ResourceTable.h"
+
+#include <memory>
+
+namespace aapt {
+
+struct Debug {
+    static void printTable(const std::shared_ptr<ResourceTable>& table);
+    static void printStyleGraph(const std::shared_ptr<ResourceTable>& table);
+};
+
+} // namespace aapt
+
+#endif // AAPT_DEBUG_H
diff --git a/tools/aapt2/JavaClassGenerator.cpp b/tools/aapt2/JavaClassGenerator.cpp
index 2bb0e65..e2ffe79 100644
--- a/tools/aapt2/JavaClassGenerator.cpp
+++ b/tools/aapt2/JavaClassGenerator.cpp
@@ -81,23 +81,28 @@
 }
 
 struct GenArgs : ValueVisitorArgs {
-    GenArgs(std::ostream* o, std::u16string* e) : out(o), entryName(e) {
+    GenArgs(std::ostream* o, const std::u16string* p, std::u16string* e) :
+            out(o), package(p), entryName(e) {
     }
 
     std::ostream* out;
+    const std::u16string* package;
     std::u16string* entryName;
 };
 
 void JavaClassGenerator::visit(const Styleable& styleable, ValueVisitorArgs& a) {
     const StringPiece finalModifier = mOptions.useFinal ? " final" : "";
     std::ostream* out = static_cast<GenArgs&>(a).out;
+    const std::u16string* package = static_cast<GenArgs&>(a).package;
     std::u16string* entryName = static_cast<GenArgs&>(a).entryName;
 
     // This must be sorted by resource ID.
     std::vector<std::pair<ResourceId, ResourceNameRef>> sortedAttributes;
     sortedAttributes.reserve(styleable.entries.size());
     for (const auto& attr : styleable.entries) {
-        assert(attr.id.isValid() && "no ID set for Styleable entry");
+        // If we are not encoding final attributes, the styleable entry may have no ID
+        // if we are building a static library.
+        assert((!mOptions.useFinal || attr.id.isValid()) && "no ID set for Styleable entry");
         assert(attr.name.isValid() && "no name set for Styleable entry");
         sortedAttributes.emplace_back(attr.id, attr.name);
     }
@@ -129,7 +134,7 @@
         // We may reference IDs from other packages, so prefix the entry name with
         // the package.
         const ResourceNameRef& itemName = sortedAttributes[i].second;
-        if (itemName.package != mTable->getPackage()) {
+        if (itemName.package != *package) {
             *out << "_" << transform(itemName.package);
         }
         *out << "_" << transform(itemName.entry) << " = " << i << ";" << std::endl;
@@ -172,7 +177,7 @@
 
         if (type.type == ResourceType::kStyleable) {
             assert(!entry->values.empty());
-            entry->values.front().value->accept(*this, GenArgs{ &out, &unmangledName });
+            entry->values.front().value->accept(*this, GenArgs{ &out, &package, &unmangledName });
         } else {
             out << "        " << "public static" << finalModifier
                 << " int " << transform(unmangledName) << " = " << id << ";" << std::endl;
diff --git a/tools/aapt2/JavaClassGenerator_test.cpp b/tools/aapt2/JavaClassGenerator_test.cpp
index d4341b6..b385ff4 100644
--- a/tools/aapt2/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/JavaClassGenerator_test.cpp
@@ -95,8 +95,9 @@
                                   SourceLine{ "lib.xml", 33 }, util::make_unique<Id>()));
     ASSERT_TRUE(mTable->merge(std::move(table)));
 
-    Linker linker(mTable, std::make_shared<MockResolver>(mTable,
-                                                         std::map<ResourceName, ResourceId>()));
+    Linker linker(mTable,
+                  std::make_shared<MockResolver>(mTable, std::map<ResourceName, ResourceId>()),
+                  {});
     ASSERT_TRUE(linker.linkAndValidate());
 
     JavaClassGenerator generator(mTable, {});
@@ -130,7 +131,7 @@
                     { ResourceName{ u"com.lib", ResourceType::kAttr, u"bar" },
                       ResourceId{ 0x02, 0x01, 0x0000 } }}));
 
-    Linker linker(mTable, resolver);
+    Linker linker(mTable, resolver, {});
     ASSERT_TRUE(linker.linkAndValidate());
 
     JavaClassGenerator generator(mTable, {});
diff --git a/tools/aapt2/Linker.cpp b/tools/aapt2/Linker.cpp
index a8b7a14..f3f04a5 100644
--- a/tools/aapt2/Linker.cpp
+++ b/tools/aapt2/Linker.cpp
@@ -40,8 +40,9 @@
 Linker::Args::Args(const ResourceNameRef& r, const SourceLine& s) : referrer(r), source(s) {
 }
 
-Linker::Linker(std::shared_ptr<ResourceTable> table, std::shared_ptr<IResolver> resolver) :
-        mTable(table), mResolver(resolver), mError(false) {
+Linker::Linker(const std::shared_ptr<ResourceTable>& table,
+               const std::shared_ptr<IResolver>& resolver, const Options& options) :
+        mResolver(resolver), mTable(table), mOptions(options), mError(false) {
 }
 
 bool Linker::linkAndValidate() {
@@ -49,7 +50,7 @@
     std::array<std::set<uint16_t>, 256> usedIds;
     usedTypeIds.set(0);
 
-    // First build the graph of references.
+    // Collect which resource IDs are already taken.
     for (auto& type : *mTable) {
         if (type->typeId != ResourceTableType::kUnsetTypeId) {
             // The ID for this type has already been set. We
@@ -66,29 +67,10 @@
                 // later.
                 usedIds[type->typeId].insert(entry->entryId);
             }
-
-            if (entry->publicStatus.isPublic && entry->values.empty()) {
-                // A public resource has no values. It will not be encoded
-                // properly without a symbol table. This is a unresolved symbol.
-                addUnresolvedSymbol(ResourceNameRef{
-                        mTable->getPackage(), type->type, entry->name },
-                        entry->publicStatus.source);
-            } else {
-                for (auto& valueConfig : entry->values) {
-                    // Dispatch to the right method of this linker
-                    // based on the value's type.
-                    valueConfig.value->accept(*this, Args{
-                            ResourceNameRef{ mTable->getPackage(), type->type, entry->name },
-                            valueConfig.source
-                    });
-                }
-            }
         }
     }
 
-    /*
-     * Assign resource IDs that are available.
-     */
+    // Assign resource IDs that are available.
     size_t nextTypeIndex = 0;
     for (auto& type : *mTable) {
         if (type->typeId == ResourceTableType::kUnsetTypeId) {
@@ -109,29 +91,32 @@
                     ++nextEntryIter;
                 }
                 entry->entryId = nextIndex++;
-
-                std::u16string unmangledPackage = mTable->getPackage();
-                std::u16string unmangledName = entry->name;
-                NameMangler::unmangle(&unmangledName, &unmangledPackage);
-
-                // Update callers of this resource with the right ID.
-                auto callersIter = mGraph.find(ResourceNameRef{
-                        unmangledPackage,
-                        type->type,
-                        unmangledName
-                });
-
-                if (callersIter != std::end(mGraph)) {
-                    for (Node& caller : callersIter->second) {
-                        caller.reference->id = ResourceId(mTable->getPackageId(),
-                                                          type->typeId,
-                                                          entry->entryId);
-                    }
-                }
             }
         }
     }
 
+    // Now do reference linking.
+    for (auto& type : *mTable) {
+        for (auto& entry : type->entries) {
+            if (entry->publicStatus.isPublic && entry->values.empty()) {
+                // A public resource has no values. It will not be encoded
+                // properly without a symbol table. This is a unresolved symbol.
+                addUnresolvedSymbol(ResourceNameRef{
+                        mTable->getPackage(), type->type, entry->name },
+                        entry->publicStatus.source);
+                continue;
+            }
+
+            for (auto& valueConfig : entry->values) {
+                // Dispatch to the right method of this linker
+                // based on the value's type.
+                valueConfig.value->accept(*this, Args{
+                        ResourceNameRef{ mTable->getPackage(), type->type, entry->name },
+                        valueConfig.source
+                });
+            }
+        }
+    }
     return !mError;
 }
 
@@ -139,12 +124,48 @@
     return mUnresolvedSymbols;
 }
 
+void Linker::doResolveReference(Reference& reference, const SourceLine& source) {
+    Maybe<ResourceId> result = mResolver->findId(reference.name);
+    if (!result) {
+        addUnresolvedSymbol(reference.name, source);
+        return;
+    }
+    assert(result.value().isValid());
+
+    if (mOptions.linkResourceIds) {
+        reference.id = result.value();
+    } else {
+        reference.id = 0;
+    }
+}
+
+const Attribute* Linker::doResolveAttribute(Reference& attribute, const SourceLine& source) {
+    Maybe<IResolver::Entry> result = mResolver->findAttribute(attribute.name);
+    if (!result || !result.value().attr) {
+        addUnresolvedSymbol(attribute.name, source);
+        return nullptr;
+    }
+
+    const IResolver::Entry& entry = result.value();
+    assert(entry.id.isValid());
+
+    if (mOptions.linkResourceIds) {
+        attribute.id = entry.id;
+    } else {
+        attribute.id = 0;
+    }
+    return entry.attr;
+}
+
 void Linker::visit(Reference& reference, ValueVisitorArgs& a) {
     Args& args = static_cast<Args&>(a);
 
     if (!reference.name.isValid()) {
         // We can't have a completely bad reference.
-        assert(reference.id.isValid());
+        if (!reference.id.isValid()) {
+            Logger::error() << "srsly? " << args.referrer << std::endl;
+            assert(reference.id.isValid());
+        }
 
         // This reference has no name but has an ID.
         // It is a really bad error to have no name and have the same
@@ -156,25 +177,7 @@
         return;
     }
 
-    Maybe<ResourceId> result = mResolver->findId(reference.name);
-    if (!result) {
-        addUnresolvedSymbol(reference.name, args.source);
-        return;
-    }
-
-    const ResourceId& id = result.value();
-    if (id.isValid()) {
-        reference.id = id;
-    } else {
-        // We need to update the ID when it is set, so add it
-        // to the graph.
-        mGraph[reference.name].push_back(Node{
-                args.referrer,
-                args.source.path,
-                args.source.line,
-                &reference
-        });
-    }
+    doResolveReference(reference, args.source);
 
     // TODO(adamlesinski): Verify the referencedType is another reference
     // or a compatible primitive.
@@ -192,7 +195,6 @@
             // We should never get here. All references would have been
             // parsed in the parser phase.
             assert(false);
-            //mTable->addResource(name, ConfigDescription{}, source, util::make_unique<Id>());
         };
 
         convertedValue = ResourceParser::parseItemForAttribute(*str.value, attr,
@@ -240,25 +242,10 @@
     }
 
     for (Style::Entry& styleEntry : style.entries) {
-        Maybe<IResolver::Entry> result = mResolver->findAttribute(styleEntry.key.name);
-        if (!result || !result.value().attr) {
-            addUnresolvedSymbol(styleEntry.key.name, args.source);
-            continue;
+        const Attribute* attr = doResolveAttribute(styleEntry.key, args.source);
+        if (attr) {
+            processAttributeValue(args.referrer, args.source, *attr, styleEntry.value);
         }
-
-        const IResolver::Entry& entry = result.value();
-        if (entry.id.isValid()) {
-            styleEntry.key.id = entry.id;
-        } else {
-            // Create a dependency for the style on this attribute.
-            mGraph[styleEntry.key.name].push_back(Node{
-                    args.referrer,
-                    args.source.path,
-                    args.source.line,
-                    &styleEntry.key
-            });
-        }
-        processAttributeValue(args.referrer, args.source, *entry.attr, styleEntry.value);
     }
 }
 
@@ -300,8 +287,4 @@
     mUnresolvedSymbols[name.toResourceName()].push_back(source);
 }
 
-::std::ostream& operator<<(::std::ostream& out, const Linker::Node& node) {
-    return out << node.name << "(" << node.source << ":" << node.line << ")";
-}
-
 } // namespace aapt
diff --git a/tools/aapt2/Linker.h b/tools/aapt2/Linker.h
index 9db64ab..6f03515 100644
--- a/tools/aapt2/Linker.h
+++ b/tools/aapt2/Linker.h
@@ -50,14 +50,25 @@
  */
 class Linker : ValueVisitor {
 public:
+    struct Options {
+        /**
+         * Assign resource Ids to references when linking.
+         * When building a static library, set this to false.
+         */
+        bool linkResourceIds = true;
+    };
+
     /**
      * Create a Linker for the given resource table with the sources available in
      * IResolver. IResolver should contain the ResourceTable as a source too.
      */
-    Linker(std::shared_ptr<ResourceTable> table, std::shared_ptr<IResolver> resolver);
+    Linker(const std::shared_ptr<ResourceTable>& table,
+           const std::shared_ptr<IResolver>& resolver, const Options& options);
 
     Linker(const Linker&) = delete;
 
+    virtual ~Linker() = default;
+
     /**
      * Entry point to the linker. Assigns resource IDs, follows references,
      * and validates types. Returns true if all references to defined values
@@ -73,6 +84,12 @@
     using ResourceNameToSourceMap = std::map<ResourceName, std::vector<SourceLine>>;
     const ResourceNameToSourceMap& getUnresolvedReferences() const;
 
+protected:
+    virtual void doResolveReference(Reference& reference, const SourceLine& source);
+    virtual const Attribute* doResolveAttribute(Reference& attribute, const SourceLine& source);
+
+    std::shared_ptr<IResolver> mResolver;
+
 private:
     struct Args : public ValueVisitorArgs {
         Args(const ResourceNameRef& r, const SourceLine& s);
@@ -92,33 +109,13 @@
     void visit(Plural& plural, ValueVisitorArgs& args) override;
 
     void processAttributeValue(const ResourceNameRef& name, const SourceLine& source,
-            const Attribute& attr, std::unique_ptr<Item>& value);
+                               const Attribute& attr, std::unique_ptr<Item>& value);
 
     void addUnresolvedSymbol(const ResourceNameRef& name, const SourceLine& source);
 
-    /**
-     * Node of the resource table graph.
-     */
-    struct Node {
-        // We use ResourceNameRef and StringPiece, which are safe so long as the ResourceTable
-        // that defines the data isn't modified.
-        ResourceNameRef name;
-        StringPiece source;
-        size_t line;
-
-        // The reference object that points to name.
-        Reference* reference;
-
-        bool operator<(const Node& rhs) const;
-        bool operator==(const Node& rhs) const;
-        bool operator!=(const Node& rhs) const;
-    };
-    friend ::std::ostream& operator<<(::std::ostream&, const Node&);
-
     std::shared_ptr<ResourceTable> mTable;
-    std::shared_ptr<IResolver> mResolver;
-    std::map<ResourceNameRef, std::vector<Node>> mGraph;
     std::map<ResourceName, std::vector<SourceLine>> mUnresolvedSymbols;
+    Options mOptions;
     bool mError;
 };
 
diff --git a/tools/aapt2/Linker_test.cpp b/tools/aapt2/Linker_test.cpp
index 3c5b8b4..66c6717 100644
--- a/tools/aapt2/Linker_test.cpp
+++ b/tools/aapt2/Linker_test.cpp
@@ -32,7 +32,8 @@
         mTable->setPackage(u"android");
         mTable->setPackageId(0x01);
         mLinker = std::make_shared<Linker>(mTable, std::make_shared<ResourceTableResolver>(
-                mTable, std::make_shared<android::AssetManager>()));
+                mTable, std::vector<std::shared_ptr<const android::AssetManager>>()),
+                Linker::Options{});
 
         // Create a few attributes for use in the tests.
 
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 3377f07..f1b7777 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -19,6 +19,7 @@
 #include "BinaryResourceParser.h"
 #include "BinaryXmlPullParser.h"
 #include "BindingXmlPullParser.h"
+#include "Debug.h"
 #include "Files.h"
 #include "Flag.h"
 #include "JavaClassGenerator.h"
@@ -55,54 +56,6 @@
 
 using namespace aapt;
 
-void printTable(const ResourceTable& table) {
-    std::cout << "ResourceTable package=" << table.getPackage();
-    if (table.getPackageId() != ResourceTable::kUnsetPackageId) {
-        std::cout << " id=" << std::hex << table.getPackageId() << std::dec;
-    }
-    std::cout << std::endl
-         << "---------------------------------------------------------" << std::endl;
-
-    for (const auto& type : table) {
-        std::cout << "Type " << type->type;
-        if (type->typeId != ResourceTableType::kUnsetTypeId) {
-            std::cout << " [" << type->typeId << "]";
-        }
-        std::cout << " (" << type->entries.size() << " entries)" << std::endl;
-        for (const auto& entry : type->entries) {
-            std::cout << "  " << entry->name;
-            if (entry->entryId != ResourceEntry::kUnsetEntryId) {
-                std::cout << " [" << entry->entryId << "]";
-            }
-            std::cout << " (" << entry->values.size() << " configurations)";
-            if (entry->publicStatus.isPublic) {
-                std::cout << " PUBLIC";
-            }
-            std::cout << std::endl;
-            for (const auto& value : entry->values) {
-                std::cout << "    " << value.config << " (" << value.source << ") : ";
-                value.value->print(std::cout);
-                std::cout << std::endl;
-            }
-        }
-    }
-}
-
-void printStringPool(const StringPool& pool) {
-    std::cout << "String pool of length " << pool.size() << std::endl
-         << "---------------------------------------------------------" << std::endl;
-
-    size_t i = 0;
-    for (const auto& entry : pool) {
-        std::cout << "[" << i << "]: "
-             << entry->value
-             << " (Priority " << entry->context.priority
-             << ", Config '" << entry->context.config << "')"
-             << std::endl;
-        i++;
-    }
-}
-
 /**
  * Collect files from 'root', filtering out any files that do not
  * match the FileFilter 'filter'.
@@ -144,29 +97,6 @@
     return !error;
 }
 
-bool loadResTable(android::ResTable* table, const Source& source) {
-    std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
-    if (!ifs) {
-        Logger::error(source) << strerror(errno) << std::endl;
-        return false;
-    }
-
-    std::streampos fsize = ifs.tellg();
-    ifs.seekg(0, std::ios::end);
-    fsize = ifs.tellg() - fsize;
-    ifs.seekg(0, std::ios::beg);
-
-    assert(fsize >= 0);
-    size_t dataSize = static_cast<size_t>(fsize);
-    char* buf = new char[dataSize];
-    ifs.read(buf, dataSize);
-
-    bool result = table->add(buf, dataSize, -1, true) == android::NO_ERROR;
-
-    delete [] buf;
-    return result;
-}
-
 void versionStylesForCompat(const std::shared_ptr<ResourceTable>& table) {
     for (auto& type : *table) {
         if (type->type != ResourceType::kStyle) {
@@ -285,8 +215,6 @@
     return str.substr(offset, str.size() - offset);
 }
 
-
-
 std::string buildFileReference(const ResourceNameRef& name, const ConfigDescription& config,
                                const StringPiece& extension) {
     std::stringstream path;
@@ -321,6 +249,8 @@
     enum class Phase {
         Link,
         Compile,
+        Dump,
+        DumpStyleGraph,
     };
 
     enum class PackageType {
@@ -366,9 +296,8 @@
     bool versionStylesAndLayouts = true;
 };
 
-
 bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
-                const CompileItem& item, std::queue<CompileItem>* outQueue, ZipFile* outApk) {
+                const CompileItem& item, ZipFile* outApk) {
     std::ifstream in(item.source.path, std::ifstream::binary);
     if (!in) {
         Logger::error(item.source) << strerror(errno) << std::endl;
@@ -382,6 +311,45 @@
 
     XmlFlattener::Options xmlOptions;
     xmlOptions.defaultPackage = table->getPackage();
+    xmlOptions.keepRawValues = true;
+
+    std::shared_ptr<XmlPullParser> parser = std::make_shared<SourceXmlPullParser>(in);
+
+    Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer,
+                                                     xmlOptions);
+    if (!minStrippedSdk) {
+        return false;
+    }
+
+    // Write the resulting compiled XML file to the output APK.
+    if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
+                nullptr) != android::NO_ERROR) {
+        Logger::error(options.output) << "failed to write compiled '" << item.source
+                                      << "' to apk." << std::endl;
+        return false;
+    }
+    return true;
+}
+
+bool linkXml(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
+             const LinkItem& item, const void* data, size_t dataLen, ZipFile* outApk,
+             std::queue<LinkItem>* outQueue) {
+    std::shared_ptr<android::ResXMLTree> tree = std::make_shared<android::ResXMLTree>();
+    if (tree->setTo(data, dataLen, false) != android::NO_ERROR) {
+        return false;
+    }
+
+    std::shared_ptr<XmlPullParser> parser = std::make_shared<BinaryXmlPullParser>(tree);
+
+    BigBuffer outBuffer(1024);
+    XmlFlattener flattener({}, resolver);
+
+    XmlFlattener::Options xmlOptions;
+    xmlOptions.defaultPackage = item.originalPackage;
+
+    if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
+        xmlOptions.keepRawValues = true;
+    }
 
     if (options.versionStylesAndLayouts) {
         // We strip attributes that do not belong in this version of the resource.
@@ -390,14 +358,14 @@
     }
 
     std::shared_ptr<BindingXmlPullParser> binding;
-    std::shared_ptr<XmlPullParser> parser = std::make_shared<SourceXmlPullParser>(in);
     if (item.name.type == ResourceType::kLayout) {
         // Layouts may have defined bindings, so we need to make sure they get processed.
         binding = std::make_shared<BindingXmlPullParser>(parser);
         parser = binding;
     }
 
-    Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer, xmlOptions);
+    Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer,
+                                                     xmlOptions);
     if (!minStrippedSdk) {
         return false;
     }
@@ -405,16 +373,15 @@
     if (minStrippedSdk.value() > 0) {
         // Something was stripped, so let's generate a new file
         // with the version of the smallest SDK version stripped.
-        CompileItem newWork = item;
+        LinkItem newWork = item;
         newWork.config.sdkVersion = minStrippedSdk.value();
         outQueue->push(newWork);
     }
 
-    // Write the resulting compiled XML file to the output APK.
-    if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
+    if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressDeflated,
                 nullptr) != android::NO_ERROR) {
-        Logger::error(options.output) << "failed to write compiled '" << item.source << "' to apk."
-                                      << std::endl;
+        Logger::error(options.output) << "failed to write linked file '"
+                                      << buildFileReference(item) << "' to apk." << std::endl;
         return false;
     }
 
@@ -444,33 +411,6 @@
     return true;
 }
 
-bool linkXml(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
-             const LinkItem& item, const void* data, size_t dataLen, ZipFile* outApk) {
-    std::shared_ptr<android::ResXMLTree> tree = std::make_shared<android::ResXMLTree>();
-    if (tree->setTo(data, dataLen, false) != android::NO_ERROR) {
-        return false;
-    }
-
-    std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<BinaryXmlPullParser>(tree);
-
-    BigBuffer outBuffer(1024);
-    XmlFlattener flattener({}, resolver);
-
-    XmlFlattener::Options xmlOptions;
-    xmlOptions.defaultPackage = item.originalPackage;
-    if (!flattener.flatten(item.source, xmlParser, &outBuffer, xmlOptions)) {
-        return false;
-    }
-
-    if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressDeflated,
-                nullptr) != android::NO_ERROR) {
-        Logger::error(options.output) << "failed to write linked file '" << item.source
-                                      << "' to apk." << std::endl;
-        return false;
-    }
-    return true;
-}
-
 bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
     std::ifstream in(item.source.path, std::ifstream::binary);
     if (!in) {
@@ -505,8 +445,8 @@
     return true;
 }
 
-bool compileManifest(const AaptOptions& options,
-                     const std::shared_ptr<ResourceTableResolver>& resolver, ZipFile* outApk) {
+bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
+                     const android::ResTable& table, ZipFile* outApk) {
     if (options.verbose) {
         Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
     }
@@ -534,7 +474,7 @@
         return false;
     }
 
-    ManifestValidator validator(resolver->getResTable());
+    ManifestValidator validator(table);
     if (!validator.validate(options.manifest, &tree)) {
         return false;
     }
@@ -690,7 +630,7 @@
 };
 
 bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable,
-          const std::shared_ptr<ResourceTableResolver>& resolver) {
+          const std::shared_ptr<IResolver>& resolver) {
     std::map<std::shared_ptr<ResourceTable>, StaticLibraryData> apkFiles;
     std::unordered_set<std::u16string> linkedPackages;
 
@@ -744,9 +684,18 @@
         }
     }
 
+    // Version all styles referencing attributes outside of their specified SDK version.
+    if (options.versionStylesAndLayouts) {
+        versionStylesForCompat(outTable);
+    }
+
     {
         // Now that everything is merged, let's link it.
-        Linker linker(outTable, resolver);
+        Linker::Options linkerOptions;
+        if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
+            linkerOptions.linkResourceIds = false;
+        }
+        Linker linker(outTable, resolver, linkerOptions);
         if (!linker.linkAndValidate()) {
             return false;
         }
@@ -771,7 +720,8 @@
         return false;
     }
 
-    if (!compileManifest(options, resolver, &outApk)) {
+    android::ResTable binTable;
+    if (!compileManifest(options, resolver, binTable, &outApk)) {
         return false;
     }
 
@@ -791,7 +741,7 @@
             assert(uncompressedData);
 
             if (!linkXml(options, resolver, item, uncompressedData, entry->getUncompressedLen(),
-                    &outApk)) {
+                    &outApk, &linkQueue)) {
                 Logger::error(options.output) << "failed to link '" << item.originalPath << "'."
                                               << std::endl;
                 return false;
@@ -864,8 +814,8 @@
 
     // Flatten the resource table.
     TableFlattener::Options flattenerOptions;
-    if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
-        flattenerOptions.useExtendedChunks = true;
+    if (options.packageType != AaptOptions::PackageType::StaticLibrary) {
+        flattenerOptions.useExtendedChunks = false;
     }
 
     if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) {
@@ -920,12 +870,6 @@
     if (error) {
         return false;
     }
-
-    // Version all styles referencing attributes outside of their specified SDK version.
-    if (options.versionStylesAndLayouts) {
-        versionStylesForCompat(table);
-    }
-
     // Open the output APK file for writing.
     ZipFile outApk;
     if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
@@ -941,7 +885,7 @@
         error |= !addFileReference(table, item);
 
         if (item.extension == "xml") {
-            error |= !compileXml(options, table, item, &compileQueue, &outApk);
+            error |= !compileXml(options, table, item, &outApk);
         } else if (item.extension == "png" || item.extension == "9.png") {
             error |= !compilePng(options, item, &outApk);
         } else {
@@ -954,7 +898,7 @@
     }
 
     // Link and assign resource IDs.
-    Linker linker(table, resolver);
+    Linker linker(table, resolver, {});
     if (!linker.linkAndValidate()) {
         return false;
     }
@@ -984,6 +928,7 @@
     std::cerr << "The following commands are supported:" << std::endl << std::endl;
     std::cerr << "compile       compiles a subset of resources" << std::endl;
     std::cerr << "link          links together compiled resources and libraries" << std::endl;
+    std::cerr << "dump          dumps resource contents to to standard out" << std::endl;
     std::cerr << std::endl;
     std::cerr << "run aapt2 with one of the commands and the -h flag for extra details."
               << std::endl;
@@ -1009,48 +954,56 @@
         options.phase = AaptOptions::Phase::Link;
     } else if (command == "compile") {
         options.phase = AaptOptions::Phase::Compile;
+    } else if (command == "dump") {
+        options.phase = AaptOptions::Phase::Dump;
+    } else if (command == "dump-style-graph") {
+        options.phase = AaptOptions::Phase::DumpStyleGraph;
     } else {
         std::cerr << "invalid command '" << command << "'." << std::endl << std::endl;
         printCommandsAndDie();
     }
 
     bool isStaticLib = false;
-    if (options.phase == AaptOptions::Phase::Compile) {
-        flag::requiredFlag("--package", "Android package name",
-                [&options](const StringPiece& arg) {
-                    options.appInfo.package = util::utf8ToUtf16(arg);
-                });
-        flag::optionalFlag("--binding", "Output directory for binding XML files",
-                [&options](const StringPiece& arg) {
-                    options.bindingOutput = Source{ arg.toString() };
-                });
-        flag::optionalSwitch("--no-version", "Disables automatic style and layout versioning",
-                             false, &options.versionStylesAndLayouts);
+    if (options.phase == AaptOptions::Phase::Compile ||
+            options.phase == AaptOptions::Phase::Link) {
+        if (options.phase == AaptOptions::Phase::Compile) {
+            flag::requiredFlag("--package", "Android package name",
+                    [&options](const StringPiece& arg) {
+                        options.appInfo.package = util::utf8ToUtf16(arg);
+                    });
+        } else if (options.phase == AaptOptions::Phase::Link) {
+            flag::requiredFlag("--manifest", "AndroidManifest.xml of your app",
+                    [&options](const StringPiece& arg) {
+                        options.manifest = Source{ arg.toString() };
+                    });
 
-    } else if (options.phase == AaptOptions::Phase::Link) {
-        flag::requiredFlag("--manifest", "AndroidManifest.xml of your app",
-                [&options](const StringPiece& arg) {
-                    options.manifest = Source{ arg.toString() };
-                });
+            flag::optionalFlag("-I", "add an Android APK to link against",
+                    [&options](const StringPiece& arg) {
+                        options.libraries.push_back(Source{ arg.toString() });
+                    });
 
-        flag::optionalFlag("-I", "add an Android APK to link against",
-                [&options](const StringPiece& arg) {
-                    options.libraries.push_back(Source{ arg.toString() });
-                });
+            flag::optionalFlag("--java", "directory in which to generate R.java",
+                    [&options](const StringPiece& arg) {
+                        options.generateJavaClass = Source{ arg.toString() };
+                    });
 
-        flag::optionalFlag("--java", "directory in which to generate R.java",
-                [&options](const StringPiece& arg) {
-                    options.generateJavaClass = Source{ arg.toString() };
-                });
-        flag::optionalSwitch("--static-lib", "generate a static Android library", true,
-                             &isStaticLib);
+            flag::optionalSwitch("--static-lib", "generate a static Android library", true,
+                                 &isStaticLib);
+
+            flag::optionalFlag("--binding", "Output directory for binding XML files",
+                    [&options](const StringPiece& arg) {
+                        options.bindingOutput = Source{ arg.toString() };
+                    });
+            flag::optionalSwitch("--no-version", "Disables automatic style and layout versioning",
+                                 false, &options.versionStylesAndLayouts);
+        }
+
+        // Common flags for all steps.
+        flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) {
+            options.output = Source{ arg.toString() };
+        });
     }
 
-    // Common flags for all steps.
-    flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) {
-        options.output = Source{ arg.toString() };
-    });
-
     bool help = false;
     flag::optionalSwitch("-v", "enables verbose logging", true, &options.verbose);
     flag::optionalSwitch("-h", "displays this help menu", true, &help);
@@ -1078,10 +1031,56 @@
     return options;
 }
 
+static bool doDump(const AaptOptions& options) {
+    for (const Source& source : options.input) {
+        std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
+        if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
+            Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
+            return false;
+        }
+
+        std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
+        std::shared_ptr<ResourceTableResolver> resolver =
+                std::make_shared<ResourceTableResolver>(
+                        table, std::vector<std::shared_ptr<const android::AssetManager>>());
+
+        ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
+        if (!entry) {
+            Logger::error(source) << "missing 'resources.arsc'." << std::endl;
+            return false;
+        }
+
+        std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
+                zipFile->uncompress(entry));
+        assert(uncompressedData);
+
+        BinaryResourceParser parser(table, resolver, source, uncompressedData.get(),
+                                    entry->getUncompressedLen());
+        if (!parser.parse()) {
+            return false;
+        }
+
+        if (options.phase == AaptOptions::Phase::Dump) {
+            Debug::printTable(table);
+        } else if (options.phase == AaptOptions::Phase::DumpStyleGraph) {
+            Debug::printStyleGraph(table);
+        }
+    }
+    return true;
+}
+
 int main(int argc, char** argv) {
     Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
     AaptOptions options = prepareArgs(argc, argv);
 
+    if (options.phase == AaptOptions::Phase::Dump ||
+            options.phase == AaptOptions::Phase::DumpStyleGraph) {
+        if (!doDump(options)) {
+            return 1;
+        }
+        return 0;
+    }
+
     // If we specified a manifest, go ahead and load the package name from the manifest.
     if (!options.manifest.path.empty()) {
         if (!loadAppInfo(options.manifest, &options.appInfo)) {
@@ -1105,37 +1104,26 @@
     }
 
     // Load the included libraries.
-    std::shared_ptr<android::AssetManager> libraries = std::make_shared<android::AssetManager>();
+    std::vector<std::shared_ptr<const android::AssetManager>> sources;
     for (const Source& source : options.libraries) {
-        if (util::stringEndsWith<char>(source.path, ".arsc")) {
-            // We'll process these last so as to avoid a cookie issue.
-            continue;
-        }
-
+        std::shared_ptr<android::AssetManager> assetManager =
+                std::make_shared<android::AssetManager>();
         int32_t cookie;
-        if (!libraries->addAssetPath(android::String8(source.path.data()), &cookie)) {
+        if (!assetManager->addAssetPath(android::String8(source.path.data()), &cookie)) {
             Logger::error(source) << "failed to load library." << std::endl;
             return false;
         }
-    }
 
-    for (const Source& source : options.libraries) {
-        if (!util::stringEndsWith<char>(source.path, ".arsc")) {
-            // We've already processed this.
-            continue;
-        }
-
-        // Dirty hack but there is no other way to get a
-        // writeable ResTable.
-        if (!loadResTable(const_cast<android::ResTable*>(&libraries->getResources(false)),
-                          source)) {
+        if (cookie == 0) {
+            Logger::error(source) << "failed to load library." << std::endl;
             return false;
         }
+        sources.push_back(assetManager);
     }
 
     // Make the resolver that will cache IDs for us.
     std::shared_ptr<ResourceTableResolver> resolver = std::make_shared<ResourceTableResolver>(
-            table, libraries);
+            table, sources);
 
     if (options.phase == AaptOptions::Phase::Compile) {
         if (!compile(options, table, resolver)) {
diff --git a/tools/aapt2/MockResolver.h b/tools/aapt2/MockResolver.h
index 48ff6a6..0c9b954 100644
--- a/tools/aapt2/MockResolver.h
+++ b/tools/aapt2/MockResolver.h
@@ -34,7 +34,7 @@
     MockResolver(const std::shared_ptr<ResourceTable>& table,
                  const std::map<ResourceName, ResourceId>& items) :
             mResolver(std::make_shared<ResourceTableResolver>(
-                        table, std::make_shared<const android::AssetManager>())),
+                    table, std::vector<std::shared_ptr<const android::AssetManager>>())),
             mAttr(false, android::ResTable_map::TYPE_ANY), mItems(items) {
     }
 
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 9468860..c93ecc7 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -42,6 +42,8 @@
 }
 
 ResourceTable::ResourceTable() : mPackageId(kUnsetPackageId) {
+    // Make sure attrs always have type ID 1.
+    findOrCreateType(ResourceType::kAttr)->typeId = 1;
 }
 
 std::unique_ptr<ResourceTableType>& ResourceTable::findOrCreateType(ResourceType type) {
@@ -142,10 +144,30 @@
 }
 
 static constexpr const char16_t* kValidNameChars = u"._-";
+static constexpr const char16_t* kValidNameMangledChars = u"._-$";
+
+bool ResourceTable::addResource(const ResourceNameRef& name, const ConfigDescription& config,
+                                const SourceLine& source, std::unique_ptr<Value> value) {
+    return addResourceImpl(name, ResourceId{}, config, source, std::move(value), kValidNameChars);
+}
 
 bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId resId,
-        const ConfigDescription& config, const SourceLine& source,
-        std::unique_ptr<Value> value) {
+                                const ConfigDescription& config, const SourceLine& source,
+                                std::unique_ptr<Value> value) {
+    return addResourceImpl(name, resId, config, source, std::move(value), kValidNameChars);
+}
+
+bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
+                                            const ConfigDescription& config,
+                                            const SourceLine& source,
+                                            std::unique_ptr<Value> value) {
+    return addResourceImpl(name, ResourceId{}, config, source, std::move(value),
+                           kValidNameMangledChars);
+}
+
+bool ResourceTable::addResourceImpl(const ResourceNameRef& name, const ResourceId resId,
+                                    const ConfigDescription& config, const SourceLine& source,
+                                    std::unique_ptr<Value> value, const char16_t* validChars) {
     if (!name.package.empty() && name.package != mPackage) {
         Logger::error(source)
                 << "resource '"
@@ -157,7 +179,7 @@
         return false;
     }
 
-    auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, kValidNameChars);
+    auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
     if (badCharIter != name.entry.end()) {
         Logger::error(source)
                 << "resource '"
@@ -233,13 +255,18 @@
     return true;
 }
 
-bool ResourceTable::addResource(const ResourceNameRef& name, const ConfigDescription& config,
-                                const SourceLine& source, std::unique_ptr<Value> value) {
-    return addResource(name, ResourceId{}, config, source, std::move(value));
-}
-
 bool ResourceTable::markPublic(const ResourceNameRef& name, const ResourceId resId,
                                const SourceLine& source) {
+    return markPublicImpl(name, resId, source, kValidNameChars);
+}
+
+bool ResourceTable::markPublicAllowMangled(const ResourceNameRef& name, const ResourceId resId,
+                                           const SourceLine& source) {
+    return markPublicImpl(name, resId, source, kValidNameMangledChars);
+}
+
+bool ResourceTable::markPublicImpl(const ResourceNameRef& name, const ResourceId resId,
+                                   const SourceLine& source, const char16_t* validChars) {
     if (!name.package.empty() && name.package != mPackage) {
         Logger::error(source)
                 << "resource '"
@@ -251,7 +278,7 @@
         return false;
     }
 
-    auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, kValidNameChars);
+    auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
     if (badCharIter != name.entry.end()) {
         Logger::error(source)
                 << "resource '"
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 94bacd8..706f56a 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -143,11 +143,21 @@
     bool addResource(const ResourceNameRef& name, const ConfigDescription& config,
                      const SourceLine& source, std::unique_ptr<Value> value);
 
+    /**
+     * Same as addResource, but doesn't verify the validity of the name. This is used
+     * when loading resources from an existing binary resource table that may have mangled
+     * names.
+     */
+    bool addResourceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config,
+                                 const SourceLine& source, std::unique_ptr<Value> value);
+
     bool addResource(const ResourceNameRef& name, const ResourceId resId,
                      const ConfigDescription& config, const SourceLine& source,
                      std::unique_ptr<Value> value);
 
     bool markPublic(const ResourceNameRef& name, const ResourceId resId, const SourceLine& source);
+    bool markPublicAllowMangled(const ResourceNameRef& name, const ResourceId resId,
+                                const SourceLine& source);
 
     /*
      * Merges the resources from `other` into this table, mangling the names of the resources
@@ -176,6 +186,12 @@
     std::unique_ptr<ResourceEntry>& findOrCreateEntry(std::unique_ptr<ResourceTableType>& type,
                                                       const StringPiece16& name);
 
+    bool addResourceImpl(const ResourceNameRef& name, const ResourceId resId,
+                         const ConfigDescription& config, const SourceLine& source,
+                         std::unique_ptr<Value> value, const char16_t* validChars);
+    bool markPublicImpl(const ResourceNameRef& name, const ResourceId resId,
+                        const SourceLine& source, const char16_t* validChars);
+
     std::u16string mPackage;
     size_t mPackageId;
 
diff --git a/tools/aapt2/ResourceTableResolver.cpp b/tools/aapt2/ResourceTableResolver.cpp
index 0a9f521..910c2c0 100644
--- a/tools/aapt2/ResourceTableResolver.cpp
+++ b/tools/aapt2/ResourceTableResolver.cpp
@@ -31,13 +31,15 @@
 
 ResourceTableResolver::ResourceTableResolver(
         std::shared_ptr<const ResourceTable> table,
-        std::shared_ptr<const android::AssetManager> sources) :
+        const std::vector<std::shared_ptr<const android::AssetManager>>& sources) :
         mTable(table), mSources(sources) {
-    const android::ResTable& resTable = mSources->getResources(false);
-    const size_t packageCount = resTable.getBasePackageCount();
-    for (size_t i = 0; i < packageCount; i++) {
-        std::u16string packageName = resTable.getBasePackageName(i).string();
-        mIncludedPackages.insert(std::move(packageName));
+    for (const auto& assetManager : mSources) {
+        const android::ResTable& resTable = assetManager->getResources(false);
+        const size_t packageCount = resTable.getBasePackageCount();
+        for (size_t i = 0; i < packageCount; i++) {
+            std::u16string packageName = resTable.getBasePackageName(i).string();
+            mIncludedPackages.insert(std::move(packageName));
+        }
     }
 }
 
@@ -99,20 +101,23 @@
 }
 
 Maybe<ResourceName> ResourceTableResolver::findName(ResourceId resId) {
-    const android::ResTable& table = mSources->getResources(false);
+    for (const auto& assetManager : mSources) {
+        const android::ResTable& table = assetManager->getResources(false);
 
-    android::ResTable::resource_name resourceName;
-    if (!table.getResourceName(resId.id, false, &resourceName)) {
-        return {};
+        android::ResTable::resource_name resourceName;
+        if (!table.getResourceName(resId.id, false, &resourceName)) {
+            continue;
+        }
+
+        const ResourceType* type = parseResourceType(StringPiece16(resourceName.type,
+                                                                   resourceName.typeLen));
+        assert(type);
+        return ResourceName{
+                { resourceName.package, resourceName.packageLen },
+                *type,
+                { resourceName.name, resourceName.nameLen } };
     }
-
-    const ResourceType* type = parseResourceType(StringPiece16(resourceName.type,
-                                                               resourceName.typeLen));
-    assert(type);
-    return ResourceName{
-            { resourceName.package, resourceName.packageLen },
-            *type,
-            { resourceName.name, resourceName.nameLen } };
+    return {};
 }
 
 /**
@@ -122,73 +127,76 @@
  */
 const ResourceTableResolver::CacheEntry* ResourceTableResolver::buildCacheEntry(
         const ResourceName& name) {
-    const android::ResTable& table = mSources->getResources(false);
+    for (const auto& assetManager : mSources) {
+        const android::ResTable& table = assetManager->getResources(false);
 
-    const StringPiece16 type16 = toString(name.type);
-    ResourceId resId {
-        table.identifierForName(
-                name.entry.data(), name.entry.size(),
-                type16.data(), type16.size(),
-                name.package.data(), name.package.size())
-    };
+        const StringPiece16 type16 = toString(name.type);
+        ResourceId resId {
+            table.identifierForName(
+                    name.entry.data(), name.entry.size(),
+                    type16.data(), type16.size(),
+                    name.package.data(), name.package.size())
+        };
 
-    if (!resId.isValid()) {
-        return nullptr;
-    }
+        if (!resId.isValid()) {
+            continue;
+        }
 
-    CacheEntry& entry = mCache[name];
-    entry.id = resId;
+        CacheEntry& entry = mCache[name];
+        entry.id = resId;
 
-    //
-    // Now check to see if this resource is an Attribute.
-    //
+        //
+        // Now check to see if this resource is an Attribute.
+        //
 
-    const android::ResTable::bag_entry* bagBegin;
-    ssize_t bags = table.lockBag(resId.id, &bagBegin);
-    if (bags < 1) {
+        const android::ResTable::bag_entry* bagBegin;
+        ssize_t bags = table.lockBag(resId.id, &bagBegin);
+        if (bags < 1) {
+            table.unlockBag(bagBegin);
+            return &entry;
+        }
+
+        // Look for the ATTR_TYPE key in the bag and check the types it supports.
+        uint32_t attrTypeMask = 0;
+        for (ssize_t i = 0; i < bags; i++) {
+            if (bagBegin[i].map.name.ident == android::ResTable_map::ATTR_TYPE) {
+                attrTypeMask = bagBegin[i].map.value.data;
+            }
+        }
+
+        entry.attr = util::make_unique<Attribute>(false);
+
+        if (attrTypeMask & android::ResTable_map::TYPE_ENUM ||
+                attrTypeMask & android::ResTable_map::TYPE_FLAGS) {
+            for (ssize_t i = 0; i < bags; i++) {
+                if (Res_INTERNALID(bagBegin[i].map.name.ident)) {
+                    // Internal IDs are special keys, which are not enum/flag symbols, so skip.
+                    continue;
+                }
+
+                android::ResTable::resource_name symbolName;
+                bool result = table.getResourceName(bagBegin[i].map.name.ident, false,
+                        &symbolName);
+                assert(result);
+                const ResourceType* type = parseResourceType(
+                        StringPiece16(symbolName.type, symbolName.typeLen));
+                assert(type);
+
+                entry.attr->symbols.push_back(Attribute::Symbol{
+                        Reference(ResourceNameRef(
+                                    StringPiece16(symbolName.package, symbolName.packageLen),
+                                    *type,
+                                    StringPiece16(symbolName.name, symbolName.nameLen))),
+                                bagBegin[i].map.value.data
+                });
+            }
+        }
+
+        entry.attr->typeMask |= attrTypeMask;
         table.unlockBag(bagBegin);
         return &entry;
     }
-
-    // Look for the ATTR_TYPE key in the bag and check the types it supports.
-    uint32_t attrTypeMask = 0;
-    for (ssize_t i = 0; i < bags; i++) {
-        if (bagBegin[i].map.name.ident == android::ResTable_map::ATTR_TYPE) {
-            attrTypeMask = bagBegin[i].map.value.data;
-        }
-    }
-
-    entry.attr = util::make_unique<Attribute>(false);
-
-    if (attrTypeMask & android::ResTable_map::TYPE_ENUM ||
-            attrTypeMask & android::ResTable_map::TYPE_FLAGS) {
-        for (ssize_t i = 0; i < bags; i++) {
-            if (Res_INTERNALID(bagBegin[i].map.name.ident)) {
-                // Internal IDs are special keys, which are not enum/flag symbols, so skip.
-                continue;
-            }
-
-            android::ResTable::resource_name symbolName;
-            bool result = table.getResourceName(bagBegin[i].map.name.ident, false,
-                    &symbolName);
-            assert(result);
-            const ResourceType* type = parseResourceType(
-                    StringPiece16(symbolName.type, symbolName.typeLen));
-            assert(type);
-
-            entry.attr->symbols.push_back(Attribute::Symbol{
-                    Reference(ResourceNameRef(
-                                StringPiece16(symbolName.package, symbolName.packageLen),
-                                *type,
-                                StringPiece16(symbolName.name, symbolName.nameLen))),
-                            bagBegin[i].map.value.data
-            });
-        }
-    }
-
-    entry.attr->typeMask |= attrTypeMask;
-    table.unlockBag(bagBegin);
-    return &entry;
+    return nullptr;
 }
 
 } // namespace aapt
diff --git a/tools/aapt2/ResourceTableResolver.h b/tools/aapt2/ResourceTableResolver.h
index c8e8ab7..8f6b0b5 100644
--- a/tools/aapt2/ResourceTableResolver.h
+++ b/tools/aapt2/ResourceTableResolver.h
@@ -24,7 +24,6 @@
 #include "ResourceValues.h"
 
 #include <androidfw/AssetManager.h>
-#include <androidfw/ResourceTypes.h>
 #include <memory>
 #include <vector>
 #include <unordered_set>
@@ -40,8 +39,9 @@
      * Creates a resolver with a local ResourceTable and an AssetManager
      * loaded with library packages.
      */
-    ResourceTableResolver(std::shared_ptr<const ResourceTable> table,
-                          std::shared_ptr<const android::AssetManager> sources);
+    ResourceTableResolver(
+            std::shared_ptr<const ResourceTable> table,
+            const std::vector<std::shared_ptr<const android::AssetManager>>& sources);
 
     ResourceTableResolver(const ResourceTableResolver&) = delete; // Not copyable.
 
@@ -51,8 +51,6 @@
 
     virtual Maybe<ResourceName> findName(ResourceId resId) override;
 
-    const android::ResTable& getResTable() const;
-
 private:
     struct CacheEntry {
         ResourceId id;
@@ -62,15 +60,11 @@
     const CacheEntry* buildCacheEntry(const ResourceName& name);
 
     std::shared_ptr<const ResourceTable> mTable;
-    std::shared_ptr<const android::AssetManager> mSources;
+    std::vector<std::shared_ptr<const android::AssetManager>> mSources;
     std::map<ResourceName, CacheEntry> mCache;
     std::unordered_set<std::u16string> mIncludedPackages;
 };
 
-inline const android::ResTable& ResourceTableResolver::getResTable() const {
-    return mSources->getResources(false);
-}
-
 } // namespace aapt
 
 #endif // AAPT_RESOURCE_TABLE_RESOLVER_H
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index 2bf38e4..be0c3f3 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -101,8 +101,8 @@
 }
 
 bool Id::flatten(android::Res_value& out) const {
-    out.dataType = android::Res_value::TYPE_NULL;
-    out.data = android::Res_value::DATA_NULL_UNDEFINED;
+    out.dataType = android::Res_value::TYPE_INT_BOOLEAN;
+    out.data = 0;
     return true;
 }
 
@@ -231,17 +231,15 @@
     return attr;
 }
 
-void Attribute::print(std::ostream& out) const {
-    out << "(attr)";
+void Attribute::printMask(std::ostream& out) const {
     if (typeMask == android::ResTable_map::TYPE_ANY) {
-        out << " any";
+        out << "any";
         return;
     }
 
     bool set = false;
     if ((typeMask & android::ResTable_map::TYPE_REFERENCE) != 0) {
         if (!set) {
-            out << " ";
             set = true;
         } else {
             out << "|";
@@ -251,7 +249,6 @@
 
     if ((typeMask & android::ResTable_map::TYPE_STRING) != 0) {
         if (!set) {
-            out << " ";
             set = true;
         } else {
             out << "|";
@@ -261,7 +258,6 @@
 
     if ((typeMask & android::ResTable_map::TYPE_INTEGER) != 0) {
         if (!set) {
-            out << " ";
             set = true;
         } else {
             out << "|";
@@ -271,7 +267,6 @@
 
     if ((typeMask & android::ResTable_map::TYPE_BOOLEAN) != 0) {
         if (!set) {
-            out << " ";
             set = true;
         } else {
             out << "|";
@@ -281,7 +276,6 @@
 
     if ((typeMask & android::ResTable_map::TYPE_COLOR) != 0) {
         if (!set) {
-            out << " ";
             set = true;
         } else {
             out << "|";
@@ -291,7 +285,6 @@
 
     if ((typeMask & android::ResTable_map::TYPE_FLOAT) != 0) {
         if (!set) {
-            out << " ";
             set = true;
         } else {
             out << "|";
@@ -301,7 +294,6 @@
 
     if ((typeMask & android::ResTable_map::TYPE_DIMENSION) != 0) {
         if (!set) {
-            out << " ";
             set = true;
         } else {
             out << "|";
@@ -311,7 +303,6 @@
 
     if ((typeMask & android::ResTable_map::TYPE_FRACTION) != 0) {
         if (!set) {
-            out << " ";
             set = true;
         } else {
             out << "|";
@@ -321,7 +312,6 @@
 
     if ((typeMask & android::ResTable_map::TYPE_ENUM) != 0) {
         if (!set) {
-            out << " ";
             set = true;
         } else {
             out << "|";
@@ -331,13 +321,17 @@
 
     if ((typeMask & android::ResTable_map::TYPE_FLAGS) != 0) {
         if (!set) {
-            out << " ";
             set = true;
         } else {
             out << "|";
         }
         out << "flags";
     }
+}
+
+void Attribute::print(std::ostream& out) const {
+    out << "(attr) ";
+    printMask(out);
 
     out << " ["
         << util::joiner(symbols.begin(), symbols.end(), ", ")
@@ -348,10 +342,6 @@
     }
 }
 
-static ::std::ostream& operator<<(::std::ostream& out, const Attribute::Symbol& s) {
-    return out << s.symbol.name.entry << "=" << s.value;
-}
-
 Style::Style(bool weak) : weak(weak) {
 }
 
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index f8ece6f..b448bd8 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -222,6 +222,7 @@
 
     bool isWeak() const override;
     virtual Attribute* clone(StringPool* newPool) const override;
+    void printMask(std::ostream& out) const;
     virtual void print(std::ostream& out) const override;
 };
 
@@ -280,6 +281,10 @@
     return out;
 }
 
+inline ::std::ostream& operator<<(::std::ostream& out, const Attribute::Symbol& s) {
+    return out << s.symbol.name.entry << "=" << s.value;
+}
+
 /**
  * The argument object that gets passed through the value
  * back to the ValueVisitor. Subclasses of ValueVisitor should
diff --git a/tools/aapt2/TableFlattener.cpp b/tools/aapt2/TableFlattener.cpp
index aa0f1d5..539c48f 100644
--- a/tools/aapt2/TableFlattener.cpp
+++ b/tools/aapt2/TableFlattener.cpp
@@ -79,6 +79,7 @@
 
         // Write the key.
         if (!Res_INTERNALID(key.id.id) && !key.id.isValid()) {
+            assert(key.name.isValid());
             mSymbols->push_back(std::make_pair(ResourceNameRef(key.name),
                     mOut->size() - sizeof(*outMapEntry)));
         }
@@ -96,6 +97,23 @@
         outMapEntry->value.size = sizeof(outMapEntry->value);
     }
 
+    void flattenValueOnly(const Item& value) {
+        mMap->count++;
+
+        android::ResTable_map* outMapEntry = mOut->nextBlock<android::ResTable_map>();
+
+        // Write the value.
+        value.flatten(outMapEntry->value);
+
+        if (outMapEntry->value.data == 0x0) {
+            visitFunc<Reference>(value, [&](const Reference& reference) {
+                mSymbols->push_back(std::make_pair(ResourceNameRef(reference.name),
+                        mOut->size() - sizeof(outMapEntry->value.data)));
+            });
+        }
+        outMapEntry->value.size = sizeof(outMapEntry->value);
+    }
+
     static bool compareStyleEntries(const Style::Entry* lhs, const Style::Entry* rhs) {
         return lhs->key.id < rhs->key.id;
     }
@@ -139,7 +157,7 @@
 
     void visit(const Array& array, ValueVisitorArgs&) override {
         for (const auto& item : array.items) {
-            flattenEntry({}, *item);
+            flattenValueOnly(*item);
         }
     }
 
@@ -334,6 +352,10 @@
         spec->id = type->typeId;
         spec->entryCount = type->entries.size();
 
+        if (type->entries.empty()) {
+            continue;
+        }
+
         // Reserve space for the masks of each resource in this type. These
         // show for which configuration axis the resource changes.
         uint32_t* configMasks = typeBlock.nextBlock<uint32_t>(type->entries.size());
diff --git a/tools/aapt2/XmlFlattener.cpp b/tools/aapt2/XmlFlattener.cpp
index 650e624..97e4f68 100644
--- a/tools/aapt2/XmlFlattener.cpp
+++ b/tools/aapt2/XmlFlattener.cpp
@@ -300,6 +300,7 @@
                 elem->attributeCount = sortedAttributes.size();
 
                 // Flatten the sorted attributes.
+                uint16_t attributeIndex = 1;
                 for (auto entry : sortedAttributes) {
                     android::ResXMLTree_attribute* attr =
                             out.nextBlock<android::ResXMLTree_attribute>();
@@ -310,44 +311,64 @@
                         attr->ns.index = -1;
                     }
 
-                    stringRefs.emplace_back(entry.nameRef, &attr->name);
-                    attr->rawValue.index = -1;
-
                     StringPool::Ref rawValueRef = pool.makeRef(entry.xmlAttr->value, lowPriority);
 
-                    if (entry.attr) {
-                        std::unique_ptr<Item> value = ResourceParser::parseItemForAttribute(
-                                entry.xmlAttr->value, *entry.attr);
-                        if (value) {
-                            AttributeValueFlattener flattener(
-                                    mResolver,
-                                    &logger,
-                                    &attr->typedValue,
-                                    parser,
-                                    &error,
-                                    rawValueRef,
-                                    &options.defaultPackage,
-                                    &stringRefs);
-                            value->accept(flattener, {});
-                        } else if (!(entry.attr->typeMask & android::ResTable_map::TYPE_STRING)) {
-                            logger.error(parser->getLineNumber())
-                                    << "'"
-                                    << *rawValueRef
-                                    << "' is not compatible with attribute "
-                                    << *entry.attr
-                                    << "."
-                                    << std::endl;
-                            error = true;
-                        } else {
-                            attr->typedValue.dataType = android::Res_value::TYPE_STRING;
-                            stringRefs.emplace_back(rawValueRef, &attr->rawValue);
-                            stringRefs.emplace_back(rawValueRef,
-                                    reinterpret_cast<android::ResStringPool_ref*>(
-                                            &attr->typedValue.data));
+                    stringRefs.emplace_back(entry.nameRef, &attr->name);
+
+                    if (options.keepRawValues) {
+                        stringRefs.emplace_back(rawValueRef, &attr->rawValue);
+                    } else {
+                        attr->rawValue.index = -1;
+                    }
+
+                    // Assign the indices for specific attributes.
+                    if (entry.xmlAttr->namespaceUri == kSchemaAndroid &&
+                            entry.xmlAttr->name == u"id") {
+                        elem->idIndex = attributeIndex;
+                    } else if (entry.xmlAttr->namespaceUri.empty()) {
+                        if (entry.xmlAttr->name == u"class") {
+                            elem->classIndex = attributeIndex;
+                        } else if (entry.xmlAttr->name == u"style") {
+                            elem->styleIndex = attributeIndex;
                         }
+                    }
+
+                    std::unique_ptr<Item> value;
+                    if (entry.attr) {
+                        value = ResourceParser::parseItemForAttribute(entry.xmlAttr->value,
+                                                                      *entry.attr);
+                    } else {
+                        bool create = false;
+                        value = ResourceParser::tryParseReference(entry.xmlAttr->value, &create);
+                    }
+
+                    if (mResolver && value) {
+                        AttributeValueFlattener flattener(
+                                mResolver,
+                                &logger,
+                                &attr->typedValue,
+                                parser,
+                                &error,
+                                rawValueRef,
+                                &options.defaultPackage,
+                                &stringRefs);
+                        value->accept(flattener, {});
+                    } else if (!value && entry.attr &&
+                            !(entry.attr->typeMask & android::ResTable_map::TYPE_STRING)) {
+                        logger.error(parser->getLineNumber())
+                                << "'"
+                                << *rawValueRef
+                                << "' is not compatible with attribute "
+                                << *entry.attr
+                                << "."
+                                << std::endl;
+                        error = true;
                     } else {
                         attr->typedValue.dataType = android::Res_value::TYPE_STRING;
-                        stringRefs.emplace_back(rawValueRef, &attr->rawValue);
+                        if (!options.keepRawValues) {
+                            // Don't set the string twice.
+                            stringRefs.emplace_back(rawValueRef, &attr->rawValue);
+                        }
                         stringRefs.emplace_back(rawValueRef,
                                 reinterpret_cast<android::ResStringPool_ref*>(
                                         &attr->typedValue.data));
@@ -440,6 +461,9 @@
     header->header.type = android::RES_XML_TYPE;
     header->header.headerSize = sizeof(*header);
 
+    // Flatten the StringPool.
+    StringPool::flattenUtf16(outBuffer, pool);
+
     // Write the array of resource IDs, indexed by StringPool order.
     const size_t beforeResIdMapIndex = outBuffer->size();
     android::ResChunk_header* resIdMapChunk = outBuffer->nextBlock<android::ResChunk_header>();
@@ -458,10 +482,7 @@
     }
     resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex;
 
-    // Flatten the StringPool.
-    StringPool::flattenUtf16(outBuffer, pool);
-
-    // Move the temporary BigBuffer into outBuffer->
+    // Move the temporary BigBuffer into outBuffer.
     outBuffer->appendBuffer(std::move(out));
 
     header->header.size = outBuffer->size() - beforeXmlTreeIndex;
diff --git a/tools/aapt2/XmlFlattener.h b/tools/aapt2/XmlFlattener.h
index 60a500e..2cfcc16 100644
--- a/tools/aapt2/XmlFlattener.h
+++ b/tools/aapt2/XmlFlattener.h
@@ -47,6 +47,12 @@
          * max SDK.
          */
         Maybe<size_t> maxSdkAttribute;
+
+        /**
+         * Setting this to true will keep the raw string value of
+         * an attribute's value when it has been resolved.
+         */
+        bool keepRawValues = false;
     };
 
     /**