AAPT2: Separate out the various steps

An early refactor. Some ideas became clearer as
development continued. Now the various phases are much
clearer and more easily reusable.

Also added a ton of tests!

Change-Id: Ic8f0a70c8222370352e63533b329c40457c0903e
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index e5c42d5..275476c 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -25,62 +25,71 @@
 
 main := Main.cpp
 sources := \
-	BigBuffer.cpp \
-	BinaryResourceParser.cpp \
-	BindingXmlPullParser.cpp \
+	compile/IdAssigner.cpp \
+	compile/Png.cpp \
+	compile/XmlIdCollector.cpp \
+	flatten/Archive.cpp \
+	flatten/TableFlattener.cpp \
+	flatten/XmlFlattener.cpp \
+	link/AutoVersioner.cpp \
+	link/PrivateAttributeMover.cpp \
+	link/ReferenceLinker.cpp \
+	link/TableMerger.cpp \
+	link/XmlReferenceLinker.cpp \
+	process/SymbolTable.cpp \
+	unflatten/BinaryResourceParser.cpp \
+	unflatten/ResChunkPullParser.cpp \
+	util/BigBuffer.cpp \
+	util/Files.cpp \
+	util/Util.cpp \
 	ConfigDescription.cpp \
 	Debug.cpp \
-	Files.cpp \
-	Flag.cpp \
+	Flags.cpp \
 	JavaClassGenerator.cpp \
-	Linker.cpp \
 	Locale.cpp \
-	Logger.cpp \
-	ManifestMerger.cpp \
-	ManifestParser.cpp \
-	ManifestValidator.cpp \
-	Png.cpp \
 	ProguardRules.cpp \
-	ResChunkPullParser.cpp \
 	Resource.cpp \
 	ResourceParser.cpp \
 	ResourceTable.cpp \
-	ResourceTableResolver.cpp \
+	ResourceUtils.cpp \
 	ResourceValues.cpp \
 	SdkConstants.cpp \
 	StringPool.cpp \
-	TableFlattener.cpp \
-	Util.cpp \
-	ScopedXmlPullParser.cpp \
-	SourceXmlPullParser.cpp \
-	XliffXmlPullParser.cpp \
 	XmlDom.cpp \
-	XmlFlattener.cpp \
-	ZipEntry.cpp \
-	ZipFile.cpp
+	XmlPullParser.cpp
 
 testSources := \
-	BigBuffer_test.cpp \
-	BindingXmlPullParser_test.cpp \
-	Compat_test.cpp \
+	compile/IdAssigner_test.cpp \
+	compile/XmlIdCollector_test.cpp \
+	flatten/FileExportWriter_test.cpp \
+	flatten/TableFlattener_test.cpp \
+	flatten/XmlFlattener_test.cpp \
+	link/AutoVersioner_test.cpp \
+	link/PrivateAttributeMover_test.cpp \
+	link/ReferenceLinker_test.cpp \
+	link/TableMerger_test.cpp \
+	link/XmlReferenceLinker_test.cpp \
+	process/SymbolTable_test.cpp \
+	unflatten/FileExportHeaderReader_test.cpp \
+	util/BigBuffer_test.cpp \
+	util/Maybe_test.cpp \
+	util/StringPiece_test.cpp \
+	util/Util_test.cpp \
 	ConfigDescription_test.cpp \
 	JavaClassGenerator_test.cpp \
-	Linker_test.cpp \
 	Locale_test.cpp \
-	ManifestMerger_test.cpp \
-	ManifestParser_test.cpp \
-	Maybe_test.cpp \
-	NameMangler_test.cpp \
-	ResourceParser_test.cpp \
 	Resource_test.cpp \
+	ResourceParser_test.cpp \
 	ResourceTable_test.cpp \
-	ScopedXmlPullParser_test.cpp \
-	StringPiece_test.cpp \
+	ResourceUtils_test.cpp \
 	StringPool_test.cpp \
-	Util_test.cpp \
-	XliffXmlPullParser_test.cpp \
+	ValueVisitor_test.cpp \
 	XmlDom_test.cpp \
-	XmlFlattener_test.cpp
+	XmlPullParser_test.cpp
+
+toolSources := \
+	compile/Compile.cpp \
+	link/Link.cpp
 
 hostLdLibs :=
 
@@ -101,7 +110,7 @@
 endif
 
 cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG
-cppFlags := -std=c++11 -Wno-missing-field-initializers -Wno-unused-private-field
+cppFlags := -std=c++11 -Wno-missing-field-initializers -fno-exceptions
 
 # ==========================================================
 # Build the host static library: libaapt2
@@ -139,7 +148,7 @@
 include $(CLEAR_VARS)
 LOCAL_MODULE := aapt2
 
-LOCAL_SRC_FILES := $(main)
+LOCAL_SRC_FILES := $(main) $(toolSources)
 
 LOCAL_STATIC_LIBRARIES += libaapt2 $(hostStaticLibs)
 LOCAL_LDLIBS += $(hostLdLibs)
diff --git a/tools/aapt2/BinaryResourceParser.cpp b/tools/aapt2/BinaryResourceParser.cpp
deleted file mode 100644
index 4f1947a..0000000
--- a/tools/aapt2/BinaryResourceParser.cpp
+++ /dev/null
@@ -1,897 +0,0 @@
-/*
- * 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 "BinaryResourceParser.h"
-#include "Logger.h"
-#include "ResChunkPullParser.h"
-#include "Resolver.h"
-#include "ResourceParser.h"
-#include "ResourceTable.h"
-#include "ResourceTypeExtensions.h"
-#include "ResourceValues.h"
-#include "Source.h"
-#include "Util.h"
-
-#include <androidfw/ResourceTypes.h>
-#include <androidfw/TypeWrappers.h>
-#include <map>
-#include <string>
-
-namespace aapt {
-
-using namespace android;
-
-/*
- * Visitor that converts a reference's resource ID to a resource name,
- * given a mapping from resource ID to resource name.
- */
-struct ReferenceIdToNameVisitor : ValueVisitor {
-    ReferenceIdToNameVisitor(const std::shared_ptr<IResolver>& resolver,
-                             std::map<ResourceId, ResourceName>* cache) :
-            mResolver(resolver), mCache(cache) {
-    }
-
-    void visit(Reference& reference, ValueVisitorArgs&) override {
-        idToName(reference);
-    }
-
-    void visit(Attribute& attr, ValueVisitorArgs&) override {
-        for (auto& entry : attr.symbols) {
-            idToName(entry.symbol);
-        }
-    }
-
-    void visit(Style& style, ValueVisitorArgs&) override {
-        if (style.parent.id.isValid()) {
-            idToName(style.parent);
-        }
-
-        for (auto& entry : style.entries) {
-            idToName(entry.key);
-            entry.value->accept(*this, {});
-        }
-    }
-
-    void visit(Styleable& styleable, ValueVisitorArgs&) override {
-        for (auto& attr : styleable.entries) {
-            idToName(attr);
-        }
-    }
-
-    void visit(Array& array, ValueVisitorArgs&) override {
-        for (auto& item : array.items) {
-            item->accept(*this, {});
-        }
-    }
-
-    void visit(Plural& plural, ValueVisitorArgs&) override {
-        for (auto& item : plural.values) {
-            if (item) {
-                item->accept(*this, {});
-            }
-        }
-    }
-
-private:
-    void idToName(Reference& reference) {
-        if (!reference.id.isValid()) {
-            return;
-        }
-
-        auto cacheIter = mCache->find(reference.id);
-        if (cacheIter != mCache->end()) {
-            reference.name = cacheIter->second;
-            reference.id = 0;
-        } else {
-            Maybe<ResourceName> result = mResolver->findName(reference.id);
-            if (result) {
-                reference.name = result.value();
-
-                // Add to cache.
-                mCache->insert({reference.id, reference.name});
-
-                reference.id = 0;
-            }
-        }
-    }
-
-    std::shared_ptr<IResolver> mResolver;
-    std::map<ResourceId, ResourceName>* mCache;
-};
-
-
-BinaryResourceParser::BinaryResourceParser(const std::shared_ptr<ResourceTable>& table,
-                                           const std::shared_ptr<IResolver>& resolver,
-                                           const Source& source,
-                                           const std::u16string& defaultPackage,
-                                           const void* data,
-                                           size_t len) :
-        mTable(table), mResolver(resolver), mSource(source), mDefaultPackage(defaultPackage),
-        mData(data), mDataLen(len) {
-}
-
-bool BinaryResourceParser::parse() {
-    ResChunkPullParser parser(mData, mDataLen);
-
-    bool error = false;
-    while(ResChunkPullParser::isGoodEvent(parser.next())) {
-        if (parser.getChunk()->type != android::RES_TABLE_TYPE) {
-            Logger::warn(mSource)
-                    << "unknown chunk of type '"
-                    << parser.getChunk()->type
-                    << "'."
-                    << std::endl;
-            continue;
-        }
-
-        error |= !parseTable(parser.getChunk());
-    }
-
-    if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
-        Logger::error(mSource)
-                << "bad document: "
-                << parser.getLastError()
-                << "."
-                << std::endl;
-        return false;
-    }
-    return !error;
-}
-
-bool BinaryResourceParser::getSymbol(const void* data, ResourceNameRef* outSymbol) {
-    if (!mSymbolEntries || mSymbolEntryCount == 0) {
-        return false;
-    }
-
-    if (reinterpret_cast<uintptr_t>(data) < reinterpret_cast<uintptr_t>(mData)) {
-        return false;
-    }
-
-    // We only support 32 bit offsets right now.
-    const uintptr_t offset = reinterpret_cast<uintptr_t>(data) -
-            reinterpret_cast<uintptr_t>(mData);
-    if (offset > std::numeric_limits<uint32_t>::max()) {
-        return false;
-    }
-
-    for (size_t i = 0; i < mSymbolEntryCount; i++) {
-        if (mSymbolEntries[i].offset == offset) {
-            // This offset is a symbol!
-            const StringPiece16 str = util::getString(mSymbolPool,
-                                                      mSymbolEntries[i].stringIndex);
-            StringPiece16 typeStr;
-            ResourceParser::extractResourceName(str, &outSymbol->package, &typeStr,
-                                                &outSymbol->entry);
-            const ResourceType* type = parseResourceType(typeStr);
-            if (!type) {
-                return false;
-            }
-            if (outSymbol->package.empty()) {
-                outSymbol->package = mTable->getPackage();
-            }
-            outSymbol->type = *type;
-
-            // Since we scan the symbol table in order, we can start looking for the
-            // next symbol from this point.
-            mSymbolEntryCount -= i + 1;
-            mSymbolEntries += i + 1;
-            return true;
-        }
-    }
-    return false;
-}
-
-bool BinaryResourceParser::parseSymbolTable(const ResChunk_header* chunk) {
-    const SymbolTable_header* symbolTableHeader = convertTo<SymbolTable_header>(chunk);
-    if (!symbolTableHeader) {
-        Logger::error(mSource)
-                << "could not parse chunk as SymbolTable_header."
-                << std::endl;
-        return false;
-    }
-
-    const size_t entrySizeBytes = symbolTableHeader->count * sizeof(SymbolTable_entry);
-    if (entrySizeBytes > getChunkDataLen(symbolTableHeader->header)) {
-        Logger::error(mSource)
-                << "entries extend beyond chunk."
-                << std::endl;
-        return false;
-    }
-
-    mSymbolEntries = reinterpret_cast<const SymbolTable_entry*>(
-            getChunkData(symbolTableHeader->header));
-    mSymbolEntryCount = symbolTableHeader->count;
-
-    ResChunkPullParser parser(getChunkData(symbolTableHeader->header) + entrySizeBytes,
-                              getChunkDataLen(symbolTableHeader->header) - entrySizeBytes);
-    if (!ResChunkPullParser::isGoodEvent(parser.next())) {
-        Logger::error(mSource)
-                << "failed to parse chunk: "
-                << parser.getLastError()
-                << "."
-                << std::endl;
-        return false;
-    }
-
-    if (parser.getChunk()->type != android::RES_STRING_POOL_TYPE) {
-        Logger::error(mSource)
-                << "expected Symbol string pool."
-                << std::endl;
-        return false;
-    }
-
-    if (mSymbolPool.setTo(parser.getChunk(), parser.getChunk()->size) != NO_ERROR) {
-        Logger::error(mSource)
-                << "failed to parse symbol string pool with code: "
-                << mSymbolPool.getError()
-                << "."
-                << std::endl;
-        return false;
-    }
-    return true;
-}
-
-bool BinaryResourceParser::parseTable(const ResChunk_header* chunk) {
-    const ResTable_header* tableHeader = convertTo<ResTable_header>(chunk);
-    if (!tableHeader) {
-        Logger::error(mSource)
-                << "could not parse chunk as ResTable_header."
-                << std::endl;
-        return false;
-    }
-
-    ResChunkPullParser parser(getChunkData(tableHeader->header),
-                              getChunkDataLen(tableHeader->header));
-    while (ResChunkPullParser::isGoodEvent(parser.next())) {
-        switch (parser.getChunk()->type) {
-        case android::RES_STRING_POOL_TYPE:
-            if (mValuePool.getError() == NO_INIT) {
-                if (mValuePool.setTo(parser.getChunk(), parser.getChunk()->size) !=
-                        NO_ERROR) {
-                    Logger::error(mSource)
-                            << "failed to parse value string pool with code: "
-                            << mValuePool.getError()
-                            << "."
-                            << std::endl;
-                    return false;
-                }
-
-                // Reserve some space for the strings we are going to add.
-                mTable->getValueStringPool().hintWillAdd(
-                        mValuePool.size(), mValuePool.styleCount());
-            } else {
-                Logger::warn(mSource)
-                    << "unexpected string pool."
-                    << std::endl;
-            }
-            break;
-
-        case RES_TABLE_SYMBOL_TABLE_TYPE:
-            if (!parseSymbolTable(parser.getChunk())) {
-                return false;
-            }
-            break;
-
-        case RES_TABLE_SOURCE_POOL_TYPE: {
-            if (mSourcePool.setTo(getChunkData(*parser.getChunk()),
-                        getChunkDataLen(*parser.getChunk())) != NO_ERROR) {
-                Logger::error(mSource)
-                        << "failed to parse source pool with code: "
-                        << mSourcePool.getError()
-                        << "."
-                        << std::endl;
-                return false;
-            }
-            break;
-        }
-
-        case android::RES_TABLE_PACKAGE_TYPE:
-            if (!parsePackage(parser.getChunk())) {
-                return false;
-            }
-            break;
-
-        default:
-            Logger::warn(mSource)
-                << "unexpected chunk of type "
-                << parser.getChunk()->type
-                << "."
-                << std::endl;
-            break;
-        }
-    }
-
-    if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
-        Logger::error(mSource)
-            << "bad resource table: " << parser.getLastError()
-            << "."
-            << std::endl;
-        return false;
-    }
-    return true;
-}
-
-bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) {
-    if (mValuePool.getError() != NO_ERROR) {
-        Logger::error(mSource)
-                << "no value string pool for ResTable."
-                << std::endl;
-        return false;
-    }
-
-    const ResTable_package* packageHeader = convertTo<ResTable_package>(chunk);
-    if (!packageHeader) {
-        Logger::error(mSource)
-                << "could not parse chunk as ResTable_header."
-                << std::endl;
-        return false;
-    }
-
-    if (mTable->getPackageId() == ResourceTable::kUnsetPackageId) {
-        // This is the first time the table has it's package ID set.
-        mTable->setPackageId(packageHeader->id);
-    } else if (mTable->getPackageId() != packageHeader->id) {
-        Logger::error(mSource)
-                << "ResTable_package has package ID "
-                << std::hex << packageHeader->id << std::dec
-                << " but ResourceTable has package ID "
-                << std::hex << mTable->getPackageId() << std::dec
-                << std::endl;
-        return false;
-    }
-
-    size_t len = strnlen16(reinterpret_cast<const char16_t*>(packageHeader->name),
-            sizeof(packageHeader->name) / sizeof(packageHeader->name[0]));
-    if (mTable->getPackage().empty() && len == 0) {
-        mTable->setPackage(mDefaultPackage);
-    } else if (len > 0) {
-        StringPiece16 thisPackage(reinterpret_cast<const char16_t*>(packageHeader->name), len);
-        if (mTable->getPackage().empty()) {
-            mTable->setPackage(thisPackage);
-        } else if (thisPackage != mTable->getPackage()) {
-            Logger::error(mSource)
-                    << "incompatible packages: "
-                    << mTable->getPackage()
-                    << " vs. "
-                    << thisPackage
-                    << std::endl;
-            return false;
-        }
-    }
-
-    ResChunkPullParser parser(getChunkData(packageHeader->header),
-                              getChunkDataLen(packageHeader->header));
-    while (ResChunkPullParser::isGoodEvent(parser.next())) {
-        switch (parser.getChunk()->type) {
-        case android::RES_STRING_POOL_TYPE:
-            if (mTypePool.getError() == NO_INIT) {
-                if (mTypePool.setTo(parser.getChunk(), parser.getChunk()->size) !=
-                        NO_ERROR) {
-                    Logger::error(mSource)
-                            << "failed to parse type string pool with code "
-                            << mTypePool.getError()
-                            << "."
-                            << std::endl;
-                    return false;
-                }
-            } else if (mKeyPool.getError() == NO_INIT) {
-                if (mKeyPool.setTo(parser.getChunk(), parser.getChunk()->size) !=
-                        NO_ERROR) {
-                    Logger::error(mSource)
-                            << "failed to parse key string pool with code "
-                            << mKeyPool.getError()
-                            << "."
-                            << std::endl;
-                    return false;
-                }
-            } else {
-                Logger::warn(mSource)
-                        << "unexpected string pool."
-                        << std::endl;
-            }
-            break;
-
-        case android::RES_TABLE_TYPE_SPEC_TYPE:
-            if (!parseTypeSpec(parser.getChunk())) {
-                return false;
-            }
-            break;
-
-        case android::RES_TABLE_TYPE_TYPE:
-            if (!parseType(parser.getChunk())) {
-                return false;
-            }
-            break;
-
-        case RES_TABLE_PUBLIC_TYPE:
-            if (!parsePublic(parser.getChunk())) {
-                return false;
-            }
-            break;
-
-        default:
-            Logger::warn(mSource)
-                    << "unexpected chunk of type "
-                    << parser.getChunk()->type
-                    << "."
-                    << std::endl;
-            break;
-        }
-    }
-
-    if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
-        Logger::error(mSource)
-                << "bad package: "
-                << parser.getLastError()
-                << "."
-                << std::endl;
-        return false;
-    }
-
-    // Now go through the table and change resource ID references to
-    // symbolic references.
-
-    ReferenceIdToNameVisitor visitor(mResolver, &mIdIndex);
-    for (auto& type : *mTable) {
-        for (auto& entry : type->entries) {
-            for (auto& configValue : entry->values) {
-                configValue.value->accept(visitor, {});
-            }
-        }
-    }
-    return true;
-}
-
-bool BinaryResourceParser::parsePublic(const ResChunk_header* chunk) {
-    const Public_header* header = convertTo<Public_header>(chunk);
-
-    if (header->typeId == 0) {
-        Logger::error(mSource)
-                << "invalid type ID " << header->typeId << std::endl;
-        return false;
-    }
-
-    const ResourceType* parsedType = parseResourceType(util::getString(mTypePool,
-                                                                       header->typeId - 1));
-    if (!parsedType) {
-        Logger::error(mSource)
-                << "invalid type " << util::getString(mTypePool, header->typeId - 1) << std::endl;
-        return false;
-    }
-
-    const uintptr_t chunkEnd = reinterpret_cast<uintptr_t>(chunk) + chunk->size;
-    const Public_entry* entry = reinterpret_cast<const Public_entry*>(
-            getChunkData(header->header));
-    for (uint32_t i = 0; i < header->count; i++) {
-        if (reinterpret_cast<uintptr_t>(entry) + sizeof(*entry) > chunkEnd) {
-            Logger::error(mSource)
-                    << "Public_entry extends beyond chunk."
-                    << std::endl;
-            return false;
-        }
-
-        const ResourceId resId = { mTable->getPackageId(), header->typeId, entry->entryId };
-        const ResourceName name = {
-                mTable->getPackage(),
-                *parsedType,
-                util::getString(mKeyPool, entry->key.index).toString() };
-
-        SourceLine source;
-        if (mSourcePool.getError() == NO_ERROR) {
-            source.path = util::utf16ToUtf8(util::getString(mSourcePool, entry->source.index));
-            source.line = entry->sourceLine;
-        }
-
-        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;
-}
-
-bool BinaryResourceParser::parseTypeSpec(const ResChunk_header* chunk) {
-    if (mTypePool.getError() != NO_ERROR) {
-        Logger::error(mSource)
-                << "no type string pool available for ResTable_typeSpec."
-                << std::endl;
-        return false;
-    }
-
-    const ResTable_typeSpec* typeSpec = convertTo<ResTable_typeSpec>(chunk);
-    if (!typeSpec) {
-        Logger::error(mSource)
-                << "could not parse chunk as ResTable_typeSpec."
-                << std::endl;
-        return false;
-    }
-
-    if (typeSpec->id == 0) {
-        Logger::error(mSource)
-                << "ResTable_typeSpec has invalid id: "
-                << typeSpec->id
-                << "."
-                << std::endl;
-        return false;
-    }
-    return true;
-}
-
-bool BinaryResourceParser::parseType(const ResChunk_header* chunk) {
-    if (mTypePool.getError() != NO_ERROR) {
-        Logger::error(mSource)
-                << "no type string pool available for ResTable_typeSpec."
-                << std::endl;
-        return false;
-    }
-
-    if (mKeyPool.getError() != NO_ERROR) {
-        Logger::error(mSource)
-                << "no key string pool available for ResTable_type."
-                << std::endl;
-        return false;
-    }
-
-    const ResTable_type* type = convertTo<ResTable_type>(chunk);
-    if (!type) {
-        Logger::error(mSource)
-                << "could not parse chunk as ResTable_type."
-                << std::endl;
-        return false;
-    }
-
-    if (type->id == 0) {
-        Logger::error(mSource)
-                << "ResTable_type has invalid id: "
-                << type->id
-                << "."
-                << std::endl;
-        return false;
-    }
-
-    const ConfigDescription config(type->config);
-    const StringPiece16 typeName = util::getString(mTypePool, type->id - 1);
-
-    const ResourceType* parsedType = parseResourceType(typeName);
-    if (!parsedType) {
-        Logger::error(mSource)
-                << "invalid type name '"
-                << typeName
-                << "' for type with ID "
-                << uint32_t(type->id)
-                << "." << std::endl;
-        return false;
-    }
-
-    android::TypeVariant tv(type);
-    for (auto it = tv.beginEntries(); it != tv.endEntries(); ++it) {
-        if (!*it) {
-            continue;
-        }
-
-        const ResTable_entry* entry = *it;
-        const ResourceName name = {
-                mTable->getPackage(),
-                *parsedType,
-                util::getString(mKeyPool, entry->key.index).toString()
-        };
-
-        const ResourceId resId = { mTable->getPackageId(), type->id, it.index() };
-
-        std::unique_ptr<Value> resourceValue;
-        const ResTable_entry_source* sourceBlock = nullptr;
-        if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
-            const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry);
-            if (mapEntry->size - sizeof(*mapEntry) == sizeof(*sourceBlock)) {
-                const uint8_t* data = reinterpret_cast<const uint8_t*>(mapEntry);
-                data += mapEntry->size - sizeof(*sourceBlock);
-                sourceBlock = reinterpret_cast<const ResTable_entry_source*>(data);
-            }
-
-            // TODO(adamlesinski): Check that the entry count is valid.
-            resourceValue = parseMapEntry(name, config, mapEntry);
-        } else {
-            if (entry->size - sizeof(*entry) == sizeof(*sourceBlock)) {
-                const uint8_t* data = reinterpret_cast<const uint8_t*>(entry);
-                data += entry->size - sizeof(*sourceBlock);
-                sourceBlock = reinterpret_cast<const ResTable_entry_source*>(data);
-            }
-
-            const Res_value* value = reinterpret_cast<const Res_value*>(
-                    reinterpret_cast<const uint8_t*>(entry) + entry->size);
-            resourceValue = parseValue(name, config, value, entry->flags);
-        }
-
-        if (!resourceValue) {
-            // TODO(adamlesinski): For now this is ok, but it really shouldn't be.
-            continue;
-        }
-
-        SourceLine source = mSource.line(0);
-        if (sourceBlock) {
-            size_t len;
-            const char* str = mSourcePool.string8At(sourceBlock->pathIndex, &len);
-            if (str) {
-                source.path.assign(str, len);
-            }
-            source.line = sourceBlock->line;
-        }
-
-        if (!mTable->addResourceAllowMangled(name, config, source, std::move(resourceValue))) {
-            return false;
-        }
-
-        if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
-            if (!mTable->markPublicAllowMangled(name, resId, mSource.line(0))) {
-                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 });
-        }
-    }
-    return true;
-}
-
-std::unique_ptr<Item> BinaryResourceParser::parseValue(const ResourceNameRef& name,
-                                                       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);
-
-        const ResStringPool_span* spans = mValuePool.styleAt(value->data);
-        if (spans != nullptr) {
-            StyleString styleStr = { str.toString() };
-            while (spans->name.index != ResStringPool_span::END) {
-                styleStr.spans.push_back(Span{
-                        util::getString(mValuePool, spans->name.index).toString(),
-                        spans->firstChar,
-                        spans->lastChar
-                });
-                spans++;
-            }
-            return util::make_unique<StyledString>(
-                    mTable->getValueStringPool().makeRef(
-                            styleStr, StringPool::Context{1, config}));
-        } else {
-            if (name.type != ResourceType::kString &&
-                    util::stringStartsWith<char16_t>(str, u"res/")) {
-                // This must be a FileReference.
-                return util::make_unique<FileReference>(mTable->getValueStringPool().makeRef(
-                            str, StringPool::Context{ 0, config }));
-            }
-
-            // There are no styles associated with this string, so treat it as
-            // a simple string.
-            return util::make_unique<String>(
-                    mTable->getValueStringPool().makeRef(
-                            str, StringPool::Context{1, config}));
-        }
-    }
-
-    if (value->dataType == Res_value::TYPE_REFERENCE ||
-            value->dataType == Res_value::TYPE_ATTRIBUTE) {
-        const Reference::Type type = (value->dataType == Res_value::TYPE_REFERENCE) ?
-                    Reference::Type::kResource : Reference::Type::kAttribute;
-
-        if (value->data != 0) {
-            // This is a normal reference.
-            return util::make_unique<Reference>(value->data, type);
-        }
-
-        // This reference has an invalid ID. Check if it is an unresolved symbol.
-        ResourceNameRef symbol;
-        if (getSymbol(&value->data, &symbol)) {
-            return util::make_unique<Reference>(symbol, type);
-        }
-
-        // This is not an unresolved symbol, so it must be the magic @null reference.
-        Res_value nullType = {};
-        nullType.dataType = Res_value::TYPE_REFERENCE;
-        return util::make_unique<BinaryPrimitive>(nullType);
-    }
-
-    if (value->dataType == ExtendedTypes::TYPE_RAW_STRING) {
-        return util::make_unique<RawString>(
-                mTable->getValueStringPool().makeRef(util::getString(mValuePool, value->data),
-                                                    StringPool::Context{ 1, config }));
-    }
-
-    // Treat this as a raw binary primitive.
-    return util::make_unique<BinaryPrimitive>(*value);
-}
-
-std::unique_ptr<Value> BinaryResourceParser::parseMapEntry(const ResourceNameRef& name,
-                                                           const ConfigDescription& config,
-                                                           const ResTable_map_entry* map) {
-    switch (name.type) {
-        case ResourceType::kStyle:
-            return parseStyle(name, config, map);
-        case ResourceType::kAttr:
-            return parseAttr(name, config, map);
-        case ResourceType::kArray:
-            return parseArray(name, config, map);
-        case ResourceType::kStyleable:
-            return parseStyleable(name, config, map);
-        case ResourceType::kPlurals:
-            return parsePlural(name, config, map);
-        default:
-            break;
-    }
-    return {};
-}
-
-std::unique_ptr<Style> BinaryResourceParser::parseStyle(const ResourceNameRef& name,
-                                                        const ConfigDescription& config,
-                                                        const ResTable_map_entry* map) {
-    std::unique_ptr<Style> style = util::make_unique<Style>();
-    if (map->parent.ident == 0) {
-        // The parent is either not set or it is an unresolved symbol.
-        // Check to see if it is a symbol.
-        ResourceNameRef symbol;
-        if (getSymbol(&map->parent.ident, &symbol)) {
-            style->parent.name = symbol.toResourceName();
-        }
-    } else {
-         // The parent is a regular reference to a resource.
-        style->parent.id = map->parent.ident;
-    }
-
-    for (const ResTable_map& mapEntry : map) {
-        style->entries.emplace_back();
-        Style::Entry& styleEntry = style->entries.back();
-
-        if (mapEntry.name.ident == 0) {
-            // The map entry's key (attribute) is not set. This must be
-            // a symbol reference, so resolve it.
-            ResourceNameRef symbol;
-            bool result = getSymbol(&mapEntry.name.ident, &symbol);
-            assert(result);
-            styleEntry.key.name = symbol.toResourceName();
-        } else {
-            // The map entry's key (attribute) is a regular reference.
-            styleEntry.key.id = mapEntry.name.ident;
-        }
-
-        // Parse the attribute's value.
-        styleEntry.value = parseValue(name, config, &mapEntry.value, 0);
-        assert(styleEntry.value);
-    }
-    return style;
-}
-
-std::unique_ptr<Attribute> BinaryResourceParser::parseAttr(const ResourceNameRef& name,
-                                                           const ConfigDescription& config,
-                                                           const ResTable_map_entry* map) {
-    const bool isWeak = (map->flags & ResTable_entry::FLAG_WEAK) != 0;
-    std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(isWeak);
-
-    // First we must discover what type of attribute this is. Find the type mask.
-    auto typeMaskIter = std::find_if(begin(map), end(map), [](const ResTable_map& entry) -> bool {
-        return entry.name.ident == ResTable_map::ATTR_TYPE;
-    });
-
-    if (typeMaskIter != end(map)) {
-        attr->typeMask = typeMaskIter->value.data;
-    }
-
-    if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
-        for (const ResTable_map& mapEntry : map) {
-            if (Res_INTERNALID(mapEntry.name.ident)) {
-                continue;
-            }
-
-            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));
-        }
-    }
-
-    // TODO(adamlesinski): Find min, max, i80n, etc attributes.
-    return attr;
-}
-
-std::unique_ptr<Array> BinaryResourceParser::parseArray(const ResourceNameRef& name,
-                                                        const ConfigDescription& config,
-                                                        const ResTable_map_entry* map) {
-    std::unique_ptr<Array> array = util::make_unique<Array>();
-    for (const ResTable_map& mapEntry : map) {
-        array->items.push_back(parseValue(name, config, &mapEntry.value, 0));
-    }
-    return array;
-}
-
-std::unique_ptr<Styleable> BinaryResourceParser::parseStyleable(const ResourceNameRef& name,
-                                                                const ConfigDescription& config,
-                                                                const ResTable_map_entry* map) {
-    std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
-    for (const ResTable_map& mapEntry : map) {
-        if (mapEntry.name.ident == 0) {
-            // The map entry's key (attribute) is not set. This must be
-            // a symbol reference, so resolve it.
-            ResourceNameRef symbol;
-            bool result = getSymbol(&mapEntry.name.ident, &symbol);
-            assert(result);
-            styleable->entries.emplace_back(symbol);
-        } else {
-            // The map entry's key (attribute) is a regular reference.
-            styleable->entries.emplace_back(mapEntry.name.ident);
-        }
-    }
-    return styleable;
-}
-
-std::unique_ptr<Plural> BinaryResourceParser::parsePlural(const ResourceNameRef& name,
-                                                          const ConfigDescription& config,
-                                                          const ResTable_map_entry* map) {
-    std::unique_ptr<Plural> plural = util::make_unique<Plural>();
-    for (const ResTable_map& mapEntry : map) {
-        std::unique_ptr<Item> item = parseValue(name, config, &mapEntry.value, 0);
-
-        switch (mapEntry.name.ident) {
-            case android::ResTable_map::ATTR_ZERO:
-                plural->values[Plural::Zero] = std::move(item);
-                break;
-            case android::ResTable_map::ATTR_ONE:
-                plural->values[Plural::One] = std::move(item);
-                break;
-            case android::ResTable_map::ATTR_TWO:
-                plural->values[Plural::Two] = std::move(item);
-                break;
-            case android::ResTable_map::ATTR_FEW:
-                plural->values[Plural::Few] = std::move(item);
-                break;
-            case android::ResTable_map::ATTR_MANY:
-                plural->values[Plural::Many] = std::move(item);
-                break;
-            case android::ResTable_map::ATTR_OTHER:
-                plural->values[Plural::Other] = std::move(item);
-                break;
-        }
-    }
-    return plural;
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/BindingXmlPullParser.cpp b/tools/aapt2/BindingXmlPullParser.cpp
deleted file mode 100644
index 4b7a656..0000000
--- a/tools/aapt2/BindingXmlPullParser.cpp
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * 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 "BindingXmlPullParser.h"
-#include "Util.h"
-
-#include <iostream>
-#include <sstream>
-#include <string>
-#include <vector>
-
-namespace aapt {
-
-constexpr const char16_t* kBindingNamespaceUri = u"http://schemas.android.com/apk/binding";
-constexpr const char16_t* kAndroidNamespaceUri = u"http://schemas.android.com/apk/res/android";
-constexpr const char16_t* kVariableTagName = u"variable";
-constexpr const char* kBindingTagPrefix = "android:binding_";
-
-BindingXmlPullParser::BindingXmlPullParser(const std::shared_ptr<XmlPullParser>& parser) :
-        mParser(parser), mOverride(false), mNextTagId(0) {
-}
-
-bool BindingXmlPullParser::readVariableDeclaration() {
-    VarDecl var;
-
-    const auto endAttrIter = mParser->endAttributes();
-    for (auto attrIter = mParser->beginAttributes(); attrIter != endAttrIter; ++attrIter) {
-        if (!attrIter->namespaceUri.empty()) {
-            continue;
-        }
-
-        if (attrIter->name == u"name") {
-            var.name = util::utf16ToUtf8(attrIter->value);
-        } else if (attrIter->name == u"type") {
-            var.type = util::utf16ToUtf8(attrIter->value);
-        }
-    }
-
-    XmlPullParser::skipCurrentElement(mParser.get());
-
-    if (var.name.empty()) {
-        mLastError = "variable declaration missing name";
-        return false;
-    }
-
-    if (var.type.empty()) {
-        mLastError = "variable declaration missing type";
-        return false;
-    }
-
-    mVarDecls.push_back(std::move(var));
-    return true;
-}
-
-bool BindingXmlPullParser::readExpressions() {
-    mOverride = true;
-    std::vector<XmlPullParser::Attribute> expressions;
-    std::string idValue;
-
-    const auto endAttrIter = mParser->endAttributes();
-    for (auto attr = mParser->beginAttributes(); attr != endAttrIter; ++attr) {
-        if (attr->namespaceUri == kAndroidNamespaceUri && attr->name == u"id") {
-            idValue = util::utf16ToUtf8(attr->value);
-        } else {
-            StringPiece16 value = util::trimWhitespace(attr->value);
-            if (util::stringStartsWith<char16_t>(value, u"@{") &&
-                    util::stringEndsWith<char16_t>(value, u"}")) {
-                // This is attribute's value is an expression of the form
-                // @{expression}. We need to capture the expression inside.
-                expressions.push_back(XmlPullParser::Attribute{
-                        attr->namespaceUri,
-                        attr->name,
-                        value.substr(2, value.size() - 3).toString()
-                });
-            } else {
-                // This is a normal attribute, use as is.
-                mAttributes.emplace_back(*attr);
-            }
-        }
-    }
-
-    // Check if we have any expressions.
-    if (!expressions.empty()) {
-        // We have expressions, so let's assign the target a tag number
-        // and add it to our targets list.
-        int32_t targetId = mNextTagId++;
-        mTargets.push_back(Target{
-                util::utf16ToUtf8(mParser->getElementName()),
-                idValue,
-                targetId,
-                std::move(expressions)
-        });
-
-        std::stringstream numGen;
-        numGen << kBindingTagPrefix << targetId;
-        mAttributes.push_back(XmlPullParser::Attribute{
-                std::u16string(kAndroidNamespaceUri),
-                std::u16string(u"tag"),
-                util::utf8ToUtf16(numGen.str())
-        });
-    }
-    return true;
-}
-
-XmlPullParser::Event BindingXmlPullParser::next() {
-    // Clear old state in preparation for the next event.
-    mOverride = false;
-    mAttributes.clear();
-
-    while (true) {
-        Event event = mParser->next();
-        if (event == Event::kStartElement) {
-            if (mParser->getElementNamespace().empty() &&
-                    mParser->getElementName() == kVariableTagName) {
-                // This is a variable tag. Record data from it, and
-                // then discard the entire element.
-                if (!readVariableDeclaration()) {
-                    // mLastError is set, so getEvent will return kBadDocument.
-                    return getEvent();
-                }
-                continue;
-            } else {
-                // Check for expressions of the form @{} in attribute text.
-                const auto endAttrIter = mParser->endAttributes();
-                for (auto attr = mParser->beginAttributes(); attr != endAttrIter; ++attr) {
-                    StringPiece16 value = util::trimWhitespace(attr->value);
-                    if (util::stringStartsWith<char16_t>(value, u"@{") &&
-                            util::stringEndsWith<char16_t>(value, u"}")) {
-                        if (!readExpressions()) {
-                            return getEvent();
-                        }
-                        break;
-                    }
-                }
-            }
-        } else if (event == Event::kStartNamespace || event == Event::kEndNamespace) {
-            if (mParser->getNamespaceUri() == kBindingNamespaceUri) {
-                // Skip binding namespace tags.
-                continue;
-            }
-        }
-        return event;
-    }
-    return Event::kBadDocument;
-}
-
-bool BindingXmlPullParser::writeToFile(std::ostream& out) const {
-    out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
-    out << "<Layout directory=\"\" layout=\"\" layoutId=\"\">\n";
-
-    // Write the variables.
-    out << "  <Variables>\n";
-    for (const VarDecl& v : mVarDecls) {
-        out << "    <entries name=\"" << v.name << "\" type=\"" << v.type << "\"/>\n";
-    }
-    out << "  </Variables>\n";
-
-    // Write the imports.
-
-    std::stringstream tagGen;
-
-    // Write the targets.
-    out << "  <Targets>\n";
-    for (const Target& t : mTargets) {
-        tagGen.str({});
-        tagGen << kBindingTagPrefix << t.tagId;
-        out << "    <Target boundClass=\"" << t.className << "\" id=\"" << t.id
-            << "\" tag=\"" << tagGen.str() << "\">\n";
-        out << "      <Expressions>\n";
-        for (const XmlPullParser::Attribute& a : t.expressions) {
-            out << "        <Expression attribute=\"" << a.namespaceUri << ":" << a.name
-                << "\" text=\"" << a.value << "\"/>\n";
-        }
-        out << "      </Expressions>\n";
-        out << "    </Target>\n";
-    }
-    out << "  </Targets>\n";
-
-    out << "</Layout>\n";
-    return bool(out);
-}
-
-XmlPullParser::const_iterator BindingXmlPullParser::beginAttributes() const {
-    if (mOverride) {
-        return mAttributes.begin();
-    }
-    return mParser->beginAttributes();
-}
-
-XmlPullParser::const_iterator BindingXmlPullParser::endAttributes() const {
-    if (mOverride) {
-        return mAttributes.end();
-    }
-    return mParser->endAttributes();
-}
-
-size_t BindingXmlPullParser::getAttributeCount() const {
-    if (mOverride) {
-        return mAttributes.size();
-    }
-    return mParser->getAttributeCount();
-}
-
-XmlPullParser::Event BindingXmlPullParser::getEvent() const {
-    if (!mLastError.empty()) {
-        return Event::kBadDocument;
-    }
-    return mParser->getEvent();
-}
-
-const std::string& BindingXmlPullParser::getLastError() const {
-    if (!mLastError.empty()) {
-        return mLastError;
-    }
-    return mParser->getLastError();
-}
-
-const std::u16string& BindingXmlPullParser::getComment() const {
-    return mParser->getComment();
-}
-
-size_t BindingXmlPullParser::getLineNumber() const {
-    return mParser->getLineNumber();
-}
-
-size_t BindingXmlPullParser::getDepth() const {
-    return mParser->getDepth();
-}
-
-const std::u16string& BindingXmlPullParser::getText() const {
-    return mParser->getText();
-}
-
-const std::u16string& BindingXmlPullParser::getNamespacePrefix() const {
-    return mParser->getNamespacePrefix();
-}
-
-const std::u16string& BindingXmlPullParser::getNamespaceUri() const {
-    return mParser->getNamespaceUri();
-}
-
-bool BindingXmlPullParser::applyPackageAlias(std::u16string* package,
-                                             const std::u16string& defaultPackage) const {
-    return mParser->applyPackageAlias(package, defaultPackage);
-}
-
-const std::u16string& BindingXmlPullParser::getElementNamespace() const {
-    return mParser->getElementNamespace();
-}
-
-const std::u16string& BindingXmlPullParser::getElementName() const {
-    return mParser->getElementName();
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/BindingXmlPullParser.h b/tools/aapt2/BindingXmlPullParser.h
deleted file mode 100644
index cfb16ef..0000000
--- a/tools/aapt2/BindingXmlPullParser.h
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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_BINDING_XML_PULL_PARSER_H
-#define AAPT_BINDING_XML_PULL_PARSER_H
-
-#include "XmlPullParser.h"
-
-#include <iostream>
-#include <memory>
-#include <string>
-
-namespace aapt {
-
-class BindingXmlPullParser : public XmlPullParser {
-public:
-    BindingXmlPullParser(const std::shared_ptr<XmlPullParser>& parser);
-    BindingXmlPullParser(const BindingXmlPullParser& rhs) = delete;
-
-    Event getEvent() const override;
-    const std::string& getLastError() const override;
-    Event next() override;
-
-    const std::u16string& getComment() const override;
-    size_t getLineNumber() const override;
-    size_t getDepth() const override;
-
-    const std::u16string& getText() const override;
-
-    const std::u16string& getNamespacePrefix() const override;
-    const std::u16string& getNamespaceUri() const override;
-    bool applyPackageAlias(std::u16string* package, const std::u16string& defaultPackage)
-            const override;
-
-    const std::u16string& getElementNamespace() const override;
-    const std::u16string& getElementName() const override;
-
-    const_iterator beginAttributes() const override;
-    const_iterator endAttributes() const override;
-    size_t getAttributeCount() const override;
-
-    bool writeToFile(std::ostream& out) const;
-
-private:
-    struct VarDecl {
-        std::string name;
-        std::string type;
-    };
-
-    struct Import {
-        std::string name;
-        std::string type;
-    };
-
-    struct Target {
-        std::string className;
-        std::string id;
-        int32_t tagId;
-
-        std::vector<XmlPullParser::Attribute> expressions;
-    };
-
-    bool readVariableDeclaration();
-    bool readExpressions();
-
-    std::shared_ptr<XmlPullParser> mParser;
-    std::string mLastError;
-    bool mOverride;
-    std::vector<XmlPullParser::Attribute> mAttributes;
-    std::vector<VarDecl> mVarDecls;
-    std::vector<Target> mTargets;
-    int32_t mNextTagId;
-};
-
-} // namespace aapt
-
-#endif // AAPT_BINDING_XML_PULL_PARSER_H
diff --git a/tools/aapt2/BindingXmlPullParser_test.cpp b/tools/aapt2/BindingXmlPullParser_test.cpp
deleted file mode 100644
index 28edcb6..0000000
--- a/tools/aapt2/BindingXmlPullParser_test.cpp
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * 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 "SourceXmlPullParser.h"
-#include "BindingXmlPullParser.h"
-
-#include <gtest/gtest.h>
-#include <sstream>
-#include <string>
-
-namespace aapt {
-
-constexpr const char16_t* kAndroidNamespaceUri = u"http://schemas.android.com/apk/res/android";
-
-TEST(BindingXmlPullParserTest, SubstituteBindingExpressionsWithTag) {
-    std::stringstream input;
-    input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
-          << "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
-          << "              xmlns:bind=\"http://schemas.android.com/apk/binding\"\n"
-          << "              android:id=\"@+id/content\">\n"
-          << "  <variable name=\"user\" type=\"com.android.test.User\"/>\n"
-          << "  <TextView android:text=\"@{user.name}\" android:layout_width=\"wrap_content\"\n"
-          << "            android:layout_height=\"wrap_content\"/>\n"
-          << "</LinearLayout>\n";
-    std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input);
-    BindingXmlPullParser parser(sourceParser);
-
-    ASSERT_EQ(XmlPullParser::Event::kStartNamespace, parser.next());
-    EXPECT_EQ(std::u16string(u"http://schemas.android.com/apk/res/android"),
-              parser.getNamespaceUri());
-
-    ASSERT_EQ(XmlPullParser::Event::kStartElement, parser.next());
-    EXPECT_EQ(std::u16string(u"LinearLayout"), parser.getElementName());
-
-    while (parser.next() == XmlPullParser::Event::kText) {}
-
-    ASSERT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
-    EXPECT_EQ(std::u16string(u"TextView"), parser.getElementName());
-
-    ASSERT_EQ(3u, parser.getAttributeCount());
-    const auto endAttr = parser.endAttributes();
-    EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"layout_width"));
-    EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"layout_height"));
-    EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"tag"));
-
-    while (parser.next() == XmlPullParser::Event::kText) {}
-
-    ASSERT_EQ(XmlPullParser::Event::kEndElement, parser.getEvent());
-
-    while (parser.next() == XmlPullParser::Event::kText) {}
-
-    ASSERT_EQ(XmlPullParser::Event::kEndElement, parser.getEvent());
-    ASSERT_EQ(XmlPullParser::Event::kEndNamespace, parser.next());
-}
-
-TEST(BindingXmlPullParserTest, GenerateVariableDeclarations) {
-    std::stringstream input;
-    input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
-          << "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
-          << "              xmlns:bind=\"http://schemas.android.com/apk/binding\"\n"
-          << "              android:id=\"@+id/content\">\n"
-          << "  <variable name=\"user\" type=\"com.android.test.User\"/>\n"
-          << "</LinearLayout>\n";
-    std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input);
-    BindingXmlPullParser parser(sourceParser);
-
-    while (XmlPullParser::isGoodEvent(parser.next())) {
-        ASSERT_NE(XmlPullParser::Event::kBadDocument, parser.getEvent());
-    }
-
-    std::stringstream output;
-    ASSERT_TRUE(parser.writeToFile(output));
-
-    std::string result = output.str();
-    EXPECT_NE(std::string::npos,
-              result.find("<entries name=\"user\" type=\"com.android.test.User\"/>"));
-}
-
-TEST(BindingXmlPullParserTest, FailOnMissingNameOrTypeInVariableDeclaration) {
-    std::stringstream input;
-    input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
-          << "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
-          << "              xmlns:bind=\"http://schemas.android.com/apk/binding\"\n"
-          << "              android:id=\"@+id/content\">\n"
-          << "  <variable name=\"user\"/>\n"
-          << "</LinearLayout>\n";
-    std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input);
-    BindingXmlPullParser parser(sourceParser);
-
-    while (XmlPullParser::isGoodEvent(parser.next())) {}
-
-    EXPECT_EQ(XmlPullParser::Event::kBadDocument, parser.getEvent());
-    EXPECT_FALSE(parser.getLastError().empty());
-}
-
-
-} // namespace aapt
diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp
index 6ddf94a..8120fa7 100644
--- a/tools/aapt2/ConfigDescription.cpp
+++ b/tools/aapt2/ConfigDescription.cpp
@@ -17,8 +17,8 @@
 #include "ConfigDescription.h"
 #include "Locale.h"
 #include "SdkConstants.h"
-#include "StringPiece.h"
-#include "Util.h"
+#include "util/StringPiece.h"
+#include "util/Util.h"
 
 #include <androidfw/ResourceTypes.h>
 #include <string>
diff --git a/tools/aapt2/ConfigDescription.h b/tools/aapt2/ConfigDescription.h
index 67b4b75..4af089d 100644
--- a/tools/aapt2/ConfigDescription.h
+++ b/tools/aapt2/ConfigDescription.h
@@ -17,7 +17,7 @@
 #ifndef AAPT_CONFIG_DESCRIPTION_H
 #define AAPT_CONFIG_DESCRIPTION_H
 
-#include "StringPiece.h"
+#include "util/StringPiece.h"
 
 #include <androidfw/ResourceTypes.h>
 #include <ostream>
diff --git a/tools/aapt2/ConfigDescription_test.cpp b/tools/aapt2/ConfigDescription_test.cpp
index c57e351..8370816 100644
--- a/tools/aapt2/ConfigDescription_test.cpp
+++ b/tools/aapt2/ConfigDescription_test.cpp
@@ -15,7 +15,7 @@
  */
 
 #include "ConfigDescription.h"
-#include "StringPiece.h"
+#include "util/StringPiece.h"
 
 #include <gtest/gtest.h>
 #include <string>
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index cf222c6..84f4385 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -17,7 +17,8 @@
 #include "Debug.h"
 #include "ResourceTable.h"
 #include "ResourceValues.h"
-#include "Util.h"
+#include "util/Util.h"
+#include "ValueVisitor.h"
 
 #include <algorithm>
 #include <iostream>
@@ -29,102 +30,119 @@
 
 namespace aapt {
 
-struct PrintVisitor : ConstValueVisitor {
-    void visit(const Attribute& attr, ValueVisitorArgs&) override {
+struct PrintVisitor : public ValueVisitor {
+    using ValueVisitor::visit;
+
+    void visit(Attribute* attr) override {
         std::cout << "(attr) type=";
-        attr.printMask(std::cout);
+        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;
+        if (attr->typeMask & kMask) {
+            for (const auto& symbol : attr->symbols) {
+                std::cout << "\n        " << symbol.symbol.name.value().entry;
+                if (symbol.symbol.id) {
+                    std::cout << " (" << symbol.symbol.id.value() << ")";
+                }
+                std::cout << " = " << symbol.value;
             }
         }
     }
 
-    void visit(const Style& style, ValueVisitorArgs&) override {
+    void visit(Style* style) override {
         std::cout << "(style)";
-        if (style.parent.name.isValid() || style.parent.id.isValid()) {
+        if (style->parent) {
             std::cout << " parent=";
-            if (style.parent.name.isValid()) {
-                std::cout << style.parent.name << " ";
+            if (style->parent.value().name) {
+                std::cout << style->parent.value().name.value() << " ";
             }
 
-            if (style.parent.id.isValid()) {
-                std::cout << style.parent.id;
+            if (style->parent.value().id) {
+                std::cout << style->parent.value().id.value();
             }
         }
 
-        for (const auto& entry : style.entries) {
+        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.name) {
+                std::cout << entry.key.name.value().package << ":" << entry.key.name.value().entry;
             }
 
-            if (entry.key.id.isValid()) {
-                std::cout << "(" << entry.key.id << ")";
+            if (entry.key.id) {
+                std::cout << "(" << entry.key.id.value() << ")";
             }
 
             std::cout << "=" << *entry.value;
         }
     }
 
-    void visit(const Array& array, ValueVisitorArgs&) override {
-        array.print(std::cout);
+    void visit(Array* array) override {
+        array->print(&std::cout);
     }
 
-    void visit(const Plural& plural, ValueVisitorArgs&) override {
-        plural.print(std::cout);
+    void visit(Plural* plural) override {
+        plural->print(&std::cout);
     }
 
-    void visit(const Styleable& styleable, ValueVisitorArgs&) override {
-        styleable.print(std::cout);
+    void visit(Styleable* styleable) override {
+        styleable->print(&std::cout);
     }
 
-    void visitItem(const Item& item, ValueVisitorArgs& args) override {
-        item.print(std::cout);
+    void visitItem(Item* item) 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;
+void Debug::printTable(ResourceTable* table) {
+    for (auto& package : table->packages) {
+        std::cout << "Package name=" << package->name;
+        if (package->id) {
+            std::cout << " id=" << std::hex << (int) package->id.value() << std::dec;
         }
-        std::cout << " entryCount=" << type->entries.size() << std::endl;
+        std::cout << 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";
+        for (const auto& type : package->types) {
+            std::cout << "  type " << type->type;
+            if (type->id) {
+                std::cout << " id=" << std::hex << (int) type->id.value() << std::dec;
             }
-            std::cout << std::endl;
+            std::cout << " entryCount=" << type->entries.size() << std::endl;
 
-            PrintVisitor visitor;
-            for (const auto& value : entry->values) {
-                std::cout << "      (" << value.config << ") ";
-                value.value->accept(visitor, {});
+            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 {
+                            if (a->id && b->id) {
+                                return a->id.value() < b->id.value();
+                            } else if (a->id) {
+                                return true;
+                            } else {
+                                return false;
+                            }
+                        });
+                sortedEntries.insert(iter, entry.get());
+            }
+
+            for (const ResourceEntry* entry : sortedEntries) {
+                ResourceId id = {
+                        package->id ? package->id.value() : uint8_t(0),
+                        type->id ? type->id.value() : uint8_t(0),
+                        entry->id ? entry->id.value() : uint16_t(0)
+                };
+
+                ResourceName name = { package->name, 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;
+                }
             }
         }
     }
@@ -136,8 +154,7 @@
     return std::distance(names.begin(), iter);
 }
 
-void Debug::printStyleGraph(const std::shared_ptr<ResourceTable>& table,
-                            const ResourceName& targetStyle) {
+void Debug::printStyleGraph(ResourceTable* table, const ResourceName& targetStyle) {
     std::map<ResourceName, std::set<ResourceName>> graph;
 
     std::queue<ResourceName> stylesToVisit;
@@ -150,17 +167,16 @@
             continue;
         }
 
-        const ResourceTableType* type;
-        const ResourceEntry* entry;
-        std::tie(type, entry) = table->findResource(styleName);
-        if (entry) {
+        Maybe<ResourceTable::SearchResult> result = table->findResource(styleName);
+        if (result) {
+            ResourceEntry* entry = result.value().entry;
             for (const auto& value : entry->values) {
-                visitFunc<Style>(*value.value, [&](const Style& style) {
-                    if (style.parent.name.isValid()) {
-                        parents.insert(style.parent.name);
-                        stylesToVisit.push(style.parent.name);
+                if (Style* style = valueCast<Style>(value.value.get())) {
+                    if (style->parent && style->parent.value().name) {
+                        parents.insert(style->parent.value().name.value());
+                        stylesToVisit.push(style->parent.value().name.value());
                     }
-                });
+                }
             }
         }
     }
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index cdb3dcb..5b0d7d6 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -20,13 +20,11 @@
 #include "Resource.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,
+    static void printTable(ResourceTable* table);
+    static void printStyleGraph(ResourceTable* table,
                                 const ResourceName& targetStyle);
 };
 
diff --git a/tools/aapt2/Diagnostics.h b/tools/aapt2/Diagnostics.h
new file mode 100644
index 0000000..d20ae1b
--- /dev/null
+++ b/tools/aapt2/Diagnostics.h
@@ -0,0 +1,98 @@
+/*
+ * 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_DIAGNOSTICS_H
+#define AAPT_DIAGNOSTICS_H
+
+#include "Source.h"
+
+#include "util/StringPiece.h"
+#include "util/Util.h"
+
+#include <iostream>
+#include <sstream>
+#include <string>
+
+namespace aapt {
+
+struct DiagMessageActual {
+    Source source;
+    std::string message;
+};
+
+struct DiagMessage {
+private:
+    Source mSource;
+    std::stringstream mMessage;
+
+public:
+    DiagMessage() = default;
+
+    DiagMessage(const StringPiece& src) : mSource(src) {
+    }
+
+    DiagMessage(const Source& src) : mSource(src) {
+    }
+
+    template <typename T> DiagMessage& operator<<(const T& value) {
+        mMessage << value;
+        return *this;
+    }
+/*
+    template <typename T> DiagMessage& operator<<(
+            const ::std::function<::std::ostream&(::std::ostream&)>& f) {
+        f(mMessage);
+        return *this;
+    }*/
+
+    DiagMessageActual build() const {
+        return DiagMessageActual{ mSource, mMessage.str() };
+    }
+};
+
+struct IDiagnostics {
+    virtual ~IDiagnostics() = default;
+
+    virtual void error(const DiagMessage& message) = 0;
+    virtual void warn(const DiagMessage& message) = 0;
+    virtual void note(const DiagMessage& message) = 0;
+};
+
+struct StdErrDiagnostics : public IDiagnostics {
+    void emit(const DiagMessage& msg, const char* tag) {
+        DiagMessageActual actual = msg.build();
+        if (!actual.source.path.empty()) {
+            std::cerr << actual.source << ": ";
+        }
+        std::cerr << tag << actual.message << "." << std::endl;
+    }
+
+    void error(const DiagMessage& msg) override {
+        emit(msg, "error: ");
+    }
+
+    void warn(const DiagMessage& msg) override {
+        emit(msg, "warn: ");
+    }
+
+    void note(const DiagMessage& msg) override {
+        emit(msg, "note: ");
+    }
+};
+
+} // namespace aapt
+
+#endif /* AAPT_DIAGNOSTICS_H */
diff --git a/tools/aapt2/Flag.cpp b/tools/aapt2/Flag.cpp
deleted file mode 100644
index 76985da..0000000
--- a/tools/aapt2/Flag.cpp
+++ /dev/null
@@ -1,132 +0,0 @@
-#include "Flag.h"
-#include "StringPiece.h"
-
-#include <functional>
-#include <iomanip>
-#include <iostream>
-#include <string>
-#include <vector>
-
-namespace aapt {
-namespace flag {
-
-struct Flag {
-    std::string name;
-    std::string description;
-    std::function<bool(const StringPiece&, std::string*)> action;
-    bool required;
-    bool* flagResult;
-    bool flagValueWhenSet;
-    bool parsed;
-};
-
-static std::vector<Flag> sFlags;
-static std::vector<std::string> sArgs;
-
-static std::function<bool(const StringPiece&, std::string*)> wrap(
-        const std::function<void(const StringPiece&)>& action) {
-    return [action](const StringPiece& arg, std::string*) -> bool {
-        action(arg);
-        return true;
-    };
-}
-
-void optionalFlag(const StringPiece& name, const StringPiece& description,
-                  std::function<void(const StringPiece&)> action) {
-    sFlags.push_back(Flag{
-            name.toString(), description.toString(), wrap(action),
-            false, nullptr, false, false });
-}
-
-void requiredFlag(const StringPiece& name, const StringPiece& description,
-                  std::function<void(const StringPiece&)> action) {
-    sFlags.push_back(Flag{ name.toString(), description.toString(), wrap(action),
-            true, nullptr, false, false });
-}
-
-void requiredFlag(const StringPiece& name, const StringPiece& description,
-                  std::function<bool(const StringPiece&, std::string*)> action) {
-    sFlags.push_back(Flag{ name.toString(), description.toString(), action,
-            true, nullptr, false, false });
-}
-
-void optionalSwitch(const StringPiece& name, const StringPiece& description, bool resultWhenSet,
-                    bool* result) {
-    sFlags.push_back(Flag{
-            name.toString(), description.toString(), {},
-            false, result, resultWhenSet, false });
-}
-
-void usageAndDie(const StringPiece& command) {
-    std::cerr << command << " [options]";
-    for (const Flag& flag : sFlags) {
-        if (flag.required) {
-            std::cerr << " " << flag.name << " arg";
-        }
-    }
-    std::cerr << " files..." << std::endl << std::endl << "Options:" << std::endl;
-
-    for (const Flag& flag : sFlags) {
-        std::string command = flag.name;
-        if (!flag.flagResult) {
-            command += " arg ";
-        }
-        std::cerr << "  " << std::setw(30) << std::left << command
-                  << flag.description << std::endl;
-    }
-    exit(1);
-}
-
-void parse(int argc, char** argv, const StringPiece& command) {
-    std::string errorStr;
-    for (int i = 0; i < argc; i++) {
-        const StringPiece arg(argv[i]);
-        if (*arg.data() != '-') {
-            sArgs.push_back(arg.toString());
-            continue;
-        }
-
-        bool match = false;
-        for (Flag& flag : sFlags) {
-            if (arg == flag.name) {
-                match = true;
-                flag.parsed = true;
-                if (flag.flagResult) {
-                    *flag.flagResult = flag.flagValueWhenSet;
-                } else {
-                    i++;
-                    if (i >= argc) {
-                        std::cerr << flag.name << " missing argument." << std::endl
-                                  << std::endl;
-                        usageAndDie(command);
-                    }
-
-                    if (!flag.action(argv[i], &errorStr)) {
-                        std::cerr << errorStr << "." << std::endl << std::endl;
-                        usageAndDie(command);
-                    }
-                }
-                break;
-            }
-        }
-
-        if (!match) {
-            std::cerr << "unknown option '" << arg << "'." << std::endl << std::endl;
-            usageAndDie(command);
-        }
-    }
-
-    for (const Flag& flag : sFlags) {
-        if (flag.required && !flag.parsed) {
-            std::cerr << "missing required flag " << flag.name << std::endl << std::endl;
-            usageAndDie(command);
-        }
-    }
-}
-
-const std::vector<std::string>& getArgs() {
-    return sArgs;
-}
-
-} // namespace flag
-} // namespace aapt
diff --git a/tools/aapt2/Flag.h b/tools/aapt2/Flag.h
deleted file mode 100644
index e863742..0000000
--- a/tools/aapt2/Flag.h
+++ /dev/null
@@ -1,34 +0,0 @@
-#ifndef AAPT_FLAG_H
-#define AAPT_FLAG_H
-
-#include "StringPiece.h"
-
-#include <functional>
-#include <string>
-#include <vector>
-
-namespace aapt {
-namespace flag {
-
-void requiredFlag(const StringPiece& name, const StringPiece& description,
-                  std::function<void(const StringPiece&)> action);
-
-void requiredFlag(const StringPiece& name, const StringPiece& description,
-                  std::function<bool(const StringPiece&, std::string*)> action);
-
-void optionalFlag(const StringPiece& name, const StringPiece& description,
-                  std::function<void(const StringPiece&)> action);
-
-void optionalSwitch(const StringPiece& name, const StringPiece& description, bool resultWhenSet,
-                    bool* result);
-
-void usageAndDie(const StringPiece& command);
-
-void parse(int argc, char** argv, const StringPiece& command);
-
-const std::vector<std::string>& getArgs();
-
-} // namespace flag
-} // namespace aapt
-
-#endif // AAPT_FLAG_H
diff --git a/tools/aapt2/Flags.cpp b/tools/aapt2/Flags.cpp
new file mode 100644
index 0000000..6ae5af7
--- /dev/null
+++ b/tools/aapt2/Flags.cpp
@@ -0,0 +1,158 @@
+/*
+ * 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 "Flags.h"
+#include "util/StringPiece.h"
+
+#include <iomanip>
+#include <iostream>
+#include <string>
+#include <vector>
+
+namespace aapt {
+
+Flags& Flags::requiredFlag(const StringPiece& name, const StringPiece& description,
+                         std::string* value) {
+    auto func = [value](const StringPiece& arg) -> bool {
+        *value = arg.toString();
+        return true;
+    };
+
+    mFlags.push_back(Flag{ name.toString(), description.toString(), func, true, 1, false});
+    return *this;
+}
+
+Flags& Flags::requiredFlagList(const StringPiece& name, const StringPiece& description,
+                               std::vector<std::string>* value) {
+    auto func = [value](const StringPiece& arg) -> bool {
+        value->push_back(arg.toString());
+        return true;
+    };
+
+    mFlags.push_back(Flag{ name.toString(), description.toString(), func, true, 1, false });
+    return *this;
+}
+
+Flags& Flags::optionalFlag(const StringPiece& name, const StringPiece& description,
+                           Maybe<std::string>* value) {
+    auto func = [value](const StringPiece& arg) -> bool {
+        *value = arg.toString();
+        return true;
+    };
+
+    mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 1, false });
+    return *this;
+}
+
+Flags& Flags::optionalFlagList(const StringPiece& name, const StringPiece& description,
+                               std::vector<std::string>* value) {
+    auto func = [value](const StringPiece& arg) -> bool {
+        value->push_back(arg.toString());
+        return true;
+    };
+
+    mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 1, false });
+    return *this;
+}
+
+Flags& Flags::optionalSwitch(const StringPiece& name, const StringPiece& description,
+                             bool* value) {
+    auto func = [value](const StringPiece& arg) -> bool {
+        *value = true;
+        return true;
+    };
+
+    mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 0, false });
+    return *this;
+}
+
+void Flags::usage(const StringPiece& command, std::ostream* out) {
+    *out << command << " [options]";
+    for (const Flag& flag : mFlags) {
+        if (flag.required) {
+            *out << " " << flag.name << " arg";
+        }
+    }
+
+    *out << " files...\n\nOptions:\n";
+
+    for (const Flag& flag : mFlags) {
+        std::string argLine = flag.name;
+        if (flag.numArgs > 0) {
+            argLine += " arg";
+        }
+        *out << " " << std::setw(30) << std::left << argLine << flag.description << "\n";
+    }
+    *out << " " << std::setw(30) << std::left << "-h" << "Displays this help menu\n";
+    out->flush();
+}
+
+bool Flags::parse(const StringPiece& command, const std::vector<StringPiece>& args,
+                  std::ostream* outError) {
+    for (size_t i = 0; i < args.size(); i++) {
+        StringPiece arg = args[i];
+        if (*(arg.data()) != '-') {
+            mArgs.push_back(arg.toString());
+            continue;
+        }
+
+        if (arg == "-h" || arg == "--help") {
+            usage(command, outError);
+            return false;
+        }
+
+        bool match = false;
+        for (Flag& flag : mFlags) {
+            if (arg == flag.name) {
+                if (flag.numArgs > 0) {
+                    i++;
+                    if (i >= args.size()) {
+                        *outError << flag.name << " missing argument.\n\n";
+                        usage(command, outError);
+                        return false;
+                    }
+                    flag.action(args[i]);
+                } else {
+                    flag.action({});
+                }
+                flag.parsed = true;
+                match = true;
+                break;
+            }
+        }
+
+        if (!match) {
+            *outError << "unknown option '" << arg << "'.\n\n";
+            usage(command, outError);
+            return false;
+        }
+    }
+
+    for (const Flag& flag : mFlags) {
+        if (flag.required && !flag.parsed) {
+            *outError << "missing required flag " << flag.name << "\n\n";
+            usage(command, outError);
+            return false;
+        }
+    }
+    return true;
+}
+
+const std::vector<std::string>& Flags::getArgs() {
+    return mArgs;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Flags.h b/tools/aapt2/Flags.h
new file mode 100644
index 0000000..ce7a485
--- /dev/null
+++ b/tools/aapt2/Flags.h
@@ -0,0 +1,67 @@
+/*
+ * 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_FLAGS_H
+#define AAPT_FLAGS_H
+
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
+
+#include <functional>
+#include <ostream>
+#include <string>
+#include <vector>
+
+namespace aapt {
+
+class Flags {
+public:
+    Flags& requiredFlag(const StringPiece& name, const StringPiece& description,
+                        std::string* value);
+    Flags& requiredFlagList(const StringPiece& name, const StringPiece& description,
+                            std::vector<std::string>* value);
+    Flags& optionalFlag(const StringPiece& name, const StringPiece& description,
+                        Maybe<std::string>* value);
+    Flags& optionalFlagList(const StringPiece& name, const StringPiece& description,
+                            std::vector<std::string>* value);
+    Flags& optionalSwitch(const StringPiece& name, const StringPiece& description,
+                          bool* value);
+
+    void usage(const StringPiece& command, std::ostream* out);
+
+    bool parse(const StringPiece& command, const std::vector<StringPiece>& args,
+               std::ostream* outError);
+
+    const std::vector<std::string>& getArgs();
+
+private:
+    struct Flag {
+        std::string name;
+        std::string description;
+        std::function<bool(const StringPiece& value)> action;
+        bool required;
+        size_t numArgs;
+
+        bool parsed;
+    };
+
+    std::vector<Flag> mFlags;
+    std::vector<std::string> mArgs;
+};
+
+} // namespace aapt
+
+#endif // AAPT_FLAGS_H
diff --git a/tools/aapt2/JavaClassGenerator.cpp b/tools/aapt2/JavaClassGenerator.cpp
index e2ffe79..84a4125 100644
--- a/tools/aapt2/JavaClassGenerator.cpp
+++ b/tools/aapt2/JavaClassGenerator.cpp
@@ -19,7 +19,7 @@
 #include "Resource.h"
 #include "ResourceTable.h"
 #include "ResourceValues.h"
-#include "StringPiece.h"
+#include "util/StringPiece.h"
 
 #include <algorithm>
 #include <ostream>
@@ -32,21 +32,18 @@
 // The number of attributes to emit per line in a Styleable array.
 constexpr size_t kAttribsPerLine = 4;
 
-JavaClassGenerator::JavaClassGenerator(const std::shared_ptr<const ResourceTable>& table,
-                                       Options options) :
+JavaClassGenerator::JavaClassGenerator(ResourceTable* table, JavaClassGeneratorOptions options) :
         mTable(table), mOptions(options) {
 }
 
-static void generateHeader(std::ostream& out, const StringPiece16& package) {
-    out << "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n"
-           " *\n"
-           " * This class was automatically generated by the\n"
-           " * aapt tool from the resource data it found. It\n"
-           " * should not be modified by hand.\n"
-           " */\n\n";
-    out << "package " << package << ";"
-        << std::endl
-        << std::endl;
+static void generateHeader(const StringPiece16& packageNameToGenerate, std::ostream* out) {
+    *out << "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n"
+            " *\n"
+            " * This class was automatically generated by the\n"
+            " * aapt tool from the resource data it found. It\n"
+            " * should not be modified by hand.\n"
+            " */\n\n"
+            "package " << packageNameToGenerate << ";\n\n";
 }
 
 static const std::set<StringPiece16> sJavaIdentifiers = {
@@ -80,42 +77,32 @@
     return output;
 }
 
-struct GenArgs : ValueVisitorArgs {
-    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) {
+void JavaClassGenerator::generateStyleable(const StringPiece16& packageNameToGenerate,
+                                           const std::u16string& entryName,
+                                           const Styleable* styleable,
+                                           std::ostream* out) {
     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) {
+    sortedAttributes.reserve(styleable->entries.size());
+    for (const auto& attr : styleable->entries) {
         // 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);
+        assert((!mOptions.useFinal || attr.id) && "no ID set for Styleable entry");
+        assert(attr.name && "no name set for Styleable entry");
+        sortedAttributes.emplace_back(attr.id ? attr.id.value() : ResourceId(0), attr.name.value());
     }
     std::sort(sortedAttributes.begin(), sortedAttributes.end());
 
     // First we emit the array containing the IDs of each attribute.
     *out << "        "
-         << "public static final int[] " << transform(*entryName) << " = {";
+         << "public static final int[] " << transform(entryName) << " = {";
 
     const size_t attrCount = sortedAttributes.size();
     for (size_t i = 0; i < attrCount; i++) {
         if (i % kAttribsPerLine == 0) {
-            *out << std::endl << "            ";
+            *out << "\n            ";
         }
 
         *out << sortedAttributes[i].first;
@@ -123,44 +110,46 @@
             *out << ", ";
         }
     }
-    *out << std::endl << "        };" << std::endl;
+    *out << "\n        };\n";
 
     // Now we emit the indices into the array.
     for (size_t i = 0; i < attrCount; i++) {
         *out << "        "
              << "public static" << finalModifier
-             << " int " << transform(*entryName);
+             << " int " << transform(entryName);
 
         // We may reference IDs from other packages, so prefix the entry name with
         // the package.
         const ResourceNameRef& itemName = sortedAttributes[i].second;
-        if (itemName.package != *package) {
+        if (packageNameToGenerate != itemName.package) {
             *out << "_" << transform(itemName.package);
         }
-        *out << "_" << transform(itemName.entry) << " = " << i << ";" << std::endl;
+        *out << "_" << transform(itemName.entry) << " = " << i << ";\n";
     }
 }
 
-bool JavaClassGenerator::generateType(const std::u16string& package, size_t packageId,
-                                      const ResourceTableType& type, std::ostream& out) {
+bool JavaClassGenerator::generateType(const StringPiece16& packageNameToGenerate,
+                                      const ResourceTablePackage* package,
+                                      const ResourceTableType* type,
+                                      std::ostream* out) {
     const StringPiece finalModifier = mOptions.useFinal ? " final" : "";
 
     std::u16string unmangledPackage;
     std::u16string unmangledName;
-    for (const auto& entry : type.entries) {
-        ResourceId id = { packageId, type.typeId, entry->entryId };
+    for (const auto& entry : type->entries) {
+        ResourceId id = { package->id.value(), type->id.value(), entry->id.value() };
         assert(id.isValid());
 
         unmangledName = entry->name;
         if (NameMangler::unmangle(&unmangledName, &unmangledPackage)) {
             // The entry name was mangled, and we successfully unmangled it.
             // Check that we want to emit this symbol.
-            if (package != unmangledPackage) {
+            if (package->name != unmangledPackage) {
                 // Skip the entry if it doesn't belong to the package we're writing.
                 continue;
             }
         } else {
-            if (package != mTable->getPackage()) {
+            if (packageNameToGenerate != package->name) {
                 // We are processing a mangled package name,
                 // but this is a non-mangled resource.
                 continue;
@@ -168,40 +157,42 @@
         }
 
         if (!isValidSymbol(unmangledName)) {
-            ResourceNameRef resourceName = { package, type.type, unmangledName };
+            ResourceNameRef resourceName = { packageNameToGenerate, type->type, unmangledName };
             std::stringstream err;
             err << "invalid symbol name '" << resourceName << "'";
             mError = err.str();
             return false;
         }
 
-        if (type.type == ResourceType::kStyleable) {
+        if (type->type == ResourceType::kStyleable) {
             assert(!entry->values.empty());
-            entry->values.front().value->accept(*this, GenArgs{ &out, &package, &unmangledName });
+            generateStyleable(packageNameToGenerate, unmangledName, static_cast<const Styleable*>(
+                    entry->values.front().value.get()), out);
         } else {
-            out << "        " << "public static" << finalModifier
-                << " int " << transform(unmangledName) << " = " << id << ";" << std::endl;
+            *out << "        " << "public static" << finalModifier
+                 << " int " << transform(unmangledName) << " = " << id << ";\n";
         }
     }
     return true;
 }
 
-bool JavaClassGenerator::generate(const std::u16string& package, std::ostream& out) {
-    const size_t packageId = mTable->getPackageId();
+bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate, std::ostream* out) {
+    generateHeader(packageNameToGenerate, out);
 
-    generateHeader(out, package);
+    *out << "public final class R {\n";
 
-    out << "public final class R {" << std::endl;
-
-    for (const auto& type : *mTable) {
-        out << "    public static final class " << type->type << " {" << std::endl;
-        if (!generateType(package, packageId, *type, out)) {
-            return false;
+    for (const auto& package : mTable->packages) {
+        for (const auto& type : package->types) {
+            *out << "    public static final class " << type->type << " {\n";
+            if (!generateType(packageNameToGenerate, package.get(), type.get(), out)) {
+                return false;
+            }
+            *out << "    }\n";
         }
-        out << "    }" << std::endl;
     }
 
-    out << "}" << std::endl;
+    *out << "}\n";
+    out->flush();
     return true;
 }
 
diff --git a/tools/aapt2/JavaClassGenerator.h b/tools/aapt2/JavaClassGenerator.h
index f8b9ee3..682bacf 100644
--- a/tools/aapt2/JavaClassGenerator.h
+++ b/tools/aapt2/JavaClassGenerator.h
@@ -20,28 +20,27 @@
 #include "ResourceTable.h"
 #include "ResourceValues.h"
 
+#include "util/StringPiece.h"
+
 #include <ostream>
 #include <string>
 
 namespace aapt {
 
+struct JavaClassGeneratorOptions {
+    /*
+     * Specifies whether to use the 'final' modifier
+     * on resource entries. Default is true.
+     */
+    bool useFinal = true;
+};
+
 /*
  * Generates the R.java file for a resource table.
  */
-class JavaClassGenerator : ConstValueVisitor {
+class JavaClassGenerator {
 public:
-    /*
-     * A set of options for this JavaClassGenerator.
-     */
-    struct Options {
-        /*
-         * Specifies whether to use the 'final' modifier
-         * on resource entries. Default is true.
-         */
-        bool useFinal = true;
-    };
-
-    JavaClassGenerator(const std::shared_ptr<const ResourceTable>& table, Options options);
+    JavaClassGenerator(ResourceTable* table, JavaClassGeneratorOptions options);
 
     /*
      * Writes the R.java file to `out`. Only symbols belonging to `package` are written.
@@ -50,21 +49,23 @@
      * We need to generate these symbols in a separate file.
      * Returns true on success.
      */
-    bool generate(const std::u16string& package, std::ostream& out);
-
-    /*
-     * ConstValueVisitor implementation.
-     */
-    void visit(const Styleable& styleable, ValueVisitorArgs& args);
+    bool generate(const StringPiece16& package, std::ostream* out);
 
     const std::string& getError() const;
 
 private:
-    bool generateType(const std::u16string& package, size_t packageId,
-                      const ResourceTableType& type, std::ostream& out);
+    bool generateType(const StringPiece16& packageNameToGenerate,
+                      const ResourceTablePackage* package,
+                      const ResourceTableType* type,
+                      std::ostream* out);
 
-    std::shared_ptr<const ResourceTable> mTable;
-    Options mOptions;
+    void generateStyleable(const StringPiece16& packageNameToGenerate,
+                           const std::u16string& entryName,
+                           const Styleable* styleable,
+                           std::ostream* out);
+
+    ResourceTable* mTable;
+    JavaClassGeneratorOptions mOptions;
     std::string mError;
 };
 
diff --git a/tools/aapt2/JavaClassGenerator_test.cpp b/tools/aapt2/JavaClassGenerator_test.cpp
index b385ff4..48fcf8c 100644
--- a/tools/aapt2/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/JavaClassGenerator_test.cpp
@@ -15,12 +15,9 @@
  */
 
 #include "JavaClassGenerator.h"
-#include "Linker.h"
-#include "MockResolver.h"
-#include "ResourceTable.h"
-#include "ResourceTableResolver.h"
-#include "ResourceValues.h"
-#include "Util.h"
+#include "util/Util.h"
+
+#include "test/Builders.h"
 
 #include <gtest/gtest.h>
 #include <sstream>
@@ -28,51 +25,34 @@
 
 namespace aapt {
 
-struct JavaClassGeneratorTest : public ::testing::Test {
-    virtual void SetUp() override {
-        mTable = std::make_shared<ResourceTable>();
-        mTable->setPackage(u"android");
-        mTable->setPackageId(0x01);
-    }
+TEST(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"android", 0x01)
+            .addSimple(u"@android:id/class", ResourceId(0x01020000))
+            .build();
 
-    bool addResource(const ResourceNameRef& name, ResourceId id) {
-        return mTable->addResource(name, id, {}, SourceLine{ "test.xml", 21 },
-                                   util::make_unique<Id>());
-    }
-
-    std::shared_ptr<ResourceTable> mTable;
-};
-
-TEST_F(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) {
-    ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"class" },
-                            ResourceId{ 0x01, 0x02, 0x0000 }));
-
-    JavaClassGenerator generator(mTable, {});
+    JavaClassGenerator generator(table.get(), {});
 
     std::stringstream out;
-    EXPECT_FALSE(generator.generate(mTable->getPackage(), out));
+    EXPECT_FALSE(generator.generate(u"android", &out));
 }
 
-TEST_F(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) {
-    ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"hey-man" },
-                            ResourceId{ 0x01, 0x02, 0x0000 }));
+TEST(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"android", 0x01)
+            .addSimple(u"@android:id/hey-man", ResourceId(0x01020000))
+            .addSimple(u"@android:attr/cool.attr", ResourceId(0x01010000))
+            .addValue(u"@android:styleable/hey.dude", ResourceId(0x01030000),
+                      test::StyleableBuilder()
+                              .addItem(u"@android:attr/cool.attr", ResourceId(0x01010000))
+                              .build())
+            .build();
 
-    ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kAttr, u"cool.attr" },
-                            ResourceId{ 0x01, 0x01, 0x0000 }));
-
-    std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
-    Reference ref(ResourceName{ u"android", ResourceType::kAttr, u"cool.attr"});
-    ref.id = ResourceId{ 0x01, 0x01, 0x0000 };
-    styleable->entries.emplace_back(ref);
-
-    ASSERT_TRUE(mTable->addResource(ResourceName{ {}, ResourceType::kStyleable, u"hey.dude" },
-                                    ResourceId{ 0x01, 0x03, 0x0000 }, {},
-                                    SourceLine{ "test.xml", 21 }, std::move(styleable)));
-
-    JavaClassGenerator generator(mTable, {});
+    JavaClassGenerator generator(table.get(), {});
 
     std::stringstream out;
-    EXPECT_TRUE(generator.generate(mTable->getPackage(), out));
+    EXPECT_TRUE(generator.generate(u"android", &out));
+
     std::string output = out.str();
 
     EXPECT_NE(std::string::npos,
@@ -85,14 +65,15 @@
               output.find("public static final int hey_dude_cool_attr = 0;"));
 }
 
-
-TEST_F(JavaClassGeneratorTest, EmitPackageMangledSymbols) {
+/*
+ * TODO(adamlesinski): Re-enable this once we get merging working again.
+ * TEST(JavaClassGeneratorTest, EmitPackageMangledSymbols) {
     ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"foo" },
                             ResourceId{ 0x01, 0x02, 0x0000 }));
     ResourceTable table;
     table.setPackage(u"com.lib");
     ASSERT_TRUE(table.addResource(ResourceName{ {}, ResourceType::kId, u"test" }, {},
-                                  SourceLine{ "lib.xml", 33 }, util::make_unique<Id>()));
+                                  Source{ "lib.xml", 33 }, util::make_unique<Id>()));
     ASSERT_TRUE(mTable->merge(std::move(table)));
 
     Linker linker(mTable,
@@ -113,34 +94,29 @@
     output = out.str();
     EXPECT_NE(std::string::npos, output.find("int test ="));
     EXPECT_EQ(std::string::npos, output.find("int foo ="));
-}
+}*/
 
-TEST_F(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) {
-    std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
-    styleable->entries.emplace_back(ResourceNameRef{ mTable->getPackage(),
-                                                     ResourceType::kAttr,
-                                                     u"bar" });
-    styleable->entries.emplace_back(ResourceNameRef{ u"com.lib", ResourceType::kAttr, u"bar" });
-    ASSERT_TRUE(mTable->addResource(ResourceName{ {}, ResourceType::kStyleable, u"Foo" }, {}, {},
-                                    std::move(styleable)));
+TEST(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+                .setPackageId(u"android", 0x01)
+                .setPackageId(u"com.lib", 0x02)
+                .addSimple(u"@android:attr/bar", ResourceId(0x01010000))
+                .addSimple(u"@com.lib:attr/bar", ResourceId(0x02010000))
+                .addValue(u"@android:styleable/foo", ResourceId(0x01030000),
+                          test::StyleableBuilder()
+                                  .addItem(u"@android:attr/bar", ResourceId(0x01010000))
+                                  .addItem(u"@com.lib:attr/bar", ResourceId(0x02010000))
+                                  .build())
+                .build();
 
-    std::shared_ptr<IResolver> resolver = std::make_shared<MockResolver>(mTable,
-            std::map<ResourceName, ResourceId>({
-                    { ResourceName{ u"android", ResourceType::kAttr, u"bar" },
-                      ResourceId{ 0x01, 0x01, 0x0000 } },
-                    { ResourceName{ u"com.lib", ResourceType::kAttr, u"bar" },
-                      ResourceId{ 0x02, 0x01, 0x0000 } }}));
-
-    Linker linker(mTable, resolver, {});
-    ASSERT_TRUE(linker.linkAndValidate());
-
-    JavaClassGenerator generator(mTable, {});
+    JavaClassGenerator generator(table.get(), {});
 
     std::stringstream out;
-    EXPECT_TRUE(generator.generate(mTable->getPackage(), out));
+    EXPECT_TRUE(generator.generate(u"android", &out));
+
     std::string output = out.str();
-    EXPECT_NE(std::string::npos, output.find("int Foo_bar ="));
-    EXPECT_NE(std::string::npos, output.find("int Foo_com_lib_bar ="));
+    EXPECT_NE(std::string::npos, output.find("int foo_bar ="));
+    EXPECT_NE(std::string::npos, output.find("int foo_com_lib_bar ="));
 }
 
 } // namespace aapt
diff --git a/tools/aapt2/Linker.cpp b/tools/aapt2/Linker.cpp
deleted file mode 100644
index c37cc93..0000000
--- a/tools/aapt2/Linker.cpp
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * 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 "Linker.h"
-#include "Logger.h"
-#include "NameMangler.h"
-#include "Resolver.h"
-#include "ResourceParser.h"
-#include "ResourceTable.h"
-#include "ResourceValues.h"
-#include "StringPiece.h"
-#include "Util.h"
-
-#include <androidfw/AssetManager.h>
-#include <array>
-#include <bitset>
-#include <iostream>
-#include <map>
-#include <ostream>
-#include <set>
-#include <sstream>
-#include <tuple>
-#include <vector>
-
-namespace aapt {
-
-Linker::Args::Args(const ResourceNameRef& r, const SourceLine& s) : referrer(r), source(s) {
-}
-
-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() {
-    std::bitset<256> usedTypeIds;
-    std::array<std::set<uint16_t>, 256> usedIds;
-    usedTypeIds.set(0);
-
-    // 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
-            // mark this ID as taken so we don't re-assign it
-            // later.
-            usedTypeIds.set(type->typeId);
-        }
-
-        for (auto& entry : type->entries) {
-            if (type->typeId != ResourceTableType::kUnsetTypeId &&
-                    entry->entryId != ResourceEntry::kUnsetEntryId) {
-                // The ID for this entry has already been set. We
-                // mark this ID as taken so we don't re-assign it
-                // later.
-                usedIds[type->typeId].insert(entry->entryId);
-            }
-        }
-    }
-
-    // Assign resource IDs that are available.
-    size_t nextTypeIndex = 0;
-    for (auto& type : *mTable) {
-        if (type->typeId == ResourceTableType::kUnsetTypeId) {
-            while (nextTypeIndex < usedTypeIds.size() && usedTypeIds[nextTypeIndex]) {
-                nextTypeIndex++;
-            }
-            type->typeId = nextTypeIndex++;
-        }
-
-        const auto endEntryIter = std::end(usedIds[type->typeId]);
-        auto nextEntryIter = std::begin(usedIds[type->typeId]);
-        size_t nextIndex = 0;
-        for (auto& entry : type->entries) {
-            if (entry->entryId == ResourceTableType::kUnsetTypeId) {
-                while (nextEntryIter != endEntryIter &&
-                        nextIndex == *nextEntryIter) {
-                    nextIndex++;
-                    ++nextEntryIter;
-                }
-                entry->entryId = nextIndex++;
-            }
-        }
-    }
-
-    // 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;
-}
-
-const Linker::ResourceNameToSourceMap& Linker::getUnresolvedReferences() const {
-    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.entry.empty()) {
-        // We can't have a completely bad reference.
-        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
-        // package ID.
-        assert(reference.id.packageId() != mTable->getPackageId());
-
-        // The reference goes outside this package, let it stay as a
-        // resource ID because it will not change.
-        return;
-    }
-
-    doResolveReference(reference, args.source);
-
-    // TODO(adamlesinski): Verify the referencedType is another reference
-    // or a compatible primitive.
-}
-
-void Linker::processAttributeValue(const ResourceNameRef& name, const SourceLine& source,
-        const Attribute& attr, std::unique_ptr<Item>& value) {
-    std::unique_ptr<Item> convertedValue;
-    visitFunc<RawString>(*value, [&](RawString& str) {
-        // This is a raw string, so check if it can be converted to anything.
-        // We can NOT swap value with the converted value in here, since
-        // we called through the original value.
-
-        auto onCreateReference = [&](const ResourceName& name) {
-            // We should never get here. All references would have been
-            // parsed in the parser phase.
-            assert(false);
-        };
-
-        convertedValue = ResourceParser::parseItemForAttribute(*str.value, attr,
-                                                               onCreateReference);
-        if (!convertedValue && attr.typeMask & android::ResTable_map::TYPE_STRING) {
-            // Last effort is to parse as a string.
-            util::StringBuilder builder;
-            builder.append(*str.value);
-            if (builder) {
-                convertedValue = util::make_unique<String>(
-                        mTable->getValueStringPool().makeRef(builder.str()));
-            }
-        }
-    });
-
-    if (convertedValue) {
-        value = std::move(convertedValue);
-    }
-
-    // Process this new or old value (it can be a reference!).
-    value->accept(*this, Args{ name, source });
-
-    // Flatten the value to see what resource type it is.
-    android::Res_value resValue;
-    value->flatten(resValue);
-
-    // Always allow references.
-    const uint32_t typeMask = attr.typeMask | android::ResTable_map::TYPE_REFERENCE;
-    if (!(typeMask & ResourceParser::androidTypeToAttributeTypeMask(resValue.dataType))) {
-        Logger::error(source)
-                << *value
-                << " is not compatible with attribute "
-                << attr
-                << "."
-                << std::endl;
-        mError = true;
-    }
-}
-
-void Linker::visit(Style& style, ValueVisitorArgs& a) {
-    Args& args = static_cast<Args&>(a);
-
-    if (style.parent.name.isValid() || style.parent.id.isValid()) {
-        visit(style.parent, a);
-    }
-
-    for (Style::Entry& styleEntry : style.entries) {
-        const Attribute* attr = doResolveAttribute(styleEntry.key, args.source);
-        if (attr) {
-            processAttributeValue(args.referrer, args.source, *attr, styleEntry.value);
-        }
-    }
-}
-
-void Linker::visit(Attribute& attr, ValueVisitorArgs& a) {
-    static constexpr uint32_t kMask = android::ResTable_map::TYPE_ENUM |
-            android::ResTable_map::TYPE_FLAGS;
-    if (attr.typeMask & kMask) {
-        for (auto& symbol : attr.symbols) {
-            visit(symbol.symbol, a);
-        }
-    }
-}
-
-void Linker::visit(Styleable& styleable, ValueVisitorArgs& a) {
-    for (auto& attrRef : styleable.entries) {
-        visit(attrRef, a);
-    }
-}
-
-void Linker::visit(Array& array, ValueVisitorArgs& a) {
-    Args& args = static_cast<Args&>(a);
-
-    for (auto& item : array.items) {
-        item->accept(*this, Args{ args.referrer, args.source });
-    }
-}
-
-void Linker::visit(Plural& plural, ValueVisitorArgs& a) {
-    Args& args = static_cast<Args&>(a);
-
-    for (auto& item : plural.values) {
-        if (item) {
-            item->accept(*this, Args{ args.referrer, args.source });
-        }
-    }
-}
-
-void Linker::addUnresolvedSymbol(const ResourceNameRef& name, const SourceLine& source) {
-    mUnresolvedSymbols[name.toResourceName()].push_back(source);
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/Linker.h b/tools/aapt2/Linker.h
deleted file mode 100644
index 6f03515..0000000
--- a/tools/aapt2/Linker.h
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * 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_LINKER_H
-#define AAPT_LINKER_H
-
-#include "Resolver.h"
-#include "ResourceTable.h"
-#include "ResourceValues.h"
-#include "Source.h"
-#include "StringPiece.h"
-
-#include <androidfw/AssetManager.h>
-#include <map>
-#include <memory>
-#include <ostream>
-#include <set>
-#include <vector>
-
-namespace aapt {
-
-/**
- * The Linker has two jobs. It follows resource references
- * and verifies that their targert exists and that their
- * types are compatible. The Linker will also assign resource
- * IDs and fill in all the dependent references with the newly
- * assigned resource IDs.
- *
- * To do this, the Linker builds a graph of references. This
- * can be useful to do other analysis, like building a
- * dependency graph of source files. The hope is to be able to
- * add functionality that operates on the graph without
- * overcomplicating the Linker.
- *
- * TODO(adamlesinski): Build the graph first then run the separate
- * steps over the graph.
- */
-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(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
-     * are type-compatible. Missing resource references are recorded but do
-     * not cause this method to fail.
-     */
-    bool linkAndValidate();
-
-    /**
-     * Returns any references to resources that were not defined in any of the
-     * sources.
-     */
-    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);
-
-        const ResourceNameRef& referrer;
-        const SourceLine& source;
-    };
-
-    //
-    // Overrides of ValueVisitor
-    //
-    void visit(Reference& reference, ValueVisitorArgs& args) override;
-    void visit(Attribute& attribute, ValueVisitorArgs& args) override;
-    void visit(Styleable& styleable, ValueVisitorArgs& args) override;
-    void visit(Style& style, ValueVisitorArgs& args) override;
-    void visit(Array& array, ValueVisitorArgs& args) override;
-    void visit(Plural& plural, ValueVisitorArgs& args) override;
-
-    void processAttributeValue(const ResourceNameRef& name, const SourceLine& source,
-                               const Attribute& attr, std::unique_ptr<Item>& value);
-
-    void addUnresolvedSymbol(const ResourceNameRef& name, const SourceLine& source);
-
-    std::shared_ptr<ResourceTable> mTable;
-    std::map<ResourceName, std::vector<SourceLine>> mUnresolvedSymbols;
-    Options mOptions;
-    bool mError;
-};
-
-} // namespace aapt
-
-#endif // AAPT_LINKER_H
diff --git a/tools/aapt2/Linker_test.cpp b/tools/aapt2/Linker_test.cpp
deleted file mode 100644
index d897f98..0000000
--- a/tools/aapt2/Linker_test.cpp
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * 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 "Linker.h"
-#include "ResourceTable.h"
-#include "ResourceTableResolver.h"
-#include "ResourceValues.h"
-#include "Util.h"
-
-#include <androidfw/AssetManager.h>
-#include <gtest/gtest.h>
-#include <string>
-
-namespace aapt {
-
-struct LinkerTest : public ::testing::Test {
-    virtual void SetUp() override {
-        mTable = std::make_shared<ResourceTable>();
-        mTable->setPackage(u"android");
-        mTable->setPackageId(0x01);
-        mLinker = std::make_shared<Linker>(mTable, std::make_shared<ResourceTableResolver>(
-                mTable, std::vector<std::shared_ptr<const android::AssetManager>>()),
-                Linker::Options{});
-
-        // Create a few attributes for use in the tests.
-
-        addResource(ResourceName{ {}, ResourceType::kAttr, u"integer" },
-                    util::make_unique<Attribute>(false, android::ResTable_map::TYPE_INTEGER));
-
-        addResource(ResourceName{ {}, ResourceType::kAttr, u"string" },
-                    util::make_unique<Attribute>(false, android::ResTable_map::TYPE_STRING));
-
-        addResource(ResourceName{ {}, ResourceType::kId, u"apple" }, util::make_unique<Id>());
-
-        addResource(ResourceName{ {}, ResourceType::kId, u"banana" }, util::make_unique<Id>());
-
-        std::unique_ptr<Attribute> flagAttr = util::make_unique<Attribute>(
-                false, android::ResTable_map::TYPE_FLAGS);
-        flagAttr->symbols.push_back(Attribute::Symbol{
-                ResourceNameRef{ u"android", ResourceType::kId, u"apple" }, 1 });
-        flagAttr->symbols.push_back(Attribute::Symbol{
-                ResourceNameRef{ u"android", ResourceType::kId, u"banana" }, 2 });
-        addResource(ResourceName{ {}, ResourceType::kAttr, u"flags" }, std::move(flagAttr));
-    }
-
-    /*
-     * Convenience method for adding resources with the default configuration and some
-     * bogus source line.
-     */
-    bool addResource(const ResourceNameRef& name, std::unique_ptr<Value> value) {
-        return mTable->addResource(name, {}, SourceLine{ "test.xml", 21 }, std::move(value));
-    }
-
-    std::shared_ptr<ResourceTable> mTable;
-    std::shared_ptr<Linker> mLinker;
-};
-
-TEST_F(LinkerTest, DoNotInterpretEscapedStringAsReference) {
-    ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kString, u"foo" },
-                util::make_unique<String>(mTable->getValueStringPool().makeRef(u"?123"))));
-
-    ASSERT_TRUE(mLinker->linkAndValidate());
-    EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
-}
-
-TEST_F(LinkerTest, EscapeAndConvertRawString) {
-    std::unique_ptr<Style> style = util::make_unique<Style>();
-    style->entries.push_back(Style::Entry{
-            ResourceNameRef{ u"android", ResourceType::kAttr, u"integer" },
-            util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"  123"))
-    });
-    const Style* result = style.get();
-    ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" },
-                std::move(style)));
-
-    ASSERT_TRUE(mLinker->linkAndValidate());
-    EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
-
-    EXPECT_NE(nullptr, dynamic_cast<BinaryPrimitive*>(result->entries.front().value.get()));
-}
-
-TEST_F(LinkerTest, FailToConvertRawString) {
-    std::unique_ptr<Style> style = util::make_unique<Style>();
-    style->entries.push_back(Style::Entry{
-            ResourceNameRef{ u"android", ResourceType::kAttr, u"integer" },
-            util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"yo what is up?"))
-    });
-    ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" },
-                std::move(style)));
-
-    ASSERT_FALSE(mLinker->linkAndValidate());
-}
-
-TEST_F(LinkerTest, ConvertRawStringToString) {
-    std::unique_ptr<Style> style = util::make_unique<Style>();
-    style->entries.push_back(Style::Entry{
-            ResourceNameRef{ u"android", ResourceType::kAttr, u"string" },
-            util::make_unique<RawString>(
-                    mTable->getValueStringPool().makeRef(u"  \"this  is  \\u00fa\"."))
-    });
-    const Style* result = style.get();
-    ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" },
-                std::move(style)));
-
-    ASSERT_TRUE(mLinker->linkAndValidate());
-    EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
-
-    const String* str = dynamic_cast<const String*>(result->entries.front().value.get());
-    ASSERT_NE(nullptr, str);
-    EXPECT_EQ(*str->value, u"this  is  \u00fa.");
-}
-
-TEST_F(LinkerTest, ConvertRawStringToFlags) {
-    std::unique_ptr<Style> style = util::make_unique<Style>();
-    style->entries.push_back(Style::Entry{
-            ResourceNameRef{ u"android", ResourceType::kAttr, u"flags" },
-            util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"banana | apple"))
-    });
-    const Style* result = style.get();
-    ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" },
-                std::move(style)));
-
-    ASSERT_TRUE(mLinker->linkAndValidate());
-    EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
-
-    const BinaryPrimitive* bin = dynamic_cast<const BinaryPrimitive*>(
-            result->entries.front().value.get());
-    ASSERT_NE(nullptr, bin);
-    EXPECT_EQ(bin->value.data, 1u | 2u);
-}
-
-TEST_F(LinkerTest, AllowReferenceWithOnlyResourceIdPointingToDifferentPackage) {
-    ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kInteger, u"foo" },
-                util::make_unique<Reference>(ResourceId{ 0x02, 0x01, 0x01 })));
-
-    ASSERT_TRUE(mLinker->linkAndValidate());
-    EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/Locale.cpp b/tools/aapt2/Locale.cpp
index eed0ea7..20a2d0c 100644
--- a/tools/aapt2/Locale.cpp
+++ b/tools/aapt2/Locale.cpp
@@ -15,7 +15,7 @@
  */
 
 #include "Locale.h"
-#include "Util.h"
+#include "util/Util.h"
 
 #include <algorithm>
 #include <ctype.h>
diff --git a/tools/aapt2/Locale_test.cpp b/tools/aapt2/Locale_test.cpp
index 4e154d6..758e1e3 100644
--- a/tools/aapt2/Locale_test.cpp
+++ b/tools/aapt2/Locale_test.cpp
@@ -15,7 +15,7 @@
  */
 
 #include "Locale.h"
-#include "Util.h"
+#include "util/Util.h"
 
 #include <gtest/gtest.h>
 #include <string>
diff --git a/tools/aapt2/Logger.cpp b/tools/aapt2/Logger.cpp
deleted file mode 100644
index 3847185..0000000
--- a/tools/aapt2/Logger.cpp
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * 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 "Logger.h"
-#include "Source.h"
-
-#include <memory>
-#include <iostream>
-
-namespace aapt {
-
-Log::Log(std::ostream& _out, std::ostream& _err) : out(_out), err(_err) {
-}
-
-std::shared_ptr<Log> Logger::sLog(std::make_shared<Log>(std::cerr, std::cerr));
-
-void Logger::setLog(const std::shared_ptr<Log>& log) {
-    sLog = log;
-}
-
-std::ostream& Logger::error() {
-    return sLog->err << "error: ";
-}
-
-std::ostream& Logger::error(const Source& source) {
-    return sLog->err << source << ": error: ";
-}
-
-std::ostream& Logger::error(const SourceLine& source) {
-    return sLog->err << source << ": error: ";
-}
-
-std::ostream& Logger::warn() {
-    return sLog->err << "warning: ";
-}
-
-std::ostream& Logger::warn(const Source& source) {
-    return sLog->err << source << ": warning: ";
-}
-
-std::ostream& Logger::warn(const SourceLine& source) {
-    return sLog->err << source << ": warning: ";
-}
-
-std::ostream& Logger::note() {
-    return sLog->out << "note: ";
-}
-
-std::ostream& Logger::note(const Source& source) {
-    return sLog->err << source << ": note: ";
-}
-
-std::ostream& Logger::note(const SourceLine& source) {
-    return sLog->err << source << ": note: ";
-}
-
-SourceLogger::SourceLogger(const Source& source)
-: mSource(source) {
-}
-
-std::ostream& SourceLogger::error() {
-    return Logger::error(mSource);
-}
-
-std::ostream& SourceLogger::error(size_t line) {
-    return Logger::error(SourceLine{ mSource.path, line });
-}
-
-std::ostream& SourceLogger::warn() {
-    return Logger::warn(mSource);
-}
-
-std::ostream& SourceLogger::warn(size_t line) {
-    return Logger::warn(SourceLine{ mSource.path, line });
-}
-
-std::ostream& SourceLogger::note() {
-    return Logger::note(mSource);
-}
-
-std::ostream& SourceLogger::note(size_t line) {
-    return Logger::note(SourceLine{ mSource.path, line });
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/Logger.h b/tools/aapt2/Logger.h
deleted file mode 100644
index eed58b8..0000000
--- a/tools/aapt2/Logger.h
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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_LOGGER_H
-#define AAPT_LOGGER_H
-
-#include "Source.h"
-#include "StringPiece.h"
-
-#include <memory>
-#include <ostream>
-#include <string>
-
-namespace aapt {
-
-struct Log {
-    Log(std::ostream& out, std::ostream& err);
-    Log(const Log& rhs) = delete;
-
-    std::ostream& out;
-    std::ostream& err;
-};
-
-class Logger {
-public:
-    static void setLog(const std::shared_ptr<Log>& log);
-
-    static std::ostream& error();
-    static std::ostream& error(const Source& source);
-    static std::ostream& error(const SourceLine& sourceLine);
-
-    static std::ostream& warn();
-    static std::ostream& warn(const Source& source);
-    static std::ostream& warn(const SourceLine& sourceLine);
-
-    static std::ostream& note();
-    static std::ostream& note(const Source& source);
-    static std::ostream& note(const SourceLine& sourceLine);
-
-private:
-    static std::shared_ptr<Log> sLog;
-};
-
-class SourceLogger {
-public:
-    SourceLogger(const Source& source);
-
-    std::ostream& error();
-    std::ostream& error(size_t line);
-
-    std::ostream& warn();
-    std::ostream& warn(size_t line);
-
-    std::ostream& note();
-    std::ostream& note(size_t line);
-
-private:
-    Source mSource;
-};
-
-} // namespace aapt
-
-#endif // AAPT_LOGGER_H
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 54a7329..248e7ad 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -14,1262 +14,39 @@
  * limitations under the License.
  */
 
-#include "AppInfo.h"
-#include "BigBuffer.h"
-#include "BinaryResourceParser.h"
-#include "BindingXmlPullParser.h"
-#include "Debug.h"
-#include "Files.h"
-#include "Flag.h"
-#include "JavaClassGenerator.h"
-#include "Linker.h"
-#include "ManifestMerger.h"
-#include "ManifestParser.h"
-#include "ManifestValidator.h"
-#include "NameMangler.h"
-#include "Png.h"
-#include "ProguardRules.h"
-#include "ResourceParser.h"
-#include "ResourceTable.h"
-#include "ResourceTableResolver.h"
-#include "ResourceValues.h"
-#include "SdkConstants.h"
-#include "SourceXmlPullParser.h"
-#include "StringPiece.h"
-#include "TableFlattener.h"
-#include "Util.h"
-#include "XmlFlattener.h"
-#include "ZipFile.h"
+#include "util/StringPiece.h"
 
-#include <algorithm>
-#include <androidfw/AssetManager.h>
-#include <cstdlib>
-#include <dirent.h>
-#include <errno.h>
-#include <fstream>
 #include <iostream>
-#include <sstream>
-#include <sys/stat.h>
-#include <unordered_set>
-#include <utils/Errors.h>
+#include <vector>
 
-constexpr const char* kAaptVersionStr = "2.0-alpha";
+namespace aapt {
 
-using namespace aapt;
+extern int compile(const std::vector<StringPiece>& args);
+extern int link(const std::vector<StringPiece>& args);
 
-/**
- * Used with smart pointers to free malloc'ed memory.
- */
-struct DeleteMalloc {
-    void operator()(void* ptr) {
-        free(ptr);
-    }
-};
-
-struct StaticLibraryData {
-    Source source;
-    std::unique_ptr<ZipFile> apk;
-};
-
-/**
- * Collect files from 'root', filtering out any files that do not
- * match the FileFilter 'filter'.
- */
-bool walkTree(const Source& root, const FileFilter& filter,
-              std::vector<Source>* outEntries) {
-    bool error = false;
-
-    for (const std::string& dirName : listFiles(root.path)) {
-        std::string dir = root.path;
-        appendPath(&dir, dirName);
-
-        FileType ft = getFileType(dir);
-        if (!filter(dirName, ft)) {
-            continue;
-        }
-
-        if (ft != FileType::kDirectory) {
-            continue;
-        }
-
-        for (const std::string& fileName : listFiles(dir)) {
-            std::string file(dir);
-            appendPath(&file, fileName);
-
-            FileType ft = getFileType(file);
-            if (!filter(fileName, ft)) {
-                continue;
-            }
-
-            if (ft != FileType::kRegular) {
-                Logger::error(Source{ file }) << "not a regular file." << std::endl;
-                error = true;
-                continue;
-            }
-            outEntries->push_back(Source{ file });
-        }
-    }
-    return !error;
-}
-
-void versionStylesForCompat(const std::shared_ptr<ResourceTable>& table) {
-    for (auto& type : *table) {
-        if (type->type != ResourceType::kStyle) {
-            continue;
-        }
-
-        for (auto& entry : type->entries) {
-            // Add the versioned styles we want to create
-            // here. They are added to the table after
-            // iterating over the original set of styles.
-            //
-            // A stack is used since auto-generated styles
-            // from later versions should override
-            // auto-generated styles from earlier versions.
-            // Iterating over the styles is done in order,
-            // so we will always visit sdkVersions from smallest
-            // to largest.
-            std::stack<ResourceConfigValue> addStack;
-
-            for (ResourceConfigValue& configValue : entry->values) {
-                visitFunc<Style>(*configValue.value, [&](Style& style) {
-                    // Collect which entries we've stripped and the smallest
-                    // SDK level which was stripped.
-                    size_t minSdkStripped = std::numeric_limits<size_t>::max();
-                    std::vector<Style::Entry> stripped;
-
-                    // Iterate over the style's entries and erase/record the
-                    // attributes whose SDK level exceeds the config's sdkVersion.
-                    auto iter = style.entries.begin();
-                    while (iter != style.entries.end()) {
-                        if (iter->key.name.package == u"android") {
-                            size_t sdkLevel = findAttributeSdkLevel(iter->key.name);
-                            if (sdkLevel > 1 && sdkLevel > configValue.config.sdkVersion) {
-                                // Record that we are about to strip this.
-                                stripped.emplace_back(std::move(*iter));
-                                minSdkStripped = std::min(minSdkStripped, sdkLevel);
-
-                                // Erase this from this style.
-                                iter = style.entries.erase(iter);
-                                continue;
-                            }
-                        }
-                        ++iter;
-                    }
-
-                    if (!stripped.empty()) {
-                        // We have stripped attributes, so let's create a new style to hold them.
-                        ConfigDescription versionConfig(configValue.config);
-                        versionConfig.sdkVersion = minSdkStripped;
-
-                        ResourceConfigValue value = {
-                                versionConfig,
-                                configValue.source,
-                                {},
-
-                                // Create a copy of the original style.
-                                std::unique_ptr<Value>(configValue.value->clone(
-                                            &table->getValueStringPool()))
-                        };
-
-                        Style& newStyle = static_cast<Style&>(*value.value);
-
-                        // Move the recorded stripped attributes into this new style.
-                        std::move(stripped.begin(), stripped.end(),
-                                  std::back_inserter(newStyle.entries));
-
-                        // We will add this style to the table later. If we do it now, we will
-                        // mess up iteration.
-                        addStack.push(std::move(value));
-                    }
-                });
-            }
-
-            auto comparator =
-                    [](const ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool {
-                        return lhs.config < rhs;
-                    };
-
-            while (!addStack.empty()) {
-                ResourceConfigValue& value = addStack.top();
-                auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
-                                             value.config, comparator);
-                if (iter == entry->values.end() || iter->config != value.config) {
-                    entry->values.insert(iter, std::move(value));
-                }
-                addStack.pop();
-            }
-        }
-    }
-}
-
-struct CompileItem {
-    ResourceName name;
-    ConfigDescription config;
-    Source source;
-    std::string extension;
-};
-
-struct LinkItem {
-    ResourceName name;
-    ConfigDescription config;
-    Source source;
-    std::string originalPath;
-    ZipFile* apk;
-    std::u16string originalPackage;
-};
-
-template <typename TChar>
-static BasicStringPiece<TChar> getExtension(const BasicStringPiece<TChar>& str) {
-    auto iter = std::find(str.begin(), str.end(), static_cast<TChar>('.'));
-    if (iter == str.end()) {
-        return BasicStringPiece<TChar>();
-    }
-    size_t offset = (iter - str.begin()) + 1;
-    return str.substr(offset, str.size() - offset);
-}
-
-std::string buildFileReference(const ResourceNameRef& name, const ConfigDescription& config,
-                               const StringPiece& extension) {
-    std::stringstream path;
-    path << "res/" << name.type;
-    if (config != ConfigDescription{}) {
-        path << "-" << config;
-    }
-    path << "/" << util::utf16ToUtf8(name.entry);
-    if (!extension.empty()) {
-        path << "." << extension;
-    }
-    return path.str();
-}
-
-std::string buildFileReference(const CompileItem& item) {
-    return buildFileReference(item.name, item.config, item.extension);
-}
-
-std::string buildFileReference(const LinkItem& item) {
-    return buildFileReference(item.name, item.config, getExtension<char>(item.originalPath));
-}
-
-template <typename T>
-bool addFileReference(const std::shared_ptr<ResourceTable>& table, const T& item) {
-    StringPool& pool = table->getValueStringPool();
-    StringPool::Ref ref = pool.makeRef(util::utf8ToUtf16(buildFileReference(item)),
-                                       StringPool::Context{ 0, item.config });
-    return table->addResource(item.name, item.config, item.source.line(0),
-                              util::make_unique<FileReference>(ref));
-}
-
-struct AaptOptions {
-    enum class Phase {
-        Link,
-        Compile,
-        Dump,
-        DumpStyleGraph,
-    };
-
-    enum class PackageType {
-        StandardApp,
-        StaticLibrary,
-    };
-
-    // The phase to process.
-    Phase phase;
-
-    // The type of package to produce.
-    PackageType packageType = PackageType::StandardApp;
-
-    // Details about the app.
-    AppInfo appInfo;
-
-    // The location of the manifest file.
-    Source manifest;
-
-    // The APK files to link.
-    std::vector<Source> input;
-
-    // The libraries these files may reference.
-    std::vector<Source> libraries;
-
-    // Output path. This can be a directory or file
-    // depending on the phase.
-    Source output;
-
-    // Directory in which to write binding xml files.
-    Source bindingOutput;
-
-    // Directory to in which to generate R.java.
-    Maybe<Source> generateJavaClass;
-
-    // File in which to produce proguard rules.
-    Maybe<Source> generateProguardRules;
-
-    // Whether to output verbose details about
-    // compilation.
-    bool verbose = false;
-
-    // Whether or not to auto-version styles or layouts
-    // referencing attributes defined in a newer SDK
-    // level than the style or layout is defined for.
-    bool versionStylesAndLayouts = true;
-
-    // The target style that will have it's style hierarchy dumped
-    // when the phase is DumpStyleGraph.
-    ResourceName dumpStyleTarget;
-};
-
-struct IdCollector : public xml::Visitor {
-    IdCollector(const Source& source, const std::shared_ptr<ResourceTable>& table) :
-            mSource(source), mTable(table) {
-    }
-
-    virtual void visit(xml::Text* node) override {}
-
-    virtual void visit(xml::Namespace* node) override {
-        for (const auto& child : node->children) {
-            child->accept(this);
-        }
-    }
-
-    virtual void visit(xml::Element* node) override {
-        for (const xml::Attribute& attr : node->attributes) {
-            bool create = false;
-            bool priv = false;
-            ResourceNameRef nameRef;
-            if (ResourceParser::tryParseReference(attr.value, &nameRef, &create, &priv)) {
-                if (create) {
-                    mTable->addResource(nameRef, {}, mSource.line(node->lineNumber),
-                                        util::make_unique<Id>());
-                }
-            }
-        }
-
-        for (const auto& child : node->children) {
-            child->accept(this);
-        }
-    }
-
-private:
-    Source mSource;
-    std::shared_ptr<ResourceTable> mTable;
-};
-
-bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
-                const CompileItem& item, ZipFile* outApk) {
-    std::ifstream in(item.source.path, std::ifstream::binary);
-    if (!in) {
-        Logger::error(item.source) << strerror(errno) << std::endl;
-        return false;
-    }
-
-    SourceLogger logger(item.source);
-    std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger);
-    if (!root) {
-        return false;
-    }
-
-    // Collect any resource ID's declared here.
-    IdCollector idCollector(item.source, table);
-    root->accept(&idCollector);
-
-    BigBuffer outBuffer(1024);
-    if (!xml::flatten(root.get(), options.appInfo.package, &outBuffer)) {
-        logger.error() << "failed to encode XML." << std::endl;
-        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;
-}
-
-/**
- * Determines if a layout should be auto generated based on SDK level. We do not
- * generate a layout if there is already a layout defined whose SDK version is greater than
- * the one we want to generate.
- */
-bool shouldGenerateVersionedResource(const std::shared_ptr<const ResourceTable>& table,
-                                     const ResourceName& name, const ConfigDescription& config,
-                                     int sdkVersionToGenerate) {
-    assert(sdkVersionToGenerate > config.sdkVersion);
-    const ResourceTableType* type;
-    const ResourceEntry* entry;
-    std::tie(type, entry) = table->findResource(name);
-    assert(type && entry);
-
-    auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), config,
-            [](const ResourceConfigValue& lhs, const ConfigDescription& config) -> bool {
-        return lhs.config < config;
-    });
-
-    assert(iter != entry->values.end());
-    ++iter;
-
-    if (iter == entry->values.end()) {
-        return true;
-    }
-
-    ConfigDescription newConfig = config;
-    newConfig.sdkVersion = sdkVersionToGenerate;
-    return newConfig < iter->config;
-}
-
-bool linkXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
-             const std::shared_ptr<IResolver>& resolver, const LinkItem& item,
-             const void* data, size_t dataLen, ZipFile* outApk, std::queue<LinkItem>* outQueue,
-             proguard::KeepSet* keepSet) {
-    SourceLogger logger(item.source);
-    std::unique_ptr<xml::Node> root = xml::inflate(data, dataLen, &logger);
-    if (!root) {
-        return false;
-    }
-
-    xml::FlattenOptions xmlOptions;
-    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.
-        // Non-version qualified resources have an implicit version 1 requirement.
-        xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1;
-    }
-
-    if (options.generateProguardRules) {
-        proguard::collectProguardRules(item.name.type, item.source, root.get(), keepSet);
-    }
-
-    BigBuffer outBuffer(1024);
-    Maybe<size_t> minStrippedSdk = xml::flattenAndLink(item.source, root.get(),
-                                                       item.originalPackage, resolver,
-                                                       xmlOptions, &outBuffer);
-    if (!minStrippedSdk) {
-        logger.error() << "failed to encode XML." << std::endl;
-        return false;
-    }
-
-    if (minStrippedSdk.value() > 0) {
-        // Something was stripped, so let's generate a new file
-        // with the version of the smallest SDK version stripped.
-        // We can only generate a versioned layout if there doesn't exist a layout
-        // with sdk version greater than the current one but less than the one we
-        // want to generate.
-        if (shouldGenerateVersionedResource(table, item.name, item.config,
-                    minStrippedSdk.value())) {
-            LinkItem newWork = item;
-            newWork.config.sdkVersion = minStrippedSdk.value();
-            outQueue->push(newWork);
-
-            if (!addFileReference(table, newWork)) {
-                Logger::error(options.output) << "failed to add auto-versioned resource '"
-                                              << newWork.name << "'." << std::endl;
-                return false;
-            }
-        }
-    }
-
-    if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressDeflated,
-                nullptr) != android::NO_ERROR) {
-        Logger::error(options.output) << "failed to write linked file '"
-                                      << buildFileReference(item) << "' 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) {
-        Logger::error(item.source) << strerror(errno) << std::endl;
-        return false;
-    }
-
-    BigBuffer outBuffer(4096);
-    std::string err;
-    Png png;
-    if (!png.process(item.source, in, &outBuffer, {}, &err)) {
-        Logger::error(item.source) << err << std::endl;
-        return false;
-    }
-
-    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 copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
-    if (outApk->add(item.source.path.data(), buildFileReference(item).data(),
-                ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
-        Logger::error(options.output) << "failed to copy file '" << item.source << "' to apk."
-                                      << std::endl;
-        return false;
-    }
-    return true;
-}
-
-bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
-                     const std::map<std::shared_ptr<ResourceTable>, StaticLibraryData>& libApks,
-                     const android::ResTable& table, ZipFile* outApk, proguard::KeepSet* keepSet) {
-    if (options.verbose) {
-        Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
-    }
-
-    std::ifstream in(options.manifest.path, std::ifstream::binary);
-    if (!in) {
-        Logger::error(options.manifest) << strerror(errno) << std::endl;
-        return false;
-    }
-
-    SourceLogger logger(options.manifest);
-    std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger);
-    if (!root) {
-        return false;
-    }
-
-    ManifestMerger merger({});
-    if (!merger.setAppManifest(options.manifest, options.appInfo.package, std::move(root))) {
-        return false;
-    }
-
-    for (const auto& entry : libApks) {
-        ZipFile* libApk = entry.second.apk.get();
-        const std::u16string& libPackage = entry.first->getPackage();
-        const Source& libSource = entry.second.source;
-
-        ZipEntry* zipEntry = libApk->getEntryByName("AndroidManifest.xml");
-        if (!zipEntry) {
-            continue;
-        }
-
-        std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
-                libApk->uncompress(zipEntry));
-        assert(uncompressedData);
-
-        SourceLogger logger(libSource);
-        std::unique_ptr<xml::Node> libRoot = xml::inflate(uncompressedData.get(),
-                                                          zipEntry->getUncompressedLen(), &logger);
-        if (!libRoot) {
-            return false;
-        }
-
-        if (!merger.mergeLibraryManifest(libSource, libPackage, std::move(libRoot))) {
-            return false;
-        }
-    }
-
-    if (options.generateProguardRules) {
-        proguard::collectProguardRulesForManifest(options.manifest, merger.getMergedXml(),
-                                                  keepSet);
-    }
-
-    BigBuffer outBuffer(1024);
-    if (!xml::flattenAndLink(options.manifest, merger.getMergedXml(), options.appInfo.package,
-                resolver, {}, &outBuffer)) {
-        return false;
-    }
-
-    std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
-
-    android::ResXMLTree tree;
-    if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) {
-        return false;
-    }
-
-    ManifestValidator validator(table);
-    if (!validator.validate(options.manifest, &tree)) {
-        return false;
-    }
-
-    if (outApk->add(data.get(), outBuffer.size(), "AndroidManifest.xml",
-                ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
-        Logger::error(options.output) << "failed to write 'AndroidManifest.xml' to apk."
-                                      << std::endl;
-        return false;
-    }
-    return true;
-}
-
-static bool compileValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
-                          const ConfigDescription& config) {
-    std::ifstream in(source.path, std::ifstream::binary);
-    if (!in) {
-        Logger::error(source) << strerror(errno) << std::endl;
-        return false;
-    }
-
-    std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
-    ResourceParser parser(table, source, config, xmlParser);
-    return parser.parse();
-}
-
-struct ResourcePathData {
-    std::u16string resourceDir;
-    std::u16string name;
-    std::string extension;
-    ConfigDescription config;
-};
-
-/**
- * Resource file paths are expected to look like:
- * [--/res/]type[-config]/name
- */
-static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
-    // TODO(adamlesinski): Use Windows path separator on windows.
-    std::vector<std::string> parts = util::splitAndLowercase(source.path, '/');
-    if (parts.size() < 2) {
-        Logger::error(source) << "bad resource path." << std::endl;
-        return {};
-    }
-
-    std::string& dir = parts[parts.size() - 2];
-    StringPiece dirStr = dir;
-
-    ConfigDescription config;
-    size_t dashPos = dir.find('-');
-    if (dashPos != std::string::npos) {
-        StringPiece configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
-        if (!ConfigDescription::parse(configStr, &config)) {
-            Logger::error(source)
-                    << "invalid configuration '"
-                    << configStr
-                    << "'."
-                    << std::endl;
-            return {};
-        }
-        dirStr = dirStr.substr(0, dashPos);
-    }
-
-    std::string& filename = parts[parts.size() - 1];
-    StringPiece name = filename;
-    StringPiece extension;
-    size_t dotPos = filename.find('.');
-    if (dotPos != std::string::npos) {
-        extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
-        name = name.substr(0, dotPos);
-    }
-
-    return ResourcePathData{
-            util::utf8ToUtf16(dirStr),
-            util::utf8ToUtf16(name),
-            extension.toString(),
-            config
-    };
-}
-
-bool writeResourceTable(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
-                        const TableFlattener::Options& flattenerOptions, ZipFile* outApk) {
-    if (table->begin() != table->end()) {
-        BigBuffer buffer(1024);
-        TableFlattener flattener(flattenerOptions);
-        if (!flattener.flatten(&buffer, *table)) {
-            Logger::error() << "failed to flatten resource table." << std::endl;
-            return false;
-        }
-
-        if (options.verbose) {
-            Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
-                           << std::endl;
-        }
-
-        if (outApk->add(buffer, "resources.arsc", ZipEntry::kCompressStored, nullptr) !=
-                android::NO_ERROR) {
-            Logger::note(options.output) << "failed to store resource table." << std::endl;
-            return false;
-        }
-    }
-    return true;
-}
-
-/**
- * For each FileReference in the table, adds a LinkItem to the link queue for processing.
- */
-static void addApkFilesToLinkQueue(const std::u16string& package, const Source& source,
-                                   const std::shared_ptr<ResourceTable>& table,
-                                   const std::unique_ptr<ZipFile>& apk,
-                                   std::queue<LinkItem>* outLinkQueue) {
-    bool mangle = package != table->getPackage();
-    for (auto& type : *table) {
-        for (auto& entry : type->entries) {
-            ResourceName name = { package, type->type, entry->name };
-            if (mangle) {
-                NameMangler::mangle(table->getPackage(), &name.entry);
-            }
-
-            for (auto& value : entry->values) {
-                visitFunc<FileReference>(*value.value, [&](FileReference& ref) {
-                    std::string pathUtf8 = util::utf16ToUtf8(*ref.path);
-                    Source newSource = source;
-                    newSource.path += "/";
-                    newSource.path += pathUtf8;
-                    outLinkQueue->push(LinkItem{
-                            name, value.config, newSource, pathUtf8, apk.get(),
-                            table->getPackage() });
-                    // Now rewrite the file path.
-                    if (mangle) {
-                        ref.path = table->getValueStringPool().makeRef(util::utf8ToUtf16(
-                                    buildFileReference(name, value.config,
-                                                       getExtension<char>(pathUtf8))));
-                    }
-                });
-            }
-        }
-    }
-}
-
-static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
-        ZipFile::kOpenReadWrite;
-
-bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable,
-          const std::shared_ptr<IResolver>& resolver) {
-    std::map<std::shared_ptr<ResourceTable>, StaticLibraryData> apkFiles;
-    std::unordered_set<std::u16string> linkedPackages;
-
-    // Populate the linkedPackages with our own.
-    linkedPackages.insert(options.appInfo.package);
-
-    // Load all APK files.
-    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>();
-
-        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, options.appInfo.package, 
-                                    uncompressedData.get(), entry->getUncompressedLen());
-        if (!parser.parse()) {
-            return false;
-        }
-
-        // Keep track of where this table came from.
-        apkFiles[table] = StaticLibraryData{ source, std::move(zipFile) };
-
-        // Add the package to the set of linked packages.
-        linkedPackages.insert(table->getPackage());
-    }
-
-    std::queue<LinkItem> linkQueue;
-    for (auto& p : apkFiles) {
-        const std::shared_ptr<ResourceTable>& inTable = p.first;
-
-        // Collect all FileReferences and add them to the queue for processing.
-        addApkFilesToLinkQueue(options.appInfo.package, p.second.source, inTable, p.second.apk,
-                               &linkQueue);
-
-        // Merge the tables.
-        if (!outTable->merge(std::move(*inTable))) {
-            return false;
-        }
-    }
-
-    // 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::Options linkerOptions;
-        if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
-            linkerOptions.linkResourceIds = false;
-        }
-        Linker linker(outTable, resolver, linkerOptions);
-        if (!linker.linkAndValidate()) {
-            return false;
-        }
-
-        // Verify that all symbols exist.
-        const auto& unresolvedRefs = linker.getUnresolvedReferences();
-        if (!unresolvedRefs.empty()) {
-            for (const auto& entry : unresolvedRefs) {
-                for (const auto& source : entry.second) {
-                    Logger::error(source) << "unresolved symbol '" << entry.first << "'."
-                                          << std::endl;
-                }
-            }
-            return false;
-        }
-    }
-
-    // Open the output APK file for writing.
-    ZipFile outApk;
-    if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
-        Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
-        return false;
-    }
-
-    proguard::KeepSet keepSet;
-
-    android::ResTable binTable;
-    if (!compileManifest(options, resolver, apkFiles, binTable, &outApk, &keepSet)) {
-        return false;
-    }
-
-    for (; !linkQueue.empty(); linkQueue.pop()) {
-        const LinkItem& item = linkQueue.front();
-
-        assert(!item.originalPackage.empty());
-        ZipEntry* entry = item.apk->getEntryByName(item.originalPath.data());
-        if (!entry) {
-            Logger::error(item.source) << "failed to find '" << item.originalPath << "'."
-                                       << std::endl;
-            return false;
-        }
-
-        if (util::stringEndsWith<char>(item.originalPath, ".xml")) {
-            void* uncompressedData = item.apk->uncompress(entry);
-            assert(uncompressedData);
-
-            if (!linkXml(options, outTable, resolver, item, uncompressedData,
-                        entry->getUncompressedLen(), &outApk, &linkQueue, &keepSet)) {
-                Logger::error(options.output) << "failed to link '" << item.originalPath << "'."
-                                              << std::endl;
-                return false;
-            }
-        } else {
-            if (outApk.add(item.apk, entry, buildFileReference(item).data(), 0, nullptr) !=
-                    android::NO_ERROR) {
-                Logger::error(options.output) << "failed to copy '" << item.originalPath << "'."
-                                              << std::endl;
-                return false;
-            }
-        }
-    }
-
-    // Generate the Java class file.
-    if (options.generateJavaClass) {
-        JavaClassGenerator::Options javaOptions;
-        if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
-            javaOptions.useFinal = false;
-        }
-        JavaClassGenerator generator(outTable, javaOptions);
-
-        for (const std::u16string& package : linkedPackages) {
-            Source outPath = options.generateJavaClass.value();
-
-            // Build the output directory from the package name.
-            // Eg. com.android.app -> com/android/app
-            const std::string packageUtf8 = util::utf16ToUtf8(package);
-            for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
-                appendPath(&outPath.path, part);
-            }
-
-            if (!mkdirs(outPath.path)) {
-                Logger::error(outPath) << strerror(errno) << std::endl;
-                return false;
-            }
-
-            appendPath(&outPath.path, "R.java");
-
-            if (options.verbose) {
-                Logger::note(outPath) << "writing Java symbols." << std::endl;
-            }
-
-            std::ofstream fout(outPath.path);
-            if (!fout) {
-                Logger::error(outPath) << strerror(errno) << std::endl;
-                return false;
-            }
-
-            if (!generator.generate(package, fout)) {
-                Logger::error(outPath) << generator.getError() << "." << std::endl;
-                return false;
-            }
-        }
-    }
-
-    // Generate the Proguard rules file.
-    if (options.generateProguardRules) {
-        const Source& outPath = options.generateProguardRules.value();
-
-        if (options.verbose) {
-            Logger::note(outPath) << "writing proguard rules." << std::endl;
-        }
-
-        std::ofstream fout(outPath.path);
-        if (!fout) {
-            Logger::error(outPath) << strerror(errno) << std::endl;
-            return false;
-        }
-
-        if (!proguard::writeKeepSet(&fout, keepSet)) {
-            Logger::error(outPath) << "failed to write proguard rules." << std::endl;
-            return false;
-        }
-    }
-
-    outTable->getValueStringPool().prune();
-    outTable->getValueStringPool().sort(
-            [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
-                if (a.context.priority < b.context.priority) {
-                    return true;
-                }
-
-                if (a.context.priority > b.context.priority) {
-                    return false;
-                }
-                return a.value < b.value;
-            });
-
-
-    // Flatten the resource table.
-    TableFlattener::Options flattenerOptions;
-    if (options.packageType != AaptOptions::PackageType::StaticLibrary) {
-        flattenerOptions.useExtendedChunks = false;
-    }
-
-    if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) {
-        return false;
-    }
-
-    outApk.flush();
-    return true;
-}
-
-bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
-             const std::shared_ptr<IResolver>& resolver) {
-    std::queue<CompileItem> compileQueue;
-    bool error = false;
-
-    // Compile all the resource files passed in on the command line.
-    for (const Source& source : options.input) {
-        // Need to parse the resource type/config/filename.
-        Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
-        if (!maybePathData) {
-            return false;
-        }
-
-        const ResourcePathData& pathData = maybePathData.value();
-        if (pathData.resourceDir == u"values") {
-            // The file is in the values directory, which means its contents will
-            // go into the resource table.
-            if (options.verbose) {
-                Logger::note(source) << "compiling values." << std::endl;
-            }
-
-            error |= !compileValues(table, source, pathData.config);
-        } else {
-            // The file is in a directory like 'layout' or 'drawable'. Find out
-            // the type.
-            const ResourceType* type = parseResourceType(pathData.resourceDir);
-            if (!type) {
-                Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
-                                      << std::endl;
-                return false;
-            }
-
-            compileQueue.push(CompileItem{
-                    ResourceName{ table->getPackage(), *type, pathData.name },
-                    pathData.config,
-                    source,
-                    pathData.extension
-            });
-        }
-    }
-
-    if (error) {
-        return false;
-    }
-    // Open the output APK file for writing.
-    ZipFile outApk;
-    if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
-        Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
-        return false;
-    }
-
-    // Compile each file.
-    for (; !compileQueue.empty(); compileQueue.pop()) {
-        const CompileItem& item = compileQueue.front();
-
-        // Add the file name to the resource table.
-        error |= !addFileReference(table, item);
-
-        if (item.extension == "xml") {
-            error |= !compileXml(options, table, item, &outApk);
-        } else if (item.extension == "png" || item.extension == "9.png") {
-            error |= !compilePng(options, item, &outApk);
-        } else {
-            error |= !copyFile(options, item, &outApk);
-        }
-    }
-
-    if (error) {
-        return false;
-    }
-
-    // Link and assign resource IDs.
-    Linker linker(table, resolver, {});
-    if (!linker.linkAndValidate()) {
-        return false;
-    }
-
-    // Flatten the resource table.
-    if (!writeResourceTable(options, table, {}, &outApk)) {
-        return false;
-    }
-
-    outApk.flush();
-    return true;
-}
-
-bool loadAppInfo(const Source& source, AppInfo* outInfo) {
-    std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
-    if (!ifs) {
-        Logger::error(source) << strerror(errno) << std::endl;
-        return false;
-    }
-
-    ManifestParser parser;
-    std::shared_ptr<XmlPullParser> pullParser = std::make_shared<SourceXmlPullParser>(ifs);
-    return parser.parse(source, pullParser, outInfo);
-}
-
-static void printCommandsAndDie() {
-    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;
-    exit(1);
-}
-
-static AaptOptions prepareArgs(int argc, char** argv) {
-    if (argc < 2) {
-        std::cerr << "no command specified." << std::endl << std::endl;
-        printCommandsAndDie();
-    }
-
-    const StringPiece command(argv[1]);
-    argc -= 2;
-    argv += 2;
-
-    AaptOptions options;
-
-    if (command == "--version" || command == "version") {
-        std::cout << kAaptVersionStr << std::endl;
-        exit(0);
-    } else if (command == "link") {
-        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::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("--java", "directory in which to generate R.java",
-                [&options](const StringPiece& arg) {
-                    options.generateJavaClass = Source{ arg.toString() };
-                });
-
-        flag::optionalFlag("--proguard", "file in which to output proguard rules",
-                [&options](const StringPiece& arg) {
-                    options.generateProguardRules = Source{ arg.toString() };
-                });
-
-        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);
-    }
-
-    if (options.phase == AaptOptions::Phase::Compile ||
-            options.phase == AaptOptions::Phase::Link) {
-        // Common flags for all steps.
-        flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) {
-            options.output = Source{ arg.toString() };
-        });
-    }
-
-    if (options.phase == AaptOptions::Phase::DumpStyleGraph) {
-        flag::requiredFlag("--style", "Name of the style to dump",
-                [&options](const StringPiece& arg, std::string* outError) -> bool {
-                    Reference styleReference;
-                    if (!ResourceParser::parseStyleParentReference(util::utf8ToUtf16(arg),
-                                &styleReference, outError)) {
-                        return false;
-                    }
-                    options.dumpStyleTarget = styleReference.name;
-                    return true;
-                });
-    }
-
-    bool help = false;
-    flag::optionalSwitch("-v", "enables verbose logging", true, &options.verbose);
-    flag::optionalSwitch("-h", "displays this help menu", true, &help);
-
-    // Build the command string for output (eg. "aapt2 compile").
-    std::string fullCommand = "aapt2";
-    fullCommand += " ";
-    fullCommand += command.toString();
-
-    // Actually read the command line flags.
-    flag::parse(argc, argv, fullCommand);
-
-    if (help) {
-        flag::usageAndDie(fullCommand);
-    }
-
-    if (isStaticLib) {
-        options.packageType = AaptOptions::PackageType::StaticLibrary;
-    }
-
-    // Copy all the remaining arguments.
-    for (const std::string& arg : flag::getArgs()) {
-        options.input.push_back(Source{ arg });
-    }
-    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, options.dumpStyleTarget);
-        }
-    }
-    return true;
-}
+} // namespace aapt
 
 int main(int argc, char** argv) {
-    Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
-    AaptOptions options = prepareArgs(argc, argv);
+    if (argc >= 2) {
+        argv += 1;
+        argc -= 1;
 
-    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)) {
-            return false;
+        std::vector<aapt::StringPiece> args;
+        for (int i = 1; i < argc; i++) {
+            args.push_back(argv[i]);
         }
 
-        if (options.appInfo.package.empty()) {
-            Logger::error() << "no package name specified." << std::endl;
-            return false;
+        aapt::StringPiece command(argv[0]);
+        if (command == "compile" || command == "c") {
+            return aapt::compile(args);
+        } else if (command == "link" || command == "l") {
+            return aapt::link(args);
         }
-    }
-
-    // Every phase needs a resource table.
-    std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
-
-    // The package name is empty when in the compile phase.
-    table->setPackage(options.appInfo.package);
-    if (options.appInfo.package == u"android") {
-        table->setPackageId(0x01);
+        std::cerr << "unknown command '" << command << "'\n";
     } else {
-        table->setPackageId(0x7f);
+        std::cerr << "no command specified\n";
     }
 
-    // Load the included libraries.
-    std::vector<std::shared_ptr<const android::AssetManager>> sources;
-    for (const Source& source : options.libraries) {
-        std::shared_ptr<android::AssetManager> assetManager =
-                std::make_shared<android::AssetManager>();
-        int32_t cookie;
-        if (!assetManager->addAssetPath(android::String8(source.path.data()), &cookie)) {
-            Logger::error(source) << "failed to load library." << std::endl;
-            return false;
-        }
-
-        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, sources);
-
-    if (options.phase == AaptOptions::Phase::Compile) {
-        if (!compile(options, table, resolver)) {
-            Logger::error() << "aapt exiting with failures." << std::endl;
-            return 1;
-        }
-    } else if (options.phase == AaptOptions::Phase::Link) {
-        if (!link(options, table, resolver)) {
-            Logger::error() << "aapt exiting with failures." << std::endl;
-            return 1;
-        }
-    }
-    return 0;
+    std::cerr << "\nusage: aapt2 [compile|link] ..." << std::endl;
+    return 1;
 }
diff --git a/tools/aapt2/ManifestMerger.cpp b/tools/aapt2/ManifestMerger.cpp
deleted file mode 100644
index 71d3424..0000000
--- a/tools/aapt2/ManifestMerger.cpp
+++ /dev/null
@@ -1,376 +0,0 @@
-#include "ManifestMerger.h"
-#include "Maybe.h"
-#include "ResourceParser.h"
-#include "Source.h"
-#include "Util.h"
-#include "XmlPullParser.h"
-
-#include <iostream>
-#include <memory>
-#include <set>
-#include <string>
-
-namespace aapt {
-
-constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
-
-static xml::Element* findManifest(xml::Node* root) {
-    if (!root) {
-        return nullptr;
-    }
-
-    while (root->type == xml::NodeType::kNamespace) {
-        if (root->children.empty()) {
-            break;
-        }
-        root = root->children[0].get();
-    }
-
-    if (root && root->type == xml::NodeType::kElement) {
-        xml::Element* el = static_cast<xml::Element*>(root);
-        if (el->namespaceUri.empty() && el->name == u"manifest") {
-            return el;
-        }
-    }
-    return nullptr;
-}
-
-static xml::Element* findChildWithSameName(xml::Element* parent, xml::Element* src) {
-    xml::Attribute* attrKey = src->findAttribute(kSchemaAndroid, u"name");
-    if (!attrKey) {
-        return nullptr;
-    }
-    return parent->findChildWithAttribute(src->namespaceUri, src->name, attrKey);
-}
-
-static bool attrLess(const xml::Attribute& lhs, const xml::Attribute& rhs) {
-    return std::tie(lhs.namespaceUri, lhs.name, lhs.value)
-            < std::tie(rhs.namespaceUri, rhs.name, rhs.value);
-}
-
-static int compare(xml::Element* lhs, xml::Element* rhs) {
-    int diff = lhs->attributes.size() - rhs->attributes.size();
-    if (diff != 0) {
-        return diff;
-    }
-
-    std::set<xml::Attribute, decltype(&attrLess)> lhsAttrs(&attrLess);
-    lhsAttrs.insert(lhs->attributes.begin(), lhs->attributes.end());
-    for (auto& attr : rhs->attributes) {
-        if (lhsAttrs.erase(attr) == 0) {
-            // The rhs attribute is not in the left.
-            return -1;
-        }
-    }
-
-    if (!lhsAttrs.empty()) {
-        // The lhs has attributes not in the rhs.
-        return 1;
-    }
-    return 0;
-}
-
-ManifestMerger::ManifestMerger(const Options& options) :
-        mOptions(options), mAppLogger({}), mLogger({}) {
-}
-
-bool ManifestMerger::setAppManifest(const Source& source, const std::u16string& package,
-                                    std::unique_ptr<xml::Node> root) {
-
-    mAppLogger = SourceLogger{ source };
-    mRoot = std::move(root);
-    return true;
-}
-
-bool ManifestMerger::checkEqual(xml::Element* elA, xml::Element* elB) {
-    if (compare(elA, elB) != 0) {
-        mLogger.error(elB->lineNumber)
-                << "library tag '" << elB->name << "' conflicts with app tag."
-                << std::endl;
-        mAppLogger.note(elA->lineNumber)
-                << "app tag '" << elA->name << "' defined here."
-                << std::endl;
-        return false;
-    }
-
-    std::vector<xml::Element*> childrenA = elA->getChildElements();
-    std::vector<xml::Element*> childrenB = elB->getChildElements();
-
-    if (childrenA.size() != childrenB.size()) {
-        mLogger.error(elB->lineNumber)
-                << "library tag '" << elB->name << "' children conflict with app tag."
-                << std::endl;
-        mAppLogger.note(elA->lineNumber)
-                << "app tag '" << elA->name << "' defined here."
-                << std::endl;
-        return false;
-    }
-
-    auto cmp = [](xml::Element* lhs, xml::Element* rhs) -> bool {
-        return compare(lhs, rhs) < 0;
-    };
-
-    std::sort(childrenA.begin(), childrenA.end(), cmp);
-    std::sort(childrenB.begin(), childrenB.end(), cmp);
-
-    for (size_t i = 0; i < childrenA.size(); i++) {
-        if (!checkEqual(childrenA[i], childrenB[i])) {
-            return false;
-        }
-    }
-    return true;
-}
-
-bool ManifestMerger::mergeNewOrEqual(xml::Element* parentA, xml::Element* elA, xml::Element* elB) {
-    if (!elA) {
-        parentA->addChild(elB->clone());
-        return true;
-    }
-    return checkEqual(elA, elB);
-}
-
-bool ManifestMerger::mergePreferRequired(xml::Element* parentA, xml::Element* elA,
-                                         xml::Element* elB) {
-    if (!elA) {
-        parentA->addChild(elB->clone());
-        return true;
-    }
-
-    xml::Attribute* reqA = elA->findAttribute(kSchemaAndroid, u"required");
-    xml::Attribute* reqB = elB->findAttribute(kSchemaAndroid, u"required");
-    bool requiredA = !reqA || (reqA->value != u"false" && reqA->value != u"FALSE");
-    bool requiredB = !reqB || (reqB->value != u"false" && reqB->value != u"FALSE");
-    if (!requiredA && requiredB) {
-        if (reqA) {
-            *reqA = xml::Attribute{ kSchemaAndroid, u"required", u"true" };
-        } else {
-            elA->attributes.push_back(xml::Attribute{ kSchemaAndroid, u"required", u"true" });
-        }
-    }
-    return true;
-}
-
-static int findIntegerValue(xml::Attribute* attr, int defaultValue) {
-    if (attr) {
-        std::unique_ptr<BinaryPrimitive> integer = ResourceParser::tryParseInt(attr->value);
-        if (integer) {
-            return integer->value.data;
-        }
-    }
-    return defaultValue;
-}
-
-bool ManifestMerger::mergeUsesSdk(xml::Element* elA, xml::Element* elB) {
-    bool error = false;
-    xml::Attribute* minAttrA = nullptr;
-    xml::Attribute* minAttrB = nullptr;
-    if (elA) {
-        minAttrA = elA->findAttribute(kSchemaAndroid, u"minSdkVersion");
-    }
-
-    if (elB) {
-        minAttrB = elB->findAttribute(kSchemaAndroid, u"minSdkVersion");
-    }
-
-    int minSdkA = findIntegerValue(minAttrA, 1);
-    int minSdkB = findIntegerValue(minAttrB, 1);
-
-    if (minSdkA < minSdkB) {
-        std::ostream* out;
-        if (minAttrA) {
-            out = &(mAppLogger.error(elA->lineNumber) << "app declares ");
-        } else if (elA) {
-            out = &(mAppLogger.error(elA->lineNumber) << "app has implied ");
-        } else {
-            out = &(mAppLogger.error() << "app has implied ");
-        }
-
-        *out << "minSdkVersion=" << minSdkA << " but library expects a higher SDK version."
-             << std::endl;
-
-        // elB is valid because minSdkB wouldn't be greater than minSdkA if it wasn't.
-        mLogger.note(elB->lineNumber)
-                << "library declares minSdkVersion=" << minSdkB << "."
-                << std::endl;
-        error = true;
-    }
-
-    xml::Attribute* targetAttrA = nullptr;
-    xml::Attribute* targetAttrB = nullptr;
-
-    if (elA) {
-        targetAttrA = elA->findAttribute(kSchemaAndroid, u"targetSdkVersion");
-    }
-
-    if (elB) {
-        targetAttrB = elB->findAttribute(kSchemaAndroid, u"targetSdkVersion");
-    }
-
-    int targetSdkA = findIntegerValue(targetAttrA, minSdkA);
-    int targetSdkB = findIntegerValue(targetAttrB, minSdkB);
-
-    if (targetSdkA < targetSdkB) {
-        std::ostream* out;
-        if (targetAttrA) {
-            out = &(mAppLogger.warn(elA->lineNumber) << "app declares ");
-        } else if (elA) {
-            out = &(mAppLogger.warn(elA->lineNumber) << "app has implied ");
-        } else {
-            out = &(mAppLogger.warn() << "app has implied ");
-        }
-
-        *out << "targetSdkVerion=" << targetSdkA << " but library expects target SDK "
-             << targetSdkB << "." << std::endl;
-
-        mLogger.note(elB->lineNumber)
-                << "library declares targetSdkVersion=" << targetSdkB << "."
-                << std::endl;
-        error = true;
-    }
-    return !error;
-}
-
-bool ManifestMerger::mergeApplication(xml::Element* applicationA, xml::Element* applicationB) {
-    if (!applicationA || !applicationB) {
-        return true;
-    }
-
-    bool error = false;
-
-    // First make sure that the names are identical.
-    xml::Attribute* nameA = applicationA->findAttribute(kSchemaAndroid, u"name");
-    xml::Attribute* nameB = applicationB->findAttribute(kSchemaAndroid, u"name");
-    if (nameB) {
-        if (!nameA) {
-            applicationA->attributes.push_back(*nameB);
-        } else if (nameA->value != nameB->value) {
-            mLogger.error(applicationB->lineNumber)
-                    << "conflicting application name '"
-                    << nameB->value
-                    << "'." << std::endl;
-            mAppLogger.note(applicationA->lineNumber)
-                    << "application defines application name '"
-                    << nameA->value
-                    << "'." << std::endl;
-            error = true;
-        }
-    }
-
-    // Now we descend into the activity/receiver/service/provider tags
-    for (xml::Element* elB : applicationB->getChildElements()) {
-        if (!elB->namespaceUri.empty()) {
-            continue;
-        }
-
-        if (elB->name == u"activity" || elB->name == u"activity-alias"
-                || elB->name == u"service" || elB->name == u"receiver"
-                || elB->name == u"provider" || elB->name == u"meta-data") {
-            xml::Element* elA = findChildWithSameName(applicationA, elB);
-            error |= !mergeNewOrEqual(applicationA, elA, elB);
-        } else if (elB->name == u"uses-library") {
-            xml::Element* elA = findChildWithSameName(applicationA, elB);
-            error |= !mergePreferRequired(applicationA, elA, elB);
-        }
-    }
-    return !error;
-}
-
-bool ManifestMerger::mergeLibraryManifest(const Source& source, const std::u16string& package,
-                                          std::unique_ptr<xml::Node> libRoot) {
-    mLogger = SourceLogger{ source };
-    xml::Element* manifestA = findManifest(mRoot.get());
-    xml::Element* manifestB = findManifest(libRoot.get());
-    if (!manifestA) {
-        mAppLogger.error() << "missing manifest tag." << std::endl;
-        return false;
-    }
-
-    if (!manifestB) {
-        mLogger.error() << "library missing manifest tag." << std::endl;
-        return false;
-    }
-
-    bool error = false;
-
-    // Do <application> first.
-    xml::Element* applicationA = manifestA->findChild({}, u"application");
-    xml::Element* applicationB = manifestB->findChild({}, u"application");
-    error |= !mergeApplication(applicationA, applicationB);
-
-    // Do <uses-sdk> next.
-    xml::Element* usesSdkA = manifestA->findChild({}, u"uses-sdk");
-    xml::Element* usesSdkB = manifestB->findChild({}, u"uses-sdk");
-    error |= !mergeUsesSdk(usesSdkA, usesSdkB);
-
-    for (xml::Element* elB : manifestB->getChildElements()) {
-        if (!elB->namespaceUri.empty()) {
-            continue;
-        }
-
-        if (elB->name == u"uses-permission" || elB->name == u"permission"
-                || elB->name == u"permission-group" || elB->name == u"permission-tree") {
-            xml::Element* elA = findChildWithSameName(manifestA, elB);
-            error |= !mergeNewOrEqual(manifestA, elA, elB);
-        } else if (elB->name == u"uses-feature") {
-            xml::Element* elA = findChildWithSameName(manifestA, elB);
-            error |= !mergePreferRequired(manifestA, elA, elB);
-        } else if (elB->name == u"uses-configuration" || elB->name == u"supports-screen"
-                || elB->name == u"compatible-screens" || elB->name == u"supports-gl-texture") {
-            xml::Element* elA = findChildWithSameName(manifestA, elB);
-            error |= !checkEqual(elA, elB);
-        }
-    }
-    return !error;
-}
-
-static void printMerged(xml::Node* node, int depth) {
-    std::string indent;
-    for (int i = 0; i < depth; i++) {
-        indent += "  ";
-    }
-
-    switch (node->type) {
-        case xml::NodeType::kNamespace:
-            std::cerr << indent << "N: "
-                      << "xmlns:" << static_cast<xml::Namespace*>(node)->namespacePrefix
-                      << "=\"" << static_cast<xml::Namespace*>(node)->namespaceUri
-                      << "\"\n";
-            break;
-
-        case xml::NodeType::kElement:
-            std::cerr << indent << "E: "
-                      << static_cast<xml::Element*>(node)->namespaceUri
-                      << ":" << static_cast<xml::Element*>(node)->name
-                      << "\n";
-            for (const auto& attr : static_cast<xml::Element*>(node)->attributes) {
-                std::cerr << indent << "  A: "
-                          << attr.namespaceUri
-                          << ":" << attr.name
-                          << "=\"" << attr.value << "\"\n";
-            }
-            break;
-
-        case xml::NodeType::kText:
-            std::cerr << indent << "T: \"" << static_cast<xml::Text*>(node)->text << "\"\n";
-            break;
-    }
-
-    for (auto& child : node->children) {
-        printMerged(child.get(), depth + 1);
-    }
-}
-
-xml::Node* ManifestMerger::getMergedXml() {
-    return mRoot.get();
-}
-
-bool ManifestMerger::printMerged() {
-    if (!mRoot) {
-        return false;
-    }
-
-    ::aapt::printMerged(mRoot.get(), 0);
-    return true;
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ManifestMerger.h b/tools/aapt2/ManifestMerger.h
deleted file mode 100644
index c6219db..0000000
--- a/tools/aapt2/ManifestMerger.h
+++ /dev/null
@@ -1,45 +0,0 @@
-#ifndef AAPT_MANIFEST_MERGER_H
-#define AAPT_MANIFEST_MERGER_H
-
-#include "Logger.h"
-#include "Source.h"
-#include "XmlDom.h"
-
-#include <memory>
-#include <string>
-
-namespace aapt {
-
-class ManifestMerger {
-public:
-    struct Options {
-    };
-
-    ManifestMerger(const Options& options);
-
-    bool setAppManifest(const Source& source, const std::u16string& package,
-                        std::unique_ptr<xml::Node> root);
-
-    bool mergeLibraryManifest(const Source& source, const std::u16string& package,
-                              std::unique_ptr<xml::Node> libRoot);
-
-    xml::Node* getMergedXml();
-
-    bool printMerged();
-
-private:
-    bool mergeNewOrEqual(xml::Element* parentA, xml::Element* elA, xml::Element* elB);
-    bool mergePreferRequired(xml::Element* parentA, xml::Element* elA, xml::Element* elB);
-    bool checkEqual(xml::Element* elA, xml::Element* elB);
-    bool mergeApplication(xml::Element* applicationA, xml::Element* applicationB);
-    bool mergeUsesSdk(xml::Element* elA, xml::Element* elB);
-
-    Options mOptions;
-    std::unique_ptr<xml::Node> mRoot;
-    SourceLogger mAppLogger;
-    SourceLogger mLogger;
-};
-
-} // namespace aapt
-
-#endif // AAPT_MANIFEST_MERGER_H
diff --git a/tools/aapt2/ManifestMerger_test.cpp b/tools/aapt2/ManifestMerger_test.cpp
deleted file mode 100644
index 6838253..0000000
--- a/tools/aapt2/ManifestMerger_test.cpp
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * 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 "ManifestMerger.h"
-#include "SourceXmlPullParser.h"
-
-#include <gtest/gtest.h>
-#include <sstream>
-#include <string>
-
-namespace aapt {
-
-constexpr const char* kAppManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
-    <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="21" />
-    <uses-permission android:name="android.permission.INTERNET"/>
-    <uses-feature android:name="android.hardware.GPS" android:required="false" />
-    <application android:name="com.android.library.Application">
-        <activity android:name="com.android.example.MainActivity">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-        </activity>
-        <service android:name="com.android.library.Service">
-            <intent-filter>
-                <action android:name="com.android.library.intent.action.SYNC" />
-            </intent-filter>
-        </service>
-    </application>
-</manifest>
-)EOF";
-
-constexpr const char* kLibManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
-    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="21" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-feature android:name="android.hardware.GPS" />
-    <uses-permission android:name="android.permission.GPS" />
-    <application android:name="com.android.library.Application">
-        <service android:name="com.android.library.Service">
-            <intent-filter>
-                <action android:name="com.android.library.intent.action.SYNC" />
-            </intent-filter>
-        </service>
-        <provider android:name="com.android.library.DocumentProvider"
-                  android:authorities="com.android.library.documents"
-                  android:grantUriPermission="true"
-                  android:exported="true"
-                  android:permission="android.permission.MANAGE_DOCUMENTS"
-                  android:enabled="@bool/atLeastKitKat">
-            <intent-filter>
-                <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
-            </intent-filter>
-        </provider>
-    </application>
-</manifest>
-)EOF";
-
-constexpr const char* kBadLibManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
-    <uses-sdk android:minSdkVersion="17" android:targetSdkVersion="22" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-feature android:name="android.hardware.GPS" />
-    <uses-permission android:name="android.permission.GPS" />
-    <application android:name="com.android.library.Application2">
-        <service android:name="com.android.library.Service">
-            <intent-filter>
-                <action android:name="com.android.library.intent.action.SYNC_ACTION" />
-            </intent-filter>
-        </service>
-    </application>
-</manifest>
-)EOF";
-
-TEST(ManifestMergerTest, MergeManifestsSuccess) {
-    std::stringstream inA(kAppManifest);
-    std::stringstream inB(kLibManifest);
-
-    const Source sourceA = { "AndroidManifest.xml" };
-    const Source sourceB = { "lib.apk/AndroidManifest.xml" };
-    SourceLogger loggerA(sourceA);
-    SourceLogger loggerB(sourceB);
-
-    ManifestMerger merger({});
-    EXPECT_TRUE(merger.setAppManifest(sourceA, u"com.android.example",
-                xml::inflate(&inA, &loggerA)));
-    EXPECT_TRUE(merger.mergeLibraryManifest(sourceB, u"com.android.library",
-                xml::inflate(&inB, &loggerB)));
-}
-
-TEST(ManifestMergerTest, MergeManifestFail) {
-    std::stringstream inA(kAppManifest);
-    std::stringstream inB(kBadLibManifest);
-
-    const Source sourceA = { "AndroidManifest.xml" };
-    const Source sourceB = { "lib.apk/AndroidManifest.xml" };
-    SourceLogger loggerA(sourceA);
-    SourceLogger loggerB(sourceB);
-
-    ManifestMerger merger({});
-    EXPECT_TRUE(merger.setAppManifest(sourceA, u"com.android.example",
-                xml::inflate(&inA, &loggerA)));
-    EXPECT_FALSE(merger.mergeLibraryManifest(sourceB, u"com.android.library",
-                xml::inflate(&inB, &loggerB)));
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ManifestParser.cpp b/tools/aapt2/ManifestParser.cpp
deleted file mode 100644
index b8f0a43..0000000
--- a/tools/aapt2/ManifestParser.cpp
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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 "AppInfo.h"
-#include "Logger.h"
-#include "ManifestParser.h"
-#include "Source.h"
-#include "XmlPullParser.h"
-
-#include <string>
-
-namespace aapt {
-
-bool ManifestParser::parse(const Source& source, std::shared_ptr<XmlPullParser> parser,
-                           AppInfo* outInfo) {
-    SourceLogger logger = { source };
-
-    int depth = 0;
-    while (XmlPullParser::isGoodEvent(parser->next())) {
-        XmlPullParser::Event event = parser->getEvent();
-        if (event == XmlPullParser::Event::kEndElement) {
-            depth--;
-            continue;
-        } else if (event != XmlPullParser::Event::kStartElement) {
-            continue;
-        }
-
-        depth++;
-
-        const std::u16string& element = parser->getElementName();
-        if (depth == 1) {
-            if (element == u"manifest") {
-                if (!parseManifest(logger, parser, outInfo)) {
-                    return false;
-                }
-            } else {
-                logger.error()
-                        << "unexpected top-level element '"
-                        << element
-                        << "'."
-                        << std::endl;
-                return false;
-            }
-        } else {
-            XmlPullParser::skipCurrentElement(parser.get());
-        }
-    }
-
-    if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
-            logger.error(parser->getLineNumber())
-                << "failed to parse manifest: "
-                << parser->getLastError()
-                << "."
-                << std::endl;
-        return false;
-    }
-    return true;
-}
-
-bool ManifestParser::parseManifest(SourceLogger& logger, std::shared_ptr<XmlPullParser> parser,
-                                   AppInfo* outInfo) {
-    auto attrIter = parser->findAttribute(u"", u"package");
-    if (attrIter == parser->endAttributes() || attrIter->value.empty()) {
-        logger.error() << "no 'package' attribute found for element <manifest>." << std::endl;
-        return false;
-    }
-    outInfo->package = attrIter->value;
-    return true;
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ManifestParser.h b/tools/aapt2/ManifestParser.h
deleted file mode 100644
index f2e43d4..0000000
--- a/tools/aapt2/ManifestParser.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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_MANIFEST_PARSER_H
-#define AAPT_MANIFEST_PARSER_H
-
-#include "AppInfo.h"
-#include "Logger.h"
-#include "Source.h"
-#include "XmlPullParser.h"
-
-namespace aapt {
-
-/*
- * Parses an AndroidManifest.xml file and fills in an AppInfo structure with
- * app data.
- */
-class ManifestParser {
-public:
-    ManifestParser() = default;
-    ManifestParser(const ManifestParser&) = delete;
-
-    bool parse(const Source& source, std::shared_ptr<XmlPullParser> parser, AppInfo* outInfo);
-
-private:
-    bool parseManifest(SourceLogger& logger, std::shared_ptr<XmlPullParser> parser,
-                       AppInfo* outInfo);
-};
-
-} // namespace aapt
-
-#endif // AAPT_MANIFEST_PARSER_H
diff --git a/tools/aapt2/ManifestParser_test.cpp b/tools/aapt2/ManifestParser_test.cpp
deleted file mode 100644
index be3a6fb..0000000
--- a/tools/aapt2/ManifestParser_test.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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 "AppInfo.h"
-#include "ManifestParser.h"
-#include "SourceXmlPullParser.h"
-
-#include <gtest/gtest.h>
-#include <sstream>
-#include <string>
-
-namespace aapt {
-
-TEST(ManifestParserTest, FindPackage) {
-    std::stringstream input;
-    input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
-             "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
-             "package=\"android\">\n"
-             "</manifest>\n";
-
-    ManifestParser parser;
-    AppInfo info;
-    std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(input);
-    ASSERT_TRUE(parser.parse(Source{ "AndroidManifest.xml" }, xmlParser, &info));
-
-    EXPECT_EQ(std::u16string(u"android"), info.package);
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ManifestValidator.cpp b/tools/aapt2/ManifestValidator.cpp
index 123b9fa..9f971fb 100644
--- a/tools/aapt2/ManifestValidator.cpp
+++ b/tools/aapt2/ManifestValidator.cpp
@@ -16,9 +16,9 @@
 
 #include "Logger.h"
 #include "ManifestValidator.h"
-#include "Maybe.h"
+#include "util/Maybe.h"
 #include "Source.h"
-#include "Util.h"
+#include "util/Util.h"
 
 #include <androidfw/ResourceTypes.h>
 
diff --git a/tools/aapt2/ManifestValidator.h b/tools/aapt2/ManifestValidator.h
index 3188784..1a7f48e 100644
--- a/tools/aapt2/ManifestValidator.h
+++ b/tools/aapt2/ManifestValidator.h
@@ -18,9 +18,9 @@
 #define AAPT_MANIFEST_VALIDATOR_H
 
 #include "Logger.h"
-#include "Maybe.h"
+#include "util/Maybe.h"
 #include "Source.h"
-#include "StringPiece.h"
+#include "util/StringPiece.h"
 
 #include <androidfw/ResourceTypes.h>
 
diff --git a/tools/aapt2/MockResolver.h b/tools/aapt2/MockResolver.h
deleted file mode 100644
index 0c9b954..0000000
--- a/tools/aapt2/MockResolver.h
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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_MOCK_RESOLVER_H
-#define AAPT_MOCK_RESOLVER_H
-
-#include "Maybe.h"
-#include "Resolver.h"
-#include "Resource.h"
-#include "ResourceTable.h"
-#include "ResourceTableResolver.h"
-#include "ResourceValues.h"
-#include "StringPiece.h"
-
-#include <map>
-#include <string>
-
-namespace aapt {
-
-struct MockResolver : public IResolver {
-    MockResolver(const std::shared_ptr<ResourceTable>& table,
-                 const std::map<ResourceName, ResourceId>& items) :
-            mResolver(std::make_shared<ResourceTableResolver>(
-                    table, std::vector<std::shared_ptr<const android::AssetManager>>())),
-            mAttr(false, android::ResTable_map::TYPE_ANY), mItems(items) {
-    }
-
-    virtual Maybe<ResourceId> findId(const ResourceName& name) override {
-        Maybe<ResourceId> result = mResolver->findId(name);
-        if (result) {
-            return result;
-        }
-
-        const auto iter = mItems.find(name);
-        if (iter != mItems.end()) {
-            return iter->second;
-        }
-        return {};
-    }
-
-    virtual Maybe<Entry> findAttribute(const ResourceName& name) override {
-        Maybe<Entry> tableResult = mResolver->findAttribute(name);
-        if (tableResult) {
-            return tableResult;
-        }
-
-        Maybe<ResourceId> result = findId(name);
-        if (result) {
-            if (name.type == ResourceType::kAttr) {
-                return Entry{ result.value(), &mAttr };
-            } else {
-                return Entry{ result.value() };
-            }
-        }
-        return {};
-    }
-
-    virtual Maybe<ResourceName> findName(ResourceId resId) override {
-        Maybe<ResourceName> result = mResolver->findName(resId);
-        if (result) {
-            return result;
-        }
-
-        for (auto& p : mItems) {
-            if (p.second == resId) {
-                return p.first;
-            }
-        }
-        return {};
-    }
-
-private:
-    std::shared_ptr<ResourceTableResolver> mResolver;
-    Attribute mAttr;
-    std::map<ResourceName, ResourceId> mItems;
-};
-
-} // namespace aapt
-
-#endif // AAPT_MOCK_RESOLVER_H
diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h
index 1e15e20..6d752bb 100644
--- a/tools/aapt2/NameMangler.h
+++ b/tools/aapt2/NameMangler.h
@@ -17,19 +17,63 @@
 #ifndef AAPT_NAME_MANGLER_H
 #define AAPT_NAME_MANGLER_H
 
+#include "Resource.h"
+
+#include "util/Maybe.h"
+
+#include <set>
 #include <string>
 
 namespace aapt {
 
-struct NameMangler {
+struct NameManglerPolicy {
     /**
-     * Mangles the name in `outName` with the `package` and stores the mangled
-     * result in `outName`. The mangled name should contain symbols that are
-     * illegal to define in XML, so that there will never be name mangling
-     * collisions.
+     * Represents the package we are trying to build. References pointing
+     * to this package are not mangled, and mangled references inherit this package name.
      */
-    static void mangle(const std::u16string& package, std::u16string* outName) {
-        *outName = package + u"$" + *outName;
+    std::u16string targetPackageName;
+
+    /**
+     * We must know which references to mangle, and which to keep (android vs. com.android.support).
+     */
+    std::set<std::u16string> packagesToMangle;
+};
+
+class NameMangler {
+private:
+    NameManglerPolicy mPolicy;
+
+public:
+    NameMangler(NameManglerPolicy policy) : mPolicy(policy) {
+    }
+
+    Maybe<ResourceName> mangleName(const ResourceName& name) {
+        if (mPolicy.targetPackageName == name.package ||
+                mPolicy.packagesToMangle.count(name.package) == 0) {
+            return {};
+        }
+
+        return ResourceName{
+                mPolicy.targetPackageName,
+                name.type,
+                mangleEntry(name.package, name.entry)
+        };
+    }
+
+    bool shouldMangle(const std::u16string& package) {
+        if (package.empty() || mPolicy.targetPackageName == package) {
+            return false;
+        }
+        return mPolicy.packagesToMangle.count(package) != 0;
+    }
+
+    /**
+     * Returns a mangled name that is a combination of `name` and `package`.
+     * The mangled name should contain symbols that are illegal to define in XML,
+     * so that there will never be name mangling collisions.
+     */
+    static std::u16string mangleEntry(const std::u16string& package, const std::u16string& name) {
+        return package + u"$" + name;
     }
 
     /**
diff --git a/tools/aapt2/ProguardRules.cpp b/tools/aapt2/ProguardRules.cpp
index e89fb7c..7f4dc91 100644
--- a/tools/aapt2/ProguardRules.cpp
+++ b/tools/aapt2/ProguardRules.cpp
@@ -15,9 +15,10 @@
  */
 
 #include "ProguardRules.h"
-#include "Util.h"
 #include "XmlDom.h"
 
+#include "util/Util.h"
+
 #include <memory>
 #include <string>
 
@@ -61,11 +62,11 @@
 
 protected:
     void addClass(size_t lineNumber, const std::u16string& className) {
-        mKeepSet->addClass(mSource.line(lineNumber), className);
+        mKeepSet->addClass(Source(mSource.path, lineNumber), className);
     }
 
     void addMethod(size_t lineNumber, const std::u16string& methodName) {
-        mKeepSet->addMethod(mSource.line(lineNumber), methodName);
+        mKeepSet->addMethod(Source(mSource.path, lineNumber), methodName);
     }
 
 private:
@@ -186,30 +187,36 @@
     std::u16string mPackage;
 };
 
-bool collectProguardRulesForManifest(const Source& source, xml::Node* node, KeepSet* keepSet) {
+bool collectProguardRulesForManifest(const Source& source, XmlResource* res, KeepSet* keepSet) {
     ManifestVisitor visitor(source, keepSet);
-    node->accept(&visitor);
-    return true;
+    if (res->root) {
+        res->root->accept(&visitor);
+        return true;
+    }
+    return false;
 }
 
-bool collectProguardRules(ResourceType type, const Source& source, xml::Node* node,
-                          KeepSet* keepSet) {
-    switch (type) {
+bool collectProguardRules(const Source& source, XmlResource* res, KeepSet* keepSet) {
+    if (!res->root) {
+        return false;
+    }
+
+    switch (res->file.name.type) {
         case ResourceType::kLayout: {
             LayoutVisitor visitor(source, keepSet);
-            node->accept(&visitor);
+            res->root->accept(&visitor);
             break;
         }
 
         case ResourceType::kXml: {
             XmlResourceVisitor visitor(source, keepSet);
-            node->accept(&visitor);
+            res->root->accept(&visitor);
             break;
         }
 
         case ResourceType::kTransition: {
             TransitionVisitor visitor(source, keepSet);
-            node->accept(&visitor);
+            res->root->accept(&visitor);
             break;
         }
 
@@ -221,14 +228,14 @@
 
 bool writeKeepSet(std::ostream* out, const KeepSet& keepSet) {
     for (const auto& entry : keepSet.mKeepSet) {
-        for (const SourceLine& source : entry.second) {
+        for (const Source& source : entry.second) {
             *out << "// Referenced at " << source << "\n";
         }
         *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl;
     }
 
     for (const auto& entry : keepSet.mKeepMethodSet) {
-        for (const SourceLine& source : entry.second) {
+        for (const Source& source : entry.second) {
             *out << "// Referenced at " << source << "\n";
         }
         *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" << std::endl;
diff --git a/tools/aapt2/ProguardRules.h b/tools/aapt2/ProguardRules.h
index bbb3e64..be61eb9 100644
--- a/tools/aapt2/ProguardRules.h
+++ b/tools/aapt2/ProguardRules.h
@@ -19,7 +19,8 @@
 
 #include "Resource.h"
 #include "Source.h"
-#include "XmlDom.h"
+
+#include "process/IResourceTableConsumer.h"
 
 #include <map>
 #include <ostream>
@@ -31,24 +32,23 @@
 
 class KeepSet {
 public:
-    inline void addClass(const SourceLine& source, const std::u16string& className) {
+    inline void addClass(const Source& source, const std::u16string& className) {
         mKeepSet[className].insert(source);
     }
 
-    inline void addMethod(const SourceLine& source, const std::u16string& methodName) {
+    inline void addMethod(const Source& source, const std::u16string& methodName) {
         mKeepMethodSet[methodName].insert(source);
     }
 
 private:
     friend bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
 
-    std::map<std::u16string, std::set<SourceLine>> mKeepSet;
-    std::map<std::u16string, std::set<SourceLine>> mKeepMethodSet;
+    std::map<std::u16string, std::set<Source>> mKeepSet;
+    std::map<std::u16string, std::set<Source>> mKeepMethodSet;
 };
 
-bool collectProguardRulesForManifest(const Source& source, xml::Node* node, KeepSet* keepSet);
-bool collectProguardRules(ResourceType type, const Source& source, xml::Node* node,
-                          KeepSet* keepSet);
+bool collectProguardRulesForManifest(const Source& source, XmlResource* res, KeepSet* keepSet);
+bool collectProguardRules(const Source& source, XmlResource* res, KeepSet* keepSet);
 
 bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
 
diff --git a/tools/aapt2/Resolver.h b/tools/aapt2/Resolver.h
deleted file mode 100644
index cb9318e..0000000
--- a/tools/aapt2/Resolver.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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_RESOLVER_H
-#define AAPT_RESOLVER_H
-
-#include "Maybe.h"
-#include "Resource.h"
-#include "ResourceValues.h"
-
-#include <androidfw/ResourceTypes.h>
-
-namespace aapt {
-
-/**
- * Resolves symbolic references (package:type/entry) into resource IDs/objects.
- */
-class IResolver {
-public:
-    virtual ~IResolver() {};
-
-    /**
-     * Holds the result of a resource name lookup.
-     */
-    struct Entry {
-        /**
-         * The ID of the resource. ResourceId::isValid() may
-         * return false if the resource has not been assigned
-         * an ID.
-         */
-        ResourceId id;
-
-        /**
-         * If the resource is an attribute, this will point
-         * to a valid Attribute object, or else it will be
-         * nullptr.
-         */
-        const Attribute* attr;
-    };
-
-    /**
-     * Returns a ResourceID if the name is found. The ResourceID
-     * may not be valid if the resource was not assigned an ID.
-     */
-    virtual Maybe<ResourceId> findId(const ResourceName& name) = 0;
-
-    /**
-     * Returns an Entry if the name is found. Entry::attr
-     * may be nullptr if the resource is not an attribute.
-     */
-    virtual Maybe<Entry> findAttribute(const ResourceName& name) = 0;
-
-    /**
-     * Find a resource by ID. Resolvers may contain resources without
-     * resource IDs assigned to them.
-     */
-    virtual Maybe<ResourceName> findName(ResourceId resId) = 0;
-};
-
-} // namespace aapt
-
-#endif // AAPT_RESOLVER_H
diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp
index 287d8de..1962f58 100644
--- a/tools/aapt2/Resource.cpp
+++ b/tools/aapt2/Resource.cpp
@@ -15,7 +15,7 @@
  */
 
 #include "Resource.h"
-#include "StringPiece.h"
+#include "util/StringPiece.h"
 
 #include <map>
 #include <string>
@@ -28,7 +28,7 @@
         case ResourceType::kAnimator:      return u"animator";
         case ResourceType::kArray:         return u"array";
         case ResourceType::kAttr:          return u"attr";
-        case ResourceType::kAttrPrivate:   return u"attr";
+        case ResourceType::kAttrPrivate:   return u"^attr-private";
         case ResourceType::kBool:          return u"bool";
         case ResourceType::kColor:         return u"color";
         case ResourceType::kDimen:         return u"dimen";
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index fa9ac07..31fe298 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -17,12 +17,16 @@
 #ifndef AAPT_RESOURCE_H
 #define AAPT_RESOURCE_H
 
-#include "StringPiece.h"
+#include "ConfigDescription.h"
+#include "Source.h"
+
+#include "util/StringPiece.h"
 
 #include <iomanip>
 #include <limits>
 #include <string>
 #include <tuple>
+#include <vector>
 
 namespace aapt {
 
@@ -78,6 +82,7 @@
     bool operator<(const ResourceName& rhs) const;
     bool operator==(const ResourceName& rhs) const;
     bool operator!=(const ResourceName& rhs) const;
+    std::u16string toString() const;
 };
 
 /**
@@ -125,7 +130,7 @@
     ResourceId();
     ResourceId(const ResourceId& rhs);
     ResourceId(uint32_t resId);
-    ResourceId(size_t p, size_t t, size_t e);
+    ResourceId(uint8_t p, uint8_t t, uint16_t e);
 
     bool isValid() const;
     uint8_t packageId() const;
@@ -135,6 +140,29 @@
     bool operator==(const ResourceId& rhs) const;
 };
 
+struct SourcedResourceName {
+    ResourceName name;
+    size_t line;
+
+    inline bool operator==(const SourcedResourceName& rhs) const {
+        return name == rhs.name && line == rhs.line;
+    }
+};
+
+struct ResourceFile {
+    // Name
+    ResourceName name;
+
+    // Configuration
+    ConfigDescription config;
+
+    // Source
+    Source source;
+
+    // Exported symbols
+    std::vector<SourcedResourceName> exportedSymbols;
+};
+
 //
 // ResourceId implementation.
 //
@@ -148,17 +176,7 @@
 inline ResourceId::ResourceId(uint32_t resId) : id(resId) {
 }
 
-inline ResourceId::ResourceId(size_t p, size_t t, size_t e) : id(0) {
-    if (p > std::numeric_limits<uint8_t>::max() ||
-            t > std::numeric_limits<uint8_t>::max() ||
-            e > std::numeric_limits<uint16_t>::max()) {
-        // This will leave the ResourceId in an invalid state.
-        return;
-    }
-
-    id = (static_cast<uint8_t>(p) << 24) |
-         (static_cast<uint8_t>(t) << 16) |
-         static_cast<uint16_t>(e);
+inline ResourceId::ResourceId(uint8_t p, uint8_t t, uint16_t e) : id((p << 24) | (t << 16) | e) {
 }
 
 inline bool ResourceId::isValid() const {
@@ -217,6 +235,10 @@
             < std::tie(rhs.package, rhs.type, rhs.entry);
 }
 
+inline bool operator<(const ResourceName& lhs, const ResourceNameRef& b) {
+    return ResourceNameRef(lhs) < b;
+}
+
 inline bool ResourceName::operator==(const ResourceName& rhs) const {
     return std::tie(package, type, entry)
             == std::tie(rhs.package, rhs.type, rhs.entry);
@@ -227,6 +249,18 @@
             != std::tie(rhs.package, rhs.type, rhs.entry);
 }
 
+inline bool operator!=(const ResourceName& lhs, const ResourceNameRef& rhs) {
+    return ResourceNameRef(lhs) != rhs;
+}
+
+inline std::u16string ResourceName::toString() const {
+    std::u16string result;
+    if (!package.empty()) {
+        result = package + u":";
+    }
+    return result + aapt::toString(type).toString() + u"/" + entry;
+}
+
 inline ::std::ostream& operator<<(::std::ostream& out, const ResourceName& name) {
     if (!name.package.empty()) {
         out << name.package << ":";
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 13f916b..5e5fc53 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -14,473 +14,43 @@
  * limitations under the License.
  */
 
-#include "Logger.h"
 #include "ResourceParser.h"
+#include "ResourceTable.h"
+#include "ResourceUtils.h"
 #include "ResourceValues.h"
-#include "ScopedXmlPullParser.h"
-#include "SourceXmlPullParser.h"
-#include "Util.h"
-#include "XliffXmlPullParser.h"
+#include "util/Util.h"
+#include "ValueVisitor.h"
+#include "XmlPullParser.h"
 
 #include <sstream>
 
 namespace aapt {
 
-void ResourceParser::extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
-                                         StringPiece16* outType, StringPiece16* outEntry) {
-    const char16_t* start = str.data();
-    const char16_t* end = start + str.size();
-    const char16_t* current = start;
-    while (current != end) {
-        if (outType->size() == 0 && *current == u'/') {
-            outType->assign(start, current - start);
-            start = current + 1;
-        } else if (outPackage->size() == 0 && *current == u':') {
-            outPackage->assign(start, current - start);
-            start = current + 1;
-        }
-        current++;
-    }
-    outEntry->assign(start, end - start);
-}
+constexpr const char16_t* sXliffNamespaceUri = u"urn:oasis:names:tc:xliff:document:1.2";
 
-bool ResourceParser::tryParseReference(const StringPiece16& str, ResourceNameRef* outRef,
-                                       bool* outCreate, bool* outPrivate) {
-    StringPiece16 trimmedStr(util::trimWhitespace(str));
-    if (trimmedStr.empty()) {
-        return false;
-    }
-
-    if (trimmedStr.data()[0] == u'@') {
-        size_t offset = 1;
-        *outCreate = false;
-        if (trimmedStr.data()[1] == u'+') {
-            *outCreate = true;
-            offset += 1;
-        } else if (trimmedStr.data()[1] == u'*') {
-            *outPrivate = true;
-            offset += 1;
-        }
-        StringPiece16 package;
-        StringPiece16 type;
-        StringPiece16 entry;
-        extractResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset),
-                            &package, &type, &entry);
-
-        const ResourceType* parsedType = parseResourceType(type);
-        if (!parsedType) {
-            return false;
-        }
-
-        if (*outCreate && *parsedType != ResourceType::kId) {
-            return false;
-        }
-
-        outRef->package = package;
-        outRef->type = *parsedType;
-        outRef->entry = entry;
-        return true;
-    }
-    return false;
-}
-
-bool ResourceParser::tryParseAttributeReference(const StringPiece16& str,
-                                                ResourceNameRef* outRef) {
-    StringPiece16 trimmedStr(util::trimWhitespace(str));
-    if (trimmedStr.empty()) {
-        return false;
-    }
-
-    if (*trimmedStr.data() == u'?') {
-        StringPiece16 package;
-        StringPiece16 type;
-        StringPiece16 entry;
-        extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1), &package, &type, &entry);
-
-        if (!type.empty() && type != u"attr") {
-            return false;
-        }
-
-        outRef->package = package;
-        outRef->type = ResourceType::kAttr;
-        outRef->entry = entry;
-        return true;
-    }
-    return false;
-}
-
-/*
- * Style parent's are a bit different. We accept the following formats:
- *
- * @[package:]style/<entry>
- * ?[package:]style/<entry>
- * <package>:[style/]<entry>
- * [package:style/]<entry>
- */
-bool ResourceParser::parseStyleParentReference(const StringPiece16& str, Reference* outReference,
-                                               std::string* outError) {
-    if (str.empty()) {
-        return true;
-    }
-
-    StringPiece16 name = str;
-
-    bool hasLeadingIdentifiers = false;
-    bool privateRef = false;
-
-    // Skip over these identifiers. A style's parent is a normal reference.
-    if (name.data()[0] == u'@' || name.data()[0] == u'?') {
-        hasLeadingIdentifiers = true;
-        name = name.substr(1, name.size() - 1);
-        if (name.data()[0] == u'*') {
-            privateRef = true;
-            name = name.substr(1, name.size() - 1);
-        }
-    }
-
-    ResourceNameRef ref;
-    ref.type = ResourceType::kStyle;
-
-    StringPiece16 typeStr;
-    extractResourceName(name, &ref.package, &typeStr, &ref.entry);
-    if (!typeStr.empty()) {
-        // If we have a type, make sure it is a Style.
-        const ResourceType* parsedType = parseResourceType(typeStr);
-        if (!parsedType || *parsedType != ResourceType::kStyle) {
-            std::stringstream err;
-            err << "invalid resource type '" << typeStr << "' for parent of style";
-            *outError = err.str();
-            return false;
-        }
-    } else {
-        // No type was defined, this should not have a leading identifier.
-        if (hasLeadingIdentifiers) {
-            std::stringstream err;
-            err << "invalid parent reference '" << str << "'";
-            *outError = err.str();
-            return false;
-        }
-    }
-
-    if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) {
-        std::stringstream err;
-        err << "invalid parent reference '" << str << "'";
-        *outError = err.str();
-        return false;
-    }
-
-    outReference->name = ref.toResourceName();
-    outReference->privateReference = privateRef;
-    return true;
-}
-
-std::unique_ptr<Reference> ResourceParser::tryParseReference(const StringPiece16& str,
-                                                             bool* outCreate) {
-    ResourceNameRef ref;
-    bool privateRef = false;
-    if (tryParseReference(str, &ref, outCreate, &privateRef)) {
-        std::unique_ptr<Reference> value = util::make_unique<Reference>(ref);
-        value->privateReference = privateRef;
-        return value;
-    }
-
-    if (tryParseAttributeReference(str, &ref)) {
-        *outCreate = false;
-        return util::make_unique<Reference>(ref, Reference::Type::kAttribute);
+static Maybe<StringPiece16> findAttribute(XmlPullParser* parser, const StringPiece16& name) {
+    auto iter = parser->findAttribute(u"", name);
+    if (iter != parser->endAttributes()) {
+        return StringPiece16(util::trimWhitespace(iter->value));
     }
     return {};
 }
 
-std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseNullOrEmpty(const StringPiece16& str) {
-    StringPiece16 trimmedStr(util::trimWhitespace(str));
-    android::Res_value value = {};
-    if (trimmedStr == u"@null") {
-        // TYPE_NULL with data set to 0 is interpreted by the runtime as an error.
-        // Instead we set the data type to TYPE_REFERENCE with a value of 0.
-        value.dataType = android::Res_value::TYPE_REFERENCE;
-    } else if (trimmedStr == u"@empty") {
-        // TYPE_NULL with value of DATA_NULL_EMPTY is handled fine by the runtime.
-        value.dataType = android::Res_value::TYPE_NULL;
-        value.data = android::Res_value::DATA_NULL_EMPTY;
-    } else {
-        return {};
-    }
-    return util::make_unique<BinaryPrimitive>(value);
-}
-
-std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseEnumSymbol(const Attribute& enumAttr,
-                                                                    const StringPiece16& str) {
-    StringPiece16 trimmedStr(util::trimWhitespace(str));
-    for (const auto& entry : enumAttr.symbols) {
-        // Enum symbols are stored as @package:id/symbol resources,
-        // so we need to match against the 'entry' part of the identifier.
-        const ResourceName& enumSymbolResourceName = entry.symbol.name;
-        if (trimmedStr == enumSymbolResourceName.entry) {
-            android::Res_value value = {};
-            value.dataType = android::Res_value::TYPE_INT_DEC;
-            value.data = entry.value;
-            return util::make_unique<BinaryPrimitive>(value);
+static Maybe<StringPiece16> findNonEmptyAttribute(XmlPullParser* parser,
+                                                  const StringPiece16& name) {
+    auto iter = parser->findAttribute(u"", name);
+    if (iter != parser->endAttributes()) {
+        StringPiece16 trimmed = util::trimWhitespace(iter->value);
+        if (!trimmed.empty()) {
+            return trimmed;
         }
     }
     return {};
 }
 
-std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseFlagSymbol(const Attribute& flagAttr,
-                                                                    const StringPiece16& str) {
-    android::Res_value flags = {};
-    flags.dataType = android::Res_value::TYPE_INT_DEC;
-
-    for (StringPiece16 part : util::tokenize(str, u'|')) {
-        StringPiece16 trimmedPart = util::trimWhitespace(part);
-
-        bool flagSet = false;
-        for (const auto& entry : flagAttr.symbols) {
-            // Flag symbols are stored as @package:id/symbol resources,
-            // so we need to match against the 'entry' part of the identifier.
-            const ResourceName& flagSymbolResourceName = entry.symbol.name;
-            if (trimmedPart == flagSymbolResourceName.entry) {
-                flags.data |= entry.value;
-                flagSet = true;
-                break;
-            }
-        }
-
-        if (!flagSet) {
-            return {};
-        }
-    }
-    return util::make_unique<BinaryPrimitive>(flags);
-}
-
-static uint32_t parseHex(char16_t c, bool* outError) {
-   if (c >= u'0' && c <= u'9') {
-        return c - u'0';
-    } else if (c >= u'a' && c <= u'f') {
-        return c - u'a' + 0xa;
-    } else if (c >= u'A' && c <= u'F') {
-        return c - u'A' + 0xa;
-    } else {
-        *outError = true;
-        return 0xffffffffu;
-    }
-}
-
-std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseColor(const StringPiece16& str) {
-    StringPiece16 colorStr(util::trimWhitespace(str));
-    const char16_t* start = colorStr.data();
-    const size_t len = colorStr.size();
-    if (len == 0 || start[0] != u'#') {
-        return {};
-    }
-
-    android::Res_value value = {};
-    bool error = false;
-    if (len == 4) {
-        value.dataType = android::Res_value::TYPE_INT_COLOR_RGB4;
-        value.data = 0xff000000u;
-        value.data |= parseHex(start[1], &error) << 20;
-        value.data |= parseHex(start[1], &error) << 16;
-        value.data |= parseHex(start[2], &error) << 12;
-        value.data |= parseHex(start[2], &error) << 8;
-        value.data |= parseHex(start[3], &error) << 4;
-        value.data |= parseHex(start[3], &error);
-    } else if (len == 5) {
-        value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4;
-        value.data |= parseHex(start[1], &error) << 28;
-        value.data |= parseHex(start[1], &error) << 24;
-        value.data |= parseHex(start[2], &error) << 20;
-        value.data |= parseHex(start[2], &error) << 16;
-        value.data |= parseHex(start[3], &error) << 12;
-        value.data |= parseHex(start[3], &error) << 8;
-        value.data |= parseHex(start[4], &error) << 4;
-        value.data |= parseHex(start[4], &error);
-    } else if (len == 7) {
-        value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8;
-        value.data = 0xff000000u;
-        value.data |= parseHex(start[1], &error) << 20;
-        value.data |= parseHex(start[2], &error) << 16;
-        value.data |= parseHex(start[3], &error) << 12;
-        value.data |= parseHex(start[4], &error) << 8;
-        value.data |= parseHex(start[5], &error) << 4;
-        value.data |= parseHex(start[6], &error);
-    } else if (len == 9) {
-        value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8;
-        value.data |= parseHex(start[1], &error) << 28;
-        value.data |= parseHex(start[2], &error) << 24;
-        value.data |= parseHex(start[3], &error) << 20;
-        value.data |= parseHex(start[4], &error) << 16;
-        value.data |= parseHex(start[5], &error) << 12;
-        value.data |= parseHex(start[6], &error) << 8;
-        value.data |= parseHex(start[7], &error) << 4;
-        value.data |= parseHex(start[8], &error);
-    } else {
-        return {};
-    }
-    return error ? std::unique_ptr<BinaryPrimitive>() : util::make_unique<BinaryPrimitive>(value);
-}
-
-std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseBool(const StringPiece16& str) {
-    StringPiece16 trimmedStr(util::trimWhitespace(str));
-    uint32_t data = 0;
-    if (trimmedStr == u"true" || trimmedStr == u"TRUE") {
-        data = 0xffffffffu;
-    } else if (trimmedStr != u"false" && trimmedStr != u"FALSE") {
-        return {};
-    }
-    android::Res_value value = {};
-    value.dataType = android::Res_value::TYPE_INT_BOOLEAN;
-    value.data = data;
-    return util::make_unique<BinaryPrimitive>(value);
-}
-
-std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseInt(const StringPiece16& str) {
-    android::Res_value value;
-    if (!android::ResTable::stringToInt(str.data(), str.size(), &value)) {
-        return {};
-    }
-    return util::make_unique<BinaryPrimitive>(value);
-}
-
-std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseFloat(const StringPiece16& str) {
-    android::Res_value value;
-    if (!android::ResTable::stringToFloat(str.data(), str.size(), &value)) {
-        return {};
-    }
-    return util::make_unique<BinaryPrimitive>(value);
-}
-
-uint32_t ResourceParser::androidTypeToAttributeTypeMask(uint16_t type) {
-    switch (type) {
-        case android::Res_value::TYPE_NULL:
-        case android::Res_value::TYPE_REFERENCE:
-        case android::Res_value::TYPE_ATTRIBUTE:
-        case android::Res_value::TYPE_DYNAMIC_REFERENCE:
-            return android::ResTable_map::TYPE_REFERENCE;
-
-        case android::Res_value::TYPE_STRING:
-            return android::ResTable_map::TYPE_STRING;
-
-        case android::Res_value::TYPE_FLOAT:
-            return android::ResTable_map::TYPE_FLOAT;
-
-        case android::Res_value::TYPE_DIMENSION:
-            return android::ResTable_map::TYPE_DIMENSION;
-
-        case android::Res_value::TYPE_FRACTION:
-            return android::ResTable_map::TYPE_FRACTION;
-
-        case android::Res_value::TYPE_INT_DEC:
-        case android::Res_value::TYPE_INT_HEX:
-            return android::ResTable_map::TYPE_INTEGER |
-                    android::ResTable_map::TYPE_ENUM |
-                    android::ResTable_map::TYPE_FLAGS;
-
-        case android::Res_value::TYPE_INT_BOOLEAN:
-            return android::ResTable_map::TYPE_BOOLEAN;
-
-        case android::Res_value::TYPE_INT_COLOR_ARGB8:
-        case android::Res_value::TYPE_INT_COLOR_RGB8:
-        case android::Res_value::TYPE_INT_COLOR_ARGB4:
-        case android::Res_value::TYPE_INT_COLOR_RGB4:
-            return android::ResTable_map::TYPE_COLOR;
-
-        default:
-            return 0;
-    };
-}
-
-std::unique_ptr<Item> ResourceParser::parseItemForAttribute(
-        const StringPiece16& value, uint32_t typeMask,
-        std::function<void(const ResourceName&)> onCreateReference) {
-    std::unique_ptr<BinaryPrimitive> nullOrEmpty = tryParseNullOrEmpty(value);
-    if (nullOrEmpty) {
-        return std::move(nullOrEmpty);
-    }
-
-    bool create = false;
-    std::unique_ptr<Reference> reference = tryParseReference(value, &create);
-    if (reference) {
-        if (create && onCreateReference) {
-            onCreateReference(reference->name);
-        }
-        return std::move(reference);
-    }
-
-    if (typeMask & android::ResTable_map::TYPE_COLOR) {
-        // Try parsing this as a color.
-        std::unique_ptr<BinaryPrimitive> color = tryParseColor(value);
-        if (color) {
-            return std::move(color);
-        }
-    }
-
-    if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
-        // Try parsing this as a boolean.
-        std::unique_ptr<BinaryPrimitive> boolean = tryParseBool(value);
-        if (boolean) {
-            return std::move(boolean);
-        }
-    }
-
-    if (typeMask & android::ResTable_map::TYPE_INTEGER) {
-        // Try parsing this as an integer.
-        std::unique_ptr<BinaryPrimitive> integer = tryParseInt(value);
-        if (integer) {
-            return std::move(integer);
-        }
-    }
-
-    const uint32_t floatMask = android::ResTable_map::TYPE_FLOAT |
-            android::ResTable_map::TYPE_DIMENSION |
-            android::ResTable_map::TYPE_FRACTION;
-    if (typeMask & floatMask) {
-        // Try parsing this as a float.
-        std::unique_ptr<BinaryPrimitive> floatingPoint = tryParseFloat(value);
-        if (floatingPoint) {
-            if (typeMask & androidTypeToAttributeTypeMask(floatingPoint->value.dataType)) {
-                return std::move(floatingPoint);
-            }
-        }
-    }
-    return {};
-}
-
-/**
- * We successively try to parse the string as a resource type that the Attribute
- * allows.
- */
-std::unique_ptr<Item> ResourceParser::parseItemForAttribute(
-        const StringPiece16& str, const Attribute& attr,
-        std::function<void(const ResourceName&)> onCreateReference) {
-    const uint32_t typeMask = attr.typeMask;
-    std::unique_ptr<Item> value = parseItemForAttribute(str, typeMask, onCreateReference);
-    if (value) {
-        return value;
-    }
-
-    if (typeMask & android::ResTable_map::TYPE_ENUM) {
-        // Try parsing this as an enum.
-        std::unique_ptr<BinaryPrimitive> enumValue = tryParseEnumSymbol(attr, str);
-        if (enumValue) {
-            return std::move(enumValue);
-        }
-    }
-
-    if (typeMask & android::ResTable_map::TYPE_FLAGS) {
-        // Try parsing this as a flag.
-        std::unique_ptr<BinaryPrimitive> flagValue = tryParseFlagSymbol(attr, str);
-        if (flagValue) {
-            return std::move(flagValue);
-        }
-    }
-    return {};
-}
-
-ResourceParser::ResourceParser(const std::shared_ptr<ResourceTable>& table, const Source& source,
-                               const ConfigDescription& config,
-                               const std::shared_ptr<XmlPullParser>& parser) :
-        mTable(table), mSource(source), mConfig(config), mLogger(source),
-        mParser(std::make_shared<XliffXmlPullParser>(parser)) {
+ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source,
+                               const ConfigDescription& config) :
+        mDiag(diag), mTable(table), mSource(source), mConfig(config) {
 }
 
 /**
@@ -497,6 +67,11 @@
     while (XmlPullParser::isGoodEvent(parser->next())) {
         const XmlPullParser::Event event = parser->getEvent();
         if (event == XmlPullParser::Event::kEndElement) {
+            if (!parser->getElementNamespace().empty()) {
+                // We already warned and skipped the start element, so just skip here too
+                continue;
+            }
+
             depth--;
             if (depth == 0) {
                 break;
@@ -512,15 +87,16 @@
             builder.append(parser->getText());
 
         } else if (event == XmlPullParser::Event::kStartElement) {
-            if (parser->getElementNamespace().size() > 0) {
-                mLogger.warn(parser->getLineNumber())
-                        << "skipping element '"
-                        << parser->getElementName()
-                        << "' with unknown namespace '"
-                        << parser->getElementNamespace()
-                        << "'."
-                        << std::endl;
-                XmlPullParser::skipCurrentElement(parser);
+            if (!parser->getElementNamespace().empty()) {
+                if (parser->getElementNamespace() != sXliffNamespaceUri) {
+                    // Only warn if this isn't an xliff namespace.
+                    mDiag->warn(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                                << "skipping element '"
+                                << parser->getElementName()
+                                << "' with unknown namespace '"
+                                << parser->getElementNamespace()
+                                << "'");
+                }
                 continue;
             }
             depth++;
@@ -536,11 +112,8 @@
             }
 
             if (builder.str().size() > std::numeric_limits<uint32_t>::max()) {
-                mLogger.error(parser->getLineNumber())
-                        << "style string '"
-                        << builder.str()
-                        << "' is too long."
-                        << std::endl;
+                mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                             << "style string '" << builder.str() << "' is too long");
                 return false;
             }
             spanStack.push_back(Span{ spanName, static_cast<uint32_t>(builder.str().size()) });
@@ -548,11 +121,7 @@
         } else if (event == XmlPullParser::Event::kComment) {
             // Skip
         } else {
-            mLogger.warn(parser->getLineNumber())
-                    << "unknown event "
-                    << event
-                    << "."
-                    << std::endl;
+            assert(false);
         }
     }
     assert(spanStack.empty() && "spans haven't been fully processed");
@@ -561,40 +130,38 @@
     return true;
 }
 
-bool ResourceParser::parse() {
-    while (XmlPullParser::isGoodEvent(mParser->next())) {
-        if (mParser->getEvent() != XmlPullParser::Event::kStartElement) {
+bool ResourceParser::parse(XmlPullParser* parser) {
+    bool error = false;
+    const size_t depth = parser->getDepth();
+    while (XmlPullParser::nextChildNode(parser, depth)) {
+        if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+            // Skip comments and text.
             continue;
         }
 
-        ScopedXmlPullParser parser(mParser.get());
-        if (!parser.getElementNamespace().empty() ||
-                parser.getElementName() != u"resources") {
-            mLogger.error(parser.getLineNumber())
-                    << "root element must be <resources> in the global namespace."
-                    << std::endl;
+        if (!parser->getElementNamespace().empty() || parser->getElementName() != u"resources") {
+            mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                         << "root element must be <resources>");
             return false;
         }
 
-        if (!parseResources(&parser)) {
-            return false;
-        }
-    }
+        error |= !parseResources(parser);
+        break;
+    };
 
-    if (mParser->getEvent() == XmlPullParser::Event::kBadDocument) {
-        mLogger.error(mParser->getLineNumber())
-                << mParser->getLastError()
-                << std::endl;
+    if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
+        mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                     << "xml parser error: " << parser->getLastError());
         return false;
     }
-    return true;
+    return !error;
 }
 
 bool ResourceParser::parseResources(XmlPullParser* parser) {
-    bool success = true;
-
+    bool error = false;
     std::u16string comment;
-    while (XmlPullParser::isGoodEvent(parser->next())) {
+    const size_t depth = parser->getDepth();
+    while (XmlPullParser::nextChildNode(parser, depth)) {
         const XmlPullParser::Event event = parser->getEvent();
         if (event == XmlPullParser::Event::kComment) {
             comment = parser->getComment();
@@ -603,134 +170,95 @@
 
         if (event == XmlPullParser::Event::kText) {
             if (!util::trimWhitespace(parser->getText()).empty()) {
-                comment = u"";
+                mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                             << "plain text not allowed here");
+                error = true;
             }
             continue;
         }
 
-        if (event != XmlPullParser::Event::kStartElement) {
-            continue;
-        }
+        assert(event == XmlPullParser::Event::kStartElement);
 
-        ScopedXmlPullParser childParser(parser);
-
-        if (!childParser.getElementNamespace().empty()) {
+        if (!parser->getElementNamespace().empty()) {
             // Skip unknown namespace.
             continue;
         }
 
-        StringPiece16 name = childParser.getElementName();
-        if (name == u"skip" || name == u"eat-comment") {
+        std::u16string elementName = parser->getElementName();
+        if (elementName == u"skip" || elementName == u"eat-comment") {
+            comment = u"";
             continue;
         }
 
-        if (name == u"private-symbols") {
-            // Handle differently.
-            mLogger.note(childParser.getLineNumber())
-                    << "got a <private-symbols> tag."
-                    << std::endl;
-            continue;
-        }
-
-        const auto endAttrIter = childParser.endAttributes();
-        auto attrIter = childParser.findAttribute(u"", u"name");
-        if (attrIter == endAttrIter || attrIter->value.empty()) {
-            mLogger.error(childParser.getLineNumber())
-                    << "<" << name << "> tag must have a 'name' attribute."
-                    << std::endl;
-            success = false;
+        Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name");
+        if (!maybeName) {
+            mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                         << "<" << elementName << "> tag must have a 'name' attribute");
+            error = true;
             continue;
         }
 
         // Copy because our iterator will go out of scope when
         // we parse more XML.
-        std::u16string attributeName = attrIter->value;
+        std::u16string name = maybeName.value().toString();
 
-        if (name == u"item") {
+        if (elementName == u"item") {
             // Items simply have their type encoded in the type attribute.
-            auto typeIter = childParser.findAttribute(u"", u"type");
-            if (typeIter == endAttrIter || typeIter->value.empty()) {
-                mLogger.error(childParser.getLineNumber())
-                        << "<item> must have a 'type' attribute."
-                        << std::endl;
-                success = false;
+            if (Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type")) {
+                elementName = maybeType.value().toString();
+            } else {
+                mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                             << "<item> must have a 'type' attribute");
+                error = true;
                 continue;
             }
-            name = typeIter->value;
         }
 
-        if (name == u"id") {
-            success &= mTable->addResource(ResourceNameRef{ {}, ResourceType::kId, attributeName },
-                                           {}, mSource.line(childParser.getLineNumber()),
-                                           util::make_unique<Id>());
-        } else if (name == u"string") {
-            success &= parseString(&childParser,
-                                   ResourceNameRef{ {}, ResourceType::kString, attributeName });
-        } else if (name == u"color") {
-            success &= parseColor(&childParser,
-                                  ResourceNameRef{ {}, ResourceType::kColor, attributeName });
-        } else if (name == u"drawable") {
-            success &= parseColor(&childParser,
-                                  ResourceNameRef{ {}, ResourceType::kDrawable, attributeName });
-        } else if (name == u"bool") {
-            success &= parsePrimitive(&childParser,
-                                      ResourceNameRef{ {}, ResourceType::kBool, attributeName });
-        } else if (name == u"integer") {
-            success &= parsePrimitive(
-                    &childParser,
-                    ResourceNameRef{ {}, ResourceType::kInteger, attributeName });
-        } else if (name == u"dimen") {
-            success &= parsePrimitive(&childParser,
-                                      ResourceNameRef{ {}, ResourceType::kDimen, attributeName });
-        } else if (name == u"fraction") {
-//          success &= parsePrimitive(
-//                  &childParser,
-//                  ResourceNameRef{ {}, ResourceType::kFraction, attributeName });
-        } else if (name == u"style") {
-            success &= parseStyle(&childParser,
-                                  ResourceNameRef{ {}, ResourceType::kStyle, attributeName });
-        } else if (name == u"plurals") {
-            success &= parsePlural(&childParser,
-                                   ResourceNameRef{ {}, ResourceType::kPlurals, attributeName });
-        } else if (name == u"array") {
-            success &= parseArray(&childParser,
-                                  ResourceNameRef{ {}, ResourceType::kArray, attributeName },
-                                  android::ResTable_map::TYPE_ANY);
-        } else if (name == u"string-array") {
-            success &= parseArray(&childParser,
-                                  ResourceNameRef{ {}, ResourceType::kArray, attributeName },
-                                  android::ResTable_map::TYPE_STRING);
-        } else if (name == u"integer-array") {
-            success &= parseArray(&childParser,
-                                  ResourceNameRef{ {}, ResourceType::kArray, attributeName },
-                                  android::ResTable_map::TYPE_INTEGER);
-        } else if (name == u"public") {
-            success &= parsePublic(&childParser, attributeName);
-        } else if (name == u"declare-styleable") {
-            success &= parseDeclareStyleable(
-                    &childParser,
-                    ResourceNameRef{ {}, ResourceType::kStyleable, attributeName });
-        } else if (name == u"attr") {
-            success &= parseAttr(&childParser,
-                                 ResourceNameRef{ {}, ResourceType::kAttr, attributeName });
-        } else if (name == u"bag") {
-        } else if (name == u"public-padding") {
-        } else if (name == u"java-symbol") {
-        } else if (name == u"add-resource") {
-       }
-    }
+        if (elementName == u"id") {
+            error |= !mTable->addResource(ResourceNameRef{ {}, ResourceType::kId, name },
+                                          {}, mSource.withLine(parser->getLineNumber()),
+                                          util::make_unique<Id>(), mDiag);
 
-    if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
-        mLogger.error(parser->getLineNumber())
-                << parser->getLastError()
-                << std::endl;
-        return false;
+        } else if (elementName == u"string") {
+            error |= !parseString(parser, ResourceNameRef{ {}, ResourceType::kString, name });
+        } else if (elementName == u"color") {
+            error |= !parseColor(parser, ResourceNameRef{ {}, ResourceType::kColor, name });
+        } else if (elementName == u"drawable") {
+            error |= !parseColor(parser, ResourceNameRef{ {}, ResourceType::kDrawable, name });
+        } else if (elementName == u"bool") {
+            error |= !parsePrimitive(parser, ResourceNameRef{ {}, ResourceType::kBool, name });
+        } else if (elementName == u"integer") {
+            error |= !parsePrimitive(parser, ResourceNameRef{ {}, ResourceType::kInteger, name });
+        } else if (elementName == u"dimen") {
+            error |= !parsePrimitive(parser, ResourceNameRef{ {}, ResourceType::kDimen, name });
+        } else if (elementName == u"style") {
+            error |= !parseStyle(parser, ResourceNameRef{ {}, ResourceType::kStyle, name });
+        } else if (elementName == u"plurals") {
+            error |= !parsePlural(parser, ResourceNameRef{ {}, ResourceType::kPlurals, name });
+        } else if (elementName == u"array") {
+            error |= !parseArray(parser, ResourceNameRef{ {}, ResourceType::kArray, name },
+                                 android::ResTable_map::TYPE_ANY);
+        } else if (elementName == u"string-array") {
+            error |= !parseArray(parser, ResourceNameRef{ {}, ResourceType::kArray, name },
+                                 android::ResTable_map::TYPE_STRING);
+        } else if (elementName == u"integer-array") {
+            error |= !parseArray(parser, ResourceNameRef{ {}, ResourceType::kArray, name },
+                                 android::ResTable_map::TYPE_INTEGER);
+        } else if (elementName == u"public") {
+            error |= !parsePublic(parser, name);
+        } else if (elementName == u"declare-styleable") {
+            error |= !parseDeclareStyleable(parser,
+                                            ResourceNameRef{ {}, ResourceType::kStyleable, name });
+        } else if (elementName == u"attr") {
+            error |= !parseAttr(parser, ResourceNameRef{ {}, ResourceType::kAttr, name });
+        } else {
+            mDiag->warn(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                        << "unknown resource type '" << elementName << "'");
+        }
     }
-    return success;
+    return !error;
 }
 
-
-
 enum {
     kAllowRawString = true,
     kNoRawString = false
@@ -753,34 +281,29 @@
         return {};
     }
 
-    StringPool& pool = mTable->getValueStringPool();
-
     if (!styleString.spans.empty()) {
         // This can only be a StyledString.
         return util::make_unique<StyledString>(
-                pool.makeRef(styleString, StringPool::Context{ 1, mConfig }));
+                mTable->stringPool.makeRef(styleString, StringPool::Context{ 1, mConfig }));
     }
 
     auto onCreateReference = [&](const ResourceName& name) {
         // name.package can be empty here, as it will assume the package name of the table.
-        mTable->addResource(name, {}, mSource.line(beginXmlLine), util::make_unique<Id>());
+        mTable->addResource(name, {}, mSource.withLine(beginXmlLine), util::make_unique<Id>(),
+                            mDiag);
     };
 
     // Process the raw value.
-    std::unique_ptr<Item> processedItem = parseItemForAttribute(rawValue, typeMask,
-                                                                onCreateReference);
+    std::unique_ptr<Item> processedItem = ResourceUtils::parseItemForAttribute(rawValue, typeMask,
+                                                                               onCreateReference);
     if (processedItem) {
         // Fix up the reference.
-        visitFunc<Reference>(*processedItem, [&](Reference& ref) {
-            if (!ref.name.package.empty()) {
-                // The package name was set, so lookup its alias.
-                parser->applyPackageAlias(&ref.name.package, mTable->getPackage());
-            } else {
-                // The package name was left empty, so it assumes the default package
-                // without alias lookup.
-                ref.name.package = mTable->getPackage();
+        if (Reference* ref = valueCast<Reference>(processedItem.get())) {
+            if (Maybe<ResourceName> transformedName =
+                    parser->transformPackage(ref->name.value(), u"")) {
+                ref->name = std::move(transformedName);
             }
-        });
+        }
         return processedItem;
     }
 
@@ -788,31 +311,25 @@
     if (typeMask & android::ResTable_map::TYPE_STRING) {
         // Use the trimmed, escaped string.
         return util::make_unique<String>(
-                pool.makeRef(styleString.str, StringPool::Context{ 1, mConfig }));
+                mTable->stringPool.makeRef(styleString.str, StringPool::Context{ 1, mConfig }));
     }
 
     // We can't parse this so return a RawString if we are allowed.
     if (allowRawValue) {
         return util::make_unique<RawString>(
-                pool.makeRef(rawValue, StringPool::Context{ 1, mConfig }));
+                mTable->stringPool.makeRef(rawValue, StringPool::Context{ 1, mConfig }));
     }
     return {};
 }
 
 bool ResourceParser::parseString(XmlPullParser* parser, const ResourceNameRef& resourceName) {
-    const SourceLine source = mSource.line(parser->getLineNumber());
+    const Source source = mSource.withLine(parser->getLineNumber());
 
-    // Mark the string as untranslateable if needed.
-    const auto endAttrIter = parser->endAttributes();
-    auto attrIter = parser->findAttribute(u"", u"untranslateable");
-    // bool untranslateable = attrIter != endAttrIter;
-    // TODO(adamlesinski): Do something with this (mark the string).
+    // TODO(adamlesinski): Read "untranslateable" attribute.
 
-    // Deal with the product.
-    attrIter = parser->findAttribute(u"", u"product");
-    if (attrIter != endAttrIter) {
-        if (attrIter->value != u"default" && attrIter->value != u"phone") {
-            // TODO(adamlesinski): Match products.
+    if (Maybe<StringPiece16> maybeProduct = findAttribute(parser, u"product")) {
+        if (maybeProduct.value() != u"default" && maybeProduct.value() != u"phone") {
+            // TODO(adamlesinski): Actually match product.
             return true;
         }
     }
@@ -820,110 +337,95 @@
     std::unique_ptr<Item> processedItem = parseXml(parser, android::ResTable_map::TYPE_STRING,
                                                    kNoRawString);
     if (!processedItem) {
-        mLogger.error(source.line)
-                << "not a valid string."
-                << std::endl;
+        mDiag->error(DiagMessage(source) << "not a valid string");
         return false;
     }
-
-    return mTable->addResource(resourceName, mConfig, source, std::move(processedItem));
+    return mTable->addResource(resourceName, mConfig, source, std::move(processedItem),
+                               mDiag);
 }
 
 bool ResourceParser::parseColor(XmlPullParser* parser, const ResourceNameRef& resourceName) {
-    const SourceLine source = mSource.line(parser->getLineNumber());
+    const Source source = mSource.withLine(parser->getLineNumber());
 
     std::unique_ptr<Item> item = parseXml(parser, android::ResTable_map::TYPE_COLOR, kNoRawString);
     if (!item) {
-        mLogger.error(source.line) << "invalid color." << std::endl;
+        mDiag->error(DiagMessage(source) << "invalid color");
         return false;
     }
-    return mTable->addResource(resourceName, mConfig, source, std::move(item));
+    return mTable->addResource(resourceName, mConfig, source, std::move(item),
+                               mDiag);
 }
 
 bool ResourceParser::parsePrimitive(XmlPullParser* parser, const ResourceNameRef& resourceName) {
-    const SourceLine source = mSource.line(parser->getLineNumber());
+    const Source source = mSource.withLine(parser->getLineNumber());
 
     uint32_t typeMask = 0;
     switch (resourceName.type) {
-        case ResourceType::kInteger:
-            typeMask |= android::ResTable_map::TYPE_INTEGER;
-            break;
+    case ResourceType::kInteger:
+        typeMask |= android::ResTable_map::TYPE_INTEGER;
+        break;
 
-        case ResourceType::kDimen:
-            typeMask |= android::ResTable_map::TYPE_DIMENSION
-                     | android::ResTable_map::TYPE_FLOAT
-                     | android::ResTable_map::TYPE_FRACTION;
-            break;
+    case ResourceType::kDimen:
+        typeMask |= android::ResTable_map::TYPE_DIMENSION
+        | android::ResTable_map::TYPE_FLOAT
+        | android::ResTable_map::TYPE_FRACTION;
+        break;
 
-        case ResourceType::kBool:
-            typeMask |= android::ResTable_map::TYPE_BOOLEAN;
-            break;
+    case ResourceType::kBool:
+        typeMask |= android::ResTable_map::TYPE_BOOLEAN;
+        break;
 
-        default:
-            assert(false);
-            break;
+    default:
+        assert(false);
+        break;
     }
 
     std::unique_ptr<Item> item = parseXml(parser, typeMask, kNoRawString);
     if (!item) {
-        mLogger.error(source.line)
-                << "invalid "
-                << resourceName.type
-                << "."
-                << std::endl;
+        mDiag->error(DiagMessage(source) << "invalid " << resourceName.type);
         return false;
     }
-
-    return mTable->addResource(resourceName, mConfig, source, std::move(item));
+    return mTable->addResource(resourceName, mConfig, source, std::move(item),
+                               mDiag);
 }
 
 bool ResourceParser::parsePublic(XmlPullParser* parser, const StringPiece16& name) {
-    const SourceLine source = mSource.line(parser->getLineNumber());
+    const Source source = mSource.withLine(parser->getLineNumber());
 
-    const auto endAttrIter = parser->endAttributes();
-    const auto typeAttrIter = parser->findAttribute(u"", u"type");
-    if (typeAttrIter == endAttrIter || typeAttrIter->value.empty()) {
-        mLogger.error(source.line)
-                << "<public> must have a 'type' attribute."
-                << std::endl;
+    Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type");
+    if (!maybeType) {
+        mDiag->error(DiagMessage(source) << "<public> must have a 'type' attribute");
         return false;
     }
 
-    const ResourceType* parsedType = parseResourceType(typeAttrIter->value);
+    const ResourceType* parsedType = parseResourceType(maybeType.value());
     if (!parsedType) {
-        mLogger.error(source.line)
-                << "invalid resource type '"
-                << typeAttrIter->value
-                << "' in <public>."
-                << std::endl;
+        mDiag->error(DiagMessage(source) << "invalid resource type '" << maybeType.value()
+                     << "' in <public>");
         return false;
     }
 
     ResourceNameRef resourceName { {}, *parsedType, name };
     ResourceId resourceId;
 
-    const auto idAttrIter = parser->findAttribute(u"", u"id");
-    if (idAttrIter != endAttrIter && !idAttrIter->value.empty()) {
+    if (Maybe<StringPiece16> maybeId = findNonEmptyAttribute(parser, u"id")) {
         android::Res_value val;
-        bool result = android::ResTable::stringToInt(idAttrIter->value.data(),
-                                                     idAttrIter->value.size(), &val);
+        bool result = android::ResTable::stringToInt(maybeId.value().data(),
+                                                     maybeId.value().size(), &val);
         resourceId.id = val.data;
         if (!result || !resourceId.isValid()) {
-            mLogger.error(source.line)
-                    << "invalid resource ID '"
-                    << idAttrIter->value
-                    << "' in <public>."
-                    << std::endl;
+            mDiag->error(DiagMessage(source) << "invalid resource ID '" << maybeId.value()
+                         << "' in <public>");
             return false;
         }
     }
 
     if (*parsedType == ResourceType::kId) {
         // An ID marked as public is also the definition of an ID.
-        mTable->addResource(resourceName, {}, source, util::make_unique<Id>());
+        mTable->addResource(resourceName, {}, source, util::make_unique<Id>(),
+                            mDiag);
     }
-
-    return mTable->markPublic(resourceName, resourceId, source);
+    return mTable->markPublic(resourceName, resourceId, source, mDiag);
 }
 
 static uint32_t parseFormatType(const StringPiece16& piece) {
@@ -954,13 +456,14 @@
 }
 
 bool ResourceParser::parseAttr(XmlPullParser* parser, const ResourceNameRef& resourceName) {
-    const SourceLine source = mSource.line(parser->getLineNumber());
+    const Source source = mSource.withLine(parser->getLineNumber());
     ResourceName actualName = resourceName.toResourceName();
     std::unique_ptr<Attribute> attr = parseAttrImpl(parser, &actualName, false);
     if (!attr) {
         return false;
     }
-    return mTable->addResource(actualName, mConfig, source, std::move(attr));
+    return mTable->addResource(actualName, mConfig, source, std::move(attr),
+                               mDiag);
 }
 
 std::unique_ptr<Attribute> ResourceParser::parseAttrImpl(XmlPullParser* parser,
@@ -968,16 +471,12 @@
                                                          bool weak) {
     uint32_t typeMask = 0;
 
-    const auto endAttrIter = parser->endAttributes();
-    const auto formatAttrIter = parser->findAttribute(u"", u"format");
-    if (formatAttrIter != endAttrIter) {
-        typeMask = parseFormatAttribute(formatAttrIter->value);
+    Maybe<StringPiece16> maybeFormat = findAttribute(parser, u"format");
+    if (maybeFormat) {
+        typeMask = parseFormatAttribute(maybeFormat.value());
         if (typeMask == 0) {
-            mLogger.error(parser->getLineNumber())
-                    << "invalid attribute format '"
-                    << formatAttrIter->value
-                    << "'."
-                    << std::endl;
+            mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                         << "invalid attribute format '" << maybeFormat.value() << "'");
             return {};
         }
     }
@@ -985,9 +484,9 @@
     // If this is a declaration, the package name may be in the name. Separate these out.
     // Eg. <attr name="android:text" />
     // No format attribute is allowed.
-    if (weak && formatAttrIter == endAttrIter) {
+    if (weak && !maybeFormat) {
         StringPiece16 package, type, name;
-        extractResourceName(resourceName->entry, &package, &type, &name);
+        ResourceUtils::extractResourceName(resourceName->entry, &package, &type, &name);
         if (type.empty() && !package.empty()) {
             resourceName->package = package.toString();
             resourceName->entry = name.toString();
@@ -996,56 +495,54 @@
 
     std::vector<Attribute::Symbol> items;
 
+    std::u16string comment;
     bool error = false;
-    while (XmlPullParser::isGoodEvent(parser->next())) {
+    const size_t depth = parser->getDepth();
+    while (XmlPullParser::nextChildNode(parser, depth)) {
         if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+            // Skip comments and text.
             continue;
         }
 
-        ScopedXmlPullParser childParser(parser);
-
-        const std::u16string& name = childParser.getElementName();
-        if (!childParser.getElementNamespace().empty()
-                || (name != u"flag" && name != u"enum")) {
-            mLogger.error(childParser.getLineNumber())
-                    << "unexpected tag <"
-                    << name
-                    << "> in <attr>."
-                    << std::endl;
-            error = true;
-            continue;
-        }
-
-        if (name == u"enum") {
-            if (typeMask & android::ResTable_map::TYPE_FLAGS) {
-                mLogger.error(childParser.getLineNumber())
-                        << "can not define an <enum>; already defined a <flag>."
-                        << std::endl;
-                error = true;
-                continue;
+        const std::u16string& elementNamespace = parser->getElementNamespace();
+        const std::u16string& elementName = parser->getElementName();
+        if (elementNamespace == u"" && (elementName == u"flag" || elementName == u"enum")) {
+            if (elementName == u"enum") {
+                if (typeMask & android::ResTable_map::TYPE_FLAGS) {
+                    mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                                 << "can not define an <enum>; already defined a <flag>");
+                    error = true;
+                    continue;
+                }
+                typeMask |= android::ResTable_map::TYPE_ENUM;
+            } else if (elementName == u"flag") {
+                if (typeMask & android::ResTable_map::TYPE_ENUM) {
+                    mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                                 << "can not define a <flag>; already defined an <enum>");
+                    error = true;
+                    continue;
+                }
+                typeMask |= android::ResTable_map::TYPE_FLAGS;
             }
-            typeMask |= android::ResTable_map::TYPE_ENUM;
-        } else if (name == u"flag") {
-            if (typeMask & android::ResTable_map::TYPE_ENUM) {
-                mLogger.error(childParser.getLineNumber())
-                        << "can not define a <flag>; already defined an <enum>."
-                        << std::endl;
-                error = true;
-                continue;
-            }
-            typeMask |= android::ResTable_map::TYPE_FLAGS;
-        }
 
-        Attribute::Symbol item;
-        if (parseEnumOrFlagItem(&childParser, name, &item)) {
-            if (!mTable->addResource(item.symbol.name, mConfig,
-                                     mSource.line(childParser.getLineNumber()),
-                                     util::make_unique<Id>())) {
-                error = true;
+            if (Maybe<Attribute::Symbol> s = parseEnumOrFlagItem(parser, elementName)) {
+                if (mTable->addResource(s.value().symbol.name.value(), mConfig,
+                                        mSource.withLine(parser->getLineNumber()),
+                                        util::make_unique<Id>(),
+                                        mDiag)) {
+                    items.push_back(std::move(s.value()));
+                } else {
+                    error = true;
+                }
             } else {
-                items.push_back(std::move(item));
+                error = true;
             }
+        } else if (elementName == u"skip" || elementName == u"eat-comment") {
+            comment = u"";
+
         } else {
+            mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                         << ":" << elementName << ">");
             error = true;
         }
     }
@@ -1060,43 +557,36 @@
     return attr;
 }
 
-bool ResourceParser::parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag,
-                                         Attribute::Symbol* outSymbol) {
-    const auto attrIterEnd = parser->endAttributes();
-    const auto nameAttrIter = parser->findAttribute(u"", u"name");
-    if (nameAttrIter == attrIterEnd || nameAttrIter->value.empty()) {
-        mLogger.error(parser->getLineNumber())
-                << "no attribute 'name' found for tag <" << tag << ">."
-                << std::endl;
-        return false;
+Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(XmlPullParser* parser,
+                                                             const StringPiece16& tag) {
+    const Source source = mSource.withLine(parser->getLineNumber());
+
+    Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name");
+    if (!maybeName) {
+        mDiag->error(DiagMessage(source) << "no attribute 'name' found for tag <" << tag << ">");
+        return {};
     }
 
-    const auto valueAttrIter = parser->findAttribute(u"", u"value");
-    if (valueAttrIter == attrIterEnd || valueAttrIter->value.empty()) {
-        mLogger.error(parser->getLineNumber())
-                << "no attribute 'value' found for tag <" << tag << ">."
-                << std::endl;
-        return false;
+    Maybe<StringPiece16> maybeValue = findNonEmptyAttribute(parser, u"value");
+    if (!maybeValue) {
+        mDiag->error(DiagMessage(source) << "no attribute 'value' found for tag <" << tag << ">");
+        return {};
     }
 
     android::Res_value val;
-    if (!android::ResTable::stringToInt(valueAttrIter->value.data(),
-                                        valueAttrIter->value.size(), &val)) {
-        mLogger.error(parser->getLineNumber())
-                << "invalid value '"
-                << valueAttrIter->value
-                << "' for <" << tag << ">; must be an integer."
-                << std::endl;
-        return false;
+    if (!android::ResTable::stringToInt(maybeValue.value().data(),
+                                        maybeValue.value().size(), &val)) {
+        mDiag->error(DiagMessage(source) << "invalid value '" << maybeValue.value()
+                     << "' for <" << tag << ">; must be an integer");
+        return {};
     }
 
-    outSymbol->symbol.name = ResourceName {
-            mTable->getPackage(), ResourceType::kId, nameAttrIter->value };
-    outSymbol->value = val.data;
-    return true;
+    return Attribute::Symbol{
+            Reference(ResourceName{ {}, ResourceType::kId, maybeName.value().toString() }),
+            val.data };
 }
 
-static bool parseXmlAttributeName(StringPiece16 str, ResourceName* outName) {
+static Maybe<ResourceName> parseXmlAttributeName(StringPiece16 str) {
     str = util::trimWhitespace(str);
     const char16_t* const start = str.data();
     const char16_t* const end = start + str.size();
@@ -1113,289 +603,279 @@
         p++;
     }
 
-    outName->package = package.toString();
-    outName->type = ResourceType::kAttr;
-    if (name.size() == 0) {
-        outName->entry = str.toString();
-    } else {
-        outName->entry = name.toString();
-    }
-    return true;
+    return ResourceName{ package.toString(), ResourceType::kAttr,
+                         name.empty() ? str.toString() : name.toString() };
 }
 
-bool ResourceParser::parseUntypedItem(XmlPullParser* parser, Style& style) {
-    const auto endAttrIter = parser->endAttributes();
-    const auto nameAttrIter = parser->findAttribute(u"", u"name");
-    if (nameAttrIter == endAttrIter || nameAttrIter->value.empty()) {
-        mLogger.error(parser->getLineNumber())
-                << "<item> must have a 'name' attribute."
-                << std::endl;
+
+bool ResourceParser::parseStyleItem(XmlPullParser* parser, Style* style) {
+    const Source source = mSource.withLine(parser->getLineNumber());
+
+    Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name");
+    if (!maybeName) {
+        mDiag->error(DiagMessage(source) << "<item> must have a 'name' attribute");
         return false;
     }
 
-    ResourceName key;
-    if (!parseXmlAttributeName(nameAttrIter->value, &key)) {
-        mLogger.error(parser->getLineNumber())
-                << "invalid attribute name '"
-                << nameAttrIter->value
-                << "'."
-                << std::endl;
+    Maybe<ResourceName> maybeKey = parseXmlAttributeName(maybeName.value());
+    if (!maybeKey) {
+        mDiag->error(DiagMessage(source) << "invalid attribute name '" << maybeName.value() << "'");
         return false;
     }
 
-    if (!key.package.empty()) {
-        // We have a package name set, so lookup its alias.
-        parser->applyPackageAlias(&key.package, mTable->getPackage());
-    } else {
-        // The package name was omitted, so use the default package name with
-        // no alias lookup.
-        key.package = mTable->getPackage();
+    if (Maybe<ResourceName> transformedName = parser->transformPackage(maybeKey.value(), u"")) {
+        maybeKey = std::move(transformedName);
     }
 
     std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString);
     if (!value) {
+        mDiag->error(DiagMessage(source) << "could not parse style item");
         return false;
     }
 
-    style.entries.push_back(Style::Entry{ Reference(key), std::move(value) });
+    style->entries.push_back(Style::Entry{ Reference(maybeKey.value()), std::move(value) });
     return true;
 }
 
 bool ResourceParser::parseStyle(XmlPullParser* parser, const ResourceNameRef& resourceName) {
-    const SourceLine source = mSource.line(parser->getLineNumber());
+    const Source source = mSource.withLine(parser->getLineNumber());
     std::unique_ptr<Style> style = util::make_unique<Style>();
 
-    const auto endAttrIter = parser->endAttributes();
-    const auto parentAttrIter = parser->findAttribute(u"", u"parent");
-    if (parentAttrIter != endAttrIter) {
-        std::string errStr;
-        if (!parseStyleParentReference(parentAttrIter->value, &style->parent, &errStr)) {
-            mLogger.error(source.line) << errStr << "." << std::endl;
-            return false;
+    Maybe<StringPiece16> maybeParent = findAttribute(parser, u"parent");
+    if (maybeParent) {
+        // If the parent is empty, we don't have a parent, but we also don't infer either.
+        if (!maybeParent.value().empty()) {
+            std::string errStr;
+            style->parent = ResourceUtils::parseStyleParentReference(maybeParent.value(), &errStr);
+            if (!style->parent) {
+                mDiag->error(DiagMessage(source) << errStr);
+                return false;
+            }
+
+            if (Maybe<ResourceName> transformedName =
+                    parser->transformPackage(style->parent.value().name.value(), u"")) {
+                style->parent.value().name = std::move(transformedName);
+            }
         }
 
-        if (!style->parent.name.package.empty()) {
-            // Try to interpret the package name as an alias. These take precedence.
-            parser->applyPackageAlias(&style->parent.name.package, mTable->getPackage());
-        } else {
-            // If no package is specified, this can not be an alias and is the local package.
-            style->parent.name.package = mTable->getPackage();
-        }
     } else {
         // No parent was specified, so try inferring it from the style name.
         std::u16string styleName = resourceName.entry.toString();
         size_t pos = styleName.find_last_of(u'.');
         if (pos != std::string::npos) {
             style->parentInferred = true;
-            style->parent.name.package = mTable->getPackage();
-            style->parent.name.type = ResourceType::kStyle;
-            style->parent.name.entry = styleName.substr(0, pos);
+            style->parent = Reference(ResourceName{
+                {}, ResourceType::kStyle, styleName.substr(0, pos) });
         }
     }
 
-    bool success = true;
-    while (XmlPullParser::isGoodEvent(parser->next())) {
-        if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
-            continue;
-        }
-
-        ScopedXmlPullParser childParser(parser);
-        const std::u16string& name = childParser.getElementName();
-        if (name == u"item") {
-            success &= parseUntypedItem(&childParser, *style);
-        } else {
-            mLogger.error(childParser.getLineNumber())
-                    << "unexpected tag <"
-                    << name
-                    << "> in <style> resource."
-                    << std::endl;
-            success = false;
-        }
-    }
-
-    if (!success) {
-        return false;
-    }
-
-    return mTable->addResource(resourceName, mConfig, source, std::move(style));
-}
-
-bool ResourceParser::parseArray(XmlPullParser* parser, const ResourceNameRef& resourceName,
-                                uint32_t typeMask) {
-    const SourceLine source = mSource.line(parser->getLineNumber());
-    std::unique_ptr<Array> array = util::make_unique<Array>();
-
     bool error = false;
-    while (XmlPullParser::isGoodEvent(parser->next())) {
+    std::u16string comment;
+    const size_t depth = parser->getDepth();
+    while (XmlPullParser::nextChildNode(parser, depth)) {
         if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+            // Skip text and comments.
             continue;
         }
 
-        ScopedXmlPullParser childParser(parser);
+        const std::u16string& elementNamespace = parser->getElementNamespace();
+        const std::u16string& elementName = parser->getElementName();
+        if (elementNamespace == u"" && elementName == u"item") {
+            error |= !parseStyleItem(parser, style.get());
 
-        if (childParser.getElementName() != u"item") {
-            mLogger.error(childParser.getLineNumber())
-                    << "unexpected tag <"
-                    << childParser.getElementName()
-                    << "> in <array> resource."
-                    << std::endl;
+        } else if (elementNamespace.empty() &&
+                (elementName == u"skip" || elementName == u"eat-comment")) {
+            comment = u"";
+
+        } else {
+            mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                         << ":" << elementName << ">");
             error = true;
-            continue;
         }
-
-        std::unique_ptr<Item> item = parseXml(&childParser, typeMask, kNoRawString);
-        if (!item) {
-            error = true;
-            continue;
-        }
-        array->items.emplace_back(std::move(item));
     }
 
     if (error) {
         return false;
     }
+    return mTable->addResource(resourceName, mConfig, source, std::move(style),
+                               mDiag);
+}
 
-    return mTable->addResource(resourceName, mConfig, source, std::move(array));
+bool ResourceParser::parseArray(XmlPullParser* parser, const ResourceNameRef& resourceName,
+                                uint32_t typeMask) {
+    const Source source = mSource.withLine(parser->getLineNumber());
+    std::unique_ptr<Array> array = util::make_unique<Array>();
+
+    std::u16string comment;
+    bool error = false;
+    const size_t depth = parser->getDepth();
+    while (XmlPullParser::nextChildNode(parser, depth)) {
+        if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+            // Skip text and comments.
+            continue;
+        }
+
+        const Source itemSource = mSource.withLine(parser->getLineNumber());
+        const std::u16string& elementNamespace = parser->getElementNamespace();
+        const std::u16string& elementName = parser->getElementName();
+        if (elementNamespace.empty() && elementName == u"item") {
+            std::unique_ptr<Item> item = parseXml(parser, typeMask, kNoRawString);
+            if (!item) {
+                mDiag->error(DiagMessage(itemSource) << "could not parse array item");
+                error = true;
+                continue;
+            }
+            array->items.emplace_back(std::move(item));
+
+        } else if (elementNamespace.empty() &&
+                (elementName == u"skip" || elementName == u"eat-comment")) {
+            comment = u"";
+
+        } else {
+            mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                         << "unknown tag <" << elementNamespace << ":" << elementName << ">");
+            error = true;
+        }
+    }
+
+    if (error) {
+        return false;
+    }
+    return mTable->addResource(resourceName, mConfig, source, std::move(array),
+                               mDiag);
 }
 
 bool ResourceParser::parsePlural(XmlPullParser* parser, const ResourceNameRef& resourceName) {
-    const SourceLine source = mSource.line(parser->getLineNumber());
+    const Source source = mSource.withLine(parser->getLineNumber());
     std::unique_ptr<Plural> plural = util::make_unique<Plural>();
 
-    bool success = true;
-    while (XmlPullParser::isGoodEvent(parser->next())) {
+    std::u16string comment;
+    bool error = false;
+    const size_t depth = parser->getDepth();
+    while (XmlPullParser::nextChildNode(parser, depth)) {
         if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+            // Skip text and comments.
             continue;
         }
 
-        ScopedXmlPullParser childParser(parser);
+        const std::u16string& elementNamespace = parser->getElementNamespace();
+        const std::u16string& elementName = parser->getElementName();
+        if (elementNamespace.empty() && elementName == u"item") {
+            const auto endAttrIter = parser->endAttributes();
+            auto attrIter = parser->findAttribute(u"", u"quantity");
+            if (attrIter == endAttrIter || attrIter->value.empty()) {
+                mDiag->error(DiagMessage(source) << "<item> in <plurals> requires attribute "
+                             << "'quantity'");
+                error = true;
+                continue;
+            }
 
-        if (!childParser.getElementNamespace().empty() ||
-                childParser.getElementName() != u"item") {
-            success = false;
-            continue;
-        }
+            StringPiece16 trimmedQuantity = util::trimWhitespace(attrIter->value);
+            size_t index = 0;
+            if (trimmedQuantity == u"zero") {
+                index = Plural::Zero;
+            } else if (trimmedQuantity == u"one") {
+                index = Plural::One;
+            } else if (trimmedQuantity == u"two") {
+                index = Plural::Two;
+            } else if (trimmedQuantity == u"few") {
+                index = Plural::Few;
+            } else if (trimmedQuantity == u"many") {
+                index = Plural::Many;
+            } else if (trimmedQuantity == u"other") {
+                index = Plural::Other;
+            } else {
+                mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                             << "<item> in <plural> has invalid value '" << trimmedQuantity
+                             << "' for attribute 'quantity'");
+                error = true;
+                continue;
+            }
 
-        const auto endAttrIter = childParser.endAttributes();
-        auto attrIter = childParser.findAttribute(u"", u"quantity");
-        if (attrIter == endAttrIter || attrIter->value.empty()) {
-            mLogger.error(childParser.getLineNumber())
-                    << "<item> in <plurals> requires attribute 'quantity'."
-                    << std::endl;
-            success = false;
-            continue;
-        }
+            if (plural->values[index]) {
+                mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                             << "duplicate quantity '" << trimmedQuantity << "'");
+                error = true;
+                continue;
+            }
 
-        StringPiece16 trimmedQuantity = util::trimWhitespace(attrIter->value);
-        size_t index = 0;
-        if (trimmedQuantity == u"zero") {
-            index = Plural::Zero;
-        } else if (trimmedQuantity == u"one") {
-            index = Plural::One;
-        } else if (trimmedQuantity == u"two") {
-            index = Plural::Two;
-        } else if (trimmedQuantity == u"few") {
-            index = Plural::Few;
-        } else if (trimmedQuantity == u"many") {
-            index = Plural::Many;
-        } else if (trimmedQuantity == u"other") {
-            index = Plural::Other;
+            if (!(plural->values[index] = parseXml(parser, android::ResTable_map::TYPE_STRING,
+                                                   kNoRawString))) {
+                error = true;
+            }
+        } else if (elementNamespace.empty() &&
+                (elementName == u"skip" || elementName == u"eat-comment")) {
+            comment = u"";
         } else {
-            mLogger.error(childParser.getLineNumber())
-                    << "<item> in <plural> has invalid value '"
-                    << trimmedQuantity
-                    << "' for attribute 'quantity'."
-                    << std::endl;
-            success = false;
-            continue;
-        }
-
-        if (plural->values[index]) {
-            mLogger.error(childParser.getLineNumber())
-                    << "duplicate quantity '"
-                    << trimmedQuantity
-                    << "'."
-                    << std::endl;
-            success = false;
-            continue;
-        }
-
-        if (!(plural->values[index] = parseXml(&childParser, android::ResTable_map::TYPE_STRING,
-                                               kNoRawString))) {
-            success = false;
+            mDiag->error(DiagMessage(source) << "unknown tag <" << elementNamespace << ":"
+                         << elementName << ">");
+            error = true;
         }
     }
 
-    if (!success) {
+    if (error) {
         return false;
     }
-
-    return mTable->addResource(resourceName, mConfig, source, std::move(plural));
+    return mTable->addResource(resourceName, mConfig, source, std::move(plural), mDiag);
 }
 
 bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser,
                                            const ResourceNameRef& resourceName) {
-    const SourceLine source = mSource.line(parser->getLineNumber());
+    const Source source = mSource.withLine(parser->getLineNumber());
     std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
 
-    bool success = true;
-    while (XmlPullParser::isGoodEvent(parser->next())) {
+    std::u16string comment;
+    bool error = false;
+    const size_t depth = parser->getDepth();
+    while (XmlPullParser::nextChildNode(parser, depth)) {
         if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+            // Ignore text and comments.
             continue;
         }
 
-        ScopedXmlPullParser childParser(parser);
-
-        const std::u16string& elementName = childParser.getElementName();
-        if (elementName == u"attr") {
-            const auto endAttrIter = childParser.endAttributes();
-            auto attrIter = childParser.findAttribute(u"", u"name");
+        const std::u16string& elementNamespace = parser->getElementNamespace();
+        const std::u16string& elementName = parser->getElementName();
+        if (elementNamespace.empty() && elementName == u"attr") {
+            const auto endAttrIter = parser->endAttributes();
+            auto attrIter = parser->findAttribute(u"", u"name");
             if (attrIter == endAttrIter || attrIter->value.empty()) {
-                mLogger.error(childParser.getLineNumber())
-                        << "<attr> tag must have a 'name' attribute."
-                        << std::endl;
-                success = false;
+                mDiag->error(DiagMessage(source) << "<attr> tag must have a 'name' attribute");
+                error = true;
                 continue;
             }
 
             // Copy because our iterator will be invalidated.
-            ResourceName attrResourceName = {
-                    mTable->getPackage(),
-                    ResourceType::kAttr,
-                    attrIter->value
-            };
+            ResourceName attrResourceName = { {}, ResourceType::kAttr, attrIter->value };
 
-            std::unique_ptr<Attribute> attr = parseAttrImpl(&childParser, &attrResourceName, true);
+            std::unique_ptr<Attribute> attr = parseAttrImpl(parser, &attrResourceName, true);
             if (!attr) {
-                success = false;
+                error = true;
                 continue;
             }
 
             styleable->entries.emplace_back(attrResourceName);
 
-            // The package may have been corrected to another package. If that is so,
-            // we don't add the declaration.
-            if (attrResourceName.package == mTable->getPackage()) {
-                success &= mTable->addResource(attrResourceName, mConfig,
-                                               mSource.line(childParser.getLineNumber()),
-                                               std::move(attr));
-            }
+            // Add the attribute to the resource table. Since it is weakly defined,
+            // it won't collide.
+            error |= !mTable->addResource(attrResourceName, mConfig,
+                                          mSource.withLine(parser->getLineNumber()),
+                                          std::move(attr), mDiag);
 
-        } else if (elementName != u"eat-comment" && elementName != u"skip") {
-            mLogger.error(childParser.getLineNumber())
-                    << "<"
-                    << elementName
-                    << "> is not allowed inside <declare-styleable>."
-                    << std::endl;
-            success = false;
+        } else if (elementNamespace.empty() &&
+                (elementName == u"skip" || elementName == u"eat-comment")) {
+            comment = u"";
+
+        } else {
+            mDiag->error(DiagMessage(source) << "unknown tag <" << elementNamespace << ":"
+                         << elementName << ">");
+            error = true;
         }
     }
 
-    if (!success) {
+    if (error) {
         return false;
     }
-
-    return mTable->addResource(resourceName, mConfig, source, std::move(styleable));
+    return mTable->addResource(resourceName, mConfig, source, std::move(styleable), mDiag);
 }
 
 } // namespace aapt
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 7618999..514e558 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -18,14 +18,15 @@
 #define AAPT_RESOURCE_PARSER_H
 
 #include "ConfigDescription.h"
-#include "Logger.h"
+#include "Diagnostics.h"
 #include "ResourceTable.h"
 #include "ResourceValues.h"
-#include "StringPiece.h"
 #include "StringPool.h"
 #include "XmlPullParser.h"
 
-#include <istream>
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
+
 #include <memory>
 
 namespace aapt {
@@ -35,118 +36,12 @@
  */
 class ResourceParser {
 public:
-    /*
-     * Extracts the package, type, and name from a string of the format:
-     *
-     *      [package:]type/name
-     *
-     * where the package can be empty. Validation must be performed on each
-     * individual extracted piece to verify that the pieces are valid.
-     */
-    static void extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
-                                    StringPiece16* outType, StringPiece16* outEntry);
-
-    /*
-     * Returns true if the string was parsed as a reference (@[+][package:]type/name), with
-     * `outReference` set to the parsed reference.
-     *
-     * If '+' was present in the reference, `outCreate` is set to true.
-     * If '*' was present in the reference, `outPrivate` is set to true.
-     */
-    static bool tryParseReference(const StringPiece16& str, ResourceNameRef* outReference,
-                                  bool* outCreate, bool* outPrivate);
-
-    /*
-     * Returns true if the string was parsed as an attribute reference (?[package:]type/name),
-     * with `outReference` set to the parsed reference.
-     */
-    static bool tryParseAttributeReference(const StringPiece16& str,
-                                           ResourceNameRef* outReference);
-
-    /*
-     * Returns true if the string `str` was parsed as a valid reference to a style.
-     * The format for a style parent is slightly more flexible than a normal reference:
-     *
-     * @[package:]style/<entry> or
-     * ?[package:]style/<entry> or
-     * <package>:[style/]<entry>
-     */
-    static bool parseStyleParentReference(const StringPiece16& str, Reference* outReference,
-                                          std::string* outError);
-
-    /*
-     * 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.
-     */
-    static std::unique_ptr<Reference> tryParseReference(const StringPiece16& str,
-                                                        bool* outCreate);
-
-    /*
-     * Returns a BinaryPrimitve object representing @null or @empty if the string was parsed
-     * as one.
-     */
-    static std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str);
-
-    /*
-     * Returns a BinaryPrimitve object representing a color if the string was parsed
-     * as one.
-     */
-    static std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str);
-
-    /*
-     * Returns a BinaryPrimitve object representing a boolean if the string was parsed
-     * as one.
-     */
-    static std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str);
-
-    /*
-     * Returns a BinaryPrimitve object representing an integer if the string was parsed
-     * as one.
-     */
-    static std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str);
-
-    /*
-     * Returns a BinaryPrimitve object representing a floating point number
-     * (float, dimension, etc) if the string was parsed as one.
-     */
-    static std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str);
-
-    /*
-     * Returns a BinaryPrimitve object representing an enum symbol if the string was parsed
-     * as one.
-     */
-    static std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute& enumAttr,
-                                                               const StringPiece16& str);
-
-    /*
-     * Returns a BinaryPrimitve object representing a flag symbol if the string was parsed
-     * as one.
-     */
-    static std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute& enumAttr,
-                                                               const StringPiece16& str);
-    /*
-     * Try to convert a string to an Item for the given attribute. The attribute will
-     * restrict what values the string can be converted to.
-     * The callback function onCreateReference is called when the parsed item is a
-     * reference to an ID that must be created (@+id/foo).
-     */
-    static std::unique_ptr<Item> parseItemForAttribute(
-            const StringPiece16& value, const Attribute& attr,
-            std::function<void(const ResourceName&)> onCreateReference = {});
-
-    static std::unique_ptr<Item> parseItemForAttribute(
-            const StringPiece16& value, uint32_t typeMask,
-            std::function<void(const ResourceName&)> onCreateReference = {});
-
-    static uint32_t androidTypeToAttributeTypeMask(uint16_t type);
-
-    ResourceParser(const std::shared_ptr<ResourceTable>& table, const Source& source,
-                   const ConfigDescription& config, const std::shared_ptr<XmlPullParser>& parser);
+    ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source,
+                   const ConfigDescription& config);
 
     ResourceParser(const ResourceParser&) = delete; // No copy.
 
-    bool parse();
+    bool parse(XmlPullParser* parser);
 
 private:
     /*
@@ -155,7 +50,7 @@
      * contains the escaped and whitespace trimmed text, while `outRawString`
      * contains the unescaped text. Returns true on success.
      */
-    bool flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString,\
+    bool flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString,
                            StyleString* outStyleString);
 
     /*
@@ -175,19 +70,17 @@
     std::unique_ptr<Attribute> parseAttrImpl(XmlPullParser* parser,
                                              ResourceName* resourceName,
                                              bool weak);
-    bool parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag,
-                             Attribute::Symbol* outSymbol);
+    Maybe<Attribute::Symbol> parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag);
     bool parseStyle(XmlPullParser* parser, const ResourceNameRef& resourceName);
-    bool parseUntypedItem(XmlPullParser* parser, Style& style);
+    bool parseStyleItem(XmlPullParser* parser, Style* style);
     bool parseDeclareStyleable(XmlPullParser* parser, const ResourceNameRef& resourceName);
     bool parseArray(XmlPullParser* parser, const ResourceNameRef& resourceName, uint32_t typeMask);
     bool parsePlural(XmlPullParser* parser, const ResourceNameRef& resourceName);
 
-    std::shared_ptr<ResourceTable> mTable;
+    IDiagnostics* mDiag;
+    ResourceTable* mTable;
     Source mSource;
     ConfigDescription mConfig;
-    SourceLogger mLogger;
-    std::shared_ptr<XmlPullParser> mParser;
 };
 
 } // namespace aapt
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index a93d0ff..cb98afd 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -16,8 +16,11 @@
 
 #include "ResourceParser.h"
 #include "ResourceTable.h"
+#include "ResourceUtils.h"
 #include "ResourceValues.h"
-#include "SourceXmlPullParser.h"
+#include "XmlPullParser.h"
+
+#include "test/Context.h"
 
 #include <gtest/gtest.h>
 #include <sstream>
@@ -27,156 +30,41 @@
 
 constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
 
-TEST(ResourceParserReferenceTest, ParseReferenceWithNoPackage) {
-    ResourceNameRef expected = { {}, ResourceType::kColor, u"foo" };
-    ResourceNameRef actual;
-    bool create = false;
-    bool privateRef = false;
-    EXPECT_TRUE(ResourceParser::tryParseReference(u"@color/foo", &actual, &create, &privateRef));
-    EXPECT_EQ(expected, actual);
-    EXPECT_FALSE(create);
-    EXPECT_FALSE(privateRef);
-}
-
-TEST(ResourceParserReferenceTest, ParseReferenceWithPackage) {
-    ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
-    ResourceNameRef actual;
-    bool create = false;
-    bool privateRef = false;
-    EXPECT_TRUE(ResourceParser::tryParseReference(u"@android:color/foo", &actual, &create,
-                                                  &privateRef));
-    EXPECT_EQ(expected, actual);
-    EXPECT_FALSE(create);
-    EXPECT_FALSE(privateRef);
-}
-
-TEST(ResourceParserReferenceTest, ParseReferenceWithSurroundingWhitespace) {
-    ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
-    ResourceNameRef actual;
-    bool create = false;
-    bool privateRef = false;
-    EXPECT_TRUE(ResourceParser::tryParseReference(u"\t @android:color/foo\n \n\t", &actual,
-                                                  &create, &privateRef));
-    EXPECT_EQ(expected, actual);
-    EXPECT_FALSE(create);
-    EXPECT_FALSE(privateRef);
-}
-
-TEST(ResourceParserReferenceTest, ParseAutoCreateIdReference) {
-    ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
-    ResourceNameRef actual;
-    bool create = false;
-    bool privateRef = false;
-    EXPECT_TRUE(ResourceParser::tryParseReference(u"@+android:id/foo", &actual, &create,
-                                                  &privateRef));
-    EXPECT_EQ(expected, actual);
-    EXPECT_TRUE(create);
-    EXPECT_FALSE(privateRef);
-}
-
-TEST(ResourceParserReferenceTest, ParsePrivateReference) {
-    ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
-    ResourceNameRef actual;
-    bool create = false;
-    bool privateRef = false;
-    EXPECT_TRUE(ResourceParser::tryParseReference(u"@*android:id/foo", &actual, &create,
-                                                  &privateRef));
-    EXPECT_EQ(expected, actual);
-    EXPECT_FALSE(create);
-    EXPECT_TRUE(privateRef);
-}
-
-TEST(ResourceParserReferenceTest, FailToParseAutoCreateNonIdReference) {
-    bool create = false;
-    bool privateRef = false;
-    ResourceNameRef actual;
-    EXPECT_FALSE(ResourceParser::tryParseReference(u"@+android:color/foo", &actual, &create,
-                                                   &privateRef));
-}
-
-TEST(ResourceParserReferenceTest, ParseStyleParentReference) {
-    Reference ref;
-    std::string errStr;
-    EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@android:style/foo", &ref, &errStr));
-    EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
-
-    EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@style/foo", &ref, &errStr));
-    EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" }));
-
-    EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?android:style/foo", &ref, &errStr));
-    EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
-
-    EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?style/foo", &ref, &errStr));
-    EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" }));
-
-    EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:style/foo", &ref, &errStr));
-    EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
-
-    EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:foo", &ref, &errStr));
-    EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
-
-    EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"foo", &ref, &errStr));
-    EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" }));
+TEST(ResourceParserSingleTest, FailToParseWithNoRootResourcesElement) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+    std::stringstream input(kXmlPreamble);
+    input << "<attr name=\"foo\"/>" << std::endl;
+    ResourceTable table;
+    ResourceParser parser(context->getDiagnostics(), &table, Source{ "test" }, {});
+    XmlPullParser xmlParser(input);
+    ASSERT_FALSE(parser.parse(&xmlParser));
 }
 
 struct ResourceParserTest : public ::testing::Test {
-    virtual void SetUp() override {
-        mTable = std::make_shared<ResourceTable>();
-        mTable->setPackage(u"android");
+    ResourceTable mTable;
+    std::unique_ptr<IAaptContext> mContext;
+
+    void SetUp() override {
+        mContext = test::ContextBuilder().build();
     }
 
     ::testing::AssertionResult testParse(const StringPiece& str) {
         std::stringstream input(kXmlPreamble);
         input << "<resources>\n" << str << "\n</resources>" << std::endl;
-        ResourceParser parser(mTable, Source{ "test" }, {},
-                              std::make_shared<SourceXmlPullParser>(input));
-        if (parser.parse()) {
+        ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{ "test" }, {});
+        XmlPullParser xmlParser(input);
+        if (parser.parse(&xmlParser)) {
             return ::testing::AssertionSuccess();
         }
         return ::testing::AssertionFailure();
     }
-
-    template <typename T>
-    const T* findResource(const ResourceNameRef& name, const ConfigDescription& config) {
-        using std::begin;
-        using std::end;
-
-        const ResourceTableType* type;
-        const ResourceEntry* entry;
-        std::tie(type, entry) = mTable->findResource(name);
-        if (!type || !entry) {
-            return nullptr;
-        }
-
-        for (const auto& configValue : entry->values) {
-            if (configValue.config == config) {
-                return dynamic_cast<const T*>(configValue.value.get());
-            }
-        }
-        return nullptr;
-    }
-
-    template <typename T>
-    const T* findResource(const ResourceNameRef& name) {
-        return findResource<T>(name, {});
-    }
-
-    std::shared_ptr<ResourceTable> mTable;
 };
 
-TEST_F(ResourceParserTest, FailToParseWithNoRootResourcesElement) {
-    std::stringstream input(kXmlPreamble);
-    input << "<attr name=\"foo\"/>" << std::endl;
-    ResourceParser parser(mTable, {}, {}, std::make_shared<SourceXmlPullParser>(input));
-    ASSERT_FALSE(parser.parse());
-}
-
 TEST_F(ResourceParserTest, ParseQuotedString) {
     std::string input = "<string name=\"foo\">   \"  hey there \" </string>";
     ASSERT_TRUE(testParse(input));
 
-    const String* str = findResource<String>(ResourceName{
-            u"android", ResourceType::kString, u"foo"});
+    String* str = test::getValue<String>(&mTable, u"@string/foo");
     ASSERT_NE(nullptr, str);
     EXPECT_EQ(std::u16string(u"  hey there "), *str->value);
 }
@@ -185,12 +73,22 @@
     std::string input = "<string name=\"foo\">\\?123</string>";
     ASSERT_TRUE(testParse(input));
 
-    const String* str = findResource<String>(ResourceName{
-            u"android", ResourceType::kString, u"foo" });
+    String* str = test::getValue<String>(&mTable, u"@string/foo");
     ASSERT_NE(nullptr, str);
     EXPECT_EQ(std::u16string(u"?123"), *str->value);
 }
 
+TEST_F(ResourceParserTest, IgnoreXliffTags) {
+    std::string input = "<string name=\"foo\" \n"
+                        "        xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
+                        "  There are <xliff:g id=\"count\">%1$d</xliff:g> apples</string>";
+    ASSERT_TRUE(testParse(input));
+
+    String* str = test::getValue<String>(&mTable, u"@string/foo");
+    ASSERT_NE(nullptr, str);
+    EXPECT_EQ(StringPiece16(u"There are %1$d apples"), StringPiece16(*str->value));
+}
+
 TEST_F(ResourceParserTest, ParseNull) {
     std::string input = "<integer name=\"foo\">@null</integer>";
     ASSERT_TRUE(testParse(input));
@@ -199,8 +97,7 @@
     // a non-existing value, and this causes problems in styles when trying to resolve
     // an attribute. Null values must be encoded as android::Res_value::TYPE_REFERENCE
     // with a data value of 0.
-    const BinaryPrimitive* integer = findResource<BinaryPrimitive>(ResourceName{
-            u"android", ResourceType::kInteger, u"foo" });
+    BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, u"@integer/foo");
     ASSERT_NE(nullptr, integer);
     EXPECT_EQ(uint16_t(android::Res_value::TYPE_REFERENCE), integer->value.dataType);
     EXPECT_EQ(0u, integer->value.data);
@@ -210,8 +107,7 @@
     std::string input = "<integer name=\"foo\">@empty</integer>";
     ASSERT_TRUE(testParse(input));
 
-    const BinaryPrimitive* integer = findResource<BinaryPrimitive>(ResourceName{
-            u"android", ResourceType::kInteger, u"foo" });
+    BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, u"@integer/foo");
     ASSERT_NE(nullptr, integer);
     EXPECT_EQ(uint16_t(android::Res_value::TYPE_NULL), integer->value.dataType);
     EXPECT_EQ(uint32_t(android::Res_value::DATA_NULL_EMPTY), integer->value.data);
@@ -222,14 +118,12 @@
                         "<attr name=\"bar\"/>";
     ASSERT_TRUE(testParse(input));
 
-    const Attribute* attr = findResource<Attribute>(ResourceName{
-            u"android", ResourceType::kAttr, u"foo"});
-    EXPECT_NE(nullptr, attr);
+    Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo");
+    ASSERT_NE(nullptr, attr);
     EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
 
-    attr = findResource<Attribute>(ResourceName{
-            u"android", ResourceType::kAttr, u"bar"});
-    EXPECT_NE(nullptr, attr);
+    attr = test::getValue<Attribute>(&mTable, u"@attr/bar");
+    ASSERT_NE(nullptr, attr);
     EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask);
 }
 
@@ -240,8 +134,7 @@
                         "<attr name=\"foo\" format=\"string\"/>";
     ASSERT_TRUE(testParse(input));
 
-    const Attribute* attr = findResource<Attribute>(ResourceName{
-            u"android", ResourceType::kAttr, u"foo"});
+    Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo");
     ASSERT_NE(nullptr, attr);
     EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
 }
@@ -255,8 +148,7 @@
                         "</declare-styleable>";
     ASSERT_TRUE(testParse(input));
 
-    const Attribute* attr = findResource<Attribute>(ResourceName{
-            u"android", ResourceType::kAttr, u"foo"});
+    Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo");
     ASSERT_NE(nullptr, attr);
     EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->typeMask);
 }
@@ -269,19 +161,21 @@
                         "</attr>";
     ASSERT_TRUE(testParse(input));
 
-    const Attribute* enumAttr = findResource<Attribute>(ResourceName{
-            u"android", ResourceType::kAttr, u"foo"});
+    Attribute* enumAttr = test::getValue<Attribute>(&mTable, u"@attr/foo");
     ASSERT_NE(enumAttr, nullptr);
     EXPECT_EQ(enumAttr->typeMask, android::ResTable_map::TYPE_ENUM);
     ASSERT_EQ(enumAttr->symbols.size(), 3u);
 
-    EXPECT_EQ(enumAttr->symbols[0].symbol.name.entry, u"bar");
+    AAPT_ASSERT_TRUE(enumAttr->symbols[0].symbol.name);
+    EXPECT_EQ(enumAttr->symbols[0].symbol.name.value().entry, u"bar");
     EXPECT_EQ(enumAttr->symbols[0].value, 0u);
 
-    EXPECT_EQ(enumAttr->symbols[1].symbol.name.entry, u"bat");
+    AAPT_ASSERT_TRUE(enumAttr->symbols[1].symbol.name);
+    EXPECT_EQ(enumAttr->symbols[1].symbol.name.value().entry, u"bat");
     EXPECT_EQ(enumAttr->symbols[1].value, 1u);
 
-    EXPECT_EQ(enumAttr->symbols[2].symbol.name.entry, u"baz");
+    AAPT_ASSERT_TRUE(enumAttr->symbols[2].symbol.name);
+    EXPECT_EQ(enumAttr->symbols[2].symbol.name.value().entry, u"baz");
     EXPECT_EQ(enumAttr->symbols[2].value, 2u);
 }
 
@@ -293,23 +187,25 @@
                         "</attr>";
     ASSERT_TRUE(testParse(input));
 
-    const Attribute* flagAttr = findResource<Attribute>(ResourceName{
-            u"android", ResourceType::kAttr, u"foo"});
+    Attribute* flagAttr = test::getValue<Attribute>(&mTable, u"@attr/foo");
     ASSERT_NE(flagAttr, nullptr);
     EXPECT_EQ(flagAttr->typeMask, android::ResTable_map::TYPE_FLAGS);
     ASSERT_EQ(flagAttr->symbols.size(), 3u);
 
-    EXPECT_EQ(flagAttr->symbols[0].symbol.name.entry, u"bar");
+    AAPT_ASSERT_TRUE(flagAttr->symbols[0].symbol.name);
+    EXPECT_EQ(flagAttr->symbols[0].symbol.name.value().entry, u"bar");
     EXPECT_EQ(flagAttr->symbols[0].value, 0u);
 
-    EXPECT_EQ(flagAttr->symbols[1].symbol.name.entry, u"bat");
+    AAPT_ASSERT_TRUE(flagAttr->symbols[1].symbol.name);
+    EXPECT_EQ(flagAttr->symbols[1].symbol.name.value().entry, u"bat");
     EXPECT_EQ(flagAttr->symbols[1].value, 1u);
 
-    EXPECT_EQ(flagAttr->symbols[2].symbol.name.entry, u"baz");
+    AAPT_ASSERT_TRUE(flagAttr->symbols[2].symbol.name);
+    EXPECT_EQ(flagAttr->symbols[2].symbol.name.value().entry, u"baz");
     EXPECT_EQ(flagAttr->symbols[2].value, 2u);
 
-    std::unique_ptr<BinaryPrimitive> flagValue =
-            ResourceParser::tryParseFlagSymbol(*flagAttr, u"baz|bat");
+    std::unique_ptr<BinaryPrimitive> flagValue = ResourceUtils::tryParseFlagSymbol(flagAttr,
+                                                                                   u"baz|bat");
     ASSERT_NE(flagValue, nullptr);
     EXPECT_EQ(flagValue->value.data, 1u | 2u);
 }
@@ -331,28 +227,32 @@
                         "</style>";
     ASSERT_TRUE(testParse(input));
 
-    const Style* style = findResource<Style>(ResourceName{
-            u"android", ResourceType::kStyle, u"foo"});
+    Style* style = test::getValue<Style>(&mTable, u"@style/foo");
     ASSERT_NE(style, nullptr);
-    EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kStyle, u"fu"), style->parent.name);
-    ASSERT_EQ(style->entries.size(), 3u);
+    AAPT_ASSERT_TRUE(style->parent);
+    AAPT_ASSERT_TRUE(style->parent.value().name);
+    EXPECT_EQ(test::parseNameOrDie(u"@style/fu"), style->parent.value().name.value());
+    ASSERT_EQ(3u, style->entries.size());
 
-    EXPECT_EQ(style->entries[0].key.name,
-              (ResourceName{ u"android", ResourceType::kAttr, u"bar" }));
-    EXPECT_EQ(style->entries[1].key.name,
-              (ResourceName{ u"android", ResourceType::kAttr, u"bat" }));
-    EXPECT_EQ(style->entries[2].key.name,
-              (ResourceName{ u"android", ResourceType::kAttr, u"baz" }));
+    AAPT_ASSERT_TRUE(style->entries[0].key.name);
+    EXPECT_EQ(test::parseNameOrDie(u"@attr/bar"), style->entries[0].key.name.value());
+
+    AAPT_ASSERT_TRUE(style->entries[1].key.name);
+    EXPECT_EQ(test::parseNameOrDie(u"@attr/bat"), style->entries[1].key.name.value());
+
+    AAPT_ASSERT_TRUE(style->entries[2].key.name);
+    EXPECT_EQ(test::parseNameOrDie(u"@attr/baz"), style->entries[2].key.name.value());
 }
 
 TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) {
     std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>";
     ASSERT_TRUE(testParse(input));
 
-    const Style* style = findResource<Style>(
-            ResourceName{ u"android", ResourceType::kStyle, u"foo" });
+    Style* style = test::getValue<Style>(&mTable, u"@style/foo");
     ASSERT_NE(style, nullptr);
-    EXPECT_EQ(ResourceNameRef(u"com.app", ResourceType::kStyle, u"Theme"), style->parent.name);
+    AAPT_ASSERT_TRUE(style->parent);
+    AAPT_ASSERT_TRUE(style->parent.value().name);
+    EXPECT_EQ(test::parseNameOrDie(u"@com.app:style/Theme"), style->parent.value().name.value());
 }
 
 TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) {
@@ -360,10 +260,11 @@
                         "       name=\"foo\" parent=\"app:Theme\"/>";
     ASSERT_TRUE(testParse(input));
 
-    const Style* style = findResource<Style>(ResourceName{
-            u"android", ResourceType::kStyle, u"foo" });
+    Style* style = test::getValue<Style>(&mTable, u"@style/foo");
     ASSERT_NE(style, nullptr);
-    EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kStyle, u"Theme"), style->parent.name);
+    AAPT_ASSERT_TRUE(style->parent);
+    AAPT_ASSERT_TRUE(style->parent.value().name);
+    EXPECT_EQ(test::parseNameOrDie(u"@android:style/Theme"), style->parent.value().name.value());
 }
 
 TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) {
@@ -373,22 +274,21 @@
             "</style>";
     ASSERT_TRUE(testParse(input));
 
-    const Style* style = findResource<Style>(ResourceName{
-            u"android", ResourceType::kStyle, u"foo" });
+    Style* style = test::getValue<Style>(&mTable, u"@style/foo");
     ASSERT_NE(style, nullptr);
     ASSERT_EQ(1u, style->entries.size());
-    EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kAttr, u"bar"),
-              style->entries[0].key.name);
+    EXPECT_EQ(test::parseNameOrDie(u"@android:attr/bar"), style->entries[0].key.name.value());
 }
 
 TEST_F(ResourceParserTest, ParseStyleWithInferredParent) {
     std::string input = "<style name=\"foo.bar\"/>";
     ASSERT_TRUE(testParse(input));
 
-    const Style* style = findResource<Style>(ResourceName{
-            u"android", ResourceType::kStyle, u"foo.bar" });
+    Style* style = test::getValue<Style>(&mTable, u"@style/foo.bar");
     ASSERT_NE(style, nullptr);
-    EXPECT_EQ(style->parent.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
+    AAPT_ASSERT_TRUE(style->parent);
+    AAPT_ASSERT_TRUE(style->parent.value().name);
+    EXPECT_EQ(style->parent.value().name.value(), test::parseNameOrDie(u"@style/foo"));
     EXPECT_TRUE(style->parentInferred);
 }
 
@@ -396,10 +296,9 @@
     std::string input = "<style name=\"foo.bar\" parent=\"\"/>";
     ASSERT_TRUE(testParse(input));
 
-    const Style* style = findResource<Style>(ResourceName{
-            u"android", ResourceType::kStyle, u"foo.bar" });
+    Style* style = test::getValue<Style>(&mTable, u"@style/foo.bar");
     ASSERT_NE(style, nullptr);
-    EXPECT_FALSE(style->parent.name.isValid());
+    AAPT_EXPECT_FALSE(style->parent);
     EXPECT_FALSE(style->parentInferred);
 }
 
@@ -407,7 +306,7 @@
     std::string input = "<string name=\"foo\">@+id/bar</string>";
     ASSERT_TRUE(testParse(input));
 
-    const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"bar"});
+    Id* id = test::getValue<Id>(&mTable, u"@id/bar");
     ASSERT_NE(id, nullptr);
 }
 
@@ -418,22 +317,20 @@
                         "</declare-styleable>";
     ASSERT_TRUE(testParse(input));
 
-    const Attribute* attr = findResource<Attribute>(ResourceName{
-            u"android", ResourceType::kAttr, u"bar"});
+    Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/bar");
     ASSERT_NE(attr, nullptr);
     EXPECT_TRUE(attr->isWeak());
 
-    attr = findResource<Attribute>(ResourceName{ u"android", ResourceType::kAttr, u"bat"});
+    attr = test::getValue<Attribute>(&mTable, u"@attr/bat");
     ASSERT_NE(attr, nullptr);
     EXPECT_TRUE(attr->isWeak());
 
-    const Styleable* styleable = findResource<Styleable>(ResourceName{
-            u"android", ResourceType::kStyleable, u"foo" });
+    Styleable* styleable = test::getValue<Styleable>(&mTable, u"@styleable/foo");
     ASSERT_NE(styleable, nullptr);
     ASSERT_EQ(2u, styleable->entries.size());
 
-    EXPECT_EQ((ResourceName{u"android", ResourceType::kAttr, u"bar"}), styleable->entries[0].name);
-    EXPECT_EQ((ResourceName{u"android", ResourceType::kAttr, u"bat"}), styleable->entries[1].name);
+    EXPECT_EQ(test::parseNameOrDie(u"@attr/bar"), styleable->entries[0].name.value());
+    EXPECT_EQ(test::parseNameOrDie(u"@attr/bat"), styleable->entries[1].name.value());
 }
 
 TEST_F(ResourceParserTest, ParseArray) {
@@ -444,14 +341,13 @@
                         "</array>";
     ASSERT_TRUE(testParse(input));
 
-    const Array* array = findResource<Array>(ResourceName{
-            u"android", ResourceType::kArray, u"foo" });
+    Array* array = test::getValue<Array>(&mTable, u"@array/foo");
     ASSERT_NE(array, nullptr);
     ASSERT_EQ(3u, array->items.size());
 
-    EXPECT_NE(nullptr, dynamic_cast<const Reference*>(array->items[0].get()));
-    EXPECT_NE(nullptr, dynamic_cast<const String*>(array->items[1].get()));
-    EXPECT_NE(nullptr, dynamic_cast<const BinaryPrimitive*>(array->items[2].get()));
+    EXPECT_NE(nullptr, valueCast<Reference>(array->items[0].get()));
+    EXPECT_NE(nullptr, valueCast<String>(array->items[1].get()));
+    EXPECT_NE(nullptr, valueCast<BinaryPrimitive>(array->items[2].get()));
 }
 
 TEST_F(ResourceParserTest, ParsePlural) {
@@ -467,11 +363,11 @@
                         "<string name=\"foo\">Hi</string>";
     ASSERT_TRUE(testParse(input));
 
-    const ResourceTableType* type;
-    const ResourceEntry* entry;
-    std::tie(type, entry) = mTable->findResource(ResourceName{
-            u"android", ResourceType::kString, u"foo"});
-    ASSERT_NE(type, nullptr);
+    Maybe<ResourceTable::SearchResult> result = mTable.findResource(
+            test::parseNameOrDie(u"@string/foo"));
+    AAPT_ASSERT_TRUE(result);
+
+    ResourceEntry* entry = result.value().entry;
     ASSERT_NE(entry, nullptr);
     ASSERT_FALSE(entry->values.empty());
     EXPECT_EQ(entry->values.front().comment, u"This is a comment");
@@ -485,7 +381,7 @@
     std::string input = "<public type=\"id\" name=\"foo\"/>";
     ASSERT_TRUE(testParse(input));
 
-    const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"foo" });
+    Id* id = test::getValue<Id>(&mTable, u"@id/foo");
     ASSERT_NE(nullptr, id);
 }
 
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index c93ecc7..a1e7d36 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -15,11 +15,11 @@
  */
 
 #include "ConfigDescription.h"
-#include "Logger.h"
 #include "NameMangler.h"
 #include "ResourceTable.h"
 #include "ResourceValues.h"
-#include "Util.h"
+#include "ValueVisitor.h"
+#include "util/Util.h"
 
 #include <algorithm>
 #include <androidfw/ResourceTypes.h>
@@ -37,65 +37,109 @@
     return lhs->type < rhs;
 }
 
-static bool lessThanEntry(const std::unique_ptr<ResourceEntry>& lhs, const StringPiece16& rhs) {
+template <typename T>
+static bool lessThanStructWithName(const std::unique_ptr<T>& lhs,
+                                   const StringPiece16& rhs) {
     return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
 }
 
-ResourceTable::ResourceTable() : mPackageId(kUnsetPackageId) {
-    // Make sure attrs always have type ID 1.
-    findOrCreateType(ResourceType::kAttr)->typeId = 1;
+ResourceTablePackage* ResourceTable::findPackage(const StringPiece16& name) {
+    const auto last = packages.end();
+    auto iter = std::lower_bound(packages.begin(), last, name,
+                                 lessThanStructWithName<ResourceTablePackage>);
+    if (iter != last && name == (*iter)->name) {
+        return iter->get();
+    }
+    return nullptr;
 }
 
-std::unique_ptr<ResourceTableType>& ResourceTable::findOrCreateType(ResourceType type) {
-    auto last = mTypes.end();
-    auto iter = std::lower_bound(mTypes.begin(), last, type, lessThanType);
-    if (iter != last) {
-        if ((*iter)->type == type) {
-            return *iter;
+ResourceTablePackage* ResourceTable::findPackageById(uint8_t id) {
+    for (auto& package : packages) {
+        if (package->id && package->id.value() == id) {
+            return package.get();
         }
     }
-    return *mTypes.emplace(iter, new ResourceTableType{ type });
+    return nullptr;
 }
 
-std::unique_ptr<ResourceEntry>& ResourceTable::findOrCreateEntry(
-        std::unique_ptr<ResourceTableType>& type, const StringPiece16& name) {
-    auto last = type->entries.end();
-    auto iter = std::lower_bound(type->entries.begin(), last, name, lessThanEntry);
-    if (iter != last) {
-        if (name == (*iter)->name) {
-            return *iter;
-        }
+ResourceTablePackage* ResourceTable::createPackage(const StringPiece16& name, uint8_t id) {
+    ResourceTablePackage* package = findOrCreatePackage(name);
+    if (!package->id) {
+        package->id = id;
+        return package;
     }
-    return *type->entries.emplace(iter, new ResourceEntry{ name });
+
+    if (package->id.value() == id) {
+        return package;
+    }
+    return nullptr;
 }
 
-struct IsAttributeVisitor : ConstValueVisitor {
-    bool isAttribute = false;
-
-    void visit(const Attribute&, ValueVisitorArgs&) override {
-        isAttribute = true;
+ResourceTablePackage* ResourceTable::findOrCreatePackage(const StringPiece16& name) {
+    const auto last = packages.end();
+    auto iter = std::lower_bound(packages.begin(), last, name,
+                                 lessThanStructWithName<ResourceTablePackage>);
+    if (iter != last && name == (*iter)->name) {
+        return iter->get();
     }
 
-    operator bool() {
-        return isAttribute;
+    std::unique_ptr<ResourceTablePackage> newPackage = util::make_unique<ResourceTablePackage>();
+    newPackage->name = name.toString();
+    return packages.emplace(iter, std::move(newPackage))->get();
+}
+
+ResourceTableType* ResourceTablePackage::findType(ResourceType type) {
+    const auto last = types.end();
+    auto iter = std::lower_bound(types.begin(), last, type, lessThanType);
+    if (iter != last && (*iter)->type == type) {
+        return iter->get();
     }
-};
+    return nullptr;
+}
+
+ResourceTableType* ResourceTablePackage::findOrCreateType(ResourceType type) {
+    const auto last = types.end();
+    auto iter = std::lower_bound(types.begin(), last, type, lessThanType);
+    if (iter != last && (*iter)->type == type) {
+        return iter->get();
+    }
+    return types.emplace(iter, new ResourceTableType{ type })->get();
+}
+
+ResourceEntry* ResourceTableType::findEntry(const StringPiece16& name) {
+    const auto last = entries.end();
+    auto iter = std::lower_bound(entries.begin(), last, name,
+                                 lessThanStructWithName<ResourceEntry>);
+    if (iter != last && name == (*iter)->name) {
+        return iter->get();
+    }
+    return nullptr;
+}
+
+ResourceEntry* ResourceTableType::findOrCreateEntry(const StringPiece16& name) {
+    auto last = entries.end();
+    auto iter = std::lower_bound(entries.begin(), last, name,
+                                 lessThanStructWithName<ResourceEntry>);
+    if (iter != last && name == (*iter)->name) {
+        return iter->get();
+    }
+    return entries.emplace(iter, new ResourceEntry{ name })->get();
+}
 
 /**
  * The default handler for collisions. A return value of -1 means keep the
  * existing value, 0 means fail, and +1 means take the incoming value.
  */
-static int defaultCollisionHandler(const Value& existing, const Value& incoming) {
-    IsAttributeVisitor existingIsAttr, incomingIsAttr;
-    existing.accept(existingIsAttr, {});
-    incoming.accept(incomingIsAttr, {});
+int ResourceTable::resolveValueCollision(Value* existing, Value* incoming) {
+    Attribute* existingAttr = valueCast<Attribute>(existing);
+    Attribute* incomingAttr = valueCast<Attribute>(incoming);
 
-    if (!incomingIsAttr) {
-        if (incoming.isWeak()) {
+    if (!incomingAttr) {
+        if (incoming->isWeak()) {
             // We're trying to add a weak resource but a resource
             // already exists. Keep the existing.
             return -1;
-        } else if (existing.isWeak()) {
+        } else if (existing->isWeak()) {
             // Override the weak resource with the new strong resource.
             return 1;
         }
@@ -104,8 +148,8 @@
         return 0;
     }
 
-    if (!existingIsAttr) {
-        if (existing.isWeak()) {
+    if (!existingAttr) {
+        if (existing->isWeak()) {
             // The existing value is not an attribute and it is weak,
             // so take the incoming attribute value.
             return 1;
@@ -115,27 +159,27 @@
         return 0;
     }
 
+    assert(incomingAttr && existingAttr);
+
     //
     // Attribute specific handling. At this point we know both
     // values are attributes. Since we can declare and define
     // attributes all-over, we do special handling to see
     // which definition sticks.
     //
-    const Attribute& existingAttr = static_cast<const Attribute&>(existing);
-    const Attribute& incomingAttr = static_cast<const Attribute&>(incoming);
-    if (existingAttr.typeMask == incomingAttr.typeMask) {
+    if (existingAttr->typeMask == incomingAttr->typeMask) {
         // The two attributes are both DECLs, but they are plain attributes
         // with the same formats.
         // Keep the strongest one.
-        return existingAttr.isWeak() ? 1 : -1;
+        return existingAttr->isWeak() ? 1 : -1;
     }
 
-    if (existingAttr.isWeak() && existingAttr.typeMask == android::ResTable_map::TYPE_ANY) {
+    if (existingAttr->isWeak() && existingAttr->typeMask == android::ResTable_map::TYPE_ANY) {
         // Any incoming attribute is better than this.
         return 1;
     }
 
-    if (incomingAttr.isWeak() && incomingAttr.typeMask == android::ResTable_map::TYPE_ANY) {
+    if (incomingAttr->isWeak() && incomingAttr->typeMask == android::ResTable_map::TYPE_ANY) {
         // The incoming attribute may be a USE instead of a DECL.
         // Keep the existing attribute.
         return -1;
@@ -147,180 +191,183 @@
 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);
+                                const Source& source, std::unique_ptr<Value> value,
+                                IDiagnostics* diag) {
+    return addResourceImpl(name, ResourceId{}, config, source, std::move(value), kValidNameChars,
+                           diag);
 }
 
 bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId resId,
-                                const ConfigDescription& config, const SourceLine& source,
-                                std::unique_ptr<Value> value) {
-    return addResourceImpl(name, resId, config, source, std::move(value), kValidNameChars);
+                                const ConfigDescription& config, const Source& source,
+                                std::unique_ptr<Value> value, IDiagnostics* diag) {
+    return addResourceImpl(name, resId, config, source, std::move(value), kValidNameChars, diag);
+}
+
+bool ResourceTable::addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
+                                     const Source& source, const StringPiece16& path,
+                                     IDiagnostics* diag) {
+    return addResourceImpl(name, ResourceId{}, config, source,
+                           util::make_unique<FileReference>(stringPool.makeRef(path)),
+                           kValidNameChars, diag);
 }
 
 bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
                                             const ConfigDescription& config,
-                                            const SourceLine& source,
-                                            std::unique_ptr<Value> value) {
+                                            const Source& source,
+                                            std::unique_ptr<Value> value,
+                                            IDiagnostics* diag) {
     return addResourceImpl(name, ResourceId{}, config, source, std::move(value),
-                           kValidNameMangledChars);
+                           kValidNameMangledChars, diag);
 }
 
 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 '"
-                << name
-                << "' has incompatible package. Must be '"
-                << mPackage
-                << "'."
-                << std::endl;
-        return false;
-    }
-
+                                    const ConfigDescription& config, const Source& source,
+                                    std::unique_ptr<Value> value, const char16_t* validChars,
+                                    IDiagnostics* diag) {
     auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
     if (badCharIter != name.entry.end()) {
-        Logger::error(source)
-                << "resource '"
-                << name
-                << "' has invalid entry name '"
-                << name.entry
-                << "'. Invalid character '"
-                << StringPiece16(badCharIter, 1)
-                << "'."
-                << std::endl;
+        diag->error(DiagMessage(source)
+                    << "resource '"
+                    << name
+                    << "' has invalid entry name '"
+                    << name.entry
+                    << "'. Invalid character '"
+                    << StringPiece16(badCharIter, 1)
+                    << "'");
         return false;
     }
 
-    std::unique_ptr<ResourceTableType>& type = findOrCreateType(name.type);
-    if (resId.isValid() && type->typeId != ResourceTableType::kUnsetTypeId &&
-            type->typeId != resId.typeId()) {
-        Logger::error(source)
-                << "trying to add resource '"
-                << name
-                << "' with ID "
-                << resId
-                << " but type '"
-                << type->type
-                << "' already has ID "
-                << std::hex << type->typeId << std::dec
-                << "."
-                << std::endl;
+    ResourceTablePackage* package = findOrCreatePackage(name.package);
+    if (resId.isValid() && package->id && package->id.value() != resId.packageId()) {
+        diag->error(DiagMessage(source)
+                    << "trying to add resource '"
+                    << name
+                    << "' with ID "
+                    << resId
+                    << " but package '"
+                    << package->name
+                    << "' already has ID "
+                    << std::hex << (int) package->id.value() << std::dec);
         return false;
     }
 
-    std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, name.entry);
-    if (resId.isValid() && entry->entryId != ResourceEntry::kUnsetEntryId &&
-            entry->entryId != resId.entryId()) {
-        Logger::error(source)
-                << "trying to add resource '"
-                << name
-                << "' with ID "
-                << resId
-                << " but resource already has ID "
-                << ResourceId(mPackageId, type->typeId, entry->entryId)
-                << "."
-                << std::endl;
+    ResourceTableType* type = package->findOrCreateType(name.type);
+    if (resId.isValid() && type->id && type->id.value() != resId.typeId()) {
+        diag->error(DiagMessage(source)
+                    << "trying to add resource '"
+                    << name
+                    << "' with ID "
+                    << resId
+                    << " but type '"
+                    << type->type
+                    << "' already has ID "
+                    << std::hex << (int) type->id.value() << std::dec);
         return false;
     }
 
-    const auto endIter = std::end(entry->values);
-    auto iter = std::lower_bound(std::begin(entry->values), endIter, config, compareConfigs);
+    ResourceEntry* entry = type->findOrCreateEntry(name.entry);
+    if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) {
+        diag->error(DiagMessage(source)
+                    << "trying to add resource '"
+                    << name
+                    << "' with ID "
+                    << resId
+                    << " but resource already has ID "
+                    << ResourceId(package->id.value(), type->id.value(), entry->id.value()));
+        return false;
+    }
+
+    const auto endIter = entry->values.end();
+    auto iter = std::lower_bound(entry->values.begin(), endIter, config, compareConfigs);
     if (iter == endIter || iter->config != config) {
         // This resource did not exist before, add it.
         entry->values.insert(iter, ResourceConfigValue{ config, source, {}, std::move(value) });
     } else {
-        int collisionResult = defaultCollisionHandler(*iter->value, *value);
+        int collisionResult = resolveValueCollision(iter->value.get(), value.get());
         if (collisionResult > 0) {
             // Take the incoming value.
             *iter = ResourceConfigValue{ config, source, {}, std::move(value) };
         } else if (collisionResult == 0) {
-            Logger::error(source)
-                    << "duplicate value for resource '" << name << "' "
-                    << "with config '" << iter->config << "'."
-                    << std::endl;
-
-            Logger::error(iter->source)
-                    << "resource previously defined here."
-                    << std::endl;
+            diag->error(DiagMessage(source)
+                        << "duplicate value for resource '" << name << "' "
+                        << "with config '" << iter->config << "'");
+            diag->error(DiagMessage(iter->source)
+                        << "resource previously defined here");
             return false;
         }
     }
 
     if (resId.isValid()) {
-        type->typeId = resId.typeId();
-        entry->entryId = resId.entryId();
+        package->id = resId.packageId();
+        type->id = resId.typeId();
+        entry->id = resId.entryId();
     }
     return true;
 }
 
 bool ResourceTable::markPublic(const ResourceNameRef& name, const ResourceId resId,
-                               const SourceLine& source) {
-    return markPublicImpl(name, resId, source, kValidNameChars);
+                               const Source& source, IDiagnostics* diag) {
+    return markPublicImpl(name, resId, source, kValidNameChars, diag);
 }
 
 bool ResourceTable::markPublicAllowMangled(const ResourceNameRef& name, const ResourceId resId,
-                                           const SourceLine& source) {
-    return markPublicImpl(name, resId, source, kValidNameMangledChars);
+                                           const Source& source, IDiagnostics* diag) {
+    return markPublicImpl(name, resId, source, kValidNameMangledChars, diag);
 }
 
 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 '"
-                << name
-                << "' has incompatible package. Must be '"
-                << mPackage
-                << "'."
-            << std::endl;
-        return false;
-    }
-
+                                   const Source& source, const char16_t* validChars,
+                                   IDiagnostics* diag) {
     auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
     if (badCharIter != name.entry.end()) {
-        Logger::error(source)
-                << "resource '"
-                << name
-                << "' has invalid entry name '"
-                << name.entry
-                << "'. Invalid character '"
-                << StringPiece16(badCharIter, 1)
-                << "'."
-                << std::endl;
+        diag->error(DiagMessage(source)
+                    << "resource '"
+                    << name
+                    << "' has invalid entry name '"
+                    << name.entry
+                    << "'. Invalid character '"
+                    << StringPiece16(badCharIter, 1)
+                    << "'");
         return false;
     }
 
-    std::unique_ptr<ResourceTableType>& type = findOrCreateType(name.type);
-    if (resId.isValid() && type->typeId != ResourceTableType::kUnsetTypeId &&
-            type->typeId != resId.typeId()) {
-        Logger::error(source)
-                << "trying to make resource '"
-                << name
-                << "' public with ID "
-                << resId
-                << " but type '"
-                << type->type
-                << "' already has ID "
-                << std::hex << type->typeId << std::dec
-                << "."
-                << std::endl;
+    ResourceTablePackage* package = findOrCreatePackage(name.package);
+    if (resId.isValid() && package->id && package->id.value() != resId.packageId()) {
+        diag->error(DiagMessage(source)
+                    << "trying to add resource '"
+                    << name
+                    << "' with ID "
+                    << resId
+                    << " but package '"
+                    << package->name
+                    << "' already has ID "
+                    << std::hex << (int) package->id.value() << std::dec);
         return false;
     }
 
-    std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, name.entry);
-    if (resId.isValid() && entry->entryId != ResourceEntry::kUnsetEntryId &&
-            entry->entryId != resId.entryId()) {
-        Logger::error(source)
-                << "trying to make resource '"
-                << name
-                << "' public with ID "
-                << resId
-                << " but resource already has ID "
-                << ResourceId(mPackageId, type->typeId, entry->entryId)
-                << "."
-                << std::endl;
+    ResourceTableType* type = package->findOrCreateType(name.type);
+    if (resId.isValid() && type->id && type->id.value() != resId.typeId()) {
+        diag->error(DiagMessage(source)
+                    << "trying to add resource '"
+                    << name
+                    << "' with ID "
+                    << resId
+                    << " but type '"
+                    << type->type
+                    << "' already has ID "
+                    << std::hex << (int) type->id.value() << std::dec);
+        return false;
+    }
+
+    ResourceEntry* entry = type->findOrCreateEntry(name.entry);
+    if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) {
+        diag->error(DiagMessage(source)
+                    << "trying to add resource '"
+                    << name
+                    << "' with ID "
+                    << resId
+                    << " but resource already has ID "
+                    << ResourceId(package->id.value(), type->id.value(), entry->id.value()));
         return false;
     }
 
@@ -329,102 +376,30 @@
     entry->publicStatus.source = source;
 
     if (resId.isValid()) {
-        type->typeId = resId.typeId();
-        entry->entryId = resId.entryId();
+        package->id = resId.packageId();
+        type->id = resId.typeId();
+        entry->id = resId.entryId();
     }
     return true;
 }
 
-bool ResourceTable::merge(ResourceTable&& other) {
-    const bool mangleNames = mPackage != other.getPackage();
-    std::u16string mangledName;
-
-    for (auto& otherType : other) {
-        std::unique_ptr<ResourceTableType>& type = findOrCreateType(otherType->type);
-        if (otherType->publicStatus.isPublic) {
-            if (type->publicStatus.isPublic && type->typeId != otherType->typeId) {
-                Logger::error() << "can not merge type '" << type->type
-                                << "': conflicting public IDs "
-                                << "(" << type->typeId << " vs " << otherType->typeId << ")."
-                                << std::endl;
-                return false;
-            }
-            type->publicStatus = std::move(otherType->publicStatus);
-            type->typeId = otherType->typeId;
-        }
-
-        for (auto& otherEntry : otherType->entries) {
-            const std::u16string* nameToAdd = &otherEntry->name;
-            if (mangleNames) {
-                mangledName = otherEntry->name;
-                NameMangler::mangle(other.getPackage(), &mangledName);
-                nameToAdd = &mangledName;
-            }
-
-            std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, *nameToAdd);
-            if (otherEntry->publicStatus.isPublic) {
-                if (entry->publicStatus.isPublic && entry->entryId != otherEntry->entryId) {
-                    Logger::error() << "can not merge entry '" << type->type << "/" << entry->name
-                                    << "': conflicting public IDs "
-                                    << "(" << entry->entryId << " vs " << entry->entryId << ")."
-                                    << std::endl;
-                    return false;
-                }
-                entry->publicStatus = std::move(otherEntry->publicStatus);
-                entry->entryId = otherEntry->entryId;
-            }
-
-            for (ResourceConfigValue& otherValue : otherEntry->values) {
-                auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
-                                             otherValue.config, compareConfigs);
-                if (iter != entry->values.end() && iter->config == otherValue.config) {
-                    int collisionResult = defaultCollisionHandler(*iter->value, *otherValue.value);
-                    if (collisionResult > 0) {
-                        // Take the incoming value.
-                        iter->source = std::move(otherValue.source);
-                        iter->comment = std::move(otherValue.comment);
-                        iter->value = std::unique_ptr<Value>(otherValue.value->clone(&mValuePool));
-                    } else if (collisionResult == 0) {
-                        ResourceNameRef resourceName = { mPackage, type->type, entry->name };
-                        Logger::error(otherValue.source)
-                                << "resource '" << resourceName << "' has a conflicting value for "
-                                << "configuration (" << otherValue.config << ")."
-                                << std::endl;
-                        Logger::note(iter->source) << "originally defined here." << std::endl;
-                        return false;
-                    }
-                } else {
-                    entry->values.insert(iter, ResourceConfigValue{
-                            otherValue.config,
-                            std::move(otherValue.source),
-                            std::move(otherValue.comment),
-                            std::unique_ptr<Value>(otherValue.value->clone(&mValuePool)),
-                    });
-                }
-            }
-        }
-    }
-    return true;
-}
-
-std::tuple<const ResourceTableType*, const ResourceEntry*>
-ResourceTable::findResource(const ResourceNameRef& name) const {
-    if (name.package != mPackage) {
+Maybe<ResourceTable::SearchResult>
+ResourceTable::findResource(const ResourceNameRef& name) {
+    ResourceTablePackage* package = findPackage(name.package);
+    if (!package) {
         return {};
     }
 
-    auto iter = std::lower_bound(mTypes.begin(), mTypes.end(), name.type, lessThanType);
-    if (iter == mTypes.end() || (*iter)->type != name.type) {
+    ResourceTableType* type = package->findType(name.type);
+    if (!type) {
         return {};
     }
 
-    const std::unique_ptr<ResourceTableType>& type = *iter;
-    auto iter2 = std::lower_bound(type->entries.begin(), type->entries.end(), name.entry,
-                                  lessThanEntry);
-    if (iter2 == type->entries.end() || name.entry != (*iter2)->name) {
+    ResourceEntry* entry = type->findEntry(name.entry);
+    if (!entry) {
         return {};
     }
-    return std::make_tuple(iter->get(), iter2->get());
+    return SearchResult{ package, type, entry };
 }
 
 } // namespace aapt
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 706f56a..a00c142 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -18,6 +18,7 @@
 #define AAPT_RESOURCE_TABLE_H
 
 #include "ConfigDescription.h"
+#include "Diagnostics.h"
 #include "Resource.h"
 #include "ResourceValues.h"
 #include "Source.h"
@@ -35,7 +36,7 @@
  */
 struct Public {
     bool isPublic = false;
-    SourceLine source;
+    Source source;
     std::u16string comment;
 };
 
@@ -44,7 +45,7 @@
  */
 struct ResourceConfigValue {
     ConfigDescription config;
-    SourceLine source;
+    Source source;
     std::u16string comment;
     std::unique_ptr<Value> value;
 };
@@ -54,10 +55,6 @@
  * varying values for each defined configuration.
  */
 struct ResourceEntry {
-    enum {
-        kUnsetEntryId = 0xffffffffu
-    };
-
     /**
      * The name of the resource. Immutable, as
      * this determines the order of this resource
@@ -68,7 +65,7 @@
     /**
      * The entry ID for this resource.
      */
-    size_t entryId;
+    Maybe<uint16_t> id;
 
     /**
      * Whether this resource is public (and must maintain the same
@@ -81,8 +78,7 @@
      */
     std::vector<ResourceConfigValue> values;
 
-    inline ResourceEntry(const StringPiece16& _name);
-    inline ResourceEntry(const ResourceEntry* rhs);
+    ResourceEntry(const StringPiece16& name) : name(name.toString()) { }
 };
 
 /**
@@ -90,10 +86,6 @@
  * for this type.
  */
 struct ResourceTableType {
-    enum {
-        kUnsetTypeId = 0xffffffffu
-    };
-
     /**
      * The logical type of resource (string, drawable, layout, etc.).
      */
@@ -102,7 +94,7 @@
     /**
      * The type ID for this resource.
      */
-    size_t typeId;
+    Maybe<uint8_t> id;
 
     /**
      * Whether this type is public (and must maintain the same
@@ -115,8 +107,30 @@
      */
     std::vector<std::unique_ptr<ResourceEntry>> entries;
 
-    ResourceTableType(const ResourceType _type);
-    ResourceTableType(const ResourceTableType* rhs);
+    explicit ResourceTableType(const ResourceType type) : type(type) { }
+
+    ResourceEntry* findEntry(const StringPiece16& name);
+
+    ResourceEntry* findOrCreateEntry(const StringPiece16& name);
+};
+
+enum class PackageType {
+    System,
+    Vendor,
+    App,
+    Dynamic
+};
+
+struct ResourceTablePackage {
+    PackageType type = PackageType::App;
+    Maybe<uint8_t> id;
+    std::u16string name;
+
+    std::vector<std::unique_ptr<ResourceTableType>> types;
+
+    ResourceTableType* findType(ResourceType type);
+
+    ResourceTableType* findOrCreateType(const ResourceType type);
 };
 
 /**
@@ -125,23 +139,28 @@
  */
 class ResourceTable {
 public:
-    using iterator = std::vector<std::unique_ptr<ResourceTableType>>::iterator;
-    using const_iterator = std::vector<std::unique_ptr<ResourceTableType>>::const_iterator;
+    ResourceTable() = default;
+    ResourceTable(const ResourceTable&) = delete;
+    ResourceTable& operator=(const ResourceTable&) = delete;
 
-    enum {
-        kUnsetPackageId = 0xffffffff
-    };
-
-    ResourceTable();
-
-    size_t getPackageId() const;
-    void setPackageId(size_t packageId);
-
-    const std::u16string& getPackage() const;
-    void setPackage(const StringPiece16& package);
+    /**
+     * When a collision of resources occurs, this method decides which value to keep.
+     * Returns -1 if the existing value should be chosen.
+     * Returns 0 if the collision can not be resolved (error).
+     * Returns 1 if the incoming value should be chosen.
+     */
+    static int resolveValueCollision(Value* existing, Value* incoming);
 
     bool addResource(const ResourceNameRef& name, const ConfigDescription& config,
-                     const SourceLine& source, std::unique_ptr<Value> value);
+                     const Source& source, std::unique_ptr<Value> value,
+                     IDiagnostics* diag);
+
+    bool addResource(const ResourceNameRef& name, const ResourceId resId,
+                     const ConfigDescription& config, const Source& source,
+                     std::unique_ptr<Value> value, IDiagnostics* diag);
+
+    bool addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
+                          const Source& source, const StringPiece16& path, IDiagnostics* diag);
 
     /**
      * Same as addResource, but doesn't verify the validity of the name. This is used
@@ -149,129 +168,59 @@
      * names.
      */
     bool addResourceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config,
-                                 const SourceLine& source, std::unique_ptr<Value> value);
+                                 const Source& source, std::unique_ptr<Value> value,
+                                 IDiagnostics* diag);
 
-    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 markPublic(const ResourceNameRef& name, const ResourceId resId, const Source& source,
+                    IDiagnostics* diag);
     bool markPublicAllowMangled(const ResourceNameRef& name, const ResourceId resId,
-                                const SourceLine& source);
+                                const Source& source, IDiagnostics* diag);
+    struct SearchResult {
+	ResourceTablePackage* package;
+	ResourceTableType* type;
+	ResourceEntry* entry;
+    };
 
-    /*
-     * Merges the resources from `other` into this table, mangling the names of the resources
-     * if `other` has a different package name.
-     */
-    bool merge(ResourceTable&& other);
+    Maybe<SearchResult> findResource(const ResourceNameRef& name);
 
     /**
-     * Returns the string pool used by this ResourceTable.
-     * Values that reference strings should use this pool to create
-     * their strings.
+     * The string pool used by this resource table. Values that reference strings must use
+     * this pool to create their strings.
+     *
+     * NOTE: `stringPool` must come before `packages` so that it is destroyed after.
+     * When `string pool` references are destroyed (as they will be when `packages`
+     * is destroyed), they decrement a refCount, which would cause invalid
+     * memory access if the pool was already destroyed.
      */
-    StringPool& getValueStringPool();
-    const StringPool& getValueStringPool() const;
+    StringPool stringPool;
 
-    std::tuple<const ResourceTableType*, const ResourceEntry*>
-    findResource(const ResourceNameRef& name) const;
+    /**
+     * The list of packages in this table, sorted alphabetically by package name.
+     */
+    std::vector<std::unique_ptr<ResourceTablePackage>> packages;
 
-    iterator begin();
-    iterator end();
-    const_iterator begin() const;
-    const_iterator end() const;
+    /**
+     * Returns the package struct with the given name, or nullptr if such a package does not
+     * exist. The empty string is a valid package and typically is used to represent the
+     * 'current' package before it is known to the ResourceTable.
+     */
+    ResourceTablePackage* findPackage(const StringPiece16& name);
+
+    ResourceTablePackage* findPackageById(uint8_t id);
+
+    ResourceTablePackage* createPackage(const StringPiece16& name, uint8_t id);
 
 private:
-    std::unique_ptr<ResourceTableType>& findOrCreateType(ResourceType type);
-    std::unique_ptr<ResourceEntry>& findOrCreateEntry(std::unique_ptr<ResourceTableType>& type,
-                                                      const StringPiece16& name);
+    ResourceTablePackage* findOrCreatePackage(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);
+                         const ConfigDescription& config, const Source& source,
+                         std::unique_ptr<Value> value, const char16_t* validChars,
+                         IDiagnostics* diag);
     bool markPublicImpl(const ResourceNameRef& name, const ResourceId resId,
-                        const SourceLine& source, const char16_t* validChars);
-
-    std::u16string mPackage;
-    size_t mPackageId;
-
-    // StringPool must come before mTypes so that it is destroyed after.
-    // When StringPool references are destroyed (as they will be when mTypes
-    // is destroyed), they decrement a refCount, which would cause invalid
-    // memory access if the pool was already destroyed.
-    StringPool mValuePool;
-
-    std::vector<std::unique_ptr<ResourceTableType>> mTypes;
+                        const Source& source, const char16_t* validChars, IDiagnostics* diag);
 };
 
-//
-// ResourceEntry implementation.
-//
-
-inline ResourceEntry::ResourceEntry(const StringPiece16& _name) :
-        name(_name.toString()), entryId(kUnsetEntryId) {
-}
-
-inline ResourceEntry::ResourceEntry(const ResourceEntry* rhs) :
-        name(rhs->name), entryId(rhs->entryId), publicStatus(rhs->publicStatus) {
-}
-
-//
-// ResourceTableType implementation.
-//
-
-inline ResourceTableType::ResourceTableType(const ResourceType _type) :
-        type(_type), typeId(kUnsetTypeId) {
-}
-
-inline ResourceTableType::ResourceTableType(const ResourceTableType* rhs) :
-        type(rhs->type), typeId(rhs->typeId), publicStatus(rhs->publicStatus) {
-}
-
-//
-// ResourceTable implementation.
-//
-
-inline StringPool& ResourceTable::getValueStringPool() {
-    return mValuePool;
-}
-
-inline const StringPool& ResourceTable::getValueStringPool() const {
-    return mValuePool;
-}
-
-inline ResourceTable::iterator ResourceTable::begin() {
-    return mTypes.begin();
-}
-
-inline ResourceTable::iterator ResourceTable::end() {
-    return mTypes.end();
-}
-
-inline ResourceTable::const_iterator ResourceTable::begin() const {
-    return mTypes.begin();
-}
-
-inline ResourceTable::const_iterator ResourceTable::end() const {
-    return mTypes.end();
-}
-
-inline const std::u16string& ResourceTable::getPackage() const {
-    return mPackage;
-}
-
-inline size_t ResourceTable::getPackageId() const {
-    return mPackageId;
-}
-
-inline void ResourceTable::setPackage(const StringPiece16& package) {
-    mPackage = package.toString();
-}
-
-inline void ResourceTable::setPackageId(size_t packageId) {
-    mPackageId = packageId;
-}
-
 } // namespace aapt
 
 #endif // AAPT_RESOURCE_TABLE_H
diff --git a/tools/aapt2/ResourceTableResolver.cpp b/tools/aapt2/ResourceTableResolver.cpp
deleted file mode 100644
index 910c2c0..0000000
--- a/tools/aapt2/ResourceTableResolver.cpp
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * 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 "Maybe.h"
-#include "NameMangler.h"
-#include "Resource.h"
-#include "ResourceTable.h"
-#include "ResourceTableResolver.h"
-#include "ResourceValues.h"
-#include "Util.h"
-
-#include <androidfw/AssetManager.h>
-#include <androidfw/ResourceTypes.h>
-#include <memory>
-#include <vector>
-
-namespace aapt {
-
-ResourceTableResolver::ResourceTableResolver(
-        std::shared_ptr<const ResourceTable> table,
-        const std::vector<std::shared_ptr<const android::AssetManager>>& sources) :
-        mTable(table), mSources(sources) {
-    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));
-        }
-    }
-}
-
-Maybe<ResourceId> ResourceTableResolver::findId(const ResourceName& name) {
-    Maybe<Entry> result = findAttribute(name);
-    if (result) {
-        return result.value().id;
-    }
-    return {};
-}
-
-Maybe<IResolver::Entry> ResourceTableResolver::findAttribute(const ResourceName& name) {
-    auto cacheIter = mCache.find(name);
-    if (cacheIter != std::end(mCache)) {
-        return Entry{ cacheIter->second.id, cacheIter->second.attr.get() };
-    }
-
-    ResourceName mangledName;
-    const ResourceName* nameToSearch = &name;
-    if (name.package != mTable->getPackage()) {
-        // This may be a reference to an included resource or
-        // to a mangled resource.
-        if (mIncludedPackages.find(name.package) == mIncludedPackages.end()) {
-            // This is not in our included set, so mangle the name and
-            // check for that.
-            mangledName.entry = name.entry;
-            NameMangler::mangle(name.package, &mangledName.entry);
-            mangledName.package = mTable->getPackage();
-            mangledName.type = name.type;
-            nameToSearch = &mangledName;
-        } else {
-            const CacheEntry* cacheEntry = buildCacheEntry(name);
-            if (cacheEntry) {
-                return Entry{ cacheEntry->id, cacheEntry->attr.get() };
-            }
-            return {};
-        }
-    }
-
-    const ResourceTableType* type;
-    const ResourceEntry* entry;
-    std::tie(type, entry) = mTable->findResource(*nameToSearch);
-    if (type && entry) {
-        Entry result = {};
-        if (mTable->getPackageId() != ResourceTable::kUnsetPackageId &&
-                type->typeId != ResourceTableType::kUnsetTypeId &&
-                entry->entryId != ResourceEntry::kUnsetEntryId) {
-            result.id = ResourceId(mTable->getPackageId(), type->typeId, entry->entryId);
-        }
-
-        if (!entry->values.empty()) {
-            visitFunc<Attribute>(*entry->values.front().value, [&result](Attribute& attr) {
-                    result.attr = &attr;
-            });
-        }
-        return result;
-    }
-    return {};
-}
-
-Maybe<ResourceName> ResourceTableResolver::findName(ResourceId resId) {
-    for (const auto& assetManager : mSources) {
-        const android::ResTable& table = assetManager->getResources(false);
-
-        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 } };
-    }
-    return {};
-}
-
-/**
- * This is called when we need to lookup a resource name in the AssetManager.
- * Since the values in the AssetManager are not parsed like in a ResourceTable,
- * we must create Attribute objects here if we find them.
- */
-const ResourceTableResolver::CacheEntry* ResourceTableResolver::buildCacheEntry(
-        const ResourceName& name) {
-    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())
-        };
-
-        if (!resId.isValid()) {
-            continue;
-        }
-
-        CacheEntry& entry = mCache[name];
-        entry.id = resId;
-
-        //
-        // 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) {
-            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
deleted file mode 100644
index 8f6b0b5..0000000
--- a/tools/aapt2/ResourceTableResolver.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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_RESOURCE_TABLE_RESOLVER_H
-#define AAPT_RESOURCE_TABLE_RESOLVER_H
-
-#include "Maybe.h"
-#include "Resolver.h"
-#include "Resource.h"
-#include "ResourceTable.h"
-#include "ResourceValues.h"
-
-#include <androidfw/AssetManager.h>
-#include <memory>
-#include <vector>
-#include <unordered_set>
-
-namespace aapt {
-
-/**
- * Encapsulates the search of library sources as well as the local ResourceTable.
- */
-class ResourceTableResolver : public IResolver {
-public:
-    /**
-     * Creates a resolver with a local ResourceTable and an AssetManager
-     * loaded with library packages.
-     */
-    ResourceTableResolver(
-            std::shared_ptr<const ResourceTable> table,
-            const std::vector<std::shared_ptr<const android::AssetManager>>& sources);
-
-    ResourceTableResolver(const ResourceTableResolver&) = delete; // Not copyable.
-
-    virtual Maybe<ResourceId> findId(const ResourceName& name) override;
-
-    virtual Maybe<Entry> findAttribute(const ResourceName& name) override;
-
-    virtual Maybe<ResourceName> findName(ResourceId resId) override;
-
-private:
-    struct CacheEntry {
-        ResourceId id;
-        std::unique_ptr<Attribute> attr;
-    };
-
-    const CacheEntry* buildCacheEntry(const ResourceName& name);
-
-    std::shared_ptr<const ResourceTable> mTable;
-    std::vector<std::shared_ptr<const android::AssetManager>> mSources;
-    std::map<ResourceName, CacheEntry> mCache;
-    std::unordered_set<std::u16string> mIncludedPackages;
-};
-
-} // namespace aapt
-
-#endif // AAPT_RESOURCE_TABLE_RESOLVER_H
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index 06d8699..2055a80 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -14,9 +14,12 @@
  * limitations under the License.
  */
 
+#include "Diagnostics.h"
 #include "ResourceTable.h"
 #include "ResourceValues.h"
-#include "Util.h"
+#include "util/Util.h"
+
+#include "test/Common.h"
 
 #include <algorithm>
 #include <gtest/gtest.h>
@@ -25,204 +28,90 @@
 
 namespace aapt {
 
-struct TestValue : public Value {
-    std::u16string value;
+struct ResourceTableTest : public ::testing::Test {
+    struct EmptyDiagnostics : public IDiagnostics {
+        void error(const DiagMessage& msg) override {}
+        void warn(const DiagMessage& msg) override {}
+        void note(const DiagMessage& msg) override {}
+    };
 
-    TestValue(StringPiece16 str) : value(str.toString()) {
-    }
-
-    TestValue* clone(StringPool* /*newPool*/) const override {
-        return new TestValue(value);
-    }
-
-    void print(std::ostream& out) const override {
-        out << "(test) " << value;
-    }
-
-    virtual void accept(ValueVisitor&, ValueVisitorArgs&&) override {}
-    virtual void accept(ConstValueVisitor&, ValueVisitorArgs&&) const override {}
+    EmptyDiagnostics mDiagnostics;
 };
 
-struct TestWeakValue : public Value {
-    bool isWeak() const override {
-        return true;
-    }
-
-    TestWeakValue* clone(StringPool* /*newPool*/) const override {
-        return new TestWeakValue();
-    }
-
-    void print(std::ostream& out) const override {
-        out << "(test) [weak]";
-    }
-
-    virtual void accept(ValueVisitor&, ValueVisitorArgs&&) override {}
-    virtual void accept(ConstValueVisitor&, ValueVisitorArgs&&) const override {}
-};
-
-TEST(ResourceTableTest, FailToAddResourceWithBadName) {
+TEST_F(ResourceTableTest, FailToAddResourceWithBadName) {
     ResourceTable table;
-    table.setPackage(u"android");
 
     EXPECT_FALSE(table.addResource(
             ResourceNameRef{ u"android", ResourceType::kId, u"hey,there" },
-            {}, SourceLine{ "test.xml", 21 },
-            util::make_unique<TestValue>(u"rawValue")));
+            {}, Source{ "test.xml", 21 },
+            util::make_unique<Id>(), &mDiagnostics));
 
     EXPECT_FALSE(table.addResource(
             ResourceNameRef{ u"android", ResourceType::kId, u"hey:there" },
-            {}, SourceLine{ "test.xml", 21 },
-            util::make_unique<TestValue>(u"rawValue")));
+            {}, Source{ "test.xml", 21 },
+            util::make_unique<Id>(), &mDiagnostics));
 }
 
-TEST(ResourceTableTest, AddOneResource) {
-    const std::u16string kAndroidPackage = u"android";
-
+TEST_F(ResourceTableTest, AddOneResource) {
     ResourceTable table;
-    table.setPackage(kAndroidPackage);
 
-    const ResourceName name = { kAndroidPackage, ResourceType::kAttr, u"id" };
+    EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/id"), {},
+                                  Source{ "test/path/file.xml", 23 },
+                                  util::make_unique<Id>(), &mDiagnostics));
 
-    EXPECT_TRUE(table.addResource(name, {}, SourceLine{ "test/path/file.xml", 23 },
-                                  util::make_unique<TestValue>(u"rawValue")));
-
-    const ResourceTableType* type;
-    const ResourceEntry* entry;
-    std::tie(type, entry) = table.findResource(name);
-    ASSERT_NE(nullptr, type);
-    ASSERT_NE(nullptr, entry);
-    EXPECT_EQ(name.entry, entry->name);
-
-    ASSERT_NE(std::end(entry->values),
-              std::find_if(std::begin(entry->values), std::end(entry->values),
-                      [](const ResourceConfigValue& val) -> bool {
-                          return val.config == ConfigDescription{};
-                      }));
+    ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id"));
 }
 
-TEST(ResourceTableTest, AddMultipleResources) {
-    const std::u16string kAndroidPackage = u"android";
+TEST_F(ResourceTableTest, AddMultipleResources) {
     ResourceTable table;
-    table.setPackage(kAndroidPackage);
 
     ConfigDescription config;
     ConfigDescription languageConfig;
     memcpy(languageConfig.language, "pl", sizeof(languageConfig.language));
 
     EXPECT_TRUE(table.addResource(
-            ResourceName{ kAndroidPackage, ResourceType::kAttr, u"layout_width" },
-            config, SourceLine{ "test/path/file.xml", 10 },
-            util::make_unique<TestValue>(u"rawValue")));
+            test::parseNameOrDie(u"@android:attr/layout_width"),
+            config, Source{ "test/path/file.xml", 10 },
+            util::make_unique<Id>(), &mDiagnostics));
 
     EXPECT_TRUE(table.addResource(
-            ResourceName{ kAndroidPackage, ResourceType::kAttr, u"id" },
-            config, SourceLine{ "test/path/file.xml", 12 },
-            util::make_unique<TestValue>(u"rawValue")));
+            test::parseNameOrDie(u"@android:attr/id"),
+            config, Source{ "test/path/file.xml", 12 },
+            util::make_unique<Id>(), &mDiagnostics));
 
     EXPECT_TRUE(table.addResource(
-            ResourceName{ kAndroidPackage, ResourceType::kString, u"ok" },
-            config, SourceLine{ "test/path/file.xml", 14 },
-            util::make_unique<TestValue>(u"Ok")));
+            test::parseNameOrDie(u"@android:string/ok"),
+            config, Source{ "test/path/file.xml", 14 },
+            util::make_unique<Id>(), &mDiagnostics));
 
     EXPECT_TRUE(table.addResource(
-            ResourceName{ kAndroidPackage, ResourceType::kString, u"ok" },
-            languageConfig, SourceLine{ "test/path/file.xml", 20 },
-            util::make_unique<TestValue>(u"Tak")));
+            test::parseNameOrDie(u"@android:string/ok"),
+            languageConfig, Source{ "test/path/file.xml", 20 },
+            util::make_unique<BinaryPrimitive>(android::Res_value{}), &mDiagnostics));
 
-    const auto endTypeIter = std::end(table);
-    auto typeIter = std::begin(table);
-
-    ASSERT_NE(endTypeIter, typeIter);
-    EXPECT_EQ(ResourceType::kAttr, (*typeIter)->type);
-
-    {
-        const std::unique_ptr<ResourceTableType>& type = *typeIter;
-        const auto endEntryIter = std::end(type->entries);
-        auto entryIter = std::begin(type->entries);
-        ASSERT_NE(endEntryIter, entryIter);
-        EXPECT_EQ(std::u16string(u"id"), (*entryIter)->name);
-
-        ++entryIter;
-        ASSERT_NE(endEntryIter, entryIter);
-        EXPECT_EQ(std::u16string(u"layout_width"), (*entryIter)->name);
-
-        ++entryIter;
-        ASSERT_EQ(endEntryIter, entryIter);
-    }
-
-    ++typeIter;
-    ASSERT_NE(endTypeIter, typeIter);
-    EXPECT_EQ(ResourceType::kString, (*typeIter)->type);
-
-    {
-        const std::unique_ptr<ResourceTableType>& type = *typeIter;
-        const auto endEntryIter = std::end(type->entries);
-        auto entryIter = std::begin(type->entries);
-        ASSERT_NE(endEntryIter, entryIter);
-        EXPECT_EQ(std::u16string(u"ok"), (*entryIter)->name);
-
-        {
-            const std::unique_ptr<ResourceEntry>& entry = *entryIter;
-            const auto endConfigIter = std::end(entry->values);
-            auto configIter = std::begin(entry->values);
-
-            ASSERT_NE(endConfigIter, configIter);
-            EXPECT_EQ(config, configIter->config);
-            const TestValue* value =
-                    dynamic_cast<const TestValue*>(configIter->value.get());
-            ASSERT_NE(nullptr, value);
-            EXPECT_EQ(std::u16string(u"Ok"), value->value);
-
-            ++configIter;
-            ASSERT_NE(endConfigIter, configIter);
-            EXPECT_EQ(languageConfig, configIter->config);
-            EXPECT_NE(nullptr, configIter->value);
-
-            value = dynamic_cast<const TestValue*>(configIter->value.get());
-            ASSERT_NE(nullptr, value);
-            EXPECT_EQ(std::u16string(u"Tak"), value->value);
-
-            ++configIter;
-            EXPECT_EQ(endConfigIter, configIter);
-        }
-
-        ++entryIter;
-        ASSERT_EQ(endEntryIter, entryIter);
-    }
-
-    ++typeIter;
-    EXPECT_EQ(endTypeIter, typeIter);
+    ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/layout_width"));
+    ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id"));
+    ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:string/ok"));
+    ASSERT_NE(nullptr, test::getValueForConfig<BinaryPrimitive>(&table, u"@android:string/ok",
+                                                                languageConfig));
 }
 
-TEST(ResourceTableTest, OverrideWeakResourceValue) {
-    const std::u16string kAndroid = u"android";
-
+TEST_F(ResourceTableTest, OverrideWeakResourceValue) {
     ResourceTable table;
-    table.setPackage(kAndroid);
-    table.setPackageId(0x01);
 
-    ASSERT_TRUE(table.addResource(
-            ResourceName{ kAndroid, ResourceType::kAttr, u"foo" },
-            {}, {}, util::make_unique<TestWeakValue>()));
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), {}, {},
+                                  util::make_unique<Attribute>(true), &mDiagnostics));
 
-    const ResourceTableType* type;
-    const ResourceEntry* entry;
-    std::tie(type, entry) = table.findResource(
-            ResourceNameRef{ kAndroid, ResourceType::kAttr, u"foo" });
-    ASSERT_NE(nullptr, type);
-    ASSERT_NE(nullptr, entry);
-    ASSERT_EQ(entry->values.size(), 1u);
-    EXPECT_TRUE(entry->values.front().value->isWeak());
+    Attribute* attr = test::getValue<Attribute>(&table, u"@android:attr/foo");
+    ASSERT_NE(nullptr, attr);
+    EXPECT_TRUE(attr->isWeak());
 
-    ASSERT_TRUE(table.addResource(ResourceName{ kAndroid, ResourceType::kAttr, u"foo" }, {}, {},
-                                  util::make_unique<TestValue>(u"bar")));
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), {}, {},
+                                  util::make_unique<Attribute>(false), &mDiagnostics));
 
-    std::tie(type, entry) = table.findResource(
-            ResourceNameRef{ kAndroid, ResourceType::kAttr, u"foo" });
-    ASSERT_NE(nullptr, type);
-    ASSERT_NE(nullptr, entry);
-    ASSERT_EQ(entry->values.size(), 1u);
-    EXPECT_FALSE(entry->values.front().value->isWeak());
+    attr = test::getValue<Attribute>(&table, u"@android:attr/foo");
+    ASSERT_NE(nullptr, attr);
+    EXPECT_FALSE(attr->isWeak());
 }
 
 } // namespace aapt
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
new file mode 100644
index 0000000..0db1c37
--- /dev/null
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -0,0 +1,481 @@
+/*
+ * 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 "ResourceUtils.h"
+#include "util/Util.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <sstream>
+
+namespace aapt {
+namespace ResourceUtils {
+
+void extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
+                         StringPiece16* outType, StringPiece16* outEntry) {
+    const char16_t* start = str.data();
+    const char16_t* end = start + str.size();
+    const char16_t* current = start;
+    while (current != end) {
+        if (outType->size() == 0 && *current == u'/') {
+            outType->assign(start, current - start);
+            start = current + 1;
+        } else if (outPackage->size() == 0 && *current == u':') {
+            outPackage->assign(start, current - start);
+            start = current + 1;
+        }
+        current++;
+    }
+    outEntry->assign(start, end - start);
+}
+
+bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool* outCreate,
+                       bool* outPrivate) {
+    StringPiece16 trimmedStr(util::trimWhitespace(str));
+    if (trimmedStr.empty()) {
+        return false;
+    }
+
+    bool create = false;
+    bool priv = false;
+    if (trimmedStr.data()[0] == u'@') {
+        size_t offset = 1;
+        if (trimmedStr.data()[1] == u'+') {
+            create = true;
+            offset += 1;
+        } else if (trimmedStr.data()[1] == u'*') {
+            priv = true;
+            offset += 1;
+        }
+        StringPiece16 package;
+        StringPiece16 type;
+        StringPiece16 entry;
+        extractResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset), &package, &type,
+                            &entry);
+
+        const ResourceType* parsedType = parseResourceType(type);
+        if (!parsedType) {
+            return false;
+        }
+
+        if (create && *parsedType != ResourceType::kId) {
+            return false;
+        }
+
+        outRef->package = package;
+        outRef->type = *parsedType;
+        outRef->entry = entry;
+        if (outCreate) {
+            *outCreate = create;
+        }
+        if (outPrivate) {
+            *outPrivate = priv;
+        }
+        return true;
+    }
+    return false;
+}
+
+bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outRef) {
+    StringPiece16 trimmedStr(util::trimWhitespace(str));
+    if (trimmedStr.empty()) {
+        return false;
+    }
+
+    if (*trimmedStr.data() == u'?') {
+        StringPiece16 package;
+        StringPiece16 type;
+        StringPiece16 entry;
+        extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1), &package, &type, &entry);
+
+        if (!type.empty() && type != u"attr") {
+            return false;
+        }
+
+        outRef->package = package;
+        outRef->type = ResourceType::kAttr;
+        outRef->entry = entry;
+        return true;
+    }
+    return false;
+}
+
+/*
+ * Style parent's are a bit different. We accept the following formats:
+ *
+ * @[package:]style/<entry>
+ * ?[package:]style/<entry>
+ * <package>:[style/]<entry>
+ * [package:style/]<entry>
+ */
+Maybe<Reference> parseStyleParentReference(const StringPiece16& str, std::string* outError) {
+    if (str.empty()) {
+        return {};
+    }
+
+    StringPiece16 name = str;
+
+    bool hasLeadingIdentifiers = false;
+    bool privateRef = false;
+
+    // Skip over these identifiers. A style's parent is a normal reference.
+    if (name.data()[0] == u'@' || name.data()[0] == u'?') {
+        hasLeadingIdentifiers = true;
+        name = name.substr(1, name.size() - 1);
+        if (name.data()[0] == u'*') {
+            privateRef = true;
+            name = name.substr(1, name.size() - 1);
+        }
+    }
+
+    ResourceNameRef ref;
+    ref.type = ResourceType::kStyle;
+
+    StringPiece16 typeStr;
+    extractResourceName(name, &ref.package, &typeStr, &ref.entry);
+    if (!typeStr.empty()) {
+        // If we have a type, make sure it is a Style.
+        const ResourceType* parsedType = parseResourceType(typeStr);
+        if (!parsedType || *parsedType != ResourceType::kStyle) {
+            std::stringstream err;
+            err << "invalid resource type '" << typeStr << "' for parent of style";
+            *outError = err.str();
+            return {};
+        }
+    } else {
+        // No type was defined, this should not have a leading identifier.
+        if (hasLeadingIdentifiers) {
+            std::stringstream err;
+            err << "invalid parent reference '" << str << "'";
+            *outError = err.str();
+            return {};
+        }
+    }
+
+    if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) {
+        std::stringstream err;
+        err << "invalid parent reference '" << str << "'";
+        *outError = err.str();
+        return {};
+    }
+
+    Reference result(ref);
+    result.privateReference = privateRef;
+    return result;
+}
+
+std::unique_ptr<Reference> tryParseReference(const StringPiece16& str, bool* outCreate) {
+    ResourceNameRef ref;
+    bool privateRef = false;
+    if (tryParseReference(str, &ref, outCreate, &privateRef)) {
+        std::unique_ptr<Reference> value = util::make_unique<Reference>(ref);
+        value->privateReference = privateRef;
+        return value;
+    }
+
+    if (tryParseAttributeReference(str, &ref)) {
+        if (outCreate) {
+            *outCreate = false;
+        }
+        return util::make_unique<Reference>(ref, Reference::Type::kAttribute);
+    }
+    return {};
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str) {
+    StringPiece16 trimmedStr(util::trimWhitespace(str));
+    android::Res_value value = { };
+    if (trimmedStr == u"@null") {
+        // TYPE_NULL with data set to 0 is interpreted by the runtime as an error.
+        // Instead we set the data type to TYPE_REFERENCE with a value of 0.
+        value.dataType = android::Res_value::TYPE_REFERENCE;
+    } else if (trimmedStr == u"@empty") {
+        // TYPE_NULL with value of DATA_NULL_EMPTY is handled fine by the runtime.
+        value.dataType = android::Res_value::TYPE_NULL;
+        value.data = android::Res_value::DATA_NULL_EMPTY;
+    } else {
+        return {};
+    }
+    return util::make_unique<BinaryPrimitive>(value);
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr,
+                                                    const StringPiece16& str) {
+    StringPiece16 trimmedStr(util::trimWhitespace(str));
+    for (const Attribute::Symbol& symbol : enumAttr->symbols) {
+        // Enum symbols are stored as @package:id/symbol resources,
+        // so we need to match against the 'entry' part of the identifier.
+        const ResourceName& enumSymbolResourceName = symbol.symbol.name.value();
+        if (trimmedStr == enumSymbolResourceName.entry) {
+            android::Res_value value = { };
+            value.dataType = android::Res_value::TYPE_INT_DEC;
+            value.data = symbol.value;
+            return util::make_unique<BinaryPrimitive>(value);
+        }
+    }
+    return {};
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* flagAttr,
+                                                    const StringPiece16& str) {
+    android::Res_value flags = { };
+    flags.dataType = android::Res_value::TYPE_INT_DEC;
+
+    for (StringPiece16 part : util::tokenize(str, u'|')) {
+        StringPiece16 trimmedPart = util::trimWhitespace(part);
+
+        bool flagSet = false;
+        for (const Attribute::Symbol& symbol : flagAttr->symbols) {
+            // Flag symbols are stored as @package:id/symbol resources,
+            // so we need to match against the 'entry' part of the identifier.
+            const ResourceName& flagSymbolResourceName = symbol.symbol.name.value();
+            if (trimmedPart == flagSymbolResourceName.entry) {
+                flags.data |= symbol.value;
+                flagSet = true;
+                break;
+            }
+        }
+
+        if (!flagSet) {
+            return {};
+        }
+    }
+    return util::make_unique<BinaryPrimitive>(flags);
+}
+
+static uint32_t parseHex(char16_t c, bool* outError) {
+    if (c >= u'0' && c <= u'9') {
+        return c - u'0';
+    } else if (c >= u'a' && c <= u'f') {
+        return c - u'a' + 0xa;
+    } else if (c >= u'A' && c <= u'F') {
+        return c - u'A' + 0xa;
+    } else {
+        *outError = true;
+        return 0xffffffffu;
+    }
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str) {
+    StringPiece16 colorStr(util::trimWhitespace(str));
+    const char16_t* start = colorStr.data();
+    const size_t len = colorStr.size();
+    if (len == 0 || start[0] != u'#') {
+        return {};
+    }
+
+    android::Res_value value = { };
+    bool error = false;
+    if (len == 4) {
+        value.dataType = android::Res_value::TYPE_INT_COLOR_RGB4;
+        value.data = 0xff000000u;
+        value.data |= parseHex(start[1], &error) << 20;
+        value.data |= parseHex(start[1], &error) << 16;
+        value.data |= parseHex(start[2], &error) << 12;
+        value.data |= parseHex(start[2], &error) << 8;
+        value.data |= parseHex(start[3], &error) << 4;
+        value.data |= parseHex(start[3], &error);
+    } else if (len == 5) {
+        value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4;
+        value.data |= parseHex(start[1], &error) << 28;
+        value.data |= parseHex(start[1], &error) << 24;
+        value.data |= parseHex(start[2], &error) << 20;
+        value.data |= parseHex(start[2], &error) << 16;
+        value.data |= parseHex(start[3], &error) << 12;
+        value.data |= parseHex(start[3], &error) << 8;
+        value.data |= parseHex(start[4], &error) << 4;
+        value.data |= parseHex(start[4], &error);
+    } else if (len == 7) {
+        value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8;
+        value.data = 0xff000000u;
+        value.data |= parseHex(start[1], &error) << 20;
+        value.data |= parseHex(start[2], &error) << 16;
+        value.data |= parseHex(start[3], &error) << 12;
+        value.data |= parseHex(start[4], &error) << 8;
+        value.data |= parseHex(start[5], &error) << 4;
+        value.data |= parseHex(start[6], &error);
+    } else if (len == 9) {
+        value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8;
+        value.data |= parseHex(start[1], &error) << 28;
+        value.data |= parseHex(start[2], &error) << 24;
+        value.data |= parseHex(start[3], &error) << 20;
+        value.data |= parseHex(start[4], &error) << 16;
+        value.data |= parseHex(start[5], &error) << 12;
+        value.data |= parseHex(start[6], &error) << 8;
+        value.data |= parseHex(start[7], &error) << 4;
+        value.data |= parseHex(start[8], &error);
+    } else {
+        return {};
+    }
+    return error ? std::unique_ptr<BinaryPrimitive>() : util::make_unique<BinaryPrimitive>(value);
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str) {
+    StringPiece16 trimmedStr(util::trimWhitespace(str));
+    uint32_t data = 0;
+    if (trimmedStr == u"true" || trimmedStr == u"TRUE") {
+        data = 0xffffffffu;
+    } else if (trimmedStr != u"false" && trimmedStr != u"FALSE") {
+        return {};
+    }
+    android::Res_value value = { };
+    value.dataType = android::Res_value::TYPE_INT_BOOLEAN;
+    value.data = data;
+    return util::make_unique<BinaryPrimitive>(value);
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str) {
+    android::Res_value value;
+    if (!android::ResTable::stringToInt(str.data(), str.size(), &value)) {
+        return {};
+    }
+    return util::make_unique<BinaryPrimitive>(value);
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str) {
+    android::Res_value value;
+    if (!android::ResTable::stringToFloat(str.data(), str.size(), &value)) {
+        return {};
+    }
+    return util::make_unique<BinaryPrimitive>(value);
+}
+
+uint32_t androidTypeToAttributeTypeMask(uint16_t type) {
+    switch (type) {
+    case android::Res_value::TYPE_NULL:
+    case android::Res_value::TYPE_REFERENCE:
+    case android::Res_value::TYPE_ATTRIBUTE:
+    case android::Res_value::TYPE_DYNAMIC_REFERENCE:
+        return android::ResTable_map::TYPE_REFERENCE;
+
+    case android::Res_value::TYPE_STRING:
+        return android::ResTable_map::TYPE_STRING;
+
+    case android::Res_value::TYPE_FLOAT:
+        return android::ResTable_map::TYPE_FLOAT;
+
+    case android::Res_value::TYPE_DIMENSION:
+        return android::ResTable_map::TYPE_DIMENSION;
+
+    case android::Res_value::TYPE_FRACTION:
+        return android::ResTable_map::TYPE_FRACTION;
+
+    case android::Res_value::TYPE_INT_DEC:
+    case android::Res_value::TYPE_INT_HEX:
+        return android::ResTable_map::TYPE_INTEGER | android::ResTable_map::TYPE_ENUM
+                | android::ResTable_map::TYPE_FLAGS;
+
+    case android::Res_value::TYPE_INT_BOOLEAN:
+        return android::ResTable_map::TYPE_BOOLEAN;
+
+    case android::Res_value::TYPE_INT_COLOR_ARGB8:
+    case android::Res_value::TYPE_INT_COLOR_RGB8:
+    case android::Res_value::TYPE_INT_COLOR_ARGB4:
+    case android::Res_value::TYPE_INT_COLOR_RGB4:
+        return android::ResTable_map::TYPE_COLOR;
+
+    default:
+        return 0;
+    };
+}
+
+std::unique_ptr<Item> parseItemForAttribute(
+        const StringPiece16& value, uint32_t typeMask,
+        std::function<void(const ResourceName&)> onCreateReference) {
+    std::unique_ptr<BinaryPrimitive> nullOrEmpty = tryParseNullOrEmpty(value);
+    if (nullOrEmpty) {
+        return std::move(nullOrEmpty);
+    }
+
+    bool create = false;
+    std::unique_ptr<Reference> reference = tryParseReference(value, &create);
+    if (reference) {
+        if (create && onCreateReference) {
+            onCreateReference(reference->name.value());
+        }
+        return std::move(reference);
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_COLOR) {
+        // Try parsing this as a color.
+        std::unique_ptr<BinaryPrimitive> color = tryParseColor(value);
+        if (color) {
+            return std::move(color);
+        }
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
+        // Try parsing this as a boolean.
+        std::unique_ptr<BinaryPrimitive> boolean = tryParseBool(value);
+        if (boolean) {
+            return std::move(boolean);
+        }
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_INTEGER) {
+        // Try parsing this as an integer.
+        std::unique_ptr<BinaryPrimitive> integer = tryParseInt(value);
+        if (integer) {
+            return std::move(integer);
+        }
+    }
+
+    const uint32_t floatMask = android::ResTable_map::TYPE_FLOAT
+            | android::ResTable_map::TYPE_DIMENSION | android::ResTable_map::TYPE_FRACTION;
+    if (typeMask & floatMask) {
+        // Try parsing this as a float.
+        std::unique_ptr<BinaryPrimitive> floatingPoint = tryParseFloat(value);
+        if (floatingPoint) {
+            if (typeMask & androidTypeToAttributeTypeMask(floatingPoint->value.dataType)) {
+                return std::move(floatingPoint);
+            }
+        }
+    }
+    return {};
+}
+
+/**
+ * We successively try to parse the string as a resource type that the Attribute
+ * allows.
+ */
+std::unique_ptr<Item> parseItemForAttribute(
+        const StringPiece16& str, const Attribute* attr,
+        std::function<void(const ResourceName&)> onCreateReference) {
+    const uint32_t typeMask = attr->typeMask;
+    std::unique_ptr<Item> value = parseItemForAttribute(str, typeMask, onCreateReference);
+    if (value) {
+        return value;
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_ENUM) {
+        // Try parsing this as an enum.
+        std::unique_ptr<BinaryPrimitive> enumValue = tryParseEnumSymbol(attr, str);
+        if (enumValue) {
+            return std::move(enumValue);
+        }
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_FLAGS) {
+        // Try parsing this as a flag.
+        std::unique_ptr<BinaryPrimitive> flagValue = tryParseFlagSymbol(attr, str);
+        if (flagValue) {
+            return std::move(flagValue);
+        }
+    }
+    return {};
+}
+
+} // namespace ResourceUtils
+} // namespace aapt
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
new file mode 100644
index 0000000..118a2ee
--- /dev/null
+++ b/tools/aapt2/ResourceUtils.h
@@ -0,0 +1,137 @@
+/*
+ * 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_RESOURCEUTILS_H
+#define AAPT_RESOURCEUTILS_H
+
+#include "Resource.h"
+#include "ResourceValues.h"
+#include "util/StringPiece.h"
+
+#include <functional>
+#include <memory>
+
+namespace aapt {
+namespace ResourceUtils {
+
+/*
+ * Extracts the package, type, and name from a string of the format:
+ *
+ *      [package:]type/name
+ *
+ * where the package can be empty. Validation must be performed on each
+ * individual extracted piece to verify that the pieces are valid.
+ */
+void extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
+                         StringPiece16* outType, StringPiece16* outEntry);
+
+/*
+ * Returns true if the string was parsed as a reference (@[+][package:]type/name), with
+ * `outReference` set to the parsed reference.
+ *
+ * If '+' was present in the reference, `outCreate` is set to true.
+ * If '*' was present in the reference, `outPrivate` is set to true.
+ */
+bool tryParseReference(const StringPiece16& str, ResourceNameRef* outReference,
+                       bool* outCreate = nullptr, bool* outPrivate = nullptr);
+
+/*
+ * Returns true if the string was parsed as an attribute reference (?[package:]type/name),
+ * with `outReference` set to the parsed reference.
+ */
+bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outReference);
+
+/*
+ * Returns a Reference, or None Maybe instance if the string `str` was parsed as a
+ * valid reference to a style.
+ * The format for a style parent is slightly more flexible than a normal reference:
+ *
+ * @[package:]style/<entry> or
+ * ?[package:]style/<entry> or
+ * <package>:[style/]<entry>
+ */
+Maybe<Reference> parseStyleParentReference(const StringPiece16& str, std::string* outError);
+
+/*
+ * 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.
+ */
+std::unique_ptr<Reference> tryParseReference(const StringPiece16& str, bool* outCreate = nullptr);
+
+/*
+ * Returns a BinaryPrimitve object representing @null or @empty if the string was parsed
+ * as one.
+ */
+std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str);
+
+/*
+ * Returns a BinaryPrimitve object representing a color if the string was parsed
+ * as one.
+ */
+std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str);
+
+/*
+ * Returns a BinaryPrimitve object representing a boolean if the string was parsed
+ * as one.
+ */
+std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str);
+
+/*
+ * Returns a BinaryPrimitve object representing an integer if the string was parsed
+ * as one.
+ */
+std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str);
+
+/*
+ * Returns a BinaryPrimitve object representing a floating point number
+ * (float, dimension, etc) if the string was parsed as one.
+ */
+std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str);
+
+/*
+ * Returns a BinaryPrimitve object representing an enum symbol if the string was parsed
+ * as one.
+ */
+std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr,
+                                                    const StringPiece16& str);
+
+/*
+ * Returns a BinaryPrimitve object representing a flag symbol if the string was parsed
+ * as one.
+ */
+std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* enumAttr,
+                                                    const StringPiece16& str);
+/*
+ * Try to convert a string to an Item for the given attribute. The attribute will
+ * restrict what values the string can be converted to.
+ * The callback function onCreateReference is called when the parsed item is a
+ * reference to an ID that must be created (@+id/foo).
+ */
+std::unique_ptr<Item> parseItemForAttribute(
+        const StringPiece16& value, const Attribute* attr,
+        std::function<void(const ResourceName&)> onCreateReference = {});
+
+std::unique_ptr<Item> parseItemForAttribute(
+        const StringPiece16& value, uint32_t typeMask,
+        std::function<void(const ResourceName&)> onCreateReference = {});
+
+uint32_t androidTypeToAttributeTypeMask(uint16_t type);
+
+} // namespace ResourceUtils
+} // namespace aapt
+
+#endif /* AAPT_RESOURCEUTILS_H */
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
new file mode 100644
index 0000000..7de8f41
--- /dev/null
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -0,0 +1,127 @@
+/*
+ * 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 "Resource.h"
+#include "ResourceUtils.h"
+
+#include "test/Common.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(ResourceUtilsTest, ParseReferenceWithNoPackage) {
+    ResourceNameRef expected = { {}, ResourceType::kColor, u"foo" };
+    ResourceNameRef actual;
+    bool create = false;
+    bool privateRef = false;
+    EXPECT_TRUE(ResourceUtils::tryParseReference(u"@color/foo", &actual, &create, &privateRef));
+    EXPECT_EQ(expected, actual);
+    EXPECT_FALSE(create);
+    EXPECT_FALSE(privateRef);
+}
+
+TEST(ResourceUtilsTest, ParseReferenceWithPackage) {
+    ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
+    ResourceNameRef actual;
+    bool create = false;
+    bool privateRef = false;
+    EXPECT_TRUE(ResourceUtils::tryParseReference(u"@android:color/foo", &actual, &create,
+                                                 &privateRef));
+    EXPECT_EQ(expected, actual);
+    EXPECT_FALSE(create);
+    EXPECT_FALSE(privateRef);
+}
+
+TEST(ResourceUtilsTest, ParseReferenceWithSurroundingWhitespace) {
+    ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
+    ResourceNameRef actual;
+    bool create = false;
+    bool privateRef = false;
+    EXPECT_TRUE(ResourceUtils::tryParseReference(u"\t @android:color/foo\n \n\t", &actual,
+                                                 &create, &privateRef));
+    EXPECT_EQ(expected, actual);
+    EXPECT_FALSE(create);
+    EXPECT_FALSE(privateRef);
+}
+
+TEST(ResourceUtilsTest, ParseAutoCreateIdReference) {
+    ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
+    ResourceNameRef actual;
+    bool create = false;
+    bool privateRef = false;
+    EXPECT_TRUE(ResourceUtils::tryParseReference(u"@+android:id/foo", &actual, &create,
+                                                 &privateRef));
+    EXPECT_EQ(expected, actual);
+    EXPECT_TRUE(create);
+    EXPECT_FALSE(privateRef);
+}
+
+TEST(ResourceUtilsTest, ParsePrivateReference) {
+    ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
+    ResourceNameRef actual;
+    bool create = false;
+    bool privateRef = false;
+    EXPECT_TRUE(ResourceUtils::tryParseReference(u"@*android:id/foo", &actual, &create,
+                                                 &privateRef));
+    EXPECT_EQ(expected, actual);
+    EXPECT_FALSE(create);
+    EXPECT_TRUE(privateRef);
+}
+
+TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) {
+    bool create = false;
+    bool privateRef = false;
+    ResourceNameRef actual;
+    EXPECT_FALSE(ResourceUtils::tryParseReference(u"@+android:color/foo", &actual, &create,
+                                                  &privateRef));
+}
+
+TEST(ResourceUtilsTest, ParseStyleParentReference) {
+    const ResourceName kAndroidStyleFooName = { u"android", ResourceType::kStyle, u"foo" };
+    const ResourceName kStyleFooName = { {}, ResourceType::kStyle, u"foo" };
+
+    std::string errStr;
+    Maybe<Reference> ref = ResourceUtils::parseStyleParentReference(u"@android:style/foo", &errStr);
+    AAPT_ASSERT_TRUE(ref);
+    EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+
+    ref = ResourceUtils::parseStyleParentReference(u"@style/foo", &errStr);
+    AAPT_ASSERT_TRUE(ref);
+    EXPECT_EQ(ref.value().name.value(), kStyleFooName);
+
+    ref = ResourceUtils::parseStyleParentReference(u"?android:style/foo", &errStr);
+    AAPT_ASSERT_TRUE(ref);
+    EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+
+    ref = ResourceUtils::parseStyleParentReference(u"?style/foo", &errStr);
+    AAPT_ASSERT_TRUE(ref);
+    EXPECT_EQ(ref.value().name.value(), kStyleFooName);
+
+    ref = ResourceUtils::parseStyleParentReference(u"android:style/foo", &errStr);
+    AAPT_ASSERT_TRUE(ref);
+    EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+
+    ref = ResourceUtils::parseStyleParentReference(u"android:foo", &errStr);
+    AAPT_ASSERT_TRUE(ref);
+    EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+
+    ref = ResourceUtils::parseStyleParentReference(u"foo", &errStr);
+    AAPT_ASSERT_TRUE(ref);
+    EXPECT_EQ(ref.value().name.value(), kStyleFooName);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index aabb375..ecc5cd2 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -15,15 +15,26 @@
  */
 
 #include "Resource.h"
-#include "ResourceTypeExtensions.h"
+#include "flatten/ResourceTypeExtensions.h"
 #include "ResourceValues.h"
-#include "Util.h"
+#include "util/Util.h"
+#include "ValueVisitor.h"
 
 #include <androidfw/ResourceTypes.h>
 #include <limits>
 
 namespace aapt {
 
+template <typename Derived>
+void BaseValue<Derived>::accept(RawValueVisitor* visitor) {
+    visitor->visit(static_cast<Derived*>(this));
+}
+
+template <typename Derived>
+void BaseItem<Derived>::accept(RawValueVisitor* visitor) {
+    visitor->visit(static_cast<Derived*>(this));
+}
+
 bool Value::isItem() const {
     return false;
 }
@@ -43,14 +54,14 @@
     return new RawString(newPool->makeRef(*value));
 }
 
-bool RawString::flatten(android::Res_value& outValue) const {
-    outValue.dataType = ExtendedTypes::TYPE_RAW_STRING;
-    outValue.data = static_cast<uint32_t>(value.getIndex());
+bool RawString::flatten(android::Res_value* outValue) const {
+    outValue->dataType = ExtendedTypes::TYPE_RAW_STRING;
+    outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
     return true;
 }
 
-void RawString::print(std::ostream& out) const {
-    out << "(raw string) " << *value;
+void RawString::print(std::ostream* out) const {
+    *out << "(raw string) " << *value;
 }
 
 Reference::Reference() : referenceType(Reference::Type::kResource) {
@@ -63,11 +74,11 @@
 Reference::Reference(const ResourceId& i, Type type) : id(i), referenceType(type) {
 }
 
-bool Reference::flatten(android::Res_value& outValue) const {
-    outValue.dataType = (referenceType == Reference::Type::kResource)
+bool Reference::flatten(android::Res_value* outValue) const {
+    outValue->dataType = (referenceType == Reference::Type::kResource)
         ? android::Res_value::TYPE_REFERENCE
         : android::Res_value::TYPE_ATTRIBUTE;
-    outValue.data = id.id;
+    outValue->data = util::hostToDevice32(id ? id.value().id : 0);
     return true;
 }
 
@@ -79,20 +90,20 @@
     return ref;
 }
 
-void Reference::print(std::ostream& out) const {
-    out << "(reference) ";
+void Reference::print(std::ostream* out) const {
+    *out << "(reference) ";
     if (referenceType == Reference::Type::kResource) {
-        out << "@";
+        *out << "@";
     } else {
-        out << "?";
+        *out << "?";
     }
 
-    if (name.isValid()) {
-        out << name;
+    if (name) {
+        *out << name.value();
     }
 
-    if (id.isValid() || Res_INTERNALID(id.id)) {
-        out << " " << id;
+    if (id && !Res_INTERNALID(id.value().id)) {
+        *out << " " << id.value();
     }
 }
 
@@ -100,9 +111,9 @@
     return true;
 }
 
-bool Id::flatten(android::Res_value& out) const {
-    out.dataType = android::Res_value::TYPE_INT_BOOLEAN;
-    out.data = 0;
+bool Id::flatten(android::Res_value* out) const {
+    out->dataType = android::Res_value::TYPE_INT_BOOLEAN;
+    out->data = util::hostToDevice32(0);
     return true;
 }
 
@@ -110,21 +121,21 @@
     return new Id();
 }
 
-void Id::print(std::ostream& out) const {
-    out << "(id)";
+void Id::print(std::ostream* out) const {
+    *out << "(id)";
 }
 
 String::String(const StringPool::Ref& ref) : value(ref) {
 }
 
-bool String::flatten(android::Res_value& outValue) const {
-    // Verify that our StringPool index is within encodeable limits.
+bool String::flatten(android::Res_value* outValue) const {
+    // Verify that our StringPool index is within encode-able limits.
     if (value.getIndex() > std::numeric_limits<uint32_t>::max()) {
         return false;
     }
 
-    outValue.dataType = android::Res_value::TYPE_STRING;
-    outValue.data = static_cast<uint32_t>(value.getIndex());
+    outValue->dataType = android::Res_value::TYPE_STRING;
+    outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
     return true;
 }
 
@@ -132,20 +143,20 @@
     return new String(newPool->makeRef(*value));
 }
 
-void String::print(std::ostream& out) const {
-    out << "(string) \"" << *value << "\"";
+void String::print(std::ostream* out) const {
+    *out << "(string) \"" << *value << "\"";
 }
 
 StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) {
 }
 
-bool StyledString::flatten(android::Res_value& outValue) const {
+bool StyledString::flatten(android::Res_value* outValue) const {
     if (value.getIndex() > std::numeric_limits<uint32_t>::max()) {
         return false;
     }
 
-    outValue.dataType = android::Res_value::TYPE_STRING;
-    outValue.data = static_cast<uint32_t>(value.getIndex());
+    outValue->dataType = android::Res_value::TYPE_STRING;
+    outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
     return true;
 }
 
@@ -153,20 +164,20 @@
     return new StyledString(newPool->makeRef(value));
 }
 
-void StyledString::print(std::ostream& out) const {
-    out << "(styled string) \"" << *value->str << "\"";
+void StyledString::print(std::ostream* out) const {
+    *out << "(styled string) \"" << *value->str << "\"";
 }
 
 FileReference::FileReference(const StringPool::Ref& _path) : path(_path) {
 }
 
-bool FileReference::flatten(android::Res_value& outValue) const {
+bool FileReference::flatten(android::Res_value* outValue) const {
     if (path.getIndex() > std::numeric_limits<uint32_t>::max()) {
         return false;
     }
 
-    outValue.dataType = android::Res_value::TYPE_STRING;
-    outValue.data = static_cast<uint32_t>(path.getIndex());
+    outValue->dataType = android::Res_value::TYPE_STRING;
+    outValue->data = util::hostToDevice32(static_cast<uint32_t>(path.getIndex()));
     return true;
 }
 
@@ -174,15 +185,21 @@
     return new FileReference(newPool->makeRef(*path));
 }
 
-void FileReference::print(std::ostream& out) const {
-    out << "(file) " << *path;
+void FileReference::print(std::ostream* out) const {
+    *out << "(file) " << *path;
 }
 
 BinaryPrimitive::BinaryPrimitive(const android::Res_value& val) : value(val) {
 }
 
-bool BinaryPrimitive::flatten(android::Res_value& outValue) const {
-    outValue = value;
+BinaryPrimitive::BinaryPrimitive(uint8_t dataType, uint32_t data) {
+    value.dataType = dataType;
+    value.data = data;
+}
+
+bool BinaryPrimitive::flatten(android::Res_value* outValue) const {
+    outValue->dataType = value.dataType;
+    outValue->data = util::hostToDevice32(value.data);
     return true;
 }
 
@@ -190,29 +207,29 @@
     return new BinaryPrimitive(value);
 }
 
-void BinaryPrimitive::print(std::ostream& out) const {
+void BinaryPrimitive::print(std::ostream* out) const {
     switch (value.dataType) {
         case android::Res_value::TYPE_NULL:
-            out << "(null)";
+            *out << "(null)";
             break;
         case android::Res_value::TYPE_INT_DEC:
-            out << "(integer) " << value.data;
+            *out << "(integer) " << value.data;
             break;
         case android::Res_value::TYPE_INT_HEX:
-            out << "(integer) " << std::hex << value.data << std::dec;
+            *out << "(integer) " << std::hex << value.data << std::dec;
             break;
         case android::Res_value::TYPE_INT_BOOLEAN:
-            out << "(boolean) " << (value.data != 0 ? "true" : "false");
+            *out << "(boolean) " << (value.data != 0 ? "true" : "false");
             break;
         case android::Res_value::TYPE_INT_COLOR_ARGB8:
         case android::Res_value::TYPE_INT_COLOR_RGB8:
         case android::Res_value::TYPE_INT_COLOR_ARGB4:
         case android::Res_value::TYPE_INT_COLOR_RGB4:
-            out << "(color) #" << std::hex << value.data << std::dec;
+            *out << "(color) #" << std::hex << value.data << std::dec;
             break;
         default:
-            out << "(unknown 0x" << std::hex << (int) value.dataType << ") 0x"
-                << std::hex << value.data << std::dec;
+            *out << "(unknown 0x" << std::hex << (int) value.dataType << ") 0x"
+                 << std::hex << value.data << std::dec;
             break;
     }
 }
@@ -231,9 +248,9 @@
     return attr;
 }
 
-void Attribute::printMask(std::ostream& out) const {
+void Attribute::printMask(std::ostream* out) const {
     if (typeMask == android::ResTable_map::TYPE_ANY) {
-        out << "any";
+        *out << "any";
         return;
     }
 
@@ -242,103 +259,105 @@
         if (!set) {
             set = true;
         } else {
-            out << "|";
+            *out << "|";
         }
-        out << "reference";
+        *out << "reference";
     }
 
     if ((typeMask & android::ResTable_map::TYPE_STRING) != 0) {
         if (!set) {
             set = true;
         } else {
-            out << "|";
+            *out << "|";
         }
-        out << "string";
+        *out << "string";
     }
 
     if ((typeMask & android::ResTable_map::TYPE_INTEGER) != 0) {
         if (!set) {
             set = true;
         } else {
-            out << "|";
+            *out << "|";
         }
-        out << "integer";
+        *out << "integer";
     }
 
     if ((typeMask & android::ResTable_map::TYPE_BOOLEAN) != 0) {
         if (!set) {
             set = true;
         } else {
-            out << "|";
+            *out << "|";
         }
-        out << "boolean";
+        *out << "boolean";
     }
 
     if ((typeMask & android::ResTable_map::TYPE_COLOR) != 0) {
         if (!set) {
             set = true;
         } else {
-            out << "|";
+            *out << "|";
         }
-        out << "color";
+        *out << "color";
     }
 
     if ((typeMask & android::ResTable_map::TYPE_FLOAT) != 0) {
         if (!set) {
             set = true;
         } else {
-            out << "|";
+            *out << "|";
         }
-        out << "float";
+        *out << "float";
     }
 
     if ((typeMask & android::ResTable_map::TYPE_DIMENSION) != 0) {
         if (!set) {
             set = true;
         } else {
-            out << "|";
+            *out << "|";
         }
-        out << "dimension";
+        *out << "dimension";
     }
 
     if ((typeMask & android::ResTable_map::TYPE_FRACTION) != 0) {
         if (!set) {
             set = true;
         } else {
-            out << "|";
+            *out << "|";
         }
-        out << "fraction";
+        *out << "fraction";
     }
 
     if ((typeMask & android::ResTable_map::TYPE_ENUM) != 0) {
         if (!set) {
             set = true;
         } else {
-            out << "|";
+            *out << "|";
         }
-        out << "enum";
+        *out << "enum";
     }
 
     if ((typeMask & android::ResTable_map::TYPE_FLAGS) != 0) {
         if (!set) {
             set = true;
         } else {
-            out << "|";
+            *out << "|";
         }
-        out << "flags";
+        *out << "flags";
     }
 }
 
-void Attribute::print(std::ostream& out) const {
-    out << "(attr) ";
+void Attribute::print(std::ostream* out) const {
+    *out << "(attr) ";
     printMask(out);
 
-    out << " ["
-        << util::joiner(symbols.begin(), symbols.end(), ", ")
-        << "]";
+    if (!symbols.empty()) {
+        *out << " ["
+            << util::joiner(symbols.begin(), symbols.end(), ", ")
+            << "]";
+    }
 
     if (weak) {
-        out << " [weak]";
+        *out << " [weak]";
     }
 }
 
@@ -355,19 +374,24 @@
     return style;
 }
 
-void Style::print(std::ostream& out) const {
-    out << "(style) ";
-    if (!parent.name.entry.empty()) {
-        out << parent.name;
+void Style::print(std::ostream* out) const {
+    *out << "(style) ";
+    if (parent && parent.value().name) {
+        *out << parent.value().name.value();
     }
-    out << " ["
+    *out << " ["
         << util::joiner(entries.begin(), entries.end(), ", ")
         << "]";
 }
 
 static ::std::ostream& operator<<(::std::ostream& out, const Style::Entry& value) {
-    out << value.key.name << " = ";
-    value.value->print(out);
+    if (value.key.name) {
+        out << value.key.name.value();
+    } else {
+        out << "???";
+    }
+    out << " = ";
+    value.value->print(&out);
     return out;
 }
 
@@ -379,8 +403,8 @@
     return array;
 }
 
-void Array::print(std::ostream& out) const {
-    out << "(array) ["
+void Array::print(std::ostream* out) const {
+    *out << "(array) ["
         << util::joiner(items.begin(), items.end(), ", ")
         << "]";
 }
@@ -396,8 +420,8 @@
     return p;
 }
 
-void Plural::print(std::ostream& out) const {
-    out << "(plural)";
+void Plural::print(std::ostream* out) const {
+    *out << "(plural)";
 }
 
 static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Item>& item) {
@@ -410,8 +434,8 @@
     return styleable;
 }
 
-void Styleable::print(std::ostream& out) const {
-    out << "(styleable) " << " ["
+void Styleable::print(std::ostream* out) const {
+    *out << "(styleable) " << " ["
         << util::joiner(entries.begin(), entries.end(), ", ")
         << "]";
 }
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index ef6594e..0dae091 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -17,6 +17,7 @@
 #ifndef AAPT_RESOURCE_VALUES_H
 #define AAPT_RESOURCE_VALUES_H
 
+#include "util/Maybe.h"
 #include "Resource.h"
 #include "StringPool.h"
 
@@ -27,9 +28,7 @@
 
 namespace aapt {
 
-struct ValueVisitor;
-struct ConstValueVisitor;
-struct ValueVisitorArgs;
+struct RawValueVisitor;
 
 /**
  * A resource value. This is an all-encompassing representation
@@ -39,13 +38,15 @@
  * but it is the simplest strategy.
  */
 struct Value {
+	virtual ~Value() = default;
+
     /**
      * Whether or not this is an Item.
      */
     virtual bool isItem() const;
 
     /**
-     * Whether this value is weak and can be overriden without
+     * Whether this value is weak and can be overridden without
      * warning or error. Default for base class is false.
      */
     virtual bool isWeak() const;
@@ -53,12 +54,7 @@
     /**
      * Calls the appropriate overload of ValueVisitor.
      */
-    virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) = 0;
-
-    /**
-     * Const version of accept().
-     */
-    virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const = 0;
+    virtual void accept(RawValueVisitor* visitor) = 0;
 
     /**
      * Clone the value.
@@ -68,7 +64,7 @@
     /**
      * Human readable printout of this value.
      */
-    virtual void print(std::ostream& out) const = 0;
+    virtual void print(std::ostream* out) const = 0;
 };
 
 /**
@@ -76,8 +72,7 @@
  */
 template <typename Derived>
 struct BaseValue : public Value {
-    virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) override;
-    virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const override;
+    void accept(RawValueVisitor* visitor) override;
 };
 
 /**
@@ -96,9 +91,9 @@
 
     /**
      * Fills in an android::Res_value structure with this Item's binary representation.
-     * Returns false if an error ocurred.
+     * Returns false if an error occurred.
      */
-    virtual bool flatten(android::Res_value& outValue) const = 0;
+    virtual bool flatten(android::Res_value* outValue) const = 0;
 };
 
 /**
@@ -106,8 +101,7 @@
  */
 template <typename Derived>
 struct BaseItem : public Item {
-    virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) override;
-    virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const override;
+    void accept(RawValueVisitor* visitor) override;
 };
 
 /**
@@ -122,8 +116,8 @@
         kAttribute,
     };
 
-    ResourceName name;
-    ResourceId id;
+    Maybe<ResourceName> name;
+    Maybe<ResourceId> id;
     Reference::Type referenceType;
     bool privateReference = false;
 
@@ -131,9 +125,9 @@
     Reference(const ResourceNameRef& n, Type type = Type::kResource);
     Reference(const ResourceId& i, Type type = Type::kResource);
 
-    bool flatten(android::Res_value& outValue) const override;
+    bool flatten(android::Res_value* outValue) const override;
     Reference* clone(StringPool* newPool) const override;
-    void print(std::ostream& out) const override;
+    void print(std::ostream* out) const override;
 };
 
 /**
@@ -141,9 +135,9 @@
  */
 struct Id : public BaseItem<Id> {
     bool isWeak() const override;
-    bool flatten(android::Res_value& out) const override;
+    bool flatten(android::Res_value* out) const override;
     Id* clone(StringPool* newPool) const override;
-    void print(std::ostream& out) const override;
+    void print(std::ostream* out) const override;
 };
 
 /**
@@ -156,9 +150,9 @@
 
     RawString(const StringPool::Ref& ref);
 
-    bool flatten(android::Res_value& outValue) const override;
+    bool flatten(android::Res_value* outValue) const override;
     RawString* clone(StringPool* newPool) const override;
-    void print(std::ostream& out) const override;
+    void print(std::ostream* out) const override;
 };
 
 struct String : public BaseItem<String> {
@@ -166,9 +160,9 @@
 
     String(const StringPool::Ref& ref);
 
-    bool flatten(android::Res_value& outValue) const override;
+    bool flatten(android::Res_value* outValue) const override;
     String* clone(StringPool* newPool) const override;
-    void print(std::ostream& out) const override;
+    void print(std::ostream* out) const override;
 };
 
 struct StyledString : public BaseItem<StyledString> {
@@ -176,9 +170,9 @@
 
     StyledString(const StringPool::StyleRef& ref);
 
-    bool flatten(android::Res_value& outValue) const override;
+    bool flatten(android::Res_value* outValue) const override;
     StyledString* clone(StringPool* newPool) const override;
-    void print(std::ostream& out) const override;
+    void print(std::ostream* out) const override;
 };
 
 struct FileReference : public BaseItem<FileReference> {
@@ -187,9 +181,9 @@
     FileReference() = default;
     FileReference(const StringPool::Ref& path);
 
-    bool flatten(android::Res_value& outValue) const override;
+    bool flatten(android::Res_value* outValue) const override;
     FileReference* clone(StringPool* newPool) const override;
-    void print(std::ostream& out) const override;
+    void print(std::ostream* out) const override;
 };
 
 /**
@@ -200,10 +194,11 @@
 
     BinaryPrimitive() = default;
     BinaryPrimitive(const android::Res_value& val);
+    BinaryPrimitive(uint8_t dataType, uint32_t data);
 
-    bool flatten(android::Res_value& outValue) const override;
+    bool flatten(android::Res_value* outValue) const override;
     BinaryPrimitive* clone(StringPool* newPool) const override;
-    void print(std::ostream& out) const override;
+    void print(std::ostream* out) const override;
 };
 
 struct Attribute : public BaseValue<Attribute> {
@@ -212,7 +207,7 @@
         uint32_t value;
     };
 
-    bool weak;
+	bool weak;
     uint32_t typeMask;
     uint32_t minInt;
     uint32_t maxInt;
@@ -221,9 +216,9 @@
     Attribute(bool w, uint32_t t = 0u);
 
     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;
+    Attribute* clone(StringPool* newPool) const override;
+    void printMask(std::ostream* out) const;
+    void print(std::ostream* out) const override;
 };
 
 struct Style : public BaseValue<Style> {
@@ -232,7 +227,7 @@
         std::unique_ptr<Item> value;
     };
 
-    Reference parent;
+    Maybe<Reference> parent;
 
     /**
      * If set to true, the parent was auto inferred from the
@@ -243,14 +238,14 @@
     std::vector<Entry> entries;
 
     Style* clone(StringPool* newPool) const override;
-    void print(std::ostream& out) const override;
+    void print(std::ostream* out) const override;
 };
 
 struct Array : public BaseValue<Array> {
     std::vector<std::unique_ptr<Item>> items;
 
     Array* clone(StringPool* newPool) const override;
-    void print(std::ostream& out) const override;
+    void print(std::ostream* out) const override;
 };
 
 struct Plural : public BaseValue<Plural> {
@@ -267,180 +262,31 @@
     std::array<std::unique_ptr<Item>, Count> values;
 
     Plural* clone(StringPool* newPool) const override;
-    void print(std::ostream& out) const override;
+    void print(std::ostream* out) const override;
 };
 
 struct Styleable : public BaseValue<Styleable> {
     std::vector<Reference> entries;
 
     Styleable* clone(StringPool* newPool) const override;
-    void print(std::ostream& out) const override;
+    void print(std::ostream* out) const override;
 };
 
 /**
  * Stream operator for printing Value objects.
  */
 inline ::std::ostream& operator<<(::std::ostream& out, const Value& value) {
-    value.print(out);
+    value.print(&out);
     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
- * subclass ValueVisitorArgs to contain the data they need
- * to operate.
- */
-struct ValueVisitorArgs {};
-
-/**
- * Visits a value and runs the appropriate method based on its type.
- */
-struct ValueVisitor {
-    virtual void visit(Reference& reference, ValueVisitorArgs& args) {
-        visitItem(reference, args);
+    if (s.symbol.name) {
+        out << s.symbol.name.value().entry;
+    } else {
+        out << "???";
     }
-
-    virtual void visit(RawString& string, ValueVisitorArgs& args) {
-        visitItem(string, args);
-    }
-
-    virtual void visit(String& string, ValueVisitorArgs& args) {
-        visitItem(string, args);
-    }
-
-    virtual void visit(StyledString& string, ValueVisitorArgs& args) {
-        visitItem(string, args);
-    }
-
-    virtual void visit(FileReference& file, ValueVisitorArgs& args) {
-        visitItem(file, args);
-    }
-
-    virtual void visit(Id& id, ValueVisitorArgs& args) {
-        visitItem(id, args);
-    }
-
-    virtual void visit(BinaryPrimitive& primitive, ValueVisitorArgs& args) {
-        visitItem(primitive, args);
-    }
-
-    virtual void visit(Attribute& attr, ValueVisitorArgs& args) {}
-    virtual void visit(Style& style, ValueVisitorArgs& args) {}
-    virtual void visit(Array& array, ValueVisitorArgs& args) {}
-    virtual void visit(Plural& array, ValueVisitorArgs& args) {}
-    virtual void visit(Styleable& styleable, ValueVisitorArgs& args) {}
-
-    virtual void visitItem(Item& item, ValueVisitorArgs& args) {}
-};
-
-/**
- * Const version of ValueVisitor.
- */
-struct ConstValueVisitor {
-    virtual void visit(const Reference& reference, ValueVisitorArgs& args) {
-        visitItem(reference, args);
-    }
-
-    virtual void visit(const RawString& string, ValueVisitorArgs& args) {
-        visitItem(string, args);
-    }
-
-    virtual void visit(const String& string, ValueVisitorArgs& args) {
-        visitItem(string, args);
-    }
-
-    virtual void visit(const StyledString& string, ValueVisitorArgs& args) {
-        visitItem(string, args);
-    }
-
-    virtual void visit(const FileReference& file, ValueVisitorArgs& args) {
-        visitItem(file, args);
-    }
-
-    virtual void visit(const Id& id, ValueVisitorArgs& args) {
-        visitItem(id, args);
-    }
-
-    virtual void visit(const BinaryPrimitive& primitive, ValueVisitorArgs& args) {
-        visitItem(primitive, args);
-    }
-
-    virtual void visit(const Attribute& attr, ValueVisitorArgs& args) {}
-    virtual void visit(const Style& style, ValueVisitorArgs& args) {}
-    virtual void visit(const Array& array, ValueVisitorArgs& args) {}
-    virtual void visit(const Plural& array, ValueVisitorArgs& args) {}
-    virtual void visit(const Styleable& styleable, ValueVisitorArgs& args) {}
-
-    virtual void visitItem(const Item& item, ValueVisitorArgs& args) {}
-};
-
-/**
- * Convenience Visitor that forwards a specific type to a function.
- * Args are not used as the function can bind variables. Do not use
- * directly, use the wrapper visitFunc() method.
- */
-template <typename T, typename TFunc>
-struct ValueVisitorFunc : ValueVisitor {
-    TFunc func;
-
-    ValueVisitorFunc(TFunc f) : func(f) {
-    }
-
-    void visit(T& value, ValueVisitorArgs&) override {
-        func(value);
-    }
-};
-
-/**
- * Const version of ValueVisitorFunc.
- */
-template <typename T, typename TFunc>
-struct ConstValueVisitorFunc : ConstValueVisitor {
-    TFunc func;
-
-    ConstValueVisitorFunc(TFunc f) : func(f) {
-    }
-
-    void visit(const T& value, ValueVisitorArgs&) override {
-        func(value);
-    }
-};
-
-template <typename T, typename TFunc>
-void visitFunc(Value& value, TFunc f) {
-    ValueVisitorFunc<T, TFunc> visitor(f);
-    value.accept(visitor, ValueVisitorArgs{});
-}
-
-template <typename T, typename TFunc>
-void visitFunc(const Value& value, TFunc f) {
-    ConstValueVisitorFunc<T, TFunc> visitor(f);
-    value.accept(visitor, ValueVisitorArgs{});
-}
-
-template <typename Derived>
-void BaseValue<Derived>::accept(ValueVisitor& visitor, ValueVisitorArgs&& args) {
-    visitor.visit(static_cast<Derived&>(*this), args);
-}
-
-template <typename Derived>
-void BaseValue<Derived>::accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const {
-    visitor.visit(static_cast<const Derived&>(*this), args);
-}
-
-template <typename Derived>
-void BaseItem<Derived>::accept(ValueVisitor& visitor, ValueVisitorArgs&& args) {
-    visitor.visit(static_cast<Derived&>(*this), args);
-}
-
-template <typename Derived>
-void BaseItem<Derived>::accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const {
-    visitor.visit(static_cast<const Derived&>(*this), args);
+    return out << "=" << s.value;
 }
 
 } // namespace aapt
diff --git a/tools/aapt2/ScopedXmlPullParser.cpp b/tools/aapt2/ScopedXmlPullParser.cpp
deleted file mode 100644
index 48da93e..0000000
--- a/tools/aapt2/ScopedXmlPullParser.cpp
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * 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 "ScopedXmlPullParser.h"
-
-#include <string>
-
-namespace aapt {
-
-ScopedXmlPullParser::ScopedXmlPullParser(XmlPullParser* parser) :
-        mParser(parser), mDepth(parser->getDepth()), mDone(false) {
-}
-
-ScopedXmlPullParser::~ScopedXmlPullParser() {
-    while (isGoodEvent(next()));
-}
-
-XmlPullParser::Event ScopedXmlPullParser::next() {
-    if (mDone) {
-        return Event::kEndDocument;
-    }
-
-    const Event event = mParser->next();
-    if (mParser->getDepth() <= mDepth) {
-        mDone = true;
-    }
-    return event;
-}
-
-XmlPullParser::Event ScopedXmlPullParser::getEvent() const {
-    return mParser->getEvent();
-}
-
-const std::string& ScopedXmlPullParser::getLastError() const {
-    return mParser->getLastError();
-}
-
-const std::u16string& ScopedXmlPullParser::getComment() const {
-    return mParser->getComment();
-}
-
-size_t ScopedXmlPullParser::getLineNumber() const {
-    return mParser->getLineNumber();
-}
-
-size_t ScopedXmlPullParser::getDepth() const {
-    const size_t depth = mParser->getDepth();
-    if (depth < mDepth) {
-        return 0;
-    }
-    return depth - mDepth;
-}
-
-const std::u16string& ScopedXmlPullParser::getText() const {
-    return mParser->getText();
-}
-
-const std::u16string& ScopedXmlPullParser::getNamespacePrefix() const {
-    return mParser->getNamespacePrefix();
-}
-
-const std::u16string& ScopedXmlPullParser::getNamespaceUri() const {
-    return mParser->getNamespaceUri();
-}
-
-bool ScopedXmlPullParser::applyPackageAlias(std::u16string* package,
-                                            const std::u16string& defaultPackage) const {
-    return mParser->applyPackageAlias(package, defaultPackage);
-}
-
-const std::u16string& ScopedXmlPullParser::getElementNamespace() const {
-    return mParser->getElementNamespace();
-}
-
-const std::u16string& ScopedXmlPullParser::getElementName() const {
-    return mParser->getElementName();
-}
-
-size_t ScopedXmlPullParser::getAttributeCount() const {
-    return mParser->getAttributeCount();
-}
-
-XmlPullParser::const_iterator ScopedXmlPullParser::beginAttributes() const {
-    return mParser->beginAttributes();
-}
-
-XmlPullParser::const_iterator ScopedXmlPullParser::endAttributes() const {
-    return mParser->endAttributes();
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ScopedXmlPullParser.h b/tools/aapt2/ScopedXmlPullParser.h
deleted file mode 100644
index a040f60..0000000
--- a/tools/aapt2/ScopedXmlPullParser.h
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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_SCOPED_XML_PULL_PARSER_H
-#define AAPT_SCOPED_XML_PULL_PARSER_H
-
-#include "XmlPullParser.h"
-
-#include <string>
-
-namespace aapt {
-
-/**
- * An XmlPullParser that will not read past the depth
- * of the underlying parser. When this parser is destroyed,
- * it moves the underlying parser to the same depth it
- * started with.
- *
- * You can write code like this:
- *
- *   while (XmlPullParser::isGoodEvent(parser.next())) {
- *     if (parser.getEvent() != XmlPullParser::Event::StartElement) {
- *       continue;
- *     }
- *
- *     ScopedXmlPullParser scoped(parser);
- *     if (parser.getElementName() == u"id") {
- *       // do work.
- *     } else {
- *       // do nothing, as all the sub elements will be skipped
- *       // when scoped goes out of scope.
- *     }
- *   }
- */
-class ScopedXmlPullParser : public XmlPullParser {
-public:
-    ScopedXmlPullParser(XmlPullParser* parser);
-    ScopedXmlPullParser(const ScopedXmlPullParser&) = delete;
-    ScopedXmlPullParser& operator=(const ScopedXmlPullParser&) = delete;
-    ~ScopedXmlPullParser();
-
-    Event getEvent() const override;
-    const std::string& getLastError() const override;
-    Event next() override;
-
-    const std::u16string& getComment() const override;
-    size_t getLineNumber() const override;
-    size_t getDepth() const override;
-
-    const std::u16string& getText() const override;
-
-    const std::u16string& getNamespacePrefix() const override;
-    const std::u16string& getNamespaceUri() const override;
-    bool applyPackageAlias(std::u16string* package, const std::u16string& defaultPackage)
-            const override;
-
-    const std::u16string& getElementNamespace() const override;
-    const std::u16string& getElementName() const override;
-
-    const_iterator beginAttributes() const override;
-    const_iterator endAttributes() const override;
-    size_t getAttributeCount() const override;
-
-private:
-    XmlPullParser* mParser;
-    size_t mDepth;
-    bool mDone;
-};
-
-} // namespace aapt
-
-#endif // AAPT_SCOPED_XML_PULL_PARSER_H
diff --git a/tools/aapt2/ScopedXmlPullParser_test.cpp b/tools/aapt2/ScopedXmlPullParser_test.cpp
deleted file mode 100644
index 342f305..0000000
--- a/tools/aapt2/ScopedXmlPullParser_test.cpp
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * 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 "ScopedXmlPullParser.h"
-#include "SourceXmlPullParser.h"
-
-#include <gtest/gtest.h>
-#include <sstream>
-#include <string>
-
-namespace aapt {
-
-TEST(ScopedXmlPullParserTest, StopIteratingAtNoNZeroDepth) {
-    std::stringstream input;
-    input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl
-          << "<resources><string></string></resources>" << std::endl;
-
-    SourceXmlPullParser sourceParser(input);
-    EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
-    EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
-
-    EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
-    EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
-
-    {
-        ScopedXmlPullParser scopedParser(&sourceParser);
-        EXPECT_EQ(XmlPullParser::Event::kEndElement, scopedParser.next());
-        EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
-
-        EXPECT_EQ(XmlPullParser::Event::kEndDocument, scopedParser.next());
-    }
-
-    EXPECT_EQ(XmlPullParser::Event::kEndElement, sourceParser.next());
-    EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
-
-    EXPECT_EQ(XmlPullParser::Event::kEndDocument, sourceParser.next());
-}
-
-TEST(ScopedXmlPullParserTest, FinishCurrentElementOnDestruction) {
-    std::stringstream input;
-    input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl
-          << "<resources><string></string></resources>" << std::endl;
-
-    SourceXmlPullParser sourceParser(input);
-    EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
-    EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
-
-    EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
-    EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
-
-    {
-        ScopedXmlPullParser scopedParser(&sourceParser);
-        EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
-    }
-
-    EXPECT_EQ(XmlPullParser::Event::kEndElement, sourceParser.next());
-    EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
-
-    EXPECT_EQ(XmlPullParser::Event::kEndDocument, sourceParser.next());
-}
-
-TEST(ScopedXmlPullParserTest, NestedParsersOperateCorrectly) {
-    std::stringstream input;
-    input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl
-          << "<resources><string><foo></foo></string></resources>" << std::endl;
-
-    SourceXmlPullParser sourceParser(input);
-    EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
-    EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
-
-    EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
-    EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
-
-    {
-        ScopedXmlPullParser scopedParser(&sourceParser);
-        EXPECT_EQ(std::u16string(u"string"), scopedParser.getElementName());
-        while (XmlPullParser::isGoodEvent(scopedParser.next())) {
-            if (scopedParser.getEvent() != XmlPullParser::Event::kStartElement) {
-                continue;
-            }
-
-            ScopedXmlPullParser subScopedParser(&scopedParser);
-            EXPECT_EQ(std::u16string(u"foo"), subScopedParser.getElementName());
-        }
-    }
-
-    EXPECT_EQ(XmlPullParser::Event::kEndElement, sourceParser.next());
-    EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
-
-    EXPECT_EQ(XmlPullParser::Event::kEndDocument, sourceParser.next());
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp
index 9bdae49..c2a22bf 100644
--- a/tools/aapt2/SdkConstants.cpp
+++ b/tools/aapt2/SdkConstants.cpp
@@ -34,8 +34,9 @@
     { 0x02bd, SDK_FROYO },
     { 0x02cb, SDK_GINGERBREAD },
     { 0x0361, SDK_HONEYCOMB },
-    { 0x0366, SDK_HONEYCOMB_MR1 },
-    { 0x03a6, SDK_HONEYCOMB_MR2 },
+    { 0x0363, SDK_HONEYCOMB_MR1 },
+    { 0x0366, SDK_HONEYCOMB_MR2 },
+    { 0x03a6, SDK_ICE_CREAM_SANDWICH },
     { 0x03ae, SDK_JELLY_BEAN },
     { 0x03cc, SDK_JELLY_BEAN_MR1 },
     { 0x03da, SDK_JELLY_BEAN_MR2 },
diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h
index 3606488..8af203c 100644
--- a/tools/aapt2/Source.h
+++ b/tools/aapt2/Source.h
@@ -17,72 +17,58 @@
 #ifndef AAPT_SOURCE_H
 #define AAPT_SOURCE_H
 
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
+
 #include <ostream>
 #include <string>
-#include <tuple>
 
 namespace aapt {
 
-struct SourceLineColumn;
-struct SourceLine;
-
 /**
  * Represents a file on disk. Used for logging and
  * showing errors.
  */
 struct Source {
     std::string path;
+    Maybe<size_t> line;
 
-    inline SourceLine line(size_t line) const;
-};
+    Source() = default;
 
-/**
- * Represents a file on disk and a line number in that file.
- * Used for logging and showing errors.
- */
-struct SourceLine {
-    std::string path;
-    size_t line;
+    inline Source(const StringPiece& path) : path(path.toString()) {
+    }
 
-    inline SourceLineColumn column(size_t column) const;
-};
+    inline Source(const StringPiece& path, size_t line) : path(path.toString()), line(line) {
+    }
 
-/**
- * Represents a file on disk and a line:column number in that file.
- * Used for logging and showing errors.
- */
-struct SourceLineColumn {
-    std::string path;
-    size_t line;
-    size_t column;
+    inline Source withLine(size_t line) const {
+        return Source(path, line);
+    }
 };
 
 //
 // Implementations
 //
 
-SourceLine Source::line(size_t line) const {
-    return SourceLine{ path, line };
-}
-
-SourceLineColumn SourceLine::column(size_t column) const {
-    return SourceLineColumn{ path, line, column };
-}
-
 inline ::std::ostream& operator<<(::std::ostream& out, const Source& source) {
-    return out << source.path;
+    out << source.path;
+    if (source.line) {
+        out << ":" << source.line.value();
+    }
+    return out;
 }
 
-inline ::std::ostream& operator<<(::std::ostream& out, const SourceLine& source) {
-    return out << source.path << ":" << source.line;
-}
-
-inline ::std::ostream& operator<<(::std::ostream& out, const SourceLineColumn& source) {
-    return out << source.path << ":" << source.line << ":" << source.column;
-}
-
-inline bool operator<(const SourceLine& lhs, const SourceLine& rhs) {
-    return std::tie(lhs.path, lhs.line) < std::tie(rhs.path, rhs.line);
+inline bool operator<(const Source& lhs, const Source& rhs) {
+    int cmp = lhs.path.compare(rhs.path);
+    if (cmp < 0) return true;
+    if (cmp > 0) return false;
+    if (lhs.line) {
+        if (rhs.line) {
+            return lhs.line.value() < rhs.line.value();
+        }
+        return false;
+    }
+    return bool(rhs.line);
 }
 
 } // namespace aapt
diff --git a/tools/aapt2/SourceXmlPullParser.h b/tools/aapt2/SourceXmlPullParser.h
deleted file mode 100644
index d8ed459..0000000
--- a/tools/aapt2/SourceXmlPullParser.h
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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_SOURCE_XML_PULL_PARSER_H
-#define AAPT_SOURCE_XML_PULL_PARSER_H
-
-#include "XmlPullParser.h"
-
-#include <istream>
-#include <expat.h>
-#include <queue>
-#include <stack>
-#include <string>
-#include <vector>
-
-namespace aapt {
-
-class SourceXmlPullParser : public XmlPullParser {
-public:
-    SourceXmlPullParser(std::istream& in);
-    SourceXmlPullParser(const SourceXmlPullParser& rhs) = delete;
-    ~SourceXmlPullParser();
-
-    Event getEvent() const override;
-    const std::string& getLastError() const override ;
-    Event next() override ;
-
-    const std::u16string& getComment() const override;
-    size_t getLineNumber() const override;
-    size_t getDepth() const override;
-
-    const std::u16string& getText() const override;
-
-    const std::u16string& getNamespacePrefix() const override;
-    const std::u16string& getNamespaceUri() const override;
-    bool applyPackageAlias(std::u16string* package,
-                           const std::u16string& defaultPackage) const override;
-
-
-    const std::u16string& getElementNamespace() const override;
-    const std::u16string& getElementName() const override;
-
-    const_iterator beginAttributes() const override;
-    const_iterator endAttributes() const override;
-    size_t getAttributeCount() const override;
-
-private:
-    static void XMLCALL startNamespaceHandler(void* userData, const char* prefix, const char* uri);
-    static void XMLCALL startElementHandler(void* userData, const char* name, const char** attrs);
-    static void XMLCALL characterDataHandler(void* userData, const char* s, int len);
-    static void XMLCALL endElementHandler(void* userData, const char* name);
-    static void XMLCALL endNamespaceHandler(void* userData, const char* prefix);
-    static void XMLCALL commentDataHandler(void* userData, const char* comment);
-
-    struct EventData {
-        Event event;
-        size_t lineNumber;
-        size_t depth;
-        std::u16string data1;
-        std::u16string data2;
-        std::u16string comment;
-        std::vector<Attribute> attributes;
-    };
-
-    std::istream& mIn;
-    XML_Parser mParser;
-    char mBuffer[16384];
-    std::queue<EventData> mEventQueue;
-    std::string mLastError;
-    const std::u16string mEmpty;
-    size_t mDepth;
-    std::stack<std::u16string> mNamespaceUris;
-    std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases;
-};
-
-} // namespace aapt
-
-#endif // AAPT_SOURCE_XML_PULL_PARSER_H
diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp
index c19aa98..8552f47 100644
--- a/tools/aapt2/StringPool.cpp
+++ b/tools/aapt2/StringPool.cpp
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-#include "BigBuffer.h"
-#include "StringPiece.h"
+#include "util/BigBuffer.h"
+#include "util/StringPiece.h"
 #include "StringPool.h"
-#include "Util.h"
+#include "util/Util.h"
 
 #include <algorithm>
 #include <androidfw/ResourceTypes.h>
@@ -219,7 +219,7 @@
     auto indexIter = std::begin(mIndexedStrings);
     while (indexIter != iterEnd) {
         if (indexIter->second->ref <= 0) {
-            mIndexedStrings.erase(indexIter++);
+            indexIter = mIndexedStrings.erase(indexIter);
         } else {
             ++indexIter;
         }
@@ -241,6 +241,12 @@
     // a deleted string from the StyleEntry.
     mStrings.erase(endIter2, std::end(mStrings));
     mStyles.erase(endIter3, std::end(mStyles));
+
+    // Reassign the indices.
+    const size_t len = mStrings.size();
+    for (size_t index = 0; index < len; index++) {
+        mStrings[index]->index = index;
+    }
 }
 
 void StringPool::sort(const std::function<bool(const Entry&, const Entry&)>& cmp) {
diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h
index 14304a6..509e304 100644
--- a/tools/aapt2/StringPool.h
+++ b/tools/aapt2/StringPool.h
@@ -17,9 +17,9 @@
 #ifndef AAPT_STRING_POOL_H
 #define AAPT_STRING_POOL_H
 
-#include "BigBuffer.h"
+#include "util/BigBuffer.h"
 #include "ConfigDescription.h"
-#include "StringPiece.h"
+#include "util/StringPiece.h"
 
 #include <functional>
 #include <map>
diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp
index 9552937..c722fbe 100644
--- a/tools/aapt2/StringPool_test.cpp
+++ b/tools/aapt2/StringPool_test.cpp
@@ -15,7 +15,7 @@
  */
 
 #include "StringPool.h"
-#include "Util.h"
+#include "util/Util.h"
 
 #include <gtest/gtest.h>
 #include <string>
@@ -67,15 +67,23 @@
 TEST(StringPoolTest, PruneStringsWithNoReferences) {
     StringPool pool;
 
+    StringPool::Ref refA = pool.makeRef(u"foo");
     {
         StringPool::Ref ref = pool.makeRef(u"wut");
         EXPECT_EQ(*ref, u"wut");
-        EXPECT_EQ(1u, pool.size());
+        EXPECT_EQ(2u, pool.size());
     }
+    StringPool::Ref refB = pool.makeRef(u"bar");
 
-    EXPECT_EQ(1u, pool.size());
+    EXPECT_EQ(3u, pool.size());
     pool.prune();
-    EXPECT_EQ(0u, pool.size());
+    EXPECT_EQ(2u, pool.size());
+    StringPool::const_iterator iter = begin(pool);
+    EXPECT_EQ((*iter)->value, u"foo");
+    EXPECT_LT((*iter)->index, 2u);
+    ++iter;
+    EXPECT_EQ((*iter)->value, u"bar");
+    EXPECT_LT((*iter)->index, 2u);
 }
 
 TEST(StringPoolTest, SortAndMaintainIndexesInReferences) {
diff --git a/tools/aapt2/TableFlattener.cpp b/tools/aapt2/TableFlattener.cpp
deleted file mode 100644
index b7c04f0..0000000
--- a/tools/aapt2/TableFlattener.cpp
+++ /dev/null
@@ -1,570 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "BigBuffer.h"
-#include "ConfigDescription.h"
-#include "Logger.h"
-#include "ResourceTable.h"
-#include "ResourceTypeExtensions.h"
-#include "ResourceValues.h"
-#include "StringPool.h"
-#include "TableFlattener.h"
-#include "Util.h"
-
-#include <algorithm>
-#include <androidfw/ResourceTypes.h>
-#include <sstream>
-
-namespace aapt {
-
-struct FlatEntry {
-    const ResourceEntry* entry;
-    const Value* value;
-    uint32_t entryKey;
-    uint32_t sourcePathKey;
-    uint32_t sourceLine;
-};
-
-/**
- * Visitor that knows how to encode Map values.
- */
-class MapFlattener : public ConstValueVisitor {
-public:
-    MapFlattener(BigBuffer* out, const FlatEntry& flatEntry, SymbolEntryVector* symbols) :
-            mOut(out), mSymbols(symbols) {
-        mMap = mOut->nextBlock<android::ResTable_map_entry>();
-        mMap->key.index = flatEntry.entryKey;
-        mMap->flags = android::ResTable_entry::FLAG_COMPLEX;
-        if (flatEntry.entry->publicStatus.isPublic) {
-            mMap->flags |= android::ResTable_entry::FLAG_PUBLIC;
-        }
-        if (flatEntry.value->isWeak()) {
-            mMap->flags |= android::ResTable_entry::FLAG_WEAK;
-        }
-
-        ResTable_entry_source* sourceBlock = mOut->nextBlock<ResTable_entry_source>();
-        sourceBlock->pathIndex = flatEntry.sourcePathKey;
-        sourceBlock->line = flatEntry.sourceLine;
-
-        mMap->size = sizeof(*mMap) + sizeof(*sourceBlock);
-    }
-
-    void flattenParent(const Reference& ref) {
-        if (!ref.id.isValid()) {
-            mSymbols->push_back({
-                    ResourceNameRef(ref.name),
-                    (mOut->size() - mMap->size) + sizeof(*mMap) - sizeof(android::ResTable_entry)
-            });
-        }
-        mMap->parent.ident = ref.id.id;
-    }
-
-    void flattenEntry(const Reference& key, const Item& value) {
-        mMap->count++;
-
-        android::ResTable_map* outMapEntry = mOut->nextBlock<android::ResTable_map>();
-
-        // Write the key.
-        if (!Res_INTERNALID(key.id.id) && !key.id.isValid()) {
-            assert(!key.name.entry.empty());
-            mSymbols->push_back(std::make_pair(ResourceNameRef(key.name),
-                    mOut->size() - sizeof(*outMapEntry)));
-        }
-        outMapEntry->name.ident = key.id.id;
-
-        // 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);
-    }
-
-    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;
-    }
-
-    void visit(const Style& style, ValueVisitorArgs&) override {
-        if (style.parent.name.isValid()) {
-            flattenParent(style.parent);
-        }
-
-        // First sort the entries by ID.
-        std::vector<const Style::Entry*> sortedEntries;
-        for (const auto& styleEntry : style.entries) {
-            auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(),
-                    &styleEntry, compareStyleEntries);
-            sortedEntries.insert(iter, &styleEntry);
-        }
-
-        for (const Style::Entry* styleEntry : sortedEntries) {
-            flattenEntry(styleEntry->key, *styleEntry->value);
-        }
-    }
-
-    void visit(const Attribute& attr, ValueVisitorArgs&) override {
-        android::Res_value tempVal;
-        tempVal.dataType = android::Res_value::TYPE_INT_DEC;
-        tempVal.data = attr.typeMask;
-        flattenEntry(Reference(ResourceId{android::ResTable_map::ATTR_TYPE}),
-                BinaryPrimitive(tempVal));
-
-        for (const auto& symbol : attr.symbols) {
-            tempVal.data = symbol.value;
-            flattenEntry(symbol.symbol, BinaryPrimitive(tempVal));
-        }
-    }
-
-    void visit(const Styleable& styleable, ValueVisitorArgs&) override {
-        for (const auto& attr : styleable.entries) {
-            flattenEntry(attr, BinaryPrimitive(android::Res_value{}));
-        }
-    }
-
-    void visit(const Array& array, ValueVisitorArgs&) override {
-        for (const auto& item : array.items) {
-            flattenValueOnly(*item);
-        }
-    }
-
-    void visit(const Plural& plural, ValueVisitorArgs&) override {
-        const size_t count = plural.values.size();
-        for (size_t i = 0; i < count; i++) {
-            if (!plural.values[i]) {
-                continue;
-            }
-
-            ResourceId q;
-            switch (i) {
-                case Plural::Zero:
-                    q.id = android::ResTable_map::ATTR_ZERO;
-                    break;
-
-                case Plural::One:
-                    q.id = android::ResTable_map::ATTR_ONE;
-                    break;
-
-                case Plural::Two:
-                    q.id = android::ResTable_map::ATTR_TWO;
-                    break;
-
-                case Plural::Few:
-                    q.id = android::ResTable_map::ATTR_FEW;
-                    break;
-
-                case Plural::Many:
-                    q.id = android::ResTable_map::ATTR_MANY;
-                    break;
-
-                case Plural::Other:
-                    q.id = android::ResTable_map::ATTR_OTHER;
-                    break;
-
-                default:
-                    assert(false);
-                    break;
-            }
-
-            flattenEntry(Reference(q), *plural.values[i]);
-        }
-    }
-
-private:
-    BigBuffer* mOut;
-    SymbolEntryVector* mSymbols;
-    android::ResTable_map_entry* mMap;
-};
-
-/**
- * Flattens a value, with special handling for References.
- */
-struct ValueFlattener : ConstValueVisitor {
-    ValueFlattener(BigBuffer* out, SymbolEntryVector* symbols) :
-            result(false), mOut(out), mOutValue(nullptr), mSymbols(symbols) {
-        mOutValue = mOut->nextBlock<android::Res_value>();
-    }
-
-    virtual void visit(const Reference& ref, ValueVisitorArgs& a) override {
-        visitItem(ref, a);
-        if (mOutValue->data == 0x0) {
-            mSymbols->push_back({
-                    ResourceNameRef(ref.name),
-                    mOut->size() - sizeof(mOutValue->data)});
-        }
-    }
-
-    virtual void visitItem(const Item& item, ValueVisitorArgs&) override {
-        result = item.flatten(*mOutValue);
-        mOutValue->res0 = 0;
-        mOutValue->size = sizeof(*mOutValue);
-    }
-
-    bool result;
-
-private:
-    BigBuffer* mOut;
-    android::Res_value* mOutValue;
-    SymbolEntryVector* mSymbols;
-};
-
-TableFlattener::TableFlattener(Options options)
-: mOptions(options) {
-}
-
-bool TableFlattener::flattenValue(BigBuffer* out, const FlatEntry& flatEntry,
-                                  SymbolEntryVector* symbols) {
-    if (flatEntry.value->isItem()) {
-        android::ResTable_entry* entry = out->nextBlock<android::ResTable_entry>();
-
-        if (flatEntry.entry->publicStatus.isPublic) {
-            entry->flags |= android::ResTable_entry::FLAG_PUBLIC;
-        }
-
-        if (flatEntry.value->isWeak()) {
-            entry->flags |= android::ResTable_entry::FLAG_WEAK;
-        }
-
-        entry->key.index = flatEntry.entryKey;
-        entry->size = sizeof(*entry);
-
-        if (mOptions.useExtendedChunks) {
-            // Write the extra source block. This will be ignored by
-            // the Android runtime.
-            ResTable_entry_source* sourceBlock = out->nextBlock<ResTable_entry_source>();
-            sourceBlock->pathIndex = flatEntry.sourcePathKey;
-            sourceBlock->line = flatEntry.sourceLine;
-            entry->size += sizeof(*sourceBlock);
-        }
-
-        const Item* item = static_cast<const Item*>(flatEntry.value);
-        ValueFlattener flattener(out, symbols);
-        item->accept(flattener, {});
-        return flattener.result;
-    }
-
-    MapFlattener flattener(out, flatEntry, symbols);
-    flatEntry.value->accept(flattener, {});
-    return true;
-}
-
-bool TableFlattener::flatten(BigBuffer* out, const ResourceTable& table) {
-    const size_t beginning = out->size();
-
-    if (table.getPackageId() == ResourceTable::kUnsetPackageId) {
-        Logger::error()
-                << "ResourceTable has no package ID set."
-                << std::endl;
-        return false;
-    }
-
-    SymbolEntryVector symbolEntries;
-
-    StringPool typePool;
-    StringPool keyPool;
-    StringPool sourcePool;
-
-    // Sort the types by their IDs. They will be inserted into the StringPool
-    // in this order.
-    std::vector<ResourceTableType*> sortedTypes;
-    for (const auto& type : table) {
-        if (type->type == ResourceType::kStyleable && !mOptions.useExtendedChunks) {
-            continue;
-        }
-
-        auto iter = std::lower_bound(std::begin(sortedTypes), std::end(sortedTypes), type.get(),
-                [](const ResourceTableType* lhs, const ResourceTableType* rhs) -> bool {
-                    return lhs->typeId < rhs->typeId;
-                });
-        sortedTypes.insert(iter, type.get());
-    }
-
-    BigBuffer typeBlock(1024);
-    size_t expectedTypeId = 1;
-    for (const ResourceTableType* type : sortedTypes) {
-        if (type->typeId == ResourceTableType::kUnsetTypeId
-                || type->typeId == 0) {
-            Logger::error()
-                    << "resource type '"
-                    << type->type
-                    << "' from package '"
-                    << table.getPackage()
-                    << "' has no ID."
-                    << std::endl;
-            return false;
-        }
-
-        // If there is a gap in the type IDs, fill in the StringPool
-        // with empty values until we reach the ID we expect.
-        while (type->typeId > expectedTypeId) {
-            std::u16string typeName(u"?");
-            typeName += expectedTypeId;
-            typePool.makeRef(typeName);
-            expectedTypeId++;
-        }
-        expectedTypeId++;
-        typePool.makeRef(toString(type->type));
-
-        android::ResTable_typeSpec* spec = typeBlock.nextBlock<android::ResTable_typeSpec>();
-        spec->header.type = android::RES_TABLE_TYPE_SPEC_TYPE;
-        spec->header.headerSize = sizeof(*spec);
-        spec->header.size = spec->header.headerSize + (type->entries.size() * sizeof(uint32_t));
-        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());
-
-        // Sort the entries by entry ID and write their configuration masks.
-        std::vector<ResourceEntry*> entries;
-        const size_t entryCount = type->entries.size();
-        for (size_t entryIndex = 0; entryIndex < entryCount; entryIndex++) {
-            const auto& entry = type->entries[entryIndex];
-
-            if (entry->entryId == ResourceEntry::kUnsetEntryId) {
-                Logger::error()
-                        << "resource '"
-                        << ResourceName{ table.getPackage(), type->type, entry->name }
-                        << "' has no ID."
-                        << std::endl;
-                return false;
-            }
-
-            auto iter = std::lower_bound(std::begin(entries), std::end(entries), entry.get(),
-                    [](const ResourceEntry* lhs, const ResourceEntry* rhs) -> bool {
-                        return lhs->entryId < rhs->entryId;
-                    });
-            entries.insert(iter, entry.get());
-
-            // Populate the config masks for this entry.
-            if (entry->publicStatus.isPublic) {
-                configMasks[entry->entryId] |= android::ResTable_typeSpec::SPEC_PUBLIC;
-            }
-
-            const size_t configCount = entry->values.size();
-            for (size_t i = 0; i < configCount; i++) {
-                const ConfigDescription& config = entry->values[i].config;
-                for (size_t j = i + 1; j < configCount; j++) {
-                    configMasks[entry->entryId] |= config.diff(entry->values[j].config);
-                }
-            }
-        }
-
-        const size_t beforePublicHeader = typeBlock.size();
-        Public_header* publicHeader = nullptr;
-        if (mOptions.useExtendedChunks) {
-            publicHeader = typeBlock.nextBlock<Public_header>();
-            publicHeader->header.type = RES_TABLE_PUBLIC_TYPE;
-            publicHeader->header.headerSize = sizeof(*publicHeader);
-            publicHeader->typeId = type->typeId;
-        }
-
-        // The binary resource table lists resource entries for each configuration.
-        // We store them inverted, where a resource entry lists the values for each
-        // configuration available. Here we reverse this to match the binary table.
-        std::map<ConfigDescription, std::vector<FlatEntry>> data;
-        for (const ResourceEntry* entry : entries) {
-            size_t keyIndex = keyPool.makeRef(entry->name).getIndex();
-
-            if (keyIndex > std::numeric_limits<uint32_t>::max()) {
-                Logger::error()
-                        << "resource key string pool exceeded max size."
-                        << std::endl;
-                return false;
-            }
-
-            if (publicHeader && entry->publicStatus.isPublic) {
-                // Write the public status of this entry.
-                Public_entry* publicEntry = typeBlock.nextBlock<Public_entry>();
-                publicEntry->entryId = static_cast<uint32_t>(entry->entryId);
-                publicEntry->key.index = static_cast<uint32_t>(keyIndex);
-                publicEntry->source.index = static_cast<uint32_t>(sourcePool.makeRef(
-                            util::utf8ToUtf16(entry->publicStatus.source.path)).getIndex());
-                publicEntry->sourceLine = static_cast<uint32_t>(entry->publicStatus.source.line);
-                publicHeader->count += 1;
-            }
-
-            for (const auto& configValue : entry->values) {
-                data[configValue.config].push_back(FlatEntry{
-                        entry,
-                        configValue.value.get(),
-                        static_cast<uint32_t>(keyIndex),
-                        static_cast<uint32_t>(sourcePool.makeRef(util::utf8ToUtf16(
-                                    configValue.source.path)).getIndex()),
-                        static_cast<uint32_t>(configValue.source.line)
-                });
-            }
-        }
-
-        if (publicHeader) {
-            typeBlock.align4();
-            publicHeader->header.size =
-                    static_cast<uint32_t>(typeBlock.size() - beforePublicHeader);
-        }
-
-        // Begin flattening a configuration for the current type.
-        for (const auto& entry : data) {
-            const size_t typeHeaderStart = typeBlock.size();
-            android::ResTable_type* typeHeader = typeBlock.nextBlock<android::ResTable_type>();
-            typeHeader->header.type = android::RES_TABLE_TYPE_TYPE;
-            typeHeader->header.headerSize = sizeof(*typeHeader);
-            typeHeader->id = type->typeId;
-            typeHeader->entryCount = type->entries.size();
-            typeHeader->entriesStart = typeHeader->header.headerSize
-                    + (sizeof(uint32_t) * type->entries.size());
-            typeHeader->config = entry.first;
-
-            uint32_t* indices = typeBlock.nextBlock<uint32_t>(type->entries.size());
-            memset(indices, 0xff, type->entries.size() * sizeof(uint32_t));
-
-            const size_t entryStart = typeBlock.size();
-            for (const FlatEntry& flatEntry : entry.second) {
-                assert(flatEntry.entry->entryId < type->entries.size());
-                indices[flatEntry.entry->entryId] = typeBlock.size() - entryStart;
-                if (!flattenValue(&typeBlock, flatEntry, &symbolEntries)) {
-                    Logger::error()
-                            << "failed to flatten resource '"
-                            << ResourceNameRef {
-                                    table.getPackage(), type->type, flatEntry.entry->name }
-                            << "' for configuration '"
-                            << entry.first
-                            << "'."
-                            << std::endl;
-                    return false;
-                }
-            }
-
-            typeBlock.align4();
-            typeHeader->header.size = typeBlock.size() - typeHeaderStart;
-        }
-    }
-
-    const size_t beforeTable = out->size();
-    android::ResTable_header* header = out->nextBlock<android::ResTable_header>();
-    header->header.type = android::RES_TABLE_TYPE;
-    header->header.headerSize = sizeof(*header);
-    header->packageCount = 1;
-
-    SymbolTable_entry* symbolEntryData = nullptr;
-    if (!symbolEntries.empty() && mOptions.useExtendedChunks) {
-        const size_t beforeSymbolTable = out->size();
-        StringPool symbolPool;
-        SymbolTable_header* symbolHeader = out->nextBlock<SymbolTable_header>();
-        symbolHeader->header.type = RES_TABLE_SYMBOL_TABLE_TYPE;
-        symbolHeader->header.headerSize = sizeof(*symbolHeader);
-        symbolHeader->count = symbolEntries.size();
-
-        symbolEntryData = out->nextBlock<SymbolTable_entry>(symbolHeader->count);
-
-        size_t i = 0;
-        for (const auto& entry : symbolEntries) {
-            symbolEntryData[i].offset = entry.second;
-            StringPool::Ref ref = symbolPool.makeRef(
-                    entry.first.package.toString() + u":" +
-                    toString(entry.first.type).toString() + u"/" +
-                    entry.first.entry.toString());
-            symbolEntryData[i].stringIndex = ref.getIndex();
-            i++;
-        }
-
-        StringPool::flattenUtf8(out, symbolPool);
-        out->align4();
-        symbolHeader->header.size = out->size() - beforeSymbolTable;
-    }
-
-    if (sourcePool.size() > 0 && mOptions.useExtendedChunks) {
-        const size_t beforeSourcePool = out->size();
-        android::ResChunk_header* sourceHeader = out->nextBlock<android::ResChunk_header>();
-        sourceHeader->type = RES_TABLE_SOURCE_POOL_TYPE;
-        sourceHeader->headerSize = sizeof(*sourceHeader);
-        StringPool::flattenUtf8(out, sourcePool);
-        out->align4();
-        sourceHeader->size = out->size() - beforeSourcePool;
-    }
-
-    StringPool::flattenUtf8(out, table.getValueStringPool());
-
-    const size_t beforePackageIndex = out->size();
-    android::ResTable_package* package = out->nextBlock<android::ResTable_package>();
-    package->header.type = android::RES_TABLE_PACKAGE_TYPE;
-    package->header.headerSize = sizeof(*package);
-
-    if (table.getPackageId() > std::numeric_limits<uint8_t>::max()) {
-        Logger::error()
-                << "package ID 0x'"
-                << std::hex << table.getPackageId() << std::dec
-                << "' is invalid."
-                << std::endl;
-        return false;
-    }
-    package->id = table.getPackageId();
-
-    if (table.getPackage().size() >= sizeof(package->name) / sizeof(package->name[0])) {
-        Logger::error()
-                << "package name '"
-                << table.getPackage()
-                << "' is too long."
-                << std::endl;
-        return false;
-    }
-    memcpy(package->name, reinterpret_cast<const uint16_t*>(table.getPackage().data()),
-            table.getPackage().length() * sizeof(char16_t));
-    package->name[table.getPackage().length()] = 0;
-
-    package->typeStrings = package->header.headerSize;
-    StringPool::flattenUtf16(out, typePool);
-    package->keyStrings = out->size() - beforePackageIndex;
-    StringPool::flattenUtf16(out, keyPool);
-
-    if (symbolEntryData != nullptr) {
-        for (size_t i = 0; i < symbolEntries.size(); i++) {
-            symbolEntryData[i].offset += out->size() - beginning;
-        }
-    }
-
-    out->appendBuffer(std::move(typeBlock));
-
-    package->header.size = out->size() - beforePackageIndex;
-    header->header.size = out->size() - beforeTable;
-    return true;
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/TableFlattener.h b/tools/aapt2/TableFlattener.h
deleted file mode 100644
index ccbb737..0000000
--- a/tools/aapt2/TableFlattener.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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_TABLE_FLATTENER_H
-#define AAPT_TABLE_FLATTENER_H
-
-#include "BigBuffer.h"
-#include "ResourceTable.h"
-
-namespace aapt {
-
-using SymbolEntryVector = std::vector<std::pair<ResourceNameRef, uint32_t>>;
-
-struct FlatEntry;
-
-/**
- * Flattens a ResourceTable into a binary format suitable
- * for loading into a ResTable on the host or device.
- */
-struct TableFlattener {
-    /**
-     * A set of options for this TableFlattener.
-     */
-    struct Options {
-        /**
-         * Specifies whether to output extended chunks, like
-         * source information and mising symbol entries. Default
-         * is true.
-         *
-         * Set this to false when emitting the final table to be used
-         * on device.
-         */
-        bool useExtendedChunks = true;
-    };
-
-    TableFlattener(Options options);
-
-    bool flatten(BigBuffer* out, const ResourceTable& table);
-
-private:
-    bool flattenValue(BigBuffer* out, const FlatEntry& flatEntry, SymbolEntryVector* symbols);
-
-    Options mOptions;
-};
-
-} // namespace aapt
-
-#endif // AAPT_TABLE_FLATTENER_H
diff --git a/tools/aapt2/Util_test.cpp b/tools/aapt2/Util_test.cpp
deleted file mode 100644
index 92f2a1c..0000000
--- a/tools/aapt2/Util_test.cpp
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * 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 <gtest/gtest.h>
-#include <string>
-
-#include "StringPiece.h"
-#include "Util.h"
-
-namespace aapt {
-
-TEST(UtilTest, TrimOnlyWhitespace) {
-    const std::u16string full = u"\n        ";
-
-    StringPiece16 trimmed = util::trimWhitespace(full);
-    EXPECT_TRUE(trimmed.empty());
-    EXPECT_EQ(0u, trimmed.size());
-}
-
-TEST(UtilTest, StringEndsWith) {
-    EXPECT_TRUE(util::stringEndsWith<char>("hello.xml", ".xml"));
-}
-
-TEST(UtilTest, StringStartsWith) {
-    EXPECT_TRUE(util::stringStartsWith<char>("hello.xml", "he"));
-}
-
-TEST(UtilTest, StringBuilderSplitEscapeSequence) {
-    EXPECT_EQ(StringPiece16(u"this is a new\nline."),
-            util::StringBuilder().append(u"this is a new\\")
-                                 .append(u"nline.")
-                                 .str());
-}
-
-TEST(UtilTest, StringBuilderWhitespaceRemoval) {
-    EXPECT_EQ(StringPiece16(u"hey guys this is so cool"),
-            util::StringBuilder().append(u"    hey guys ")
-                                 .append(u" this is so cool ")
-                                 .str());
-
-    EXPECT_EQ(StringPiece16(u" wow,  so many \t spaces. what?"),
-            util::StringBuilder().append(u" \" wow,  so many \t ")
-                                 .append(u"spaces. \"what? ")
-                                 .str());
-
-    EXPECT_EQ(StringPiece16(u"where is the pie?"),
-            util::StringBuilder().append(u"  where \t ")
-                                 .append(u" \nis the "" pie?")
-                                 .str());
-}
-
-TEST(UtilTest, StringBuilderEscaping) {
-    EXPECT_EQ(StringPiece16(u"hey guys\n this \t is so\\ cool"),
-            util::StringBuilder().append(u"    hey guys\\n ")
-                                 .append(u" this \\t is so\\\\ cool ")
-                                 .str());
-
-    EXPECT_EQ(StringPiece16(u"@?#\\\'"),
-            util::StringBuilder().append(u"\\@\\?\\#\\\\\\'")
-                                 .str());
-}
-
-TEST(UtilTest, StringBuilderMisplacedQuote) {
-    util::StringBuilder builder{};
-    EXPECT_FALSE(builder.append(u"they're coming!"));
-}
-
-TEST(UtilTest, StringBuilderUnicodeCodes) {
-    EXPECT_EQ(StringPiece16(u"\u00AF\u0AF0 woah"),
-            util::StringBuilder().append(u"\\u00AF\\u0AF0 woah")
-                                 .str());
-
-    EXPECT_FALSE(util::StringBuilder().append(u"\\u00 yo"));
-}
-
-TEST(UtilTest, TokenizeInput) {
-    auto tokenizer = util::tokenize(StringPiece16(u"this| is|the|end"), u'|');
-    auto iter = tokenizer.begin();
-    ASSERT_EQ(*iter, StringPiece16(u"this"));
-    ++iter;
-    ASSERT_EQ(*iter, StringPiece16(u" is"));
-    ++iter;
-    ASSERT_EQ(*iter, StringPiece16(u"the"));
-    ++iter;
-    ASSERT_EQ(*iter, StringPiece16(u"end"));
-    ++iter;
-    ASSERT_EQ(tokenizer.end(), iter);
-}
-
-TEST(UtilTest, IsJavaClassName) {
-    EXPECT_TRUE(util::isJavaClassName(u"android.test.Class"));
-    EXPECT_TRUE(util::isJavaClassName(u"android.test.Class$Inner"));
-    EXPECT_TRUE(util::isJavaClassName(u"android_test.test.Class"));
-    EXPECT_TRUE(util::isJavaClassName(u"_android_.test._Class_"));
-    EXPECT_FALSE(util::isJavaClassName(u"android.test.$Inner"));
-    EXPECT_FALSE(util::isJavaClassName(u"android.test.Inner$"));
-    EXPECT_FALSE(util::isJavaClassName(u".test.Class"));
-    EXPECT_FALSE(util::isJavaClassName(u"android"));
-}
-
-TEST(UtilTest, FullyQualifiedClassName) {
-    Maybe<std::u16string> res = util::getFullyQualifiedClassName(u"android", u"asdf");
-    ASSERT_TRUE(res);
-    EXPECT_EQ(res.value(), u"android.asdf");
-
-    res = util::getFullyQualifiedClassName(u"android", u".asdf");
-    ASSERT_TRUE(res);
-    EXPECT_EQ(res.value(), u"android.asdf");
-
-    res = util::getFullyQualifiedClassName(u"android", u".a.b");
-    ASSERT_TRUE(res);
-    EXPECT_EQ(res.value(), u"android.a.b");
-
-    res = util::getFullyQualifiedClassName(u"android", u"a.b");
-    ASSERT_TRUE(res);
-    EXPECT_EQ(res.value(), u"a.b");
-
-    res = util::getFullyQualifiedClassName(u"", u"a.b");
-    ASSERT_TRUE(res);
-    EXPECT_EQ(res.value(), u"a.b");
-
-    res = util::getFullyQualifiedClassName(u"", u"");
-    ASSERT_FALSE(res);
-
-    res = util::getFullyQualifiedClassName(u"android", u"./Apple");
-    ASSERT_FALSE(res);
-}
-
-
-} // namespace aapt
diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h
new file mode 100644
index 0000000..ee058aa
--- /dev/null
+++ b/tools/aapt2/ValueVisitor.h
@@ -0,0 +1,133 @@
+/*
+ * 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_VALUE_VISITOR_H
+#define AAPT_VALUE_VISITOR_H
+
+#include "ResourceValues.h"
+
+namespace aapt {
+
+/**
+ * Visits a value and invokes the appropriate method based on its type. Does not traverse
+ * into compound types. Use ValueVisitor for that.
+ */
+struct RawValueVisitor {
+    virtual ~RawValueVisitor() = default;
+
+    virtual void visitItem(Item* value) {}
+    virtual void visit(Reference* value) { visitItem(value); }
+    virtual void visit(RawString* value) { visitItem(value); }
+    virtual void visit(String* value) { visitItem(value); }
+    virtual void visit(StyledString* value) { visitItem(value); }
+    virtual void visit(FileReference* value) { visitItem(value); }
+    virtual void visit(Id* value) { visitItem(value); }
+    virtual void visit(BinaryPrimitive* value) { visitItem(value); }
+
+    virtual void visit(Attribute* value) {}
+    virtual void visit(Style* value) {}
+    virtual void visit(Array* value) {}
+    virtual void visit(Plural* value) {}
+    virtual void visit(Styleable* value) {}
+};
+
+#define DECL_VISIT_COMPOUND_VALUE(T) \
+    virtual void visit(T* value) { \
+        visitSubValues(value); \
+    }
+
+/**
+ * Visits values, and if they are compound values, visits the components as well.
+ */
+struct ValueVisitor : public RawValueVisitor {
+    // The compiler will think we're hiding an overload, when we actually intend
+    // to call into RawValueVisitor. This will expose the visit methods in the super
+    // class so the compiler knows we are trying to call them.
+    using RawValueVisitor::visit;
+
+    void visitSubValues(Attribute* attribute) {
+        for (Attribute::Symbol& symbol : attribute->symbols) {
+            visit(&symbol.symbol);
+        }
+    }
+
+    void visitSubValues(Style* style) {
+        if (style->parent) {
+            visit(&style->parent.value());
+        }
+
+        for (Style::Entry& entry : style->entries) {
+            visit(&entry.key);
+            entry.value->accept(this);
+        }
+    }
+
+    void visitSubValues(Array* array) {
+        for (std::unique_ptr<Item>& item : array->items) {
+            item->accept(this);
+        }
+    }
+
+    void visitSubValues(Plural* plural) {
+        for (std::unique_ptr<Item>& item : plural->values) {
+            if (item) {
+                item->accept(this);
+            }
+        }
+    }
+
+    void visitSubValues(Styleable* styleable) {
+        for (Reference& reference : styleable->entries) {
+            visit(&reference);
+        }
+    }
+
+    DECL_VISIT_COMPOUND_VALUE(Attribute);
+    DECL_VISIT_COMPOUND_VALUE(Style);
+    DECL_VISIT_COMPOUND_VALUE(Array);
+    DECL_VISIT_COMPOUND_VALUE(Plural);
+    DECL_VISIT_COMPOUND_VALUE(Styleable);
+};
+
+/**
+ * Do not use directly. Helper struct for dyn_cast.
+ */
+template <typename T>
+struct DynCastVisitor : public RawValueVisitor {
+    T* value = nullptr;
+
+    void visit(T* v) override {
+        value = v;
+    }
+};
+
+/**
+ * Returns a valid pointer to T if the Value is of subtype T.
+ * Otherwise, returns nullptr.
+ */
+template <typename T>
+T* valueCast(Value* value) {
+    if (!value) {
+        return nullptr;
+    }
+    DynCastVisitor<T> visitor;
+    value->accept(&visitor);
+    return visitor.value;
+}
+
+} // namespace aapt
+
+#endif // AAPT_VALUE_VISITOR_H
diff --git a/tools/aapt2/ValueVisitor_test.cpp b/tools/aapt2/ValueVisitor_test.cpp
new file mode 100644
index 0000000..1624079
--- /dev/null
+++ b/tools/aapt2/ValueVisitor_test.cpp
@@ -0,0 +1,87 @@
+/*
+ * 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 <gtest/gtest.h>
+#include <string>
+
+#include "ResourceValues.h"
+#include "util/Util.h"
+#include "ValueVisitor.h"
+#include "test/Builders.h"
+
+namespace aapt {
+
+struct SingleReferenceVisitor : public ValueVisitor {
+    using ValueVisitor::visit;
+
+    Reference* visited = nullptr;
+
+    void visit(Reference* ref) override {
+        visited = ref;
+    }
+};
+
+struct StyleVisitor : public ValueVisitor {
+    using ValueVisitor::visit;
+
+    std::list<Reference*> visitedRefs;
+    Style* visitedStyle = nullptr;
+
+    void visit(Reference* ref) override {
+        visitedRefs.push_back(ref);
+    }
+
+    void visit(Style* style) override {
+        visitedStyle = style;
+        ValueVisitor::visit(style);
+    }
+};
+
+TEST(ValueVisitorTest, VisitsReference) {
+    Reference ref(ResourceName{u"android", ResourceType::kAttr, u"foo"});
+    SingleReferenceVisitor visitor;
+    ref.accept(&visitor);
+
+    EXPECT_EQ(visitor.visited, &ref);
+}
+
+TEST(ValueVisitorTest, VisitsReferencesInStyle) {
+    std::unique_ptr<Style> style = test::StyleBuilder()
+            .setParent(u"@android:style/foo")
+            .addItem(u"@android:attr/one", test::buildReference(u"@android:id/foo"))
+            .build();
+
+    StyleVisitor visitor;
+    style->accept(&visitor);
+
+    ASSERT_EQ(style.get(), visitor.visitedStyle);
+
+    // Entry attribute references, plus the parent reference, plus one value reference.
+    ASSERT_EQ(style->entries.size() + 2, visitor.visitedRefs.size());
+}
+
+TEST(ValueVisitorTest, ValueCast) {
+    std::unique_ptr<Reference> ref = test::buildReference(u"@android:color/white");
+    EXPECT_NE(valueCast<Reference>(ref.get()), nullptr);
+
+    std::unique_ptr<Style> style = test::StyleBuilder()
+            .addItem(u"@android:attr/foo", test::buildReference(u"@android:color/black"))
+            .build();
+    EXPECT_NE(valueCast<Style>(style.get()), nullptr);
+    EXPECT_EQ(valueCast<Reference>(style.get()), nullptr);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/XliffXmlPullParser.cpp b/tools/aapt2/XliffXmlPullParser.cpp
deleted file mode 100644
index 31115f2..0000000
--- a/tools/aapt2/XliffXmlPullParser.cpp
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * 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 "XliffXmlPullParser.h"
-
-#include <string>
-
-namespace aapt {
-
-XliffXmlPullParser::XliffXmlPullParser(const std::shared_ptr<XmlPullParser>& parser) :
-        mParser(parser) {
-}
-
-XmlPullParser::Event XliffXmlPullParser::next() {
-    while (XmlPullParser::isGoodEvent(mParser->next())) {
-        Event event = mParser->getEvent();
-        if (event != Event::kStartElement && event != Event::kEndElement) {
-            break;
-        }
-
-        if (mParser->getElementNamespace() !=
-                u"urn:oasis:names:tc:xliff:document:1.2") {
-            break;
-        }
-
-        const std::u16string& name = mParser->getElementName();
-        if (name != u"bpt"
-                && name != u"ept"
-                && name != u"it"
-                && name != u"ph"
-                && name != u"g"
-                && name != u"bx"
-                && name != u"ex"
-                && name != u"x") {
-            break;
-        }
-
-        // We hit a tag that was ignored, so get the next event.
-    }
-    return mParser->getEvent();
-}
-
-XmlPullParser::Event XliffXmlPullParser::getEvent() const {
-    return mParser->getEvent();
-}
-
-const std::string& XliffXmlPullParser::getLastError() const {
-    return mParser->getLastError();
-}
-
-const std::u16string& XliffXmlPullParser::getComment() const {
-    return mParser->getComment();
-}
-
-size_t XliffXmlPullParser::getLineNumber() const {
-    return mParser->getLineNumber();
-}
-
-size_t XliffXmlPullParser::getDepth() const {
-    return mParser->getDepth();
-}
-
-const std::u16string& XliffXmlPullParser::getText() const {
-    return mParser->getText();
-}
-
-const std::u16string& XliffXmlPullParser::getNamespacePrefix() const {
-    return mParser->getNamespacePrefix();
-}
-
-const std::u16string& XliffXmlPullParser::getNamespaceUri() const {
-    return mParser->getNamespaceUri();
-}
-
-bool XliffXmlPullParser::applyPackageAlias(std::u16string* package,
-                                           const std::u16string& defaultPackage) const {
-    return mParser->applyPackageAlias(package, defaultPackage);
-}
-
-const std::u16string& XliffXmlPullParser::getElementNamespace() const {
-    return mParser->getElementNamespace();
-}
-
-const std::u16string& XliffXmlPullParser::getElementName() const {
-    return mParser->getElementName();
-}
-
-size_t XliffXmlPullParser::getAttributeCount() const {
-    return mParser->getAttributeCount();
-}
-
-XmlPullParser::const_iterator XliffXmlPullParser::beginAttributes() const {
-    return mParser->beginAttributes();
-}
-
-XmlPullParser::const_iterator XliffXmlPullParser::endAttributes() const {
-    return mParser->endAttributes();
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/XliffXmlPullParser.h b/tools/aapt2/XliffXmlPullParser.h
deleted file mode 100644
index 7791227..0000000
--- a/tools/aapt2/XliffXmlPullParser.h
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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_XLIFF_XML_PULL_PARSER_H
-#define AAPT_XLIFF_XML_PULL_PARSER_H
-
-#include "XmlPullParser.h"
-
-#include <memory>
-#include <string>
-
-namespace aapt {
-
-/**
- * Strips xliff elements and provides the caller with a view of the
- * underlying XML without xliff.
- */
-class XliffXmlPullParser : public XmlPullParser {
-public:
-    XliffXmlPullParser(const std::shared_ptr<XmlPullParser>& parser);
-    XliffXmlPullParser(const XliffXmlPullParser& rhs) = delete;
-
-    Event getEvent() const override;
-    const std::string& getLastError() const override;
-    Event next() override;
-
-    const std::u16string& getComment() const override;
-    size_t getLineNumber() const override;
-    size_t getDepth() const override;
-
-    const std::u16string& getText() const override;
-
-    const std::u16string& getNamespacePrefix() const override;
-    const std::u16string& getNamespaceUri() const override;
-    bool applyPackageAlias(std::u16string* package, const std::u16string& defaultPackage)
-            const override;
-
-    const std::u16string& getElementNamespace() const override;
-    const std::u16string& getElementName() const override;
-
-    const_iterator beginAttributes() const override;
-    const_iterator endAttributes() const override;
-    size_t getAttributeCount() const override;
-
-private:
-    std::shared_ptr<XmlPullParser> mParser;
-};
-
-} // namespace aapt
-
-#endif // AAPT_XLIFF_XML_PULL_PARSER_H
diff --git a/tools/aapt2/XliffXmlPullParser_test.cpp b/tools/aapt2/XliffXmlPullParser_test.cpp
deleted file mode 100644
index f9030724..0000000
--- a/tools/aapt2/XliffXmlPullParser_test.cpp
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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 "SourceXmlPullParser.h"
-#include "XliffXmlPullParser.h"
-
-#include <gtest/gtest.h>
-#include <sstream>
-#include <string>
-
-namespace aapt {
-
-TEST(XliffXmlPullParserTest, IgnoreXliffTags) {
-    std::stringstream input;
-    input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl
-          << "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">" << std::endl
-          << "<string name=\"foo\">"
-          << "Hey <xliff:g><xliff:it>there</xliff:it></xliff:g> world</string>" << std::endl
-          << "</resources>" << std::endl;
-    std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input);
-    XliffXmlPullParser parser(sourceParser);
-    EXPECT_EQ(XmlPullParser::Event::kStartDocument, parser.getEvent());
-
-    EXPECT_EQ(XmlPullParser::Event::kStartNamespace, parser.next());
-    EXPECT_EQ(parser.getNamespaceUri(), u"urn:oasis:names:tc:xliff:document:1.2");
-    EXPECT_EQ(parser.getNamespacePrefix(), u"xliff");
-
-    EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.next());
-    EXPECT_EQ(parser.getElementNamespace(), u"");
-    EXPECT_EQ(parser.getElementName(), u"resources");
-    EXPECT_EQ(XmlPullParser::Event::kText, parser.next()); // Account for newline/whitespace.
-
-    EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.next());
-    EXPECT_EQ(parser.getElementNamespace(), u"");
-    EXPECT_EQ(parser.getElementName(), u"string");
-
-    EXPECT_EQ(XmlPullParser::Event::kText, parser.next());
-    EXPECT_EQ(parser.getText(), u"Hey ");
-
-    EXPECT_EQ(XmlPullParser::Event::kText, parser.next());
-    EXPECT_EQ(parser.getText(), u"there");
-
-    EXPECT_EQ(XmlPullParser::Event::kText, parser.next());
-    EXPECT_EQ(parser.getText(), u" world");
-
-    EXPECT_EQ(XmlPullParser::Event::kEndElement, parser.next());
-    EXPECT_EQ(parser.getElementNamespace(), u"");
-    EXPECT_EQ(parser.getElementName(), u"string");
-    EXPECT_EQ(XmlPullParser::Event::kText, parser.next()); // Account for newline/whitespace.
-
-    EXPECT_EQ(XmlPullParser::Event::kEndElement, parser.next());
-    EXPECT_EQ(parser.getElementNamespace(), u"");
-    EXPECT_EQ(parser.getElementName(), u"resources");
-
-    EXPECT_EQ(XmlPullParser::Event::kEndNamespace, parser.next());
-    EXPECT_EQ(parser.getNamespacePrefix(), u"xliff");
-    EXPECT_EQ(parser.getNamespaceUri(), u"urn:oasis:names:tc:xliff:document:1.2");
-
-    EXPECT_EQ(XmlPullParser::Event::kEndDocument, parser.next());
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/XmlDom.cpp b/tools/aapt2/XmlDom.cpp
index b8b2d12..d948775 100644
--- a/tools/aapt2/XmlDom.cpp
+++ b/tools/aapt2/XmlDom.cpp
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#include "Logger.h"
-#include "Util.h"
+#include "util/Util.h"
 #include "XmlDom.h"
 #include "XmlPullParser.h"
 
@@ -65,7 +64,7 @@
         stack->root = std::move(node);
     }
 
-    if (thisNode->type != NodeType::kText) {
+    if (!nodeCast<Text>(thisNode)) {
         stack->nodeStack.push(thisNode);
     }
 }
@@ -143,8 +142,7 @@
         Node* currentParent = stack->nodeStack.top();
         if (!currentParent->children.empty()) {
             Node* lastChild = currentParent->children.back().get();
-            if (lastChild->type == NodeType::kText) {
-                Text* text = static_cast<Text*>(lastChild);
+            if (Text* text = nodeCast<Text>(lastChild)) {
                 text->text += util::utf8ToUtf16(StringPiece(s, len));
                 return;
             }
@@ -166,7 +164,7 @@
     stack->pendingComment += util::utf8ToUtf16(comment);
 }
 
-std::unique_ptr<Node> inflate(std::istream* in, SourceLogger* logger) {
+std::unique_ptr<XmlResource> inflate(std::istream* in, IDiagnostics* diag, const Source& source) {
     Stack stack;
 
     XML_Parser parser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep);
@@ -182,20 +180,23 @@
         in->read(buffer, sizeof(buffer) / sizeof(buffer[0]));
         if (in->bad() && !in->eof()) {
             stack.root = {};
-            logger->error() << strerror(errno) << std::endl;
+            diag->error(DiagMessage(source) << strerror(errno));
             break;
         }
 
         if (XML_Parse(parser, buffer, in->gcount(), in->eof()) == XML_STATUS_ERROR) {
             stack.root = {};
-            logger->error(XML_GetCurrentLineNumber(parser))
-                    << XML_ErrorString(XML_GetErrorCode(parser)) << std::endl;
+            diag->error(DiagMessage(source.withLine(XML_GetCurrentLineNumber(parser)))
+                        << XML_ErrorString(XML_GetErrorCode(parser)));
             break;
         }
     }
 
     XML_ParserFree(parser);
-    return std::move(stack.root);
+    if (stack.root) {
+        return util::make_unique<XmlResource>(ResourceFile{}, std::move(stack.root));
+    }
+    return {};
 }
 
 static void copyAttributes(Element* el, android::ResXMLParser* parser) {
@@ -224,7 +225,8 @@
     }
 }
 
-std::unique_ptr<Node> inflate(const void* data, size_t dataLen, SourceLogger* logger) {
+std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag,
+                                     const Source& source) {
     std::unique_ptr<Node> root;
     std::stack<Node*> nodeStack;
 
@@ -307,15 +309,12 @@
                 nodeStack.top()->addChild(std::move(newNode));
             }
 
-            if (thisNode->type != NodeType::kText) {
+            if (!nodeCast<Text>(thisNode)) {
                 nodeStack.push(thisNode);
             }
         }
     }
-    return root;
-}
-
-Node::Node(NodeType type) : type(type), parent(nullptr), lineNumber(0), columnNumber(0) {
+    return util::make_unique<XmlResource>(ResourceFile{}, std::move(root));
 }
 
 void Node::addChild(std::unique_ptr<Node> child) {
@@ -323,39 +322,6 @@
     children.push_back(std::move(child));
 }
 
-Namespace::Namespace() : BaseNode(NodeType::kNamespace) {
-}
-
-std::unique_ptr<Node> Namespace::clone() const {
-    Namespace* ns = new Namespace();
-    ns->lineNumber = lineNumber;
-    ns->columnNumber = columnNumber;
-    ns->comment = comment;
-    ns->namespacePrefix = namespacePrefix;
-    ns->namespaceUri = namespaceUri;
-    for (auto& child : children) {
-        ns->addChild(child->clone());
-    }
-    return std::unique_ptr<Node>(ns);
-}
-
-Element::Element() : BaseNode(NodeType::kElement) {
-}
-
-std::unique_ptr<Node> Element::clone() const {
-    Element* el = new Element();
-    el->lineNumber = lineNumber;
-    el->columnNumber = columnNumber;
-    el->comment = comment;
-    el->namespaceUri = namespaceUri;
-    el->name = name;
-    el->attributes = attributes;
-    for (auto& child : children) {
-        el->addChild(child->clone());
-    }
-    return std::unique_ptr<Node>(el);
-}
-
 Attribute* Element::findAttribute(const StringPiece16& ns, const StringPiece16& name) {
     for (auto& attr : attributes) {
         if (ns == attr.namespaceUri && name == attr.name) {
@@ -366,29 +332,29 @@
 }
 
 Element* Element::findChild(const StringPiece16& ns, const StringPiece16& name) {
-    return findChildWithAttribute(ns, name, nullptr);
+    return findChildWithAttribute(ns, name, {}, {}, {});
 }
 
 Element* Element::findChildWithAttribute(const StringPiece16& ns, const StringPiece16& name,
-                                         const Attribute* reqAttr) {
+                                         const StringPiece16& attrNs, const StringPiece16& attrName,
+                                         const StringPiece16& attrValue) {
     for (auto& childNode : children) {
         Node* child = childNode.get();
-        while (child->type == NodeType::kNamespace) {
+        while (nodeCast<Namespace>(child)) {
             if (child->children.empty()) {
                 break;
             }
             child = child->children[0].get();
         }
 
-        if (child->type == NodeType::kElement) {
-            Element* el = static_cast<Element*>(child);
+        if (Element* el = nodeCast<Element>(child)) {
             if (ns == el->namespaceUri && name == el->name) {
-                if (!reqAttr) {
+                if (attrNs.empty() && attrName.empty()) {
                     return el;
                 }
 
-                Attribute* attrName = el->findAttribute(reqAttr->namespaceUri, reqAttr->name);
-                if (attrName && attrName->value == reqAttr->value) {
+                Attribute* attr = el->findAttribute(attrNs, attrName);
+                if (attr && attrValue == attr->value) {
                     return el;
                 }
             }
@@ -401,31 +367,19 @@
     std::vector<Element*> elements;
     for (auto& childNode : children) {
         Node* child = childNode.get();
-        while (child->type == NodeType::kNamespace) {
+        while (nodeCast<Namespace>(child)) {
             if (child->children.empty()) {
                 break;
             }
             child = child->children[0].get();
         }
 
-        if (child->type == NodeType::kElement) {
-            elements.push_back(static_cast<Element*>(child));
+        if (Element* el = nodeCast<Element>(child)) {
+            elements.push_back(el);
         }
     }
     return elements;
 }
 
-Text::Text() : BaseNode(NodeType::kText) {
-}
-
-std::unique_ptr<Node> Text::clone() const {
-    Text* el = new Text();
-    el->lineNumber = lineNumber;
-    el->columnNumber = columnNumber;
-    el->comment = comment;
-    el->text = text;
-    return std::unique_ptr<Node>(el);
-}
-
 } // namespace xml
 } // namespace aapt
diff --git a/tools/aapt2/XmlDom.h b/tools/aapt2/XmlDom.h
index 035e7c4..c095f08 100644
--- a/tools/aapt2/XmlDom.h
+++ b/tools/aapt2/XmlDom.h
@@ -17,8 +17,13 @@
 #ifndef AAPT_XML_DOM_H
 #define AAPT_XML_DOM_H
 
-#include "Logger.h"
-#include "StringPiece.h"
+#include "Diagnostics.h"
+#include "Resource.h"
+#include "ResourceValues.h"
+#include "util/StringPiece.h"
+#include "util/Util.h"
+
+#include "process/IResourceTableConsumer.h"
 
 #include <istream>
 #include <expat.h>
@@ -29,7 +34,7 @@
 namespace aapt {
 namespace xml {
 
-struct Visitor;
+struct RawVisitor;
 
 /**
  * The type of node. Can be used to downcast to the concrete XML node
@@ -45,17 +50,14 @@
  * Base class for all XML nodes.
  */
 struct Node {
-    NodeType type;
-    Node* parent;
-    size_t lineNumber;
-    size_t columnNumber;
+    Node* parent = nullptr;
+    size_t lineNumber = 0;
+    size_t columnNumber = 0;
     std::u16string comment;
     std::vector<std::unique_ptr<Node>> children;
 
-    Node(NodeType type);
     void addChild(std::unique_ptr<Node> child);
-    virtual std::unique_ptr<Node> clone() const = 0;
-    virtual void accept(Visitor* visitor) = 0;
+    virtual void accept(RawVisitor* visitor) = 0;
     virtual ~Node() {}
 };
 
@@ -65,8 +67,7 @@
  */
 template <typename Derived>
 struct BaseNode : public Node {
-    BaseNode(NodeType t);
-    virtual void accept(Visitor* visitor) override;
+    virtual void accept(RawVisitor* visitor) override;
 };
 
 /**
@@ -75,9 +76,11 @@
 struct Namespace : public BaseNode<Namespace> {
     std::u16string namespacePrefix;
     std::u16string namespaceUri;
+};
 
-    Namespace();
-    virtual std::unique_ptr<Node> clone() const override;
+struct AaptAttribute {
+    ResourceId id;
+    aapt::Attribute attribute;
 };
 
 /**
@@ -87,6 +90,9 @@
     std::u16string namespaceUri;
     std::u16string name;
     std::u16string value;
+
+    Maybe<AaptAttribute> compiledAttribute;
+    std::unique_ptr<Item> compiledValue;
 };
 
 /**
@@ -97,12 +103,12 @@
     std::u16string name;
     std::vector<Attribute> attributes;
 
-    Element();
-    virtual std::unique_ptr<Node> clone() const override;
     Attribute* findAttribute(const StringPiece16& ns, const StringPiece16& name);
     xml::Element* findChild(const StringPiece16& ns, const StringPiece16& name);
     xml::Element* findChildWithAttribute(const StringPiece16& ns, const StringPiece16& name,
-                                         const xml::Attribute* reqAttr);
+                                         const StringPiece16& attrNs,
+                                         const StringPiece16& attrName,
+                                         const StringPiece16& attrValue);
     std::vector<xml::Element*> getChildElements();
 };
 
@@ -111,41 +117,133 @@
  */
 struct Text : public BaseNode<Text> {
     std::u16string text;
-
-    Text();
-    virtual std::unique_ptr<Node> clone() const override;
 };
 
 /**
  * Inflates an XML DOM from a text stream, logging errors to the logger.
  * Returns the root node on success, or nullptr on failure.
  */
-std::unique_ptr<Node> inflate(std::istream* in, SourceLogger* logger);
+std::unique_ptr<XmlResource> inflate(std::istream* in, IDiagnostics* diag, const Source& source);
 
 /**
  * Inflates an XML DOM from a binary ResXMLTree, logging errors to the logger.
  * Returns the root node on success, or nullptr on failure.
  */
-std::unique_ptr<Node> inflate(const void* data, size_t dataLen, SourceLogger* logger);
+std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag,
+                                     const Source& source);
 
 /**
- * A visitor interface for the different XML Node subtypes.
+ * A visitor interface for the different XML Node subtypes. This will not traverse into
+ * children. Use Visitor for that.
  */
-struct Visitor {
-    virtual void visit(Namespace* node) = 0;
-    virtual void visit(Element* node) = 0;
-    virtual void visit(Text* text) = 0;
+struct RawVisitor {
+    virtual ~RawVisitor() = default;
+
+    virtual void visit(Namespace* node) {}
+    virtual void visit(Element* node) {}
+    virtual void visit(Text* text) {}
+};
+
+/**
+ * Visitor whose default implementation visits the children nodes of any node.
+ */
+struct Visitor : public RawVisitor {
+    using RawVisitor::visit;
+
+    void visit(Namespace* node) override {
+        visitChildren(node);
+    }
+
+    void visit(Element* node) override {
+        visitChildren(node);
+    }
+
+    void visit(Text* text) override {
+        visitChildren(text);
+    }
+
+    void visitChildren(Node* node) {
+        for (auto& child : node->children) {
+            child->accept(this);
+        }
+    }
+};
+
+/**
+ * An XML DOM visitor that will record the package name for a namespace prefix.
+ */
+class PackageAwareVisitor : public Visitor, public IPackageDeclStack {
+private:
+    struct PackageDecl {
+        std::u16string prefix;
+        std::u16string package;
+    };
+
+    std::vector<PackageDecl> mPackageDecls;
+
+public:
+    using Visitor::visit;
+
+    void visit(Namespace* ns) override {
+        bool added = false;
+        {
+            Maybe<std::u16string> package = util::extractPackageFromNamespace(ns->namespaceUri);
+            if (package) {
+                mPackageDecls.push_back(PackageDecl{ ns->namespacePrefix, package.value() });
+                added = true;
+            }
+        }
+
+        Visitor::visit(ns);
+
+        if (added) {
+            mPackageDecls.pop_back();
+        }
+    }
+
+    Maybe<ResourceName> transformPackage(const ResourceName& name,
+                                         const StringPiece16& localPackage) const override {
+        if (name.package.empty()) {
+            return ResourceName{ localPackage.toString(), name.type, name.entry };
+        }
+
+        const auto rend = mPackageDecls.rend();
+        for (auto iter = mPackageDecls.rbegin(); iter != rend; ++iter) {
+            if (name.package == iter->prefix) {
+                if (iter->package.empty()) {
+                    return ResourceName{ localPackage.toString(), name.type, name.entry };
+                } else {
+                    return ResourceName{ iter->package, name.type, name.entry };
+                }
+            }
+        }
+        return {};
+    }
 };
 
 // Implementations
 
 template <typename Derived>
-BaseNode<Derived>::BaseNode(NodeType type) : Node(type) {
+void BaseNode<Derived>::accept(RawVisitor* visitor) {
+    visitor->visit(static_cast<Derived*>(this));
 }
 
-template <typename Derived>
-void BaseNode<Derived>::accept(Visitor* visitor) {
-    visitor->visit(static_cast<Derived*>(this));
+template <typename T>
+struct NodeCastImpl : public RawVisitor {
+    using RawVisitor::visit;
+
+    T* value = nullptr;
+
+    void visit(T* v) override {
+        value = v;
+    }
+};
+
+template <typename T>
+T* nodeCast(Node* node) {
+    NodeCastImpl<T> visitor;
+    node->accept(&visitor);
+    return visitor.value;
 }
 
 } // namespace xml
diff --git a/tools/aapt2/XmlDom_test.cpp b/tools/aapt2/XmlDom_test.cpp
index 0217144..a1b9ed0 100644
--- a/tools/aapt2/XmlDom_test.cpp
+++ b/tools/aapt2/XmlDom_test.cpp
@@ -36,12 +36,13 @@
         </Layout>
     )EOF";
 
-    SourceLogger logger(Source{ "/test/path" });
-    std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger);
-    ASSERT_NE(root, nullptr);
+    const Source source = { "test.xml" };
+    StdErrDiagnostics diag;
+    std::unique_ptr<XmlResource> doc = xml::inflate(&in, &diag, source);
+    ASSERT_NE(doc, nullptr);
 
-    EXPECT_EQ(root->type, xml::NodeType::kNamespace);
-    xml::Namespace* ns = static_cast<xml::Namespace*>(root.get());
+    xml::Namespace* ns = xml::nodeCast<xml::Namespace>(doc->root.get());
+    ASSERT_NE(ns, nullptr);
     EXPECT_EQ(ns->namespaceUri, u"http://schemas.android.com/apk/res/android");
     EXPECT_EQ(ns->namespacePrefix, u"android");
 }
diff --git a/tools/aapt2/XmlFlattener.cpp b/tools/aapt2/XmlFlattener.cpp
deleted file mode 100644
index 56b5613d..0000000
--- a/tools/aapt2/XmlFlattener.cpp
+++ /dev/null
@@ -1,574 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "BigBuffer.h"
-#include "Logger.h"
-#include "Maybe.h"
-#include "Resolver.h"
-#include "Resource.h"
-#include "ResourceParser.h"
-#include "ResourceValues.h"
-#include "SdkConstants.h"
-#include "Source.h"
-#include "StringPool.h"
-#include "Util.h"
-#include "XmlFlattener.h"
-
-#include <androidfw/ResourceTypes.h>
-#include <limits>
-#include <map>
-#include <string>
-#include <vector>
-
-namespace aapt {
-namespace xml {
-
-constexpr uint32_t kLowPriority = 0xffffffffu;
-
-// A vector that maps String refs to their final destination in the out buffer.
-using FlatStringRefList = std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>;
-
-struct XmlFlattener : public Visitor {
-    XmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs,
-                 const std::u16string& defaultPackage) :
-            mOut(outBuffer), mPool(pool), mStringRefs(stringRefs),
-            mDefaultPackage(defaultPackage) {
-    }
-
-    // No copying.
-    XmlFlattener(const XmlFlattener&) = delete;
-    XmlFlattener& operator=(const XmlFlattener&) = delete;
-
-    void writeNamespace(Namespace* node, uint16_t type) {
-        const size_t startIndex = mOut->size();
-        android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
-        android::ResXMLTree_namespaceExt* flatNs =
-                mOut->nextBlock<android::ResXMLTree_namespaceExt>();
-        mOut->align4();
-
-        flatNode->header = { type, sizeof(*flatNode), (uint32_t)(mOut->size() - startIndex) };
-        flatNode->lineNumber = node->lineNumber;
-        flatNode->comment.index = -1;
-        addString(node->namespacePrefix, kLowPriority, &flatNs->prefix);
-        addString(node->namespaceUri, kLowPriority, &flatNs->uri);
-    }
-
-    virtual void visit(Namespace* node) override {
-        // Extract the package/prefix from this namespace node.
-        Maybe<std::u16string> package = util::extractPackageFromNamespace(node->namespaceUri);
-        if (package) {
-            mPackageAliases.emplace_back(
-                    node->namespacePrefix,
-                    package.value().empty() ? mDefaultPackage : package.value());
-        }
-
-        writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE);
-        for (const auto& child : node->children) {
-            child->accept(this);
-        }
-        writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE);
-
-        if (package) {
-            mPackageAliases.pop_back();
-        }
-    }
-
-    virtual void visit(Text* node) override {
-        if (util::trimWhitespace(node->text).empty()) {
-            return;
-        }
-
-        const size_t startIndex = mOut->size();
-        android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
-        android::ResXMLTree_cdataExt* flatText = mOut->nextBlock<android::ResXMLTree_cdataExt>();
-        mOut->align4();
-
-        const uint16_t type = android::RES_XML_CDATA_TYPE;
-        flatNode->header = { type, sizeof(*flatNode), (uint32_t)(mOut->size() - startIndex) };
-        flatNode->lineNumber = node->lineNumber;
-        flatNode->comment.index = -1;
-        addString(node->text, kLowPriority, &flatText->data);
-    }
-
-    virtual void visit(Element* node) override {
-        const size_t startIndex = mOut->size();
-        android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
-        android::ResXMLTree_attrExt* flatElem = mOut->nextBlock<android::ResXMLTree_attrExt>();
-
-        const uint16_t type = android::RES_XML_START_ELEMENT_TYPE;
-        flatNode->header = { type, sizeof(*flatNode), 0 };
-        flatNode->lineNumber = node->lineNumber;
-        flatNode->comment.index = -1;
-
-        addString(node->namespaceUri, kLowPriority, &flatElem->ns);
-        addString(node->name, kLowPriority, &flatElem->name);
-        flatElem->attributeStart = sizeof(*flatElem);
-        flatElem->attributeSize = sizeof(android::ResXMLTree_attribute);
-        flatElem->attributeCount = node->attributes.size();
-
-        if (!writeAttributes(mOut, node, flatElem)) {
-            mError = true;
-        }
-
-        mOut->align4();
-        flatNode->header.size = (uint32_t)(mOut->size() - startIndex);
-
-        for (const auto& child : node->children) {
-            child->accept(this);
-        }
-
-        const size_t startEndIndex = mOut->size();
-        android::ResXMLTree_node* flatEndNode = mOut->nextBlock<android::ResXMLTree_node>();
-        android::ResXMLTree_endElementExt* flatEndElem =
-                mOut->nextBlock<android::ResXMLTree_endElementExt>();
-        mOut->align4();
-
-        const uint16_t endType = android::RES_XML_END_ELEMENT_TYPE;
-        flatEndNode->header = { endType, sizeof(*flatEndNode),
-                (uint32_t)(mOut->size() - startEndIndex) };
-        flatEndNode->lineNumber = node->lineNumber;
-        flatEndNode->comment.index = -1;
-
-        addString(node->namespaceUri, kLowPriority, &flatEndElem->ns);
-        addString(node->name, kLowPriority, &flatEndElem->name);
-    }
-
-    bool success() const {
-        return !mError;
-    }
-
-protected:
-    void addString(const StringPiece16& str, uint32_t priority, android::ResStringPool_ref* dest) {
-        if (!str.empty()) {
-            mStringRefs->emplace_back(mPool->makeRef(str, StringPool::Context{ priority }), dest);
-        } else {
-            // The device doesn't think a string of size 0 is the same as null.
-            dest->index = -1;
-        }
-    }
-
-    void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
-        mStringRefs->emplace_back(ref, dest);
-    }
-
-    Maybe<std::u16string> getPackageAlias(const std::u16string& prefix) {
-        const auto endIter = mPackageAliases.rend();
-        for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) {
-            if (iter->first == prefix) {
-                return iter->second;
-            }
-        }
-        return {};
-    }
-
-    const std::u16string& getDefaultPackage() const {
-        return mDefaultPackage;
-    }
-
-    /**
-     * Subclasses override this to deal with attributes. Attributes can be flattened as
-     * raw values or as resources.
-     */
-    virtual bool writeAttributes(BigBuffer* out, Element* node,
-                                 android::ResXMLTree_attrExt* flatElem) = 0;
-
-private:
-    BigBuffer* mOut;
-    StringPool* mPool;
-    FlatStringRefList* mStringRefs;
-    std::u16string mDefaultPackage;
-    bool mError = false;
-    std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases;
-};
-
-/**
- * Flattens XML, encoding the attributes as raw strings. This is used in the compile phase.
- */
-struct CompileXmlFlattener : public XmlFlattener {
-    CompileXmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs,
-                        const std::u16string& defaultPackage) :
-            XmlFlattener(outBuffer, pool, stringRefs, defaultPackage) {
-    }
-
-    virtual bool writeAttributes(BigBuffer* out, Element* node,
-                                 android::ResXMLTree_attrExt* flatElem) override {
-        flatElem->attributeCount = node->attributes.size();
-        if (node->attributes.empty()) {
-            return true;
-        }
-
-        android::ResXMLTree_attribute* flatAttrs = out->nextBlock<android::ResXMLTree_attribute>(
-                node->attributes.size());
-        for (const Attribute& attr : node->attributes) {
-            addString(attr.namespaceUri, kLowPriority, &flatAttrs->ns);
-            addString(attr.name, kLowPriority, &flatAttrs->name);
-            addString(attr.value, kLowPriority, &flatAttrs->rawValue);
-            flatAttrs++;
-        }
-        return true;
-    }
-};
-
-struct AttributeToFlatten {
-    uint32_t resourceId = 0;
-    const Attribute* xmlAttr = nullptr;
-    const ::aapt::Attribute* resourceAttr = nullptr;
-};
-
-static bool lessAttributeId(const AttributeToFlatten& a, uint32_t id) {
-    return a.resourceId < id;
-}
-
-/**
- * Flattens XML, encoding the attributes as resources.
- */
-struct LinkedXmlFlattener : public XmlFlattener {
-    LinkedXmlFlattener(BigBuffer* outBuffer, StringPool* pool,
-                       std::map<std::u16string, StringPool>* packagePools,
-                       FlatStringRefList* stringRefs,
-                       const std::u16string& defaultPackage,
-                       const std::shared_ptr<IResolver>& resolver,
-                       SourceLogger* logger,
-                       const FlattenOptions& options) :
-            XmlFlattener(outBuffer, pool, stringRefs, defaultPackage), mResolver(resolver),
-            mLogger(logger), mPackagePools(packagePools), mOptions(options) {
-    }
-
-    virtual bool writeAttributes(BigBuffer* out, Element* node,
-                                 android::ResXMLTree_attrExt* flatElem) override {
-        bool error = false;
-        std::vector<AttributeToFlatten> sortedAttributes;
-        uint32_t nextAttributeId = 0x80000000u;
-
-        // Sort and filter attributes by their resource ID.
-        for (const Attribute& attr : node->attributes) {
-            AttributeToFlatten attrToFlatten;
-            attrToFlatten.xmlAttr = &attr;
-
-            Maybe<std::u16string> package = util::extractPackageFromNamespace(attr.namespaceUri);
-            if (package) {
-                // Find the Attribute object via our Resolver.
-                ResourceName attrName = { package.value(), ResourceType::kAttr, attr.name };
-                if (attrName.package.empty()) {
-                    attrName.package = getDefaultPackage();
-                }
-
-                Maybe<IResolver::Entry> result = mResolver->findAttribute(attrName);
-                if (!result || !result.value().id.isValid() || !result.value().attr) {
-                    error = true;
-                    mLogger->error(node->lineNumber)
-                            << "unresolved attribute '" << attrName << "'."
-                            << std::endl;
-                } else {
-                    attrToFlatten.resourceId = result.value().id.id;
-                    attrToFlatten.resourceAttr = result.value().attr;
-
-                    size_t sdk = findAttributeSdkLevel(attrToFlatten.resourceId);
-                    if (mOptions.maxSdkAttribute && sdk > mOptions.maxSdkAttribute.value()) {
-                        // We need to filter this attribute out.
-                        mSmallestFilteredSdk = std::min(mSmallestFilteredSdk, sdk);
-                        continue;
-                    }
-                }
-            }
-
-            if (attrToFlatten.resourceId == 0) {
-                // Attributes that have no resource ID (because they don't belong to a
-                // package) should appear after those that do have resource IDs. Assign
-                // them some integer value that will appear after.
-                attrToFlatten.resourceId = nextAttributeId++;
-            }
-
-            // Insert the attribute into the sorted vector.
-            auto iter = std::lower_bound(sortedAttributes.begin(), sortedAttributes.end(),
-                                         attrToFlatten.resourceId, lessAttributeId);
-            sortedAttributes.insert(iter, std::move(attrToFlatten));
-        }
-
-        flatElem->attributeCount = sortedAttributes.size();
-        if (sortedAttributes.empty()) {
-            return true;
-        }
-
-        android::ResXMLTree_attribute* flatAttr = out->nextBlock<android::ResXMLTree_attribute>(
-                sortedAttributes.size());
-
-        // Now that we have sorted the attributes into their final encoded order, it's time
-        // to actually write them out.
-        uint16_t attributeIndex = 1;
-        for (const AttributeToFlatten& attrToFlatten : sortedAttributes) {
-            Maybe<std::u16string> package = util::extractPackageFromNamespace(
-                    attrToFlatten.xmlAttr->namespaceUri);
-
-            // Assign the indices for specific attributes.
-            if (package && package.value() == u"android" && attrToFlatten.xmlAttr->name == u"id") {
-                flatElem->idIndex = attributeIndex;
-            } else if (attrToFlatten.xmlAttr->namespaceUri.empty()) {
-                if (attrToFlatten.xmlAttr->name == u"class") {
-                    flatElem->classIndex = attributeIndex;
-                } else if (attrToFlatten.xmlAttr->name == u"style") {
-                    flatElem->styleIndex = attributeIndex;
-                }
-            }
-            attributeIndex++;
-
-            // Add the namespaceUri and name to the list of StringRefs to encode.
-            addString(attrToFlatten.xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns);
-            flatAttr->rawValue.index = -1;
-
-            if (!attrToFlatten.resourceAttr) {
-                addString(attrToFlatten.xmlAttr->name, kLowPriority, &flatAttr->name);
-            } else {
-                // We've already extracted the package successfully before.
-                assert(package);
-
-                // Attribute names are stored without packages, but we use
-                // their StringPool index to lookup their resource IDs.
-                // This will cause collisions, so we can't dedupe
-                // attribute names from different packages. We use separate
-                // pools that we later combine.
-                //
-                // Lookup the StringPool for this package and make the reference there.
-                StringPool::Ref nameRef = (*mPackagePools)[package.value()].makeRef(
-                        attrToFlatten.xmlAttr->name,
-                        StringPool::Context{ attrToFlatten.resourceId });
-
-                // Add it to the list of strings to flatten.
-                addString(nameRef, &flatAttr->name);
-
-                if (mOptions.keepRawValues) {
-                    // Keep raw values (this is for static libraries).
-                    // TODO(with a smarter inflater for binary XML, we can do without this).
-                    addString(attrToFlatten.xmlAttr->value, kLowPriority, &flatAttr->rawValue);
-                }
-            }
-
-            error |= !flattenItem(node, attrToFlatten.xmlAttr->value, attrToFlatten.resourceAttr,
-                                  flatAttr);
-            flatAttr->typedValue.size = sizeof(flatAttr->typedValue);
-            flatAttr++;
-        }
-        return !error;
-    }
-
-    Maybe<size_t> getSmallestFilteredSdk() const {
-        if (mSmallestFilteredSdk == std::numeric_limits<size_t>::max()) {
-            return {};
-        }
-        return mSmallestFilteredSdk;
-    }
-
-private:
-    bool flattenItem(const Node* el, const std::u16string& value, const ::aapt::Attribute* attr,
-                     android::ResXMLTree_attribute* flatAttr) {
-        std::unique_ptr<Item> item;
-        if (!attr) {
-            bool create = false;
-            item = ResourceParser::tryParseReference(value, &create);
-            if (!item) {
-                flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
-                addString(value, kLowPriority, &flatAttr->rawValue);
-                addString(value, kLowPriority, reinterpret_cast<android::ResStringPool_ref*>(
-                        &flatAttr->typedValue.data));
-                return true;
-            }
-        } else {
-            item = ResourceParser::parseItemForAttribute(value, *attr);
-            if (!item) {
-                if (!(attr->typeMask & android::ResTable_map::TYPE_STRING)) {
-                    mLogger->error(el->lineNumber)
-                            << "'"
-                            << value
-                            << "' is not compatible with attribute '"
-                            << *attr
-                            << "'."
-                            << std::endl;
-                    return false;
-                }
-
-                flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
-                addString(value, kLowPriority, &flatAttr->rawValue);
-                addString(value, kLowPriority, reinterpret_cast<android::ResStringPool_ref*>(
-                        &flatAttr->typedValue.data));
-                return true;
-            }
-        }
-
-        assert(item);
-
-        bool error = false;
-
-        // If this is a reference, resolve the name into an ID.
-        visitFunc<Reference>(*item, [&](Reference& reference) {
-            // First see if we can convert the package name from a prefix to a real
-            // package name.
-            ResourceName realName = reference.name;
-            if (!realName.package.empty()) {
-                Maybe<std::u16string> package = getPackageAlias(realName.package);
-                if (package) {
-                    realName.package = package.value();
-                }
-            } else {
-                realName.package = getDefaultPackage();
-            }
-
-            Maybe<ResourceId> result = mResolver->findId(realName);
-            if (!result || !result.value().isValid()) {
-                std::ostream& out = mLogger->error(el->lineNumber)
-                        << "unresolved reference '"
-                        << reference.name
-                        << "'";
-                if (realName != reference.name) {
-                    out << " (aka '" << realName << "')";
-                }
-                out << "'." << std::endl;
-                error = true;
-            } else {
-                reference.id = result.value();
-            }
-        });
-
-        if (error) {
-            return false;
-        }
-
-        item->flatten(flatAttr->typedValue);
-        return true;
-    }
-
-    std::shared_ptr<IResolver> mResolver;
-    SourceLogger* mLogger;
-    std::map<std::u16string, StringPool>* mPackagePools;
-    FlattenOptions mOptions;
-    size_t mSmallestFilteredSdk = std::numeric_limits<size_t>::max();
-};
-
-/**
- * The binary XML file expects the StringPool to appear first, but we haven't collected the
- * strings yet. We write to a temporary BigBuffer while parsing the input, adding strings
- * we encounter to the StringPool. At the end, we write the StringPool to the given BigBuffer and
- * then move the data from the temporary BigBuffer into the given one. This incurs no
- * copies as the given BigBuffer simply takes ownership of the data.
- */
-static void flattenXml(StringPool* pool, FlatStringRefList* stringRefs, BigBuffer* outBuffer,
-                       BigBuffer&& xmlTreeBuffer) {
-    // Sort the string pool so that attribute resource IDs show up first.
-    pool->sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
-        return a.context.priority < b.context.priority;
-    });
-
-    // Now we flatten the string pool references into the correct places.
-    for (const auto& refEntry : *stringRefs) {
-        refEntry.second->index = refEntry.first.getIndex();
-    }
-
-    // Write the XML header.
-    const size_t beforeXmlTreeIndex = outBuffer->size();
-    android::ResXMLTree_header* header = outBuffer->nextBlock<android::ResXMLTree_header>();
-    header->header.type = android::RES_XML_TYPE;
-    header->header.headerSize = sizeof(*header);
-
-    // Flatten the StringPool.
-    StringPool::flattenUtf16(outBuffer, *pool);
-
-    // Write the array of resource IDs, indexed by StringPool order.
-    const size_t beforeResIdMapIndex = outBuffer->size();
-    android::ResChunk_header* resIdMapChunk = outBuffer->nextBlock<android::ResChunk_header>();
-    resIdMapChunk->type = android::RES_XML_RESOURCE_MAP_TYPE;
-    resIdMapChunk->headerSize = sizeof(*resIdMapChunk);
-    for (const auto& str : *pool) {
-        ResourceId id { str->context.priority };
-        if (id.id == kLowPriority || !id.isValid()) {
-            // When we see the first non-resource ID,
-            // we're done.
-            break;
-        }
-
-        *outBuffer->nextBlock<uint32_t>() = id.id;
-    }
-    resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex;
-
-    // Move the temporary BigBuffer into outBuffer.
-    outBuffer->appendBuffer(std::move(xmlTreeBuffer));
-    header->header.size = outBuffer->size() - beforeXmlTreeIndex;
-}
-
-bool flatten(Node* root, const std::u16string& defaultPackage, BigBuffer* outBuffer) {
-    StringPool pool;
-
-    // This will hold the StringRefs and the location in which to write the index.
-    // Once we sort the StringPool, we can assign the updated indices
-    // to the correct data locations.
-    FlatStringRefList stringRefs;
-
-    // Since we don't know the size of the final StringPool, we write to this
-    // temporary BigBuffer, which we will append to outBuffer later.
-    BigBuffer out(1024);
-
-    CompileXmlFlattener flattener(&out, &pool, &stringRefs, defaultPackage);
-    root->accept(&flattener);
-
-    if (!flattener.success()) {
-        return false;
-    }
-
-    flattenXml(&pool, &stringRefs, outBuffer, std::move(out));
-    return true;
-};
-
-Maybe<size_t> flattenAndLink(const Source& source, Node* root,
-                             const std::u16string& defaultPackage,
-                             const std::shared_ptr<IResolver>& resolver,
-                             const FlattenOptions& options, BigBuffer* outBuffer) {
-    SourceLogger logger(source);
-    StringPool pool;
-
-    // Attribute names are stored without packages, but we use
-    // their StringPool index to lookup their resource IDs.
-    // This will cause collisions, so we can't dedupe
-    // attribute names from different packages. We use separate
-    // pools that we later combine.
-    std::map<std::u16string, StringPool> packagePools;
-
-    FlatStringRefList stringRefs;
-
-    // Since we don't know the size of the final StringPool, we write to this
-    // temporary BigBuffer, which we will append to outBuffer later.
-    BigBuffer out(1024);
-
-    LinkedXmlFlattener flattener(&out, &pool, &packagePools, &stringRefs, defaultPackage, resolver,
-                                 &logger, options);
-    root->accept(&flattener);
-
-    if (!flattener.success()) {
-        return {};
-    }
-
-    // Merge the package pools into the main pool.
-    for (auto& packagePoolEntry : packagePools) {
-        pool.merge(std::move(packagePoolEntry.second));
-    }
-
-    flattenXml(&pool, &stringRefs, outBuffer, std::move(out));
-
-    if (flattener.getSmallestFilteredSdk()) {
-        return flattener.getSmallestFilteredSdk();
-    }
-    return 0;
-}
-
-} // namespace xml
-} // namespace aapt
diff --git a/tools/aapt2/XmlFlattener.h b/tools/aapt2/XmlFlattener.h
deleted file mode 100644
index 4ece0a3..0000000
--- a/tools/aapt2/XmlFlattener.h
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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_XML_FLATTENER_H
-#define AAPT_XML_FLATTENER_H
-
-#include "BigBuffer.h"
-#include "Maybe.h"
-#include "Resolver.h"
-#include "Source.h"
-#include "XmlDom.h"
-
-#include <string>
-
-namespace aapt {
-namespace xml {
-
-/**
- * Flattens an XML file into a binary representation parseable by
- * the Android resource system.
- */
-bool flatten(Node* root, const std::u16string& defaultPackage, BigBuffer* outBuffer);
-
-/**
- * Options for flattenAndLink.
- */
-struct FlattenOptions {
-    /**
-     * Keep attribute raw string values along with typed values.
-     */
-    bool keepRawValues = false;
-
-    /**
-     * If set, any attribute introduced in a later SDK will not be encoded.
-     */
-    Maybe<size_t> maxSdkAttribute;
-};
-
-/**
- * Like flatten(Node*,BigBuffer*), but references to resources are checked
- * and string values are transformed to typed data where possible.
- *
- * `defaultPackage` is used when a reference has no package or the namespace URI
- * "http://schemas.android.com/apk/res-auto" is used.
- *
- * `resolver` is used to resolve references to resources.
- */
-Maybe<size_t> flattenAndLink(const Source& source, Node* root,
-                             const std::u16string& defaultPackage,
-                             const std::shared_ptr<IResolver>& resolver,
-                             const FlattenOptions& options, BigBuffer* outBuffer);
-
-} // namespace xml
-} // namespace aapt
-
-#endif // AAPT_XML_FLATTENER_H
diff --git a/tools/aapt2/XmlFlattener_test.cpp b/tools/aapt2/XmlFlattener_test.cpp
deleted file mode 100644
index 8915d24..0000000
--- a/tools/aapt2/XmlFlattener_test.cpp
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * 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 "MockResolver.h"
-#include "ResourceTable.h"
-#include "ResourceValues.h"
-#include "Util.h"
-#include "XmlFlattener.h"
-
-#include <androidfw/AssetManager.h>
-#include <androidfw/ResourceTypes.h>
-#include <gtest/gtest.h>
-#include <sstream>
-#include <string>
-
-using namespace android;
-
-namespace aapt {
-namespace xml {
-
-constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
-
-class XmlFlattenerTest : public ::testing::Test {
-public:
-    virtual void SetUp() override {
-        mResolver = std::make_shared<MockResolver>(
-                std::make_shared<ResourceTable>(),
-                std::map<ResourceName, ResourceId>({
-                        { ResourceName{ u"android", ResourceType::kAttr, u"attr" },
-                          ResourceId{ 0x01010000u } },
-                        { ResourceName{ u"android", ResourceType::kId, u"id" },
-                          ResourceId{ 0x01020000u } },
-                        { ResourceName{ u"com.lib", ResourceType::kAttr, u"attr" },
-                          ResourceId{ 0x01010001u } },
-                        { ResourceName{ u"com.lib", ResourceType::kId, u"id" },
-                          ResourceId{ 0x01020001u } }}));
-    }
-
-    ::testing::AssertionResult testFlatten(const std::string& in, ResXMLTree* outTree) {
-        std::stringstream input(kXmlPreamble);
-        input << in << std::endl;
-
-        SourceLogger logger(Source{ "test.xml" });
-        std::unique_ptr<Node> root = inflate(&input, &logger);
-        if (!root) {
-            return ::testing::AssertionFailure();
-        }
-
-        BigBuffer outBuffer(1024);
-        if (!flattenAndLink(Source{ "test.xml" }, root.get(), std::u16string(u"android"),
-                    mResolver, {}, &outBuffer)) {
-            return ::testing::AssertionFailure();
-        }
-
-        std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
-        if (outTree->setTo(data.get(), outBuffer.size(), true) != NO_ERROR) {
-            return ::testing::AssertionFailure();
-        }
-        return ::testing::AssertionSuccess();
-    }
-
-    std::shared_ptr<IResolver> mResolver;
-};
-
-TEST_F(XmlFlattenerTest, ParseSimpleView) {
-    std::string input = R"EOF(
-        <View xmlns:android="http://schemas.android.com/apk/res/android"
-              android:attr="@id/id"
-              class="str"
-              style="@id/id">
-        </View>
-    )EOF";
-    ResXMLTree tree;
-    ASSERT_TRUE(testFlatten(input, &tree));
-
-    while (tree.next() != ResXMLTree::START_TAG) {
-        ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
-        ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
-    }
-
-    const StringPiece16 androidNs = u"http://schemas.android.com/apk/res/android";
-    const StringPiece16 attrName = u"attr";
-    ssize_t idx = tree.indexOfAttribute(androidNs.data(), androidNs.size(), attrName.data(),
-                                        attrName.size());
-    ASSERT_GE(idx, 0);
-    EXPECT_EQ(tree.getAttributeNameResID(idx), 0x01010000u);
-    EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_REFERENCE);
-
-    const StringPiece16 class16 = u"class";
-    idx = tree.indexOfAttribute(nullptr, 0, class16.data(), class16.size());
-    ASSERT_GE(idx, 0);
-    EXPECT_EQ(tree.getAttributeNameResID(idx), 0u);
-    EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_STRING);
-    EXPECT_EQ(tree.getAttributeData(idx), tree.getAttributeValueStringID(idx));
-
-    const StringPiece16 style16 = u"style";
-    idx = tree.indexOfAttribute(nullptr, 0, style16.data(), style16.size());
-    ASSERT_GE(idx, 0);
-    EXPECT_EQ(tree.getAttributeNameResID(idx), 0u);
-    EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_REFERENCE);
-    EXPECT_EQ((uint32_t) tree.getAttributeData(idx), 0x01020000u);
-    EXPECT_EQ(tree.getAttributeValueStringID(idx), -1);
-
-    while (tree.next() != ResXMLTree::END_DOCUMENT) {
-        ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
-    }
-}
-
-TEST_F(XmlFlattenerTest, ParseViewWithPackageAlias) {
-    std::string input = "<View xmlns:ns1=\"http://schemas.android.com/apk/res/android\"\n"
-                        "      xmlns:ns2=\"http://schemas.android.com/apk/res/android\"\n"
-                        "      ns1:attr=\"@ns2:id/id\">\n"
-                        "</View>";
-    ResXMLTree tree;
-    ASSERT_TRUE(testFlatten(input, &tree));
-
-    while (tree.next() != ResXMLTree::END_DOCUMENT) {
-        ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
-    }
-}
-
-::testing::AssertionResult attributeNameAndValueEquals(ResXMLTree* tree, size_t index,
-                                                       ResourceId nameId, ResourceId valueId) {
-    if (index >= tree->getAttributeCount()) {
-        return ::testing::AssertionFailure() << "index " << index << " is out of bounds ("
-                                             << tree->getAttributeCount() << ")";
-    }
-
-    if (tree->getAttributeNameResID(index) != nameId.id) {
-        return ::testing::AssertionFailure()
-                << "attribute at index " << index << " has ID "
-                << ResourceId{ (uint32_t) tree->getAttributeNameResID(index) }
-                << ". Expected ID " << nameId;
-    }
-
-    if (tree->getAttributeDataType(index) != Res_value::TYPE_REFERENCE) {
-        return ::testing::AssertionFailure() << "attribute at index " << index << " has value of "
-                                             << "type " << std::hex
-                                             << tree->getAttributeDataType(index) << std::dec
-                                             << ". Expected reference (" << std::hex
-                                             << Res_value::TYPE_REFERENCE << std::dec << ")";
-    }
-
-    if ((uint32_t) tree->getAttributeData(index) != valueId.id) {
-        return ::testing::AssertionFailure()
-                << "attribute at index " << index << " has value " << "with ID "
-                << ResourceId{ (uint32_t) tree->getAttributeData(index) }
-                << ". Expected ID " << valueId;
-    }
-    return ::testing::AssertionSuccess();
-}
-
-TEST_F(XmlFlattenerTest, ParseViewWithShadowedPackageAlias) {
-    std::string input = "<View xmlns:app=\"http://schemas.android.com/apk/res/android\"\n"
-                        "      app:attr=\"@app:id/id\">\n"
-                        "  <View xmlns:app=\"http://schemas.android.com/apk/res/com.lib\"\n"
-                        "        app:attr=\"@app:id/id\"/>\n"
-                        "</View>";
-    ResXMLTree tree;
-    ASSERT_TRUE(testFlatten(input, &tree));
-
-    while (tree.next() != ResXMLTree::START_TAG) {
-        ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
-        ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
-    }
-
-    ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010000u },
-                                            ResourceId{ 0x01020000u }));
-
-    while (tree.next() != ResXMLTree::START_TAG) {
-        ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
-        ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
-    }
-
-    ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010001u },
-                                            ResourceId{ 0x01020001u }));
-}
-
-TEST_F(XmlFlattenerTest, ParseViewWithLocalPackageAndAliasOfTheSameName) {
-    std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/com.lib\"\n"
-                        "      android:attr=\"@id/id\"/>";
-    ResXMLTree tree;
-    ASSERT_TRUE(testFlatten(input, &tree));
-
-    while (tree.next() != ResXMLTree::START_TAG) {
-        ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
-        ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
-    }
-
-    // We expect the 'android:attr' to be converted to 'com.lib:attr' due to the namespace
-    // assignment.
-    // However, we didn't give '@id/id' a package, so it should use the default package
-    // 'android', and not be converted from 'android' to 'com.lib'.
-    ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010001u },
-                                            ResourceId{ 0x01020000u }));
-}
-
-/*
- * The device ResXMLParser in libandroidfw differentiates between empty namespace and null
- * namespace.
- */
-TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) {
-    std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
-                        "      package=\"android\"/>";
-
-    ResXMLTree tree;
-    ASSERT_TRUE(testFlatten(input, &tree));
-
-    while (tree.next() != ResXMLTree::START_TAG) {
-        ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
-        ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
-    }
-
-    const StringPiece16 kPackage = u"package";
-    EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0);
-}
-
-} // namespace xml
-} // namespace aapt
diff --git a/tools/aapt2/SourceXmlPullParser.cpp b/tools/aapt2/XmlPullParser.cpp
similarity index 73%
rename from tools/aapt2/SourceXmlPullParser.cpp
rename to tools/aapt2/XmlPullParser.cpp
index 8099044..1b9499d 100644
--- a/tools/aapt2/SourceXmlPullParser.cpp
+++ b/tools/aapt2/XmlPullParser.cpp
@@ -14,18 +14,18 @@
  * limitations under the License.
  */
 
+#include "util/Maybe.h"
+#include "util/Util.h"
+#include "XmlPullParser.h"
+
 #include <iostream>
 #include <string>
 
-#include "Maybe.h"
-#include "SourceXmlPullParser.h"
-#include "Util.h"
-
 namespace aapt {
 
 constexpr char kXmlNamespaceSep = 1;
 
-SourceXmlPullParser::SourceXmlPullParser(std::istream& in) : mIn(in), mEmpty(), mDepth(0) {
+XmlPullParser::XmlPullParser(std::istream& in) : mIn(in), mEmpty(), mDepth(0) {
     mParser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep);
     XML_SetUserData(mParser, this);
     XML_SetElementHandler(mParser, startElementHandler, endElementHandler);
@@ -35,11 +35,11 @@
     mEventQueue.push(EventData{ Event::kStartDocument, 0, mDepth++ });
 }
 
-SourceXmlPullParser::~SourceXmlPullParser() {
+XmlPullParser::~XmlPullParser() {
     XML_ParserFree(mParser);
 }
 
-SourceXmlPullParser::Event SourceXmlPullParser::next() {
+XmlPullParser::Event XmlPullParser::next() {
     const Event currentEvent = getEvent();
     if (currentEvent == Event::kBadDocument || currentEvent == Event::kEndDocument) {
         return currentEvent;
@@ -88,34 +88,34 @@
     return event;
 }
 
-SourceXmlPullParser::Event SourceXmlPullParser::getEvent() const {
+XmlPullParser::Event XmlPullParser::getEvent() const {
     return mEventQueue.front().event;
 }
 
-const std::string& SourceXmlPullParser::getLastError() const {
+const std::string& XmlPullParser::getLastError() const {
     return mLastError;
 }
 
-const std::u16string& SourceXmlPullParser::getComment() const {
+const std::u16string& XmlPullParser::getComment() const {
     return mEventQueue.front().comment;
 }
 
-size_t SourceXmlPullParser::getLineNumber() const {
+size_t XmlPullParser::getLineNumber() const {
     return mEventQueue.front().lineNumber;
 }
 
-size_t SourceXmlPullParser::getDepth() const {
+size_t XmlPullParser::getDepth() const {
     return mEventQueue.front().depth;
 }
 
-const std::u16string& SourceXmlPullParser::getText() const {
+const std::u16string& XmlPullParser::getText() const {
     if (getEvent() != Event::kText) {
         return mEmpty;
     }
     return mEventQueue.front().data1;
 }
 
-const std::u16string& SourceXmlPullParser::getNamespacePrefix() const {
+const std::u16string& XmlPullParser::getNamespacePrefix() const {
     const Event currentEvent = getEvent();
     if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) {
         return mEmpty;
@@ -123,7 +123,7 @@
     return mEventQueue.front().data1;
 }
 
-const std::u16string& SourceXmlPullParser::getNamespaceUri() const {
+const std::u16string& XmlPullParser::getNamespaceUri() const {
     const Event currentEvent = getEvent();
     if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) {
         return mEmpty;
@@ -131,23 +131,26 @@
     return mEventQueue.front().data2;
 }
 
-bool SourceXmlPullParser::applyPackageAlias(std::u16string* package,
-                                            const std::u16string& defaultPackage) const {
+Maybe<ResourceName> XmlPullParser::transformPackage(
+        const ResourceName& name, const StringPiece16& localPackage) const {
+    if (name.package.empty()) {
+        return ResourceName{ localPackage.toString(), name.type, name.entry };
+    }
+
     const auto endIter = mPackageAliases.rend();
     for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) {
-        if (iter->first == *package) {
+        if (name.package == iter->first) {
             if (iter->second.empty()) {
-                *package = defaultPackage;
+                return ResourceName{ localPackage.toString(), name.type, name.entry };
             } else {
-                *package = iter->second;
+                return ResourceName{ iter->second, name.type, name.entry };
             }
-            return true;
         }
     }
-    return false;
+    return {};
 }
 
-const std::u16string& SourceXmlPullParser::getElementNamespace() const {
+const std::u16string& XmlPullParser::getElementNamespace() const {
     const Event currentEvent = getEvent();
     if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) {
         return mEmpty;
@@ -155,7 +158,7 @@
     return mEventQueue.front().data1;
 }
 
-const std::u16string& SourceXmlPullParser::getElementName() const {
+const std::u16string& XmlPullParser::getElementName() const {
     const Event currentEvent = getEvent();
     if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) {
         return mEmpty;
@@ -163,15 +166,15 @@
     return mEventQueue.front().data2;
 }
 
-XmlPullParser::const_iterator SourceXmlPullParser::beginAttributes() const {
+XmlPullParser::const_iterator XmlPullParser::beginAttributes() const {
     return mEventQueue.front().attributes.begin();
 }
 
-XmlPullParser::const_iterator SourceXmlPullParser::endAttributes() const {
+XmlPullParser::const_iterator XmlPullParser::endAttributes() const {
     return mEventQueue.front().attributes.end();
 }
 
-size_t SourceXmlPullParser::getAttributeCount() const {
+size_t XmlPullParser::getAttributeCount() const {
     if (getEvent() != Event::kStartElement) {
         return 0;
     }
@@ -196,9 +199,9 @@
     }
 }
 
-void XMLCALL SourceXmlPullParser::startNamespaceHandler(void* userData, const char* prefix,
+void XMLCALL XmlPullParser::startNamespaceHandler(void* userData, const char* prefix,
         const char* uri) {
-    SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+    XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
     std::u16string namespaceUri = uri != nullptr ? util::utf8ToUtf16(uri) : std::u16string();
     parser->mNamespaceUris.push(namespaceUri);
     parser->mEventQueue.push(EventData{
@@ -210,9 +213,9 @@
     });
 }
 
-void XMLCALL SourceXmlPullParser::startElementHandler(void* userData, const char* name,
+void XMLCALL XmlPullParser::startElementHandler(void* userData, const char* name,
         const char** attrs) {
-    SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+    XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
 
     EventData data = {
             Event::kStartElement, XML_GetCurrentLineNumber(parser->mParser), parser->mDepth++
@@ -233,8 +236,8 @@
     parser->mEventQueue.push(std::move(data));
 }
 
-void XMLCALL SourceXmlPullParser::characterDataHandler(void* userData, const char* s, int len) {
-    SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+void XMLCALL XmlPullParser::characterDataHandler(void* userData, const char* s, int len) {
+    XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
 
     parser->mEventQueue.push(EventData{
             Event::kText,
@@ -244,8 +247,8 @@
     });
 }
 
-void XMLCALL SourceXmlPullParser::endElementHandler(void* userData, const char* name) {
-    SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+void XMLCALL XmlPullParser::endElementHandler(void* userData, const char* name) {
+    XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
 
     EventData data = {
             Event::kEndElement, XML_GetCurrentLineNumber(parser->mParser), --(parser->mDepth)
@@ -256,8 +259,8 @@
     parser->mEventQueue.push(std::move(data));
 }
 
-void XMLCALL SourceXmlPullParser::endNamespaceHandler(void* userData, const char* prefix) {
-    SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+void XMLCALL XmlPullParser::endNamespaceHandler(void* userData, const char* prefix) {
+    XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
 
     parser->mEventQueue.push(EventData{
             Event::kEndNamespace,
@@ -269,8 +272,8 @@
     parser->mNamespaceUris.pop();
 }
 
-void XMLCALL SourceXmlPullParser::commentDataHandler(void* userData, const char* comment) {
-    SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+void XMLCALL XmlPullParser::commentDataHandler(void* userData, const char* comment) {
+    XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
 
     parser->mEventQueue.push(EventData{
             Event::kComment,
diff --git a/tools/aapt2/XmlPullParser.h b/tools/aapt2/XmlPullParser.h
index accfd30a..f7d7a03 100644
--- a/tools/aapt2/XmlPullParser.h
+++ b/tools/aapt2/XmlPullParser.h
@@ -17,16 +17,24 @@
 #ifndef AAPT_XML_PULL_PARSER_H
 #define AAPT_XML_PULL_PARSER_H
 
+#include "util/Maybe.h"
+#include "Resource.h"
+#include "util/StringPiece.h"
+
+#include "process/IResourceTableConsumer.h"
+
 #include <algorithm>
+#include <expat.h>
+#include <istream>
 #include <ostream>
+#include <queue>
+#include <stack>
 #include <string>
 #include <vector>
 
-#include "StringPiece.h"
-
 namespace aapt {
 
-class XmlPullParser {
+class XmlPullParser : public IPackageDeclStack {
 public:
     enum class Event {
         kBadDocument,
@@ -41,43 +49,51 @@
         kComment,
     };
 
-    static void skipCurrentElement(XmlPullParser* parser);
+    /**
+     * Skips to the next direct descendant node of the given startDepth,
+     * skipping namespace nodes.
+     *
+     * When nextChildNode returns true, you can expect Comments, Text, and StartElement events.
+     */
+    static bool nextChildNode(XmlPullParser* parser, size_t startDepth);
+    static bool skipCurrentElement(XmlPullParser* parser);
     static bool isGoodEvent(Event event);
 
-    virtual ~XmlPullParser() {}
+    XmlPullParser(std::istream& in);
+    virtual ~XmlPullParser();
 
     /**
      * Returns the current event that is being processed.
      */
-    virtual Event getEvent() const = 0;
+    Event getEvent() const;
 
-    virtual const std::string& getLastError() const = 0;
+    const std::string& getLastError() const;
 
     /**
      * Note, unlike XmlPullParser, the first call to next() will return
      * StartElement of the first element.
      */
-    virtual Event next() = 0;
+    Event next();
 
     //
     // These are available for all nodes.
     //
 
-    virtual const std::u16string& getComment() const = 0;
-    virtual size_t getLineNumber() const = 0;
-    virtual size_t getDepth() const = 0;
+    const std::u16string& getComment() const;
+    size_t getLineNumber() const;
+    size_t getDepth() const;
 
     /**
      * Returns the character data for a Text event.
      */
-    virtual const std::u16string& getText() const = 0;
+    const std::u16string& getText() const;
 
     //
     // Namespace prefix and URI are available for StartNamespace and EndNamespace.
     //
 
-    virtual const std::u16string& getNamespacePrefix() const = 0;
-    virtual const std::u16string& getNamespaceUri() const = 0;
+    const std::u16string& getNamespacePrefix() const;
+    const std::u16string& getNamespaceUri() const;
 
     /*
      * Uses the current stack of namespaces to resolve the package. Eg:
@@ -90,15 +106,17 @@
      * If xmlns:app="http://schemas.android.com/apk/res-auto", then
      * 'package' will be set to 'defaultPackage'.
      */
-    virtual bool applyPackageAlias(std::u16string* package,
-                                   const std::u16string& defaultPackage) const = 0;
+    //
 
     //
     // These are available for StartElement and EndElement.
     //
 
-    virtual const std::u16string& getElementNamespace() const = 0;
-    virtual const std::u16string& getElementName() const = 0;
+    const std::u16string& getElementNamespace() const;
+    const std::u16string& getElementName() const;
+
+    Maybe<ResourceName> transformPackage(const ResourceName& name,
+                                         const StringPiece16& localPackage) const override;
 
     //
     // Remaining methods are for retrieving information about attributes
@@ -121,10 +139,38 @@
 
     using const_iterator = std::vector<Attribute>::const_iterator;
 
-    virtual const_iterator beginAttributes() const = 0;
-    virtual const_iterator endAttributes() const = 0;
-    virtual size_t getAttributeCount() const = 0;
+    const_iterator beginAttributes() const;
+    const_iterator endAttributes() const;
+    size_t getAttributeCount() const;
     const_iterator findAttribute(StringPiece16 namespaceUri, StringPiece16 name) const;
+
+private:
+    static void XMLCALL startNamespaceHandler(void* userData, const char* prefix, const char* uri);
+    static void XMLCALL startElementHandler(void* userData, const char* name, const char** attrs);
+    static void XMLCALL characterDataHandler(void* userData, const char* s, int len);
+    static void XMLCALL endElementHandler(void* userData, const char* name);
+    static void XMLCALL endNamespaceHandler(void* userData, const char* prefix);
+    static void XMLCALL commentDataHandler(void* userData, const char* comment);
+
+    struct EventData {
+        Event event;
+        size_t lineNumber;
+        size_t depth;
+        std::u16string data1;
+        std::u16string data2;
+        std::u16string comment;
+        std::vector<Attribute> attributes;
+    };
+
+    std::istream& mIn;
+    XML_Parser mParser;
+    char mBuffer[16384];
+    std::queue<EventData> mEventQueue;
+    std::string mLastError;
+    const std::u16string mEmpty;
+    size_t mDepth;
+    std::stack<std::u16string> mNamespaceUris;
+    std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases;
 };
 
 //
@@ -146,13 +192,35 @@
     return out;
 }
 
-inline void XmlPullParser::skipCurrentElement(XmlPullParser* parser) {
+inline bool XmlPullParser::nextChildNode(XmlPullParser* parser, size_t startDepth) {
+    Event event;
+
+    // First get back to the start depth.
+    while (isGoodEvent(event = parser->next()) && parser->getDepth() > startDepth + 1) {}
+
+    // Now look for the first good node.
+    while ((event != Event::kEndElement || parser->getDepth() > startDepth) && isGoodEvent(event)) {
+        switch (event) {
+        case Event::kText:
+        case Event::kComment:
+        case Event::kStartElement:
+            return true;
+        default:
+            break;
+        }
+        event = parser->next();
+    }
+    return false;
+}
+
+inline bool XmlPullParser::skipCurrentElement(XmlPullParser* parser) {
     int depth = 1;
     while (depth > 0) {
         switch (parser->next()) {
             case Event::kEndDocument:
+                return true;
             case Event::kBadDocument:
-                return;
+                return false;
             case Event::kStartElement:
                 depth++;
                 break;
@@ -163,6 +231,7 @@
                 break;
         }
     }
+    return true;
 }
 
 inline bool XmlPullParser::isGoodEvent(XmlPullParser::Event event) {
diff --git a/tools/aapt2/XmlPullParser_test.cpp b/tools/aapt2/XmlPullParser_test.cpp
new file mode 100644
index 0000000..1c99a43
--- /dev/null
+++ b/tools/aapt2/XmlPullParser_test.cpp
@@ -0,0 +1,55 @@
+/*
+ * 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 "util/StringPiece.h"
+#include "XmlPullParser.h"
+
+#include <gtest/gtest.h>
+#include <sstream>
+
+namespace aapt {
+
+TEST(XmlPullParserTest, NextChildNodeTraversesCorrectly) {
+    std::stringstream str;
+    str << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+            "<a><b><c xmlns:a=\"http://schema.org\"><d/></c><e/></b></a>";
+    XmlPullParser parser(str);
+
+    const size_t depthOuter = parser.getDepth();
+    ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthOuter));
+
+    EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
+    EXPECT_EQ(StringPiece16(u"a"), StringPiece16(parser.getElementName()));
+
+    const size_t depthA = parser.getDepth();
+    ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthA));
+    EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
+    EXPECT_EQ(StringPiece16(u"b"), StringPiece16(parser.getElementName()));
+
+    const size_t depthB = parser.getDepth();
+    ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthB));
+    EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
+    EXPECT_EQ(StringPiece16(u"c"), StringPiece16(parser.getElementName()));
+
+    ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthB));
+    EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
+    EXPECT_EQ(StringPiece16(u"e"), StringPiece16(parser.getElementName()));
+
+    ASSERT_FALSE(XmlPullParser::nextChildNode(&parser, depthOuter));
+    EXPECT_EQ(XmlPullParser::Event::kEndDocument, parser.getEvent());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ZipEntry.cpp b/tools/aapt2/ZipEntry.cpp
deleted file mode 100644
index 891b4e1..0000000
--- a/tools/aapt2/ZipEntry.cpp
+++ /dev/null
@@ -1,745 +0,0 @@
-/*
- * Copyright (C) 2006 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.
- */
-
-//
-// Access to entries in a Zip archive.
-//
-
-#define LOG_TAG "zip"
-
-#include "ZipEntry.h"
-#include <utils/Log.h>
-
-#include <stdio.h>
-#include <string.h>
-#include <assert.h>
-
-namespace aapt {
-
-using namespace android;
-
-/*
- * Initialize a new ZipEntry structure from a FILE* positioned at a
- * CentralDirectoryEntry.
- *
- * On exit, the file pointer will be at the start of the next CDE or
- * at the EOCD.
- */
-status_t ZipEntry::initFromCDE(FILE* fp)
-{
-    status_t result;
-    long posn;
-    bool hasDD;
-
-    //ALOGV("initFromCDE ---\n");
-
-    /* read the CDE */
-    result = mCDE.read(fp);
-    if (result != NO_ERROR) {
-        ALOGD("mCDE.read failed\n");
-        return result;
-    }
-
-    //mCDE.dump();
-
-    /* using the info in the CDE, go load up the LFH */
-    posn = ftell(fp);
-    if (fseek(fp, mCDE.mLocalHeaderRelOffset, SEEK_SET) != 0) {
-        ALOGD("local header seek failed (%ld)\n",
-            mCDE.mLocalHeaderRelOffset);
-        return UNKNOWN_ERROR;
-    }
-
-    result = mLFH.read(fp);
-    if (result != NO_ERROR) {
-        ALOGD("mLFH.read failed\n");
-        return result;
-    }
-
-    if (fseek(fp, posn, SEEK_SET) != 0)
-        return UNKNOWN_ERROR;
-
-    //mLFH.dump();
-
-    /*
-     * We *might* need to read the Data Descriptor at this point and
-     * integrate it into the LFH.  If this bit is set, the CRC-32,
-     * compressed size, and uncompressed size will be zero.  In practice
-     * these seem to be rare.
-     */
-    hasDD = (mLFH.mGPBitFlag & kUsesDataDescr) != 0;
-    if (hasDD) {
-        // do something clever
-        //ALOGD("+++ has data descriptor\n");
-    }
-
-    /*
-     * Sanity-check the LFH.  Note that this will fail if the "kUsesDataDescr"
-     * flag is set, because the LFH is incomplete.  (Not a problem, since we
-     * prefer the CDE values.)
-     */
-    if (!hasDD && !compareHeaders()) {
-        ALOGW("warning: header mismatch\n");
-        // keep going?
-    }
-
-    /*
-     * If the mVersionToExtract is greater than 20, we may have an
-     * issue unpacking the record -- could be encrypted, compressed
-     * with something we don't support, or use Zip64 extensions.  We
-     * can defer worrying about that to when we're extracting data.
-     */
-
-    return NO_ERROR;
-}
-
-/*
- * Initialize a new entry.  Pass in the file name and an optional comment.
- *
- * Initializes the CDE and the LFH.
- */
-void ZipEntry::initNew(const char* fileName, const char* comment)
-{
-    assert(fileName != NULL && *fileName != '\0');  // name required
-
-    /* most fields are properly initialized by constructor */
-    mCDE.mVersionMadeBy = kDefaultMadeBy;
-    mCDE.mVersionToExtract = kDefaultVersion;
-    mCDE.mCompressionMethod = kCompressStored;
-    mCDE.mFileNameLength = strlen(fileName);
-    if (comment != NULL)
-        mCDE.mFileCommentLength = strlen(comment);
-    mCDE.mExternalAttrs = 0x81b60020;   // matches what WinZip does
-
-    if (mCDE.mFileNameLength > 0) {
-        mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1];
-        strcpy((char*) mCDE.mFileName, fileName);
-    }
-    if (mCDE.mFileCommentLength > 0) {
-        /* TODO: stop assuming null-terminated ASCII here? */
-        mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1];
-        strcpy((char*) mCDE.mFileComment, comment);
-    }
-
-    copyCDEtoLFH();
-}
-
-/*
- * Initialize a new entry, starting with the ZipEntry from a different
- * archive.
- *
- * Initializes the CDE and the LFH.
- */
-status_t ZipEntry::initFromExternal(const ZipFile* /* pZipFile */,
-    const ZipEntry* pEntry, const char* storageName)
-{
-    mCDE = pEntry->mCDE;
-    if (storageName && *storageName != 0) {
-        mCDE.mFileNameLength = strlen(storageName);
-        mCDE.mFileName = new unsigned char[mCDE.mFileNameLength + 1];
-        strcpy((char*) mCDE.mFileName, storageName);
-    }
-
-    // Check whether we got all the memory needed.
-    if ((mCDE.mFileNameLength > 0 && mCDE.mFileName == NULL) ||
-            (mCDE.mFileCommentLength > 0 && mCDE.mFileComment == NULL) ||
-            (mCDE.mExtraFieldLength > 0 && mCDE.mExtraField == NULL)) {
-        return NO_MEMORY;
-    }
-
-    /* construct the LFH from the CDE */
-    copyCDEtoLFH();
-
-    /*
-     * The LFH "extra" field is independent of the CDE "extra", so we
-     * handle it here.
-     */
-    assert(mLFH.mExtraField == NULL);
-    mLFH.mExtraFieldLength = pEntry->mLFH.mExtraFieldLength;
-    if (mLFH.mExtraFieldLength > 0) {
-        mLFH.mExtraField = new unsigned char[mLFH.mExtraFieldLength+1];
-        if (mLFH.mExtraField == NULL)
-            return NO_MEMORY;
-        memcpy(mLFH.mExtraField, pEntry->mLFH.mExtraField,
-            mLFH.mExtraFieldLength+1);
-    }
-
-    return NO_ERROR;
-}
-
-/*
- * Insert pad bytes in the LFH by tweaking the "extra" field.  This will
- * potentially confuse something that put "extra" data in here earlier,
- * but I can't find an actual problem.
- */
-status_t ZipEntry::addPadding(int padding)
-{
-    if (padding <= 0)
-        return INVALID_OPERATION;
-
-    //ALOGI("HEY: adding %d pad bytes to existing %d in %s\n",
-    //    padding, mLFH.mExtraFieldLength, mCDE.mFileName);
-
-    if (mLFH.mExtraFieldLength > 0) {
-        /* extend existing field */
-        unsigned char* newExtra;
-
-        newExtra = new unsigned char[mLFH.mExtraFieldLength + padding];
-        if (newExtra == NULL)
-            return NO_MEMORY;
-        memset(newExtra + mLFH.mExtraFieldLength, 0, padding);
-        memcpy(newExtra, mLFH.mExtraField, mLFH.mExtraFieldLength);
-
-        delete[] mLFH.mExtraField;
-        mLFH.mExtraField = newExtra;
-        mLFH.mExtraFieldLength += padding;
-    } else {
-        /* create new field */
-        mLFH.mExtraField = new unsigned char[padding];
-        memset(mLFH.mExtraField, 0, padding);
-        mLFH.mExtraFieldLength = padding;
-    }
-
-    return NO_ERROR;
-}
-
-/*
- * Set the fields in the LFH equal to the corresponding fields in the CDE.
- *
- * This does not touch the LFH "extra" field.
- */
-void ZipEntry::copyCDEtoLFH(void)
-{
-    mLFH.mVersionToExtract  = mCDE.mVersionToExtract;
-    mLFH.mGPBitFlag         = mCDE.mGPBitFlag;
-    mLFH.mCompressionMethod = mCDE.mCompressionMethod;
-    mLFH.mLastModFileTime   = mCDE.mLastModFileTime;
-    mLFH.mLastModFileDate   = mCDE.mLastModFileDate;
-    mLFH.mCRC32             = mCDE.mCRC32;
-    mLFH.mCompressedSize    = mCDE.mCompressedSize;
-    mLFH.mUncompressedSize  = mCDE.mUncompressedSize;
-    mLFH.mFileNameLength    = mCDE.mFileNameLength;
-    // the "extra field" is independent
-
-    delete[] mLFH.mFileName;
-    if (mLFH.mFileNameLength > 0) {
-        mLFH.mFileName = new unsigned char[mLFH.mFileNameLength+1];
-        strcpy((char*) mLFH.mFileName, (const char*) mCDE.mFileName);
-    } else {
-        mLFH.mFileName = NULL;
-    }
-}
-
-/*
- * Set some information about a file after we add it.
- */
-void ZipEntry::setDataInfo(long uncompLen, long compLen, unsigned long crc32,
-    int compressionMethod)
-{
-    mCDE.mCompressionMethod = compressionMethod;
-    mCDE.mCRC32 = crc32;
-    mCDE.mCompressedSize = compLen;
-    mCDE.mUncompressedSize = uncompLen;
-    mCDE.mCompressionMethod = compressionMethod;
-    if (compressionMethod == kCompressDeflated) {
-        mCDE.mGPBitFlag |= 0x0002;      // indicates maximum compression used
-    }
-    copyCDEtoLFH();
-}
-
-/*
- * See if the data in mCDE and mLFH match up.  This is mostly useful for
- * debugging these classes, but it can be used to identify damaged
- * archives.
- *
- * Returns "false" if they differ.
- */
-bool ZipEntry::compareHeaders(void) const
-{
-    if (mCDE.mVersionToExtract != mLFH.mVersionToExtract) {
-        ALOGV("cmp: VersionToExtract\n");
-        return false;
-    }
-    if (mCDE.mGPBitFlag != mLFH.mGPBitFlag) {
-        ALOGV("cmp: GPBitFlag\n");
-        return false;
-    }
-    if (mCDE.mCompressionMethod != mLFH.mCompressionMethod) {
-        ALOGV("cmp: CompressionMethod\n");
-        return false;
-    }
-    if (mCDE.mLastModFileTime != mLFH.mLastModFileTime) {
-        ALOGV("cmp: LastModFileTime\n");
-        return false;
-    }
-    if (mCDE.mLastModFileDate != mLFH.mLastModFileDate) {
-        ALOGV("cmp: LastModFileDate\n");
-        return false;
-    }
-    if (mCDE.mCRC32 != mLFH.mCRC32) {
-        ALOGV("cmp: CRC32\n");
-        return false;
-    }
-    if (mCDE.mCompressedSize != mLFH.mCompressedSize) {
-        ALOGV("cmp: CompressedSize\n");
-        return false;
-    }
-    if (mCDE.mUncompressedSize != mLFH.mUncompressedSize) {
-        ALOGV("cmp: UncompressedSize\n");
-        return false;
-    }
-    if (mCDE.mFileNameLength != mLFH.mFileNameLength) {
-        ALOGV("cmp: FileNameLength\n");
-        return false;
-    }
-#if 0       // this seems to be used for padding, not real data
-    if (mCDE.mExtraFieldLength != mLFH.mExtraFieldLength) {
-        ALOGV("cmp: ExtraFieldLength\n");
-        return false;
-    }
-#endif
-    if (mCDE.mFileName != NULL) {
-        if (strcmp((char*) mCDE.mFileName, (char*) mLFH.mFileName) != 0) {
-            ALOGV("cmp: FileName\n");
-            return false;
-        }
-    }
-
-    return true;
-}
-
-
-/*
- * Convert the DOS date/time stamp into a UNIX time stamp.
- */
-time_t ZipEntry::getModWhen(void) const
-{
-    struct tm parts;
-
-    parts.tm_sec = (mCDE.mLastModFileTime & 0x001f) << 1;
-    parts.tm_min = (mCDE.mLastModFileTime & 0x07e0) >> 5;
-    parts.tm_hour = (mCDE.mLastModFileTime & 0xf800) >> 11;
-    parts.tm_mday = (mCDE.mLastModFileDate & 0x001f);
-    parts.tm_mon = ((mCDE.mLastModFileDate & 0x01e0) >> 5) -1;
-    parts.tm_year = ((mCDE.mLastModFileDate & 0xfe00) >> 9) + 80;
-    parts.tm_wday = parts.tm_yday = 0;
-    parts.tm_isdst = -1;        // DST info "not available"
-
-    return mktime(&parts);
-}
-
-/*
- * Set the CDE/LFH timestamp from UNIX time.
- */
-void ZipEntry::setModWhen(time_t when)
-{
-#if !defined(_WIN32)
-    struct tm tmResult;
-#endif
-    time_t even;
-    unsigned short zdate, ztime;
-
-    struct tm* ptm;
-
-    /* round up to an even number of seconds */
-    even = (time_t)(((unsigned long)(when) + 1) & (~1));
-
-    /* expand */
-#if !defined(_WIN32)
-    ptm = localtime_r(&even, &tmResult);
-#else
-    ptm = localtime(&even);
-#endif
-
-    int year;
-    year = ptm->tm_year;
-    if (year < 80)
-        year = 80;
-
-    zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday;
-    ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1;
-
-    mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime;
-    mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate;
-}
-
-
-/*
- * ===========================================================================
- *      ZipEntry::LocalFileHeader
- * ===========================================================================
- */
-
-/*
- * Read a local file header.
- *
- * On entry, "fp" points to the signature at the start of the header.
- * On exit, "fp" points to the start of data.
- */
-status_t ZipEntry::LocalFileHeader::read(FILE* fp)
-{
-    status_t result = NO_ERROR;
-    unsigned char buf[kLFHLen];
-
-    assert(mFileName == NULL);
-    assert(mExtraField == NULL);
-
-    if (fread(buf, 1, kLFHLen, fp) != kLFHLen) {
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-
-    if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
-        ALOGD("whoops: didn't find expected signature\n");
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-
-    mVersionToExtract = ZipEntry::getShortLE(&buf[0x04]);
-    mGPBitFlag = ZipEntry::getShortLE(&buf[0x06]);
-    mCompressionMethod = ZipEntry::getShortLE(&buf[0x08]);
-    mLastModFileTime = ZipEntry::getShortLE(&buf[0x0a]);
-    mLastModFileDate = ZipEntry::getShortLE(&buf[0x0c]);
-    mCRC32 = ZipEntry::getLongLE(&buf[0x0e]);
-    mCompressedSize = ZipEntry::getLongLE(&buf[0x12]);
-    mUncompressedSize = ZipEntry::getLongLE(&buf[0x16]);
-    mFileNameLength = ZipEntry::getShortLE(&buf[0x1a]);
-    mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1c]);
-
-    // TODO: validate sizes
-
-    /* grab filename */
-    if (mFileNameLength != 0) {
-        mFileName = new unsigned char[mFileNameLength+1];
-        if (mFileName == NULL) {
-            result = NO_MEMORY;
-            goto bail;
-        }
-        if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
-            result = UNKNOWN_ERROR;
-            goto bail;
-        }
-        mFileName[mFileNameLength] = '\0';
-    }
-
-    /* grab extra field */
-    if (mExtraFieldLength != 0) {
-        mExtraField = new unsigned char[mExtraFieldLength+1];
-        if (mExtraField == NULL) {
-            result = NO_MEMORY;
-            goto bail;
-        }
-        if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
-            result = UNKNOWN_ERROR;
-            goto bail;
-        }
-        mExtraField[mExtraFieldLength] = '\0';
-    }
-
-bail:
-    return result;
-}
-
-/*
- * Write a local file header.
- */
-status_t ZipEntry::LocalFileHeader::write(FILE* fp)
-{
-    unsigned char buf[kLFHLen];
-
-    ZipEntry::putLongLE(&buf[0x00], kSignature);
-    ZipEntry::putShortLE(&buf[0x04], mVersionToExtract);
-    ZipEntry::putShortLE(&buf[0x06], mGPBitFlag);
-    ZipEntry::putShortLE(&buf[0x08], mCompressionMethod);
-    ZipEntry::putShortLE(&buf[0x0a], mLastModFileTime);
-    ZipEntry::putShortLE(&buf[0x0c], mLastModFileDate);
-    ZipEntry::putLongLE(&buf[0x0e], mCRC32);
-    ZipEntry::putLongLE(&buf[0x12], mCompressedSize);
-    ZipEntry::putLongLE(&buf[0x16], mUncompressedSize);
-    ZipEntry::putShortLE(&buf[0x1a], mFileNameLength);
-    ZipEntry::putShortLE(&buf[0x1c], mExtraFieldLength);
-
-    if (fwrite(buf, 1, kLFHLen, fp) != kLFHLen)
-        return UNKNOWN_ERROR;
-
-    /* write filename */
-    if (mFileNameLength != 0) {
-        if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
-            return UNKNOWN_ERROR;
-    }
-
-    /* write "extra field" */
-    if (mExtraFieldLength != 0) {
-        if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
-            return UNKNOWN_ERROR;
-    }
-
-    return NO_ERROR;
-}
-
-
-/*
- * Dump the contents of a LocalFileHeader object.
- */
-void ZipEntry::LocalFileHeader::dump(void) const
-{
-    ALOGD(" LocalFileHeader contents:\n");
-    ALOGD("  versToExt=%u gpBits=0x%04x compression=%u\n",
-        mVersionToExtract, mGPBitFlag, mCompressionMethod);
-    ALOGD("  modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
-        mLastModFileTime, mLastModFileDate, mCRC32);
-    ALOGD("  compressedSize=%lu uncompressedSize=%lu\n",
-        mCompressedSize, mUncompressedSize);
-    ALOGD("  filenameLen=%u extraLen=%u\n",
-        mFileNameLength, mExtraFieldLength);
-    if (mFileName != NULL)
-        ALOGD("  filename: '%s'\n", mFileName);
-}
-
-
-/*
- * ===========================================================================
- *      ZipEntry::CentralDirEntry
- * ===========================================================================
- */
-
-/*
- * Read the central dir entry that appears next in the file.
- *
- * On entry, "fp" should be positioned on the signature bytes for the
- * entry.  On exit, "fp" will point at the signature word for the next
- * entry or for the EOCD.
- */
-status_t ZipEntry::CentralDirEntry::read(FILE* fp)
-{
-    status_t result = NO_ERROR;
-    unsigned char buf[kCDELen];
-
-    /* no re-use */
-    assert(mFileName == NULL);
-    assert(mExtraField == NULL);
-    assert(mFileComment == NULL);
-
-    if (fread(buf, 1, kCDELen, fp) != kCDELen) {
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-
-    if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
-        ALOGD("Whoops: didn't find expected signature\n");
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-
-    mVersionMadeBy = ZipEntry::getShortLE(&buf[0x04]);
-    mVersionToExtract = ZipEntry::getShortLE(&buf[0x06]);
-    mGPBitFlag = ZipEntry::getShortLE(&buf[0x08]);
-    mCompressionMethod = ZipEntry::getShortLE(&buf[0x0a]);
-    mLastModFileTime = ZipEntry::getShortLE(&buf[0x0c]);
-    mLastModFileDate = ZipEntry::getShortLE(&buf[0x0e]);
-    mCRC32 = ZipEntry::getLongLE(&buf[0x10]);
-    mCompressedSize = ZipEntry::getLongLE(&buf[0x14]);
-    mUncompressedSize = ZipEntry::getLongLE(&buf[0x18]);
-    mFileNameLength = ZipEntry::getShortLE(&buf[0x1c]);
-    mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1e]);
-    mFileCommentLength = ZipEntry::getShortLE(&buf[0x20]);
-    mDiskNumberStart = ZipEntry::getShortLE(&buf[0x22]);
-    mInternalAttrs = ZipEntry::getShortLE(&buf[0x24]);
-    mExternalAttrs = ZipEntry::getLongLE(&buf[0x26]);
-    mLocalHeaderRelOffset = ZipEntry::getLongLE(&buf[0x2a]);
-
-    // TODO: validate sizes and offsets
-
-    /* grab filename */
-    if (mFileNameLength != 0) {
-        mFileName = new unsigned char[mFileNameLength+1];
-        if (mFileName == NULL) {
-            result = NO_MEMORY;
-            goto bail;
-        }
-        if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
-            result = UNKNOWN_ERROR;
-            goto bail;
-        }
-        mFileName[mFileNameLength] = '\0';
-    }
-
-    /* read "extra field" */
-    if (mExtraFieldLength != 0) {
-        mExtraField = new unsigned char[mExtraFieldLength+1];
-        if (mExtraField == NULL) {
-            result = NO_MEMORY;
-            goto bail;
-        }
-        if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
-            result = UNKNOWN_ERROR;
-            goto bail;
-        }
-        mExtraField[mExtraFieldLength] = '\0';
-    }
-
-
-    /* grab comment, if any */
-    if (mFileCommentLength != 0) {
-        mFileComment = new unsigned char[mFileCommentLength+1];
-        if (mFileComment == NULL) {
-            result = NO_MEMORY;
-            goto bail;
-        }
-        if (fread(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
-        {
-            result = UNKNOWN_ERROR;
-            goto bail;
-        }
-        mFileComment[mFileCommentLength] = '\0';
-    }
-
-bail:
-    return result;
-}
-
-/*
- * Write a central dir entry.
- */
-status_t ZipEntry::CentralDirEntry::write(FILE* fp)
-{
-    unsigned char buf[kCDELen];
-
-    ZipEntry::putLongLE(&buf[0x00], kSignature);
-    ZipEntry::putShortLE(&buf[0x04], mVersionMadeBy);
-    ZipEntry::putShortLE(&buf[0x06], mVersionToExtract);
-    ZipEntry::putShortLE(&buf[0x08], mGPBitFlag);
-    ZipEntry::putShortLE(&buf[0x0a], mCompressionMethod);
-    ZipEntry::putShortLE(&buf[0x0c], mLastModFileTime);
-    ZipEntry::putShortLE(&buf[0x0e], mLastModFileDate);
-    ZipEntry::putLongLE(&buf[0x10], mCRC32);
-    ZipEntry::putLongLE(&buf[0x14], mCompressedSize);
-    ZipEntry::putLongLE(&buf[0x18], mUncompressedSize);
-    ZipEntry::putShortLE(&buf[0x1c], mFileNameLength);
-    ZipEntry::putShortLE(&buf[0x1e], mExtraFieldLength);
-    ZipEntry::putShortLE(&buf[0x20], mFileCommentLength);
-    ZipEntry::putShortLE(&buf[0x22], mDiskNumberStart);
-    ZipEntry::putShortLE(&buf[0x24], mInternalAttrs);
-    ZipEntry::putLongLE(&buf[0x26], mExternalAttrs);
-    ZipEntry::putLongLE(&buf[0x2a], mLocalHeaderRelOffset);
-
-    if (fwrite(buf, 1, kCDELen, fp) != kCDELen)
-        return UNKNOWN_ERROR;
-
-    /* write filename */
-    if (mFileNameLength != 0) {
-        if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
-            return UNKNOWN_ERROR;
-    }
-
-    /* write "extra field" */
-    if (mExtraFieldLength != 0) {
-        if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
-            return UNKNOWN_ERROR;
-    }
-
-    /* write comment */
-    if (mFileCommentLength != 0) {
-        if (fwrite(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
-            return UNKNOWN_ERROR;
-    }
-
-    return NO_ERROR;
-}
-
-/*
- * Dump the contents of a CentralDirEntry object.
- */
-void ZipEntry::CentralDirEntry::dump(void) const
-{
-    ALOGD(" CentralDirEntry contents:\n");
-    ALOGD("  versMadeBy=%u versToExt=%u gpBits=0x%04x compression=%u\n",
-        mVersionMadeBy, mVersionToExtract, mGPBitFlag, mCompressionMethod);
-    ALOGD("  modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
-        mLastModFileTime, mLastModFileDate, mCRC32);
-    ALOGD("  compressedSize=%lu uncompressedSize=%lu\n",
-        mCompressedSize, mUncompressedSize);
-    ALOGD("  filenameLen=%u extraLen=%u commentLen=%u\n",
-        mFileNameLength, mExtraFieldLength, mFileCommentLength);
-    ALOGD("  diskNumStart=%u intAttr=0x%04x extAttr=0x%08lx relOffset=%lu\n",
-        mDiskNumberStart, mInternalAttrs, mExternalAttrs,
-        mLocalHeaderRelOffset);
-
-    if (mFileName != NULL)
-        ALOGD("  filename: '%s'\n", mFileName);
-    if (mFileComment != NULL)
-        ALOGD("  comment: '%s'\n", mFileComment);
-}
-
-/*
- * Copy-assignment operator for CentralDirEntry.
- */
-ZipEntry::CentralDirEntry& ZipEntry::CentralDirEntry::operator=(const ZipEntry::CentralDirEntry& src) {
-    if (this == &src) {
-        return *this;
-    }
-
-    // Free up old data.
-    delete[] mFileName;
-    delete[] mExtraField;
-    delete[] mFileComment;
-
-    // Copy scalars.
-    mVersionMadeBy = src.mVersionMadeBy;
-    mVersionToExtract = src.mVersionToExtract;
-    mGPBitFlag = src.mGPBitFlag;
-    mCompressionMethod = src.mCompressionMethod;
-    mLastModFileTime = src.mLastModFileTime;
-    mLastModFileDate = src.mLastModFileDate;
-    mCRC32 = src.mCRC32;
-    mCompressedSize = src.mCompressedSize;
-    mUncompressedSize = src.mUncompressedSize;
-    mFileNameLength = src.mFileNameLength;
-    mExtraFieldLength = src.mExtraFieldLength;
-    mFileCommentLength = src.mFileCommentLength;
-    mDiskNumberStart = src.mDiskNumberStart;
-    mInternalAttrs = src.mInternalAttrs;
-    mExternalAttrs = src.mExternalAttrs;
-    mLocalHeaderRelOffset = src.mLocalHeaderRelOffset;
-
-    // Copy strings, if necessary.
-    if (mFileNameLength > 0) {
-        mFileName = new unsigned char[mFileNameLength + 1];
-        if (mFileName != NULL)
-            strcpy((char*)mFileName, (char*)src.mFileName);
-    } else {
-        mFileName = NULL;
-    }
-    if (mFileCommentLength > 0) {
-        mFileComment = new unsigned char[mFileCommentLength + 1];
-        if (mFileComment != NULL)
-            strcpy((char*)mFileComment, (char*)src.mFileComment);
-    } else {
-        mFileComment = NULL;
-    }
-    if (mExtraFieldLength > 0) {
-        /* we null-terminate this, though it may not be a string */
-        mExtraField = new unsigned char[mExtraFieldLength + 1];
-        if (mExtraField != NULL)
-            memcpy(mExtraField, src.mExtraField, mExtraFieldLength + 1);
-    } else {
-        mExtraField = NULL;
-    }
-
-    return *this;
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ZipEntry.h b/tools/aapt2/ZipEntry.h
deleted file mode 100644
index 2745a43..0000000
--- a/tools/aapt2/ZipEntry.h
+++ /dev/null
@@ -1,350 +0,0 @@
-/*
- * Copyright (C) 2006 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.
- */
-
-//
-// Zip archive entries.
-//
-// The ZipEntry class is tightly meshed with the ZipFile class.
-//
-#ifndef __LIBS_ZIPENTRY_H
-#define __LIBS_ZIPENTRY_H
-
-#include <utils/Errors.h>
-
-#include <stdlib.h>
-#include <stdio.h>
-
-namespace aapt {
-
-using android::status_t;
-
-class ZipFile;
-
-/*
- * ZipEntry objects represent a single entry in a Zip archive.
- *
- * You can use one of these to get or set information about an entry, but
- * there are no functions here for accessing the data itself.  (We could
- * tuck a pointer to the ZipFile in here for convenience, but that raises
- * the likelihood of using ZipEntry objects after discarding the ZipFile.)
- *
- * File information is stored in two places: next to the file data (the Local
- * File Header, and possibly a Data Descriptor), and at the end of the file
- * (the Central Directory Entry).  The two must be kept in sync.
- */
-class ZipEntry {
-public:
-    friend class ZipFile;
-
-    ZipEntry(void)
-        : mDeleted(false), mMarked(false)
-        {}
-    ~ZipEntry(void) {}
-
-    /*
-     * Returns "true" if the data is compressed.
-     */
-    bool isCompressed(void) const {
-        return mCDE.mCompressionMethod != kCompressStored;
-    }
-    int getCompressionMethod(void) const { return mCDE.mCompressionMethod; }
-
-    /*
-     * Return the uncompressed length.
-     */
-    off_t getUncompressedLen(void) const { return mCDE.mUncompressedSize; }
-
-    /*
-     * Return the compressed length.  For uncompressed data, this returns
-     * the same thing as getUncompresesdLen().
-     */
-    off_t getCompressedLen(void) const { return mCDE.mCompressedSize; }
-
-    /*
-     * Return the offset of the local file header.
-     */
-    off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; }
-
-    /*
-     * Return the absolute file offset of the start of the compressed or
-     * uncompressed data.
-     */
-    off_t getFileOffset(void) const {
-        return mCDE.mLocalHeaderRelOffset +
-                LocalFileHeader::kLFHLen +
-                mLFH.mFileNameLength +
-                mLFH.mExtraFieldLength;
-    }
-
-    /*
-     * Return the data CRC.
-     */
-    unsigned long getCRC32(void) const { return mCDE.mCRC32; }
-
-    /*
-     * Return file modification time in UNIX seconds-since-epoch.
-     */
-    time_t getModWhen(void) const;
-
-    /*
-     * Return the archived file name.
-     */
-    const char* getFileName(void) const { return (const char*) mCDE.mFileName; }
-
-    /*
-     * Application-defined "mark".  Can be useful when synchronizing the
-     * contents of an archive with contents on disk.
-     */
-    bool getMarked(void) const { return mMarked; }
-    void setMarked(bool val) { mMarked = val; }
-
-    /*
-     * Some basic functions for raw data manipulation.  "LE" means
-     * Little Endian.
-     */
-    static inline unsigned short getShortLE(const unsigned char* buf) {
-        return buf[0] | (buf[1] << 8);
-    }
-    static inline unsigned long getLongLE(const unsigned char* buf) {
-        return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
-    }
-    static inline void putShortLE(unsigned char* buf, short val) {
-        buf[0] = (unsigned char) val;
-        buf[1] = (unsigned char) (val >> 8);
-    }
-    static inline void putLongLE(unsigned char* buf, long val) {
-        buf[0] = (unsigned char) val;
-        buf[1] = (unsigned char) (val >> 8);
-        buf[2] = (unsigned char) (val >> 16);
-        buf[3] = (unsigned char) (val >> 24);
-    }
-
-    /* defined for Zip archives */
-    enum {
-        kCompressStored     = 0,        // no compression
-        // shrunk           = 1,
-        // reduced 1        = 2,
-        // reduced 2        = 3,
-        // reduced 3        = 4,
-        // reduced 4        = 5,
-        // imploded         = 6,
-        // tokenized        = 7,
-        kCompressDeflated   = 8,        // standard deflate
-        // Deflate64        = 9,
-        // lib imploded     = 10,
-        // reserved         = 11,
-        // bzip2            = 12,
-    };
-
-    /*
-     * Deletion flag.  If set, the entry will be removed on the next
-     * call to "flush".
-     */
-    bool getDeleted(void) const { return mDeleted; }
-
-protected:
-    /*
-     * Initialize the structure from the file, which is pointing at
-     * our Central Directory entry.
-     */
-    status_t initFromCDE(FILE* fp);
-
-    /*
-     * Initialize the structure for a new file.  We need the filename
-     * and comment so that we can properly size the LFH area.  The
-     * filename is mandatory, the comment is optional.
-     */
-    void initNew(const char* fileName, const char* comment);
-
-    /*
-     * Initialize the structure with the contents of a ZipEntry from
-     * another file. If fileName is non-NULL, override the name with fileName.
-     */
-    status_t initFromExternal(const ZipFile* pZipFile, const ZipEntry* pEntry,
-                              const char* fileName);
-
-    /*
-     * Add some pad bytes to the LFH.  We do this by adding or resizing
-     * the "extra" field.
-     */
-    status_t addPadding(int padding);
-
-    /*
-     * Set information about the data for this entry.
-     */
-    void setDataInfo(long uncompLen, long compLen, unsigned long crc32,
-        int compressionMethod);
-
-    /*
-     * Set the modification date.
-     */
-    void setModWhen(time_t when);
-
-    /*
-     * Set the offset of the local file header, relative to the start of
-     * the current file.
-     */
-    void setLFHOffset(off_t offset) {
-        mCDE.mLocalHeaderRelOffset = (long) offset;
-    }
-
-    /* mark for deletion; used by ZipFile::remove() */
-    void setDeleted(void) { mDeleted = true; }
-
-private:
-    /* these are private and not defined */
-    ZipEntry(const ZipEntry& src);
-    ZipEntry& operator=(const ZipEntry& src);
-
-    /* returns "true" if the CDE and the LFH agree */
-    bool compareHeaders(void) const;
-    void copyCDEtoLFH(void);
-
-    bool        mDeleted;       // set if entry is pending deletion
-    bool        mMarked;        // app-defined marker
-
-    /*
-     * Every entry in the Zip archive starts off with one of these.
-     */
-    class LocalFileHeader {
-    public:
-        LocalFileHeader(void) :
-            mVersionToExtract(0),
-            mGPBitFlag(0),
-            mCompressionMethod(0),
-            mLastModFileTime(0),
-            mLastModFileDate(0),
-            mCRC32(0),
-            mCompressedSize(0),
-            mUncompressedSize(0),
-            mFileNameLength(0),
-            mExtraFieldLength(0),
-            mFileName(NULL),
-            mExtraField(NULL)
-        {}
-        virtual ~LocalFileHeader(void) {
-            delete[] mFileName;
-            delete[] mExtraField;
-        }
-
-        status_t read(FILE* fp);
-        status_t write(FILE* fp);
-
-        // unsigned long mSignature;
-        unsigned short  mVersionToExtract;
-        unsigned short  mGPBitFlag;
-        unsigned short  mCompressionMethod;
-        unsigned short  mLastModFileTime;
-        unsigned short  mLastModFileDate;
-        unsigned long   mCRC32;
-        unsigned long   mCompressedSize;
-        unsigned long   mUncompressedSize;
-        unsigned short  mFileNameLength;
-        unsigned short  mExtraFieldLength;
-        unsigned char*  mFileName;
-        unsigned char*  mExtraField;
-
-        enum {
-            kSignature      = 0x04034b50,
-            kLFHLen         = 30,       // LocalFileHdr len, excl. var fields
-        };
-
-        void dump(void) const;
-    };
-
-    /*
-     * Every entry in the Zip archive has one of these in the "central
-     * directory" at the end of the file.
-     */
-    class CentralDirEntry {
-    public:
-        CentralDirEntry(void) :
-            mVersionMadeBy(0),
-            mVersionToExtract(0),
-            mGPBitFlag(0),
-            mCompressionMethod(0),
-            mLastModFileTime(0),
-            mLastModFileDate(0),
-            mCRC32(0),
-            mCompressedSize(0),
-            mUncompressedSize(0),
-            mFileNameLength(0),
-            mExtraFieldLength(0),
-            mFileCommentLength(0),
-            mDiskNumberStart(0),
-            mInternalAttrs(0),
-            mExternalAttrs(0),
-            mLocalHeaderRelOffset(0),
-            mFileName(NULL),
-            mExtraField(NULL),
-            mFileComment(NULL)
-        {}
-        virtual ~CentralDirEntry(void) {
-            delete[] mFileName;
-            delete[] mExtraField;
-            delete[] mFileComment;
-        }
-
-        status_t read(FILE* fp);
-        status_t write(FILE* fp);
-
-        CentralDirEntry& operator=(const CentralDirEntry& src);
-
-        // unsigned long mSignature;
-        unsigned short  mVersionMadeBy;
-        unsigned short  mVersionToExtract;
-        unsigned short  mGPBitFlag;
-        unsigned short  mCompressionMethod;
-        unsigned short  mLastModFileTime;
-        unsigned short  mLastModFileDate;
-        unsigned long   mCRC32;
-        unsigned long   mCompressedSize;
-        unsigned long   mUncompressedSize;
-        unsigned short  mFileNameLength;
-        unsigned short  mExtraFieldLength;
-        unsigned short  mFileCommentLength;
-        unsigned short  mDiskNumberStart;
-        unsigned short  mInternalAttrs;
-        unsigned long   mExternalAttrs;
-        unsigned long   mLocalHeaderRelOffset;
-        unsigned char*  mFileName;
-        unsigned char*  mExtraField;
-        unsigned char*  mFileComment;
-
-        void dump(void) const;
-
-        enum {
-            kSignature      = 0x02014b50,
-            kCDELen         = 46,       // CentralDirEnt len, excl. var fields
-        };
-    };
-
-    enum {
-        //kDataDescriptorSignature  = 0x08074b50,   // currently unused
-        kDataDescriptorLen  = 16,           // four 32-bit fields
-
-        kDefaultVersion     = 20,           // need deflate, nothing much else
-        kDefaultMadeBy      = 0x0317,       // 03=UNIX, 17=spec v2.3
-        kUsesDataDescr      = 0x0008,       // GPBitFlag bit 3
-    };
-
-    LocalFileHeader     mLFH;
-    CentralDirEntry     mCDE;
-};
-
-}; // namespace aapt
-
-#endif // __LIBS_ZIPENTRY_H
diff --git a/tools/aapt2/ZipFile.cpp b/tools/aapt2/ZipFile.cpp
deleted file mode 100644
index 268c15e..0000000
--- a/tools/aapt2/ZipFile.cpp
+++ /dev/null
@@ -1,1306 +0,0 @@
-/*
- * Copyright (C) 2006 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.
- */
-
-//
-// Access to Zip archives.
-//
-
-#define LOG_TAG "zip"
-
-#include <androidfw/ZipUtils.h>
-#include <utils/Log.h>
-
-#include "ZipFile.h"
-#include "Util.h"
-
-#include <zlib.h>
-#define DEF_MEM_LEVEL 8                // normally in zutil.h?
-
-#include <memory.h>
-#include <sys/stat.h>
-#include <errno.h>
-#include <assert.h>
-
-namespace aapt {
-
-using namespace android;
-
-/*
- * Some environments require the "b", some choke on it.
- */
-#define FILE_OPEN_RO        "rb"
-#define FILE_OPEN_RW        "r+b"
-#define FILE_OPEN_RW_CREATE "w+b"
-
-/* should live somewhere else? */
-static status_t errnoToStatus(int err)
-{
-    if (err == ENOENT)
-        return NAME_NOT_FOUND;
-    else if (err == EACCES)
-        return PERMISSION_DENIED;
-    else
-        return UNKNOWN_ERROR;
-}
-
-/*
- * Open a file and parse its guts.
- */
-status_t ZipFile::open(const char* zipFileName, int flags)
-{
-    bool newArchive = false;
-
-    assert(mZipFp == NULL);     // no reopen
-
-    if ((flags & kOpenTruncate))
-        flags |= kOpenCreate;           // trunc implies create
-
-    if ((flags & kOpenReadOnly) && (flags & kOpenReadWrite))
-        return INVALID_OPERATION;       // not both
-    if (!((flags & kOpenReadOnly) || (flags & kOpenReadWrite)))
-        return INVALID_OPERATION;       // not neither
-    if ((flags & kOpenCreate) && !(flags & kOpenReadWrite))
-        return INVALID_OPERATION;       // create requires write
-
-    if (flags & kOpenTruncate) {
-        newArchive = true;
-    } else {
-        newArchive = (access(zipFileName, F_OK) != 0);
-        if (!(flags & kOpenCreate) && newArchive) {
-            /* not creating, must already exist */
-            ALOGD("File %s does not exist", zipFileName);
-            return NAME_NOT_FOUND;
-        }
-    }
-
-    /* open the file */
-    const char* openflags;
-    if (flags & kOpenReadWrite) {
-        if (newArchive)
-            openflags = FILE_OPEN_RW_CREATE;
-        else
-            openflags = FILE_OPEN_RW;
-    } else {
-        openflags = FILE_OPEN_RO;
-    }
-    mZipFp = fopen(zipFileName, openflags);
-    if (mZipFp == NULL) {
-        int err = errno;
-        ALOGD("fopen failed: %d\n", err);
-        return errnoToStatus(err);
-    }
-
-    status_t result;
-    if (!newArchive) {
-        /*
-         * Load the central directory.  If that fails, then this probably
-         * isn't a Zip archive.
-         */
-        result = readCentralDir();
-    } else {
-        /*
-         * Newly-created.  The EndOfCentralDir constructor actually
-         * sets everything to be the way we want it (all zeroes).  We
-         * set mNeedCDRewrite so that we create *something* if the
-         * caller doesn't add any files.  (We could also just unlink
-         * the file if it's brand new and nothing was added, but that's
-         * probably doing more than we really should -- the user might
-         * have a need for empty zip files.)
-         */
-        mNeedCDRewrite = true;
-        result = NO_ERROR;
-    }
-
-    if (flags & kOpenReadOnly)
-        mReadOnly = true;
-    else
-        assert(!mReadOnly);
-
-    return result;
-}
-
-/*
- * Return the Nth entry in the archive.
- */
-ZipEntry* ZipFile::getEntryByIndex(int idx) const
-{
-    if (idx < 0 || idx >= (int) mEntries.size())
-        return NULL;
-
-    return mEntries[idx];
-}
-
-/*
- * Find an entry by name.
- */
-ZipEntry* ZipFile::getEntryByName(const char* fileName) const
-{
-    /*
-     * Do a stupid linear string-compare search.
-     *
-     * There are various ways to speed this up, especially since it's rare
-     * to intermingle changes to the archive with "get by name" calls.  We
-     * don't want to sort the mEntries vector itself, however, because
-     * it's used to recreate the Central Directory.
-     *
-     * (Hash table works, parallel list of pointers in sorted order is good.)
-     */
-    int idx;
-
-    for (idx = mEntries.size()-1; idx >= 0; idx--) {
-        ZipEntry* pEntry = mEntries[idx];
-        if (!pEntry->getDeleted() &&
-            strcmp(fileName, pEntry->getFileName()) == 0)
-        {
-            return pEntry;
-        }
-    }
-
-    return NULL;
-}
-
-/*
- * Empty the mEntries vector.
- */
-void ZipFile::discardEntries(void)
-{
-    int count = mEntries.size();
-
-    while (--count >= 0)
-        delete mEntries[count];
-
-    mEntries.clear();
-}
-
-
-/*
- * Find the central directory and read the contents.
- *
- * The fun thing about ZIP archives is that they may or may not be
- * readable from start to end.  In some cases, notably for archives
- * that were written to stdout, the only length information is in the
- * central directory at the end of the file.
- *
- * Of course, the central directory can be followed by a variable-length
- * comment field, so we have to scan through it backwards.  The comment
- * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff
- * itself, plus apparently sometimes people throw random junk on the end
- * just for the fun of it.
- *
- * This is all a little wobbly.  If the wrong value ends up in the EOCD
- * area, we're hosed.  This appears to be the way that everbody handles
- * it though, so we're in pretty good company if this fails.
- */
-status_t ZipFile::readCentralDir(void)
-{
-    status_t result = NO_ERROR;
-    unsigned char* buf = NULL;
-    off_t fileLength, seekStart;
-    long readAmount;
-    int i;
-
-    fseek(mZipFp, 0, SEEK_END);
-    fileLength = ftell(mZipFp);
-    rewind(mZipFp);
-
-    /* too small to be a ZIP archive? */
-    if (fileLength < EndOfCentralDir::kEOCDLen) {
-        ALOGD("Length is %ld -- too small\n", (long)fileLength);
-        result = INVALID_OPERATION;
-        goto bail;
-    }
-
-    buf = new unsigned char[EndOfCentralDir::kMaxEOCDSearch];
-    if (buf == NULL) {
-        ALOGD("Failure allocating %d bytes for EOCD search",
-             EndOfCentralDir::kMaxEOCDSearch);
-        result = NO_MEMORY;
-        goto bail;
-    }
-
-    if (fileLength > EndOfCentralDir::kMaxEOCDSearch) {
-        seekStart = fileLength - EndOfCentralDir::kMaxEOCDSearch;
-        readAmount = EndOfCentralDir::kMaxEOCDSearch;
-    } else {
-        seekStart = 0;
-        readAmount = (long) fileLength;
-    }
-    if (fseek(mZipFp, seekStart, SEEK_SET) != 0) {
-        ALOGD("Failure seeking to end of zip at %ld", (long) seekStart);
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-
-    /* read the last part of the file into the buffer */
-    if (fread(buf, 1, readAmount, mZipFp) != (size_t) readAmount) {
-        ALOGD("short file? wanted %ld\n", readAmount);
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-
-    /* find the end-of-central-dir magic */
-    for (i = readAmount - 4; i >= 0; i--) {
-        if (buf[i] == 0x50 &&
-            ZipEntry::getLongLE(&buf[i]) == EndOfCentralDir::kSignature)
-        {
-            ALOGV("+++ Found EOCD at buf+%d\n", i);
-            break;
-        }
-    }
-    if (i < 0) {
-        ALOGD("EOCD not found, not Zip\n");
-        result = INVALID_OPERATION;
-        goto bail;
-    }
-
-    /* extract eocd values */
-    result = mEOCD.readBuf(buf + i, readAmount - i);
-    if (result != NO_ERROR) {
-        ALOGD("Failure reading %ld bytes of EOCD values", readAmount - i);
-        goto bail;
-    }
-    //mEOCD.dump();
-
-    if (mEOCD.mDiskNumber != 0 || mEOCD.mDiskWithCentralDir != 0 ||
-        mEOCD.mNumEntries != mEOCD.mTotalNumEntries)
-    {
-        ALOGD("Archive spanning not supported\n");
-        result = INVALID_OPERATION;
-        goto bail;
-    }
-
-    /*
-     * So far so good.  "mCentralDirSize" is the size in bytes of the
-     * central directory, so we can just seek back that far to find it.
-     * We can also seek forward mCentralDirOffset bytes from the
-     * start of the file.
-     *
-     * We're not guaranteed to have the rest of the central dir in the
-     * buffer, nor are we guaranteed that the central dir will have any
-     * sort of convenient size.  We need to skip to the start of it and
-     * read the header, then the other goodies.
-     *
-     * The only thing we really need right now is the file comment, which
-     * we're hoping to preserve.
-     */
-    if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
-        ALOGD("Failure seeking to central dir offset %ld\n",
-             mEOCD.mCentralDirOffset);
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-
-    /*
-     * Loop through and read the central dir entries.
-     */
-    ALOGV("Scanning %d entries...\n", mEOCD.mTotalNumEntries);
-    int entry;
-    for (entry = 0; entry < mEOCD.mTotalNumEntries; entry++) {
-        ZipEntry* pEntry = new ZipEntry;
-
-        result = pEntry->initFromCDE(mZipFp);
-        if (result != NO_ERROR) {
-            ALOGD("initFromCDE failed\n");
-            delete pEntry;
-            goto bail;
-        }
-
-        mEntries.push_back(pEntry);
-    }
-
-
-    /*
-     * If all went well, we should now be back at the EOCD.
-     */
-    {
-        unsigned char checkBuf[4];
-        if (fread(checkBuf, 1, 4, mZipFp) != 4) {
-            ALOGD("EOCD check read failed\n");
-            result = INVALID_OPERATION;
-            goto bail;
-        }
-        if (ZipEntry::getLongLE(checkBuf) != EndOfCentralDir::kSignature) {
-            ALOGD("EOCD read check failed\n");
-            result = UNKNOWN_ERROR;
-            goto bail;
-        }
-        ALOGV("+++ EOCD read check passed\n");
-    }
-
-bail:
-    delete[] buf;
-    return result;
-}
-
-status_t ZipFile::add(const BigBuffer& buffer, const char* storageName, int compressionMethod,
-                      ZipEntry** ppEntry) {
-    std::unique_ptr<uint8_t[]> data = util::copy(buffer);
-    return add(data.get(), buffer.size(), storageName, compressionMethod, ppEntry);
-}
-
-
-/*
- * Add a new file to the archive.
- *
- * This requires creating and populating a ZipEntry structure, and copying
- * the data into the file at the appropriate position.  The "appropriate
- * position" is the current location of the central directory, which we
- * casually overwrite (we can put it back later).
- *
- * If we were concerned about safety, we would want to make all changes
- * in a temp file and then overwrite the original after everything was
- * safely written.  Not really a concern for us.
- */
-status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size,
-    const char* storageName, int sourceType, int compressionMethod,
-    ZipEntry** ppEntry)
-{
-    ZipEntry* pEntry = NULL;
-    status_t result = NO_ERROR;
-    long lfhPosn, startPosn, endPosn, uncompressedLen;
-    FILE* inputFp = NULL;
-    unsigned long crc;
-    time_t modWhen;
-
-    if (mReadOnly)
-        return INVALID_OPERATION;
-
-    assert(compressionMethod == ZipEntry::kCompressDeflated ||
-           compressionMethod == ZipEntry::kCompressStored);
-
-    /* make sure we're in a reasonable state */
-    assert(mZipFp != NULL);
-    assert(mEntries.size() == mEOCD.mTotalNumEntries);
-
-    /* make sure it doesn't already exist */
-    if (getEntryByName(storageName) != NULL)
-        return ALREADY_EXISTS;
-
-    if (!data) {
-        inputFp = fopen(fileName, FILE_OPEN_RO);
-        if (inputFp == NULL)
-            return errnoToStatus(errno);
-    }
-
-    if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-
-    pEntry = new ZipEntry;
-    pEntry->initNew(storageName, NULL);
-
-    /*
-     * From here on out, failures are more interesting.
-     */
-    mNeedCDRewrite = true;
-
-    /*
-     * Write the LFH, even though it's still mostly blank.  We need it
-     * as a place-holder.  In theory the LFH isn't necessary, but in
-     * practice some utilities demand it.
-     */
-    lfhPosn = ftell(mZipFp);
-    pEntry->mLFH.write(mZipFp);
-    startPosn = ftell(mZipFp);
-
-    /*
-     * Copy the data in, possibly compressing it as we go.
-     */
-    if (sourceType == ZipEntry::kCompressStored) {
-        if (compressionMethod == ZipEntry::kCompressDeflated) {
-            bool failed = false;
-            result = compressFpToFp(mZipFp, inputFp, data, size, &crc);
-            if (result != NO_ERROR) {
-                ALOGD("compression failed, storing\n");
-                failed = true;
-            } else {
-                /*
-                 * Make sure it has compressed "enough".  This probably ought
-                 * to be set through an API call, but I don't expect our
-                 * criteria to change over time.
-                 */
-                long src = inputFp ? ftell(inputFp) : size;
-                long dst = ftell(mZipFp) - startPosn;
-                if (dst + (dst / 10) > src) {
-                    ALOGD("insufficient compression (src=%ld dst=%ld), storing\n",
-                        src, dst);
-                    failed = true;
-                }
-            }
-
-            if (failed) {
-                compressionMethod = ZipEntry::kCompressStored;
-                if (inputFp) rewind(inputFp);
-                fseek(mZipFp, startPosn, SEEK_SET);
-                /* fall through to kCompressStored case */
-            }
-        }
-        /* handle "no compression" request, or failed compression from above */
-        if (compressionMethod == ZipEntry::kCompressStored) {
-            if (inputFp) {
-                result = copyFpToFp(mZipFp, inputFp, &crc);
-            } else {
-                result = copyDataToFp(mZipFp, data, size, &crc);
-            }
-            if (result != NO_ERROR) {
-                // don't need to truncate; happens in CDE rewrite
-                ALOGD("failed copying data in\n");
-                goto bail;
-            }
-        }
-
-        // currently seeked to end of file
-        uncompressedLen = inputFp ? ftell(inputFp) : size;
-    } else if (sourceType == ZipEntry::kCompressDeflated) {
-        /* we should support uncompressed-from-compressed, but it's not
-         * important right now */
-        assert(compressionMethod == ZipEntry::kCompressDeflated);
-
-        bool scanResult;
-        int method;
-        long compressedLen;
-
-        scanResult = ZipUtils::examineGzip(inputFp, &method, &uncompressedLen,
-                        &compressedLen, &crc);
-        if (!scanResult || method != ZipEntry::kCompressDeflated) {
-            ALOGD("this isn't a deflated gzip file?");
-            result = UNKNOWN_ERROR;
-            goto bail;
-        }
-
-        result = copyPartialFpToFp(mZipFp, inputFp, compressedLen, NULL);
-        if (result != NO_ERROR) {
-            ALOGD("failed copying gzip data in\n");
-            goto bail;
-        }
-    } else {
-        assert(false);
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-
-    /*
-     * We could write the "Data Descriptor", but there doesn't seem to
-     * be any point since we're going to go back and write the LFH.
-     *
-     * Update file offsets.
-     */
-    endPosn = ftell(mZipFp);            // seeked to end of compressed data
-
-    /*
-     * Success!  Fill out new values.
-     */
-    pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc,
-        compressionMethod);
-    modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp));
-    pEntry->setModWhen(modWhen);
-    pEntry->setLFHOffset(lfhPosn);
-    mEOCD.mNumEntries++;
-    mEOCD.mTotalNumEntries++;
-    mEOCD.mCentralDirSize = 0;      // mark invalid; set by flush()
-    mEOCD.mCentralDirOffset = endPosn;
-
-    /*
-     * Go back and write the LFH.
-     */
-    if (fseek(mZipFp, lfhPosn, SEEK_SET) != 0) {
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-    pEntry->mLFH.write(mZipFp);
-
-    /*
-     * Add pEntry to the list.
-     */
-    mEntries.push_back(pEntry);
-    if (ppEntry != NULL)
-        *ppEntry = pEntry;
-    pEntry = NULL;
-
-bail:
-    if (inputFp != NULL)
-        fclose(inputFp);
-    delete pEntry;
-    return result;
-}
-
-/*
- * Add an entry by copying it from another zip file.  If "padding" is
- * nonzero, the specified number of bytes will be added to the "extra"
- * field in the header.
- *
- * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
- */
-status_t ZipFile::add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
-                      const char* storageName, int padding, ZipEntry** ppEntry)
-{
-    ZipEntry* pEntry = NULL;
-    status_t result;
-    long lfhPosn, endPosn;
-
-    if (mReadOnly)
-        return INVALID_OPERATION;
-
-    /* make sure we're in a reasonable state */
-    assert(mZipFp != NULL);
-    assert(mEntries.size() == mEOCD.mTotalNumEntries);
-
-    if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-
-    pEntry = new ZipEntry;
-    if (pEntry == NULL) {
-        result = NO_MEMORY;
-        goto bail;
-    }
-
-    result = pEntry->initFromExternal(pSourceZip, pSourceEntry, storageName);
-    if (result != NO_ERROR) {
-        goto bail;
-    }
-    if (padding != 0) {
-        result = pEntry->addPadding(padding);
-        if (result != NO_ERROR)
-            goto bail;
-    }
-
-    /*
-     * From here on out, failures are more interesting.
-     */
-    mNeedCDRewrite = true;
-
-    /*
-     * Write the LFH.  Since we're not recompressing the data, we already
-     * have all of the fields filled out.
-     */
-    lfhPosn = ftell(mZipFp);
-    pEntry->mLFH.write(mZipFp);
-
-    /*
-     * Copy the data over.
-     *
-     * If the "has data descriptor" flag is set, we want to copy the DD
-     * fields as well.  This is a fixed-size area immediately following
-     * the data.
-     */
-    if (fseek(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0)
-    {
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-
-    off_t copyLen;
-    copyLen = pSourceEntry->getCompressedLen();
-    if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0)
-        copyLen += ZipEntry::kDataDescriptorLen;
-
-    if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL)
-        != NO_ERROR)
-    {
-        ALOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName);
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-
-    /*
-     * Update file offsets.
-     */
-    endPosn = ftell(mZipFp);
-
-    /*
-     * Success!  Fill out new values.
-     */
-    pEntry->setLFHOffset(lfhPosn);      // sets mCDE.mLocalHeaderRelOffset
-    mEOCD.mNumEntries++;
-    mEOCD.mTotalNumEntries++;
-    mEOCD.mCentralDirSize = 0;      // mark invalid; set by flush()
-    mEOCD.mCentralDirOffset = endPosn;
-
-    /*
-     * Add pEntry to the list.
-     */
-    mEntries.push_back(pEntry);
-    if (ppEntry != NULL)
-        *ppEntry = pEntry;
-    pEntry = NULL;
-
-    result = NO_ERROR;
-
-bail:
-    delete pEntry;
-    return result;
-}
-
-/*
- * Copy all of the bytes in "src" to "dst".
- *
- * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
- * will be seeked immediately past the data.
- */
-status_t ZipFile::copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32)
-{
-    unsigned char tmpBuf[32768];
-    size_t count;
-
-    *pCRC32 = crc32(0L, Z_NULL, 0);
-
-    while (1) {
-        count = fread(tmpBuf, 1, sizeof(tmpBuf), srcFp);
-        if (ferror(srcFp) || ferror(dstFp))
-            return errnoToStatus(errno);
-        if (count == 0)
-            break;
-
-        *pCRC32 = crc32(*pCRC32, tmpBuf, count);
-
-        if (fwrite(tmpBuf, 1, count, dstFp) != count) {
-            ALOGD("fwrite %d bytes failed\n", (int) count);
-            return UNKNOWN_ERROR;
-        }
-    }
-
-    return NO_ERROR;
-}
-
-/*
- * Copy all of the bytes in "src" to "dst".
- *
- * On exit, "dstFp" will be seeked immediately past the data.
- */
-status_t ZipFile::copyDataToFp(FILE* dstFp,
-    const void* data, size_t size, unsigned long* pCRC32)
-{
-    *pCRC32 = crc32(0L, Z_NULL, 0);
-    if (size > 0) {
-        *pCRC32 = crc32(*pCRC32, (const unsigned char*)data, size);
-        if (fwrite(data, 1, size, dstFp) != size) {
-            ALOGD("fwrite %d bytes failed\n", (int) size);
-            return UNKNOWN_ERROR;
-        }
-    }
-
-    return NO_ERROR;
-}
-
-/*
- * Copy some of the bytes in "src" to "dst".
- *
- * If "pCRC32" is NULL, the CRC will not be computed.
- *
- * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
- * will be seeked immediately past the data just written.
- */
-status_t ZipFile::copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length,
-    unsigned long* pCRC32)
-{
-    unsigned char tmpBuf[32768];
-    size_t count;
-
-    if (pCRC32 != NULL)
-        *pCRC32 = crc32(0L, Z_NULL, 0);
-
-    while (length) {
-        long readSize;
-
-        readSize = sizeof(tmpBuf);
-        if (readSize > length)
-            readSize = length;
-
-        count = fread(tmpBuf, 1, readSize, srcFp);
-        if ((long) count != readSize) {     // error or unexpected EOF
-            ALOGD("fread %d bytes failed\n", (int) readSize);
-            return UNKNOWN_ERROR;
-        }
-
-        if (pCRC32 != NULL)
-            *pCRC32 = crc32(*pCRC32, tmpBuf, count);
-
-        if (fwrite(tmpBuf, 1, count, dstFp) != count) {
-            ALOGD("fwrite %d bytes failed\n", (int) count);
-            return UNKNOWN_ERROR;
-        }
-
-        length -= readSize;
-    }
-
-    return NO_ERROR;
-}
-
-/*
- * Compress all of the data in "srcFp" and write it to "dstFp".
- *
- * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
- * will be seeked immediately past the compressed data.
- */
-status_t ZipFile::compressFpToFp(FILE* dstFp, FILE* srcFp,
-    const void* data, size_t size, unsigned long* pCRC32)
-{
-    status_t result = NO_ERROR;
-    const size_t kBufSize = 32768;
-    unsigned char* inBuf = NULL;
-    unsigned char* outBuf = NULL;
-    z_stream zstream;
-    bool atEof = false;     // no feof() aviailable yet
-    unsigned long crc;
-    int zerr;
-
-    /*
-     * Create an input buffer and an output buffer.
-     */
-    inBuf = new unsigned char[kBufSize];
-    outBuf = new unsigned char[kBufSize];
-    if (inBuf == NULL || outBuf == NULL) {
-        result = NO_MEMORY;
-        goto bail;
-    }
-
-    /*
-     * Initialize the zlib stream.
-     */
-    memset(&zstream, 0, sizeof(zstream));
-    zstream.zalloc = Z_NULL;
-    zstream.zfree = Z_NULL;
-    zstream.opaque = Z_NULL;
-    zstream.next_in = NULL;
-    zstream.avail_in = 0;
-    zstream.next_out = outBuf;
-    zstream.avail_out = kBufSize;
-    zstream.data_type = Z_UNKNOWN;
-
-    zerr = deflateInit2(&zstream, Z_BEST_COMPRESSION,
-        Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
-    if (zerr != Z_OK) {
-        result = UNKNOWN_ERROR;
-        if (zerr == Z_VERSION_ERROR) {
-            ALOGE("Installed zlib is not compatible with linked version (%s)\n",
-                ZLIB_VERSION);
-        } else {
-            ALOGD("Call to deflateInit2 failed (zerr=%d)\n", zerr);
-        }
-        goto bail;
-    }
-
-    crc = crc32(0L, Z_NULL, 0);
-
-    /*
-     * Loop while we have data.
-     */
-    do {
-        size_t getSize;
-        int flush;
-
-        /* only read if the input buffer is empty */
-        if (zstream.avail_in == 0 && !atEof) {
-            ALOGV("+++ reading %d bytes\n", (int)kBufSize);
-            if (data) {
-                getSize = size > kBufSize ? kBufSize : size;
-                memcpy(inBuf, data, getSize);
-                data = ((const char*)data) + getSize;
-                size -= getSize;
-            } else {
-                getSize = fread(inBuf, 1, kBufSize, srcFp);
-                if (ferror(srcFp)) {
-                    ALOGD("deflate read failed (errno=%d)\n", errno);
-                    goto z_bail;
-                }
-            }
-            if (getSize < kBufSize) {
-                ALOGV("+++  got %d bytes, EOF reached\n",
-                    (int)getSize);
-                atEof = true;
-            }
-
-            crc = crc32(crc, inBuf, getSize);
-
-            zstream.next_in = inBuf;
-            zstream.avail_in = getSize;
-        }
-
-        if (atEof)
-            flush = Z_FINISH;       /* tell zlib that we're done */
-        else
-            flush = Z_NO_FLUSH;     /* more to come! */
-
-        zerr = deflate(&zstream, flush);
-        if (zerr != Z_OK && zerr != Z_STREAM_END) {
-            ALOGD("zlib deflate call failed (zerr=%d)\n", zerr);
-            result = UNKNOWN_ERROR;
-            goto z_bail;
-        }
-
-        /* write when we're full or when we're done */
-        if (zstream.avail_out == 0 ||
-            (zerr == Z_STREAM_END && zstream.avail_out != (uInt) kBufSize))
-        {
-            ALOGV("+++ writing %d bytes\n", (int) (zstream.next_out - outBuf));
-            if (fwrite(outBuf, 1, zstream.next_out - outBuf, dstFp) !=
-                (size_t)(zstream.next_out - outBuf))
-            {
-                ALOGD("write %d failed in deflate\n",
-                    (int) (zstream.next_out - outBuf));
-                goto z_bail;
-            }
-
-            zstream.next_out = outBuf;
-            zstream.avail_out = kBufSize;
-        }
-    } while (zerr == Z_OK);
-
-    assert(zerr == Z_STREAM_END);       /* other errors should've been caught */
-
-    *pCRC32 = crc;
-
-z_bail:
-    deflateEnd(&zstream);        /* free up any allocated structures */
-
-bail:
-    delete[] inBuf;
-    delete[] outBuf;
-
-    return result;
-}
-
-/*
- * Mark an entry as deleted.
- *
- * We will eventually need to crunch the file down, but if several files
- * are being removed (perhaps as part of an "update" process) we can make
- * things considerably faster by deferring the removal to "flush" time.
- */
-status_t ZipFile::remove(ZipEntry* pEntry)
-{
-    /*
-     * Should verify that pEntry is actually part of this archive, and
-     * not some stray ZipEntry from a different file.
-     */
-
-    /* mark entry as deleted, and mark archive as dirty */
-    pEntry->setDeleted();
-    mNeedCDRewrite = true;
-    return NO_ERROR;
-}
-
-/*
- * Flush any pending writes.
- *
- * In particular, this will crunch out deleted entries, and write the
- * Central Directory and EOCD if we have stomped on them.
- */
-status_t ZipFile::flush(void)
-{
-    status_t result = NO_ERROR;
-    long eocdPosn;
-    int i, count;
-
-    if (mReadOnly)
-        return INVALID_OPERATION;
-    if (!mNeedCDRewrite)
-        return NO_ERROR;
-
-    assert(mZipFp != NULL);
-
-    result = crunchArchive();
-    if (result != NO_ERROR)
-        return result;
-
-    if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0)
-        return UNKNOWN_ERROR;
-
-    count = mEntries.size();
-    for (i = 0; i < count; i++) {
-        ZipEntry* pEntry = mEntries[i];
-        pEntry->mCDE.write(mZipFp);
-    }
-
-    eocdPosn = ftell(mZipFp);
-    mEOCD.mCentralDirSize = eocdPosn - mEOCD.mCentralDirOffset;
-
-    mEOCD.write(mZipFp);
-
-    /*
-     * If we had some stuff bloat up during compression and get replaced
-     * with plain files, or if we deleted some entries, there's a lot
-     * of wasted space at the end of the file.  Remove it now.
-     */
-    if (ftruncate(fileno(mZipFp), ftell(mZipFp)) != 0) {
-        ALOGW("ftruncate failed %ld: %s\n", ftell(mZipFp), strerror(errno));
-        // not fatal
-    }
-
-    /* should we clear the "newly added" flag in all entries now? */
-
-    mNeedCDRewrite = false;
-    return NO_ERROR;
-}
-
-/*
- * Crunch deleted files out of an archive by shifting the later files down.
- *
- * Because we're not using a temp file, we do the operation inside the
- * current file.
- */
-status_t ZipFile::crunchArchive(void)
-{
-    status_t result = NO_ERROR;
-    int i, count;
-    long delCount, adjust;
-
-#if 0
-    printf("CONTENTS:\n");
-    for (i = 0; i < (int) mEntries.size(); i++) {
-        printf(" %d: lfhOff=%ld del=%d\n",
-            i, mEntries[i]->getLFHOffset(), mEntries[i]->getDeleted());
-    }
-    printf("  END is %ld\n", (long) mEOCD.mCentralDirOffset);
-#endif
-
-    /*
-     * Roll through the set of files, shifting them as appropriate.  We
-     * could probably get a slight performance improvement by sliding
-     * multiple files down at once (because we could use larger reads
-     * when operating on batches of small files), but it's not that useful.
-     */
-    count = mEntries.size();
-    delCount = adjust = 0;
-    for (i = 0; i < count; i++) {
-        ZipEntry* pEntry = mEntries[i];
-        long span;
-
-        if (pEntry->getLFHOffset() != 0) {
-            long nextOffset;
-
-            /* Get the length of this entry by finding the offset
-             * of the next entry.  Directory entries don't have
-             * file offsets, so we need to find the next non-directory
-             * entry.
-             */
-            nextOffset = 0;
-            for (int ii = i+1; nextOffset == 0 && ii < count; ii++)
-                nextOffset = mEntries[ii]->getLFHOffset();
-            if (nextOffset == 0)
-                nextOffset = mEOCD.mCentralDirOffset;
-            span = nextOffset - pEntry->getLFHOffset();
-
-            assert(span >= ZipEntry::LocalFileHeader::kLFHLen);
-        } else {
-            /* This is a directory entry.  It doesn't have
-             * any actual file contents, so there's no need to
-             * move anything.
-             */
-            span = 0;
-        }
-
-        //printf("+++ %d: off=%ld span=%ld del=%d [count=%d]\n",
-        //    i, pEntry->getLFHOffset(), span, pEntry->getDeleted(), count);
-
-        if (pEntry->getDeleted()) {
-            adjust += span;
-            delCount++;
-
-            delete pEntry;
-            mEntries.erase(mEntries.begin() + i);
-
-            /* adjust loop control */
-            count--;
-            i--;
-        } else if (span != 0 && adjust > 0) {
-            /* shuffle this entry back */
-            //printf("+++ Shuffling '%s' back %ld\n",
-            //    pEntry->getFileName(), adjust);
-            result = filemove(mZipFp, pEntry->getLFHOffset() - adjust,
-                        pEntry->getLFHOffset(), span);
-            if (result != NO_ERROR) {
-                /* this is why you use a temp file */
-                ALOGE("error during crunch - archive is toast\n");
-                return result;
-            }
-
-            pEntry->setLFHOffset(pEntry->getLFHOffset() - adjust);
-        }
-    }
-
-    /*
-     * Fix EOCD info.  We have to wait until the end to do some of this
-     * because we use mCentralDirOffset to determine "span" for the
-     * last entry.
-     */
-    mEOCD.mCentralDirOffset -= adjust;
-    mEOCD.mNumEntries -= delCount;
-    mEOCD.mTotalNumEntries -= delCount;
-    mEOCD.mCentralDirSize = 0;  // mark invalid; set by flush()
-
-    assert(mEOCD.mNumEntries == mEOCD.mTotalNumEntries);
-    assert(mEOCD.mNumEntries == count);
-
-    return result;
-}
-
-/*
- * Works like memmove(), but on pieces of a file.
- */
-status_t ZipFile::filemove(FILE* fp, off_t dst, off_t src, size_t n)
-{
-    if (dst == src || n <= 0)
-        return NO_ERROR;
-
-    unsigned char readBuf[32768];
-
-    if (dst < src) {
-        /* shift stuff toward start of file; must read from start */
-        while (n != 0) {
-            size_t getSize = sizeof(readBuf);
-            if (getSize > n)
-                getSize = n;
-
-            if (fseek(fp, (long) src, SEEK_SET) != 0) {
-                ALOGD("filemove src seek %ld failed\n", (long) src);
-                return UNKNOWN_ERROR;
-            }
-
-            if (fread(readBuf, 1, getSize, fp) != getSize) {
-                ALOGD("filemove read %ld off=%ld failed\n",
-                    (long) getSize, (long) src);
-                return UNKNOWN_ERROR;
-            }
-
-            if (fseek(fp, (long) dst, SEEK_SET) != 0) {
-                ALOGD("filemove dst seek %ld failed\n", (long) dst);
-                return UNKNOWN_ERROR;
-            }
-
-            if (fwrite(readBuf, 1, getSize, fp) != getSize) {
-                ALOGD("filemove write %ld off=%ld failed\n",
-                    (long) getSize, (long) dst);
-                return UNKNOWN_ERROR;
-            }
-
-            src += getSize;
-            dst += getSize;
-            n -= getSize;
-        }
-    } else {
-        /* shift stuff toward end of file; must read from end */
-        assert(false);      // write this someday, maybe
-        return UNKNOWN_ERROR;
-    }
-
-    return NO_ERROR;
-}
-
-
-/*
- * Get the modification time from a file descriptor.
- */
-time_t ZipFile::getModTime(int fd)
-{
-    struct stat sb;
-
-    if (fstat(fd, &sb) < 0) {
-        ALOGD("HEY: fstat on fd %d failed\n", fd);
-        return (time_t) -1;
-    }
-
-    return sb.st_mtime;
-}
-
-
-#if 0       /* this is a bad idea */
-/*
- * Get a copy of the Zip file descriptor.
- *
- * We don't allow this if the file was opened read-write because we tend
- * to leave the file contents in an uncertain state between calls to
- * flush().  The duplicated file descriptor should only be valid for reads.
- */
-int ZipFile::getZipFd(void) const
-{
-    if (!mReadOnly)
-        return INVALID_OPERATION;
-    assert(mZipFp != NULL);
-
-    int fd;
-    fd = dup(fileno(mZipFp));
-    if (fd < 0) {
-        ALOGD("didn't work, errno=%d\n", errno);
-    }
-
-    return fd;
-}
-#endif
-
-
-#if 0
-/*
- * Expand data.
- */
-bool ZipFile::uncompress(const ZipEntry* pEntry, void* buf) const
-{
-    return false;
-}
-#endif
-
-// free the memory when you're done
-void* ZipFile::uncompress(const ZipEntry* entry)
-{
-    size_t unlen = entry->getUncompressedLen();
-    size_t clen = entry->getCompressedLen();
-
-    void* buf = malloc(unlen);
-    if (buf == NULL) {
-        return NULL;
-    }
-
-    fseek(mZipFp, 0, SEEK_SET);
-
-    off_t offset = entry->getFileOffset();
-    if (fseek(mZipFp, offset, SEEK_SET) != 0) {
-        goto bail;
-    }
-
-    switch (entry->getCompressionMethod())
-    {
-        case ZipEntry::kCompressStored: {
-            ssize_t amt = fread(buf, 1, unlen, mZipFp);
-            if (amt != (ssize_t)unlen) {
-                goto bail;
-            }
-#if 0
-            printf("data...\n");
-            const unsigned char* p = (unsigned char*)buf;
-            const unsigned char* end = p+unlen;
-            for (int i=0; i<32 && p < end; i++) {
-                printf("0x%08x ", (int)(offset+(i*0x10)));
-                for (int j=0; j<0x10 && p < end; j++) {
-                    printf(" %02x", *p);
-                    p++;
-                }
-                printf("\n");
-            }
-#endif
-
-            }
-            break;
-        case ZipEntry::kCompressDeflated: {
-            if (!ZipUtils::inflateToBuffer(mZipFp, buf, unlen, clen)) {
-                goto bail;
-            }
-            }
-            break;
-        default:
-            goto bail;
-    }
-    return buf;
-
-bail:
-    free(buf);
-    return NULL;
-}
-
-
-/*
- * ===========================================================================
- *      ZipFile::EndOfCentralDir
- * ===========================================================================
- */
-
-/*
- * Read the end-of-central-dir fields.
- *
- * "buf" should be positioned at the EOCD signature, and should contain
- * the entire EOCD area including the comment.
- */
-status_t ZipFile::EndOfCentralDir::readBuf(const unsigned char* buf, int len)
-{
-    /* don't allow re-use */
-    assert(mComment == NULL);
-
-    if (len < kEOCDLen) {
-        /* looks like ZIP file got truncated */
-        ALOGD(" Zip EOCD: expected >= %d bytes, found %d\n",
-            kEOCDLen, len);
-        return INVALID_OPERATION;
-    }
-
-    /* this should probably be an assert() */
-    if (ZipEntry::getLongLE(&buf[0x00]) != kSignature)
-        return UNKNOWN_ERROR;
-
-    mDiskNumber = ZipEntry::getShortLE(&buf[0x04]);
-    mDiskWithCentralDir = ZipEntry::getShortLE(&buf[0x06]);
-    mNumEntries = ZipEntry::getShortLE(&buf[0x08]);
-    mTotalNumEntries = ZipEntry::getShortLE(&buf[0x0a]);
-    mCentralDirSize = ZipEntry::getLongLE(&buf[0x0c]);
-    mCentralDirOffset = ZipEntry::getLongLE(&buf[0x10]);
-    mCommentLen = ZipEntry::getShortLE(&buf[0x14]);
-
-    // TODO: validate mCentralDirOffset
-
-    if (mCommentLen > 0) {
-        if (kEOCDLen + mCommentLen > len) {
-            ALOGD("EOCD(%d) + comment(%d) exceeds len (%d)\n",
-                kEOCDLen, mCommentLen, len);
-            return UNKNOWN_ERROR;
-        }
-        mComment = new unsigned char[mCommentLen];
-        memcpy(mComment, buf + kEOCDLen, mCommentLen);
-    }
-
-    return NO_ERROR;
-}
-
-/*
- * Write an end-of-central-directory section.
- */
-status_t ZipFile::EndOfCentralDir::write(FILE* fp)
-{
-    unsigned char buf[kEOCDLen];
-
-    ZipEntry::putLongLE(&buf[0x00], kSignature);
-    ZipEntry::putShortLE(&buf[0x04], mDiskNumber);
-    ZipEntry::putShortLE(&buf[0x06], mDiskWithCentralDir);
-    ZipEntry::putShortLE(&buf[0x08], mNumEntries);
-    ZipEntry::putShortLE(&buf[0x0a], mTotalNumEntries);
-    ZipEntry::putLongLE(&buf[0x0c], mCentralDirSize);
-    ZipEntry::putLongLE(&buf[0x10], mCentralDirOffset);
-    ZipEntry::putShortLE(&buf[0x14], mCommentLen);
-
-    if (fwrite(buf, 1, kEOCDLen, fp) != kEOCDLen)
-        return UNKNOWN_ERROR;
-    if (mCommentLen > 0) {
-        assert(mComment != NULL);
-        if (fwrite(mComment, mCommentLen, 1, fp) != mCommentLen)
-            return UNKNOWN_ERROR;
-    }
-
-    return NO_ERROR;
-}
-
-/*
- * Dump the contents of an EndOfCentralDir object.
- */
-void ZipFile::EndOfCentralDir::dump(void) const
-{
-    ALOGD(" EndOfCentralDir contents:\n");
-    ALOGD("  diskNum=%u diskWCD=%u numEnt=%u totalNumEnt=%u\n",
-        mDiskNumber, mDiskWithCentralDir, mNumEntries, mTotalNumEntries);
-    ALOGD("  centDirSize=%lu centDirOff=%lu commentLen=%u\n",
-        mCentralDirSize, mCentralDirOffset, mCommentLen);
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ZipFile.h b/tools/aapt2/ZipFile.h
deleted file mode 100644
index 9de92dd..0000000
--- a/tools/aapt2/ZipFile.h
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * Copyright (C) 2006 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.
- */
-
-//
-// General-purpose Zip archive access.  This class allows both reading and
-// writing to Zip archives, including deletion of existing entries.
-//
-#ifndef __LIBS_ZIPFILE_H
-#define __LIBS_ZIPFILE_H
-
-#include "BigBuffer.h"
-#include "ZipEntry.h"
-
-#include <stdio.h>
-#include <utils/Errors.h>
-#include <vector>
-
-namespace aapt {
-
-using android::status_t;
-
-/*
- * Manipulate a Zip archive.
- *
- * Some changes will not be visible in the until until "flush" is called.
- *
- * The correct way to update a file archive is to make all changes to a
- * copy of the archive in a temporary file, and then unlink/rename over
- * the original after everything completes.  Because we're only interested
- * in using this for packaging, we don't worry about such things.  Crashing
- * after making changes and before flush() completes could leave us with
- * an unusable Zip archive.
- */
-class ZipFile {
-public:
-    ZipFile(void)
-      : mZipFp(NULL), mReadOnly(false), mNeedCDRewrite(false)
-      {}
-    ~ZipFile(void) {
-        if (!mReadOnly)
-            flush();
-        if (mZipFp != NULL)
-            fclose(mZipFp);
-        discardEntries();
-    }
-
-    /*
-     * Open a new or existing archive.
-     */
-    enum {
-        kOpenReadOnly   = 0x01,
-        kOpenReadWrite  = 0x02,
-        kOpenCreate     = 0x04,     // create if it doesn't exist
-        kOpenTruncate   = 0x08,     // if it exists, empty it
-    };
-    status_t open(const char* zipFileName, int flags);
-
-    /*
-     * Add a file to the end of the archive.  Specify whether you want the
-     * library to try to store it compressed.
-     *
-     * If "storageName" is specified, the archive will use that instead
-     * of "fileName".
-     *
-     * If there is already an entry with the same name, the call fails.
-     * Existing entries with the same name must be removed first.
-     *
-     * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
-     */
-    status_t add(const char* fileName, int compressionMethod,
-        ZipEntry** ppEntry)
-    {
-        return add(fileName, fileName, compressionMethod, ppEntry);
-    }
-    status_t add(const char* fileName, const char* storageName,
-        int compressionMethod, ZipEntry** ppEntry)
-    {
-        return addCommon(fileName, NULL, 0, storageName,
-                         ZipEntry::kCompressStored,
-                         compressionMethod, ppEntry);
-    }
-
-    /*
-     * Add a file that is already compressed with gzip.
-     *
-     * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
-     */
-    status_t addGzip(const char* fileName, const char* storageName,
-        ZipEntry** ppEntry)
-    {
-        return addCommon(fileName, NULL, 0, storageName,
-                         ZipEntry::kCompressDeflated,
-                         ZipEntry::kCompressDeflated, ppEntry);
-    }
-
-    /*
-     * Add a file from an in-memory data buffer.
-     *
-     * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
-     */
-    status_t add(const void* data, size_t size, const char* storageName,
-        int compressionMethod, ZipEntry** ppEntry)
-    {
-        return addCommon(NULL, data, size, storageName,
-                         ZipEntry::kCompressStored,
-                         compressionMethod, ppEntry);
-    }
-
-    status_t add(const BigBuffer& data, const char* storageName,
-        int compressionMethod, ZipEntry** ppEntry);
-
-    /*
-     * Add an entry by copying it from another zip file.  If storageName is
-     * non-NULL, the entry will be inserted with the name storageName, otherwise
-     * it will have the same name as the source entry.  If "padding" is
-     * nonzero, the specified number of bytes will be added to the "extra"
-     * field in the header.
-     *
-     * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
-     */
-    status_t add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
-                 const char* storageName, int padding, ZipEntry** ppEntry);
-
-    /*
-     * Mark an entry as having been removed.  It is not actually deleted
-     * from the archive or our internal data structures until flush() is
-     * called.
-     */
-    status_t remove(ZipEntry* pEntry);
-
-    /*
-     * Flush changes.  If mNeedCDRewrite is set, this writes the central dir.
-     */
-    status_t flush(void);
-
-    /*
-     * Expand the data into the buffer provided.  The buffer must hold
-     * at least <uncompressed len> bytes.  Variation expands directly
-     * to a file.
-     *
-     * Returns "false" if an error was encountered in the compressed data.
-     */
-    //bool uncompress(const ZipEntry* pEntry, void* buf) const;
-    //bool uncompress(const ZipEntry* pEntry, FILE* fp) const;
-    void* uncompress(const ZipEntry* pEntry);
-
-    /*
-     * Get an entry, by name.  Returns NULL if not found.
-     *
-     * Does not return entries pending deletion.
-     */
-    ZipEntry* getEntryByName(const char* fileName) const;
-
-    /*
-     * Get the Nth entry in the archive.
-     *
-     * This will return an entry that is pending deletion.
-     */
-    int getNumEntries(void) const { return mEntries.size(); }
-    ZipEntry* getEntryByIndex(int idx) const;
-
-private:
-    /* these are private and not defined */
-    ZipFile(const ZipFile& src);
-    ZipFile& operator=(const ZipFile& src);
-
-    class EndOfCentralDir {
-    public:
-        EndOfCentralDir(void) :
-            mDiskNumber(0),
-            mDiskWithCentralDir(0),
-            mNumEntries(0),
-            mTotalNumEntries(0),
-            mCentralDirSize(0),
-            mCentralDirOffset(0),
-            mCommentLen(0),
-            mComment(NULL)
-            {}
-        virtual ~EndOfCentralDir(void) {
-            delete[] mComment;
-        }
-
-        status_t readBuf(const unsigned char* buf, int len);
-        status_t write(FILE* fp);
-
-        //unsigned long   mSignature;
-        unsigned short  mDiskNumber;
-        unsigned short  mDiskWithCentralDir;
-        unsigned short  mNumEntries;
-        unsigned short  mTotalNumEntries;
-        unsigned long   mCentralDirSize;
-        unsigned long   mCentralDirOffset;      // offset from first disk
-        unsigned short  mCommentLen;
-        unsigned char*  mComment;
-
-        enum {
-            kSignature      = 0x06054b50,
-            kEOCDLen        = 22,       // EndOfCentralDir len, excl. comment
-
-            kMaxCommentLen  = 65535,    // longest possible in ushort
-            kMaxEOCDSearch  = kMaxCommentLen + EndOfCentralDir::kEOCDLen,
-
-        };
-
-        void dump(void) const;
-    };
-
-
-    /* read all entries in the central dir */
-    status_t readCentralDir(void);
-
-    /* crunch deleted entries out */
-    status_t crunchArchive(void);
-
-    /* clean up mEntries */
-    void discardEntries(void);
-
-    /* common handler for all "add" functions */
-    status_t addCommon(const char* fileName, const void* data, size_t size,
-        const char* storageName, int sourceType, int compressionMethod,
-        ZipEntry** ppEntry);
-
-    /* copy all of "srcFp" into "dstFp" */
-    status_t copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32);
-    /* copy all of "data" into "dstFp" */
-    status_t copyDataToFp(FILE* dstFp,
-        const void* data, size_t size, unsigned long* pCRC32);
-    /* copy some of "srcFp" into "dstFp" */
-    status_t copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length,
-        unsigned long* pCRC32);
-    /* like memmove(), but on parts of a single file */
-    status_t filemove(FILE* fp, off_t dest, off_t src, size_t n);
-    /* compress all of "srcFp" into "dstFp", using Deflate */
-    status_t compressFpToFp(FILE* dstFp, FILE* srcFp,
-        const void* data, size_t size, unsigned long* pCRC32);
-
-    /* get modification date from a file descriptor */
-    time_t getModTime(int fd);
-
-    /*
-     * We use stdio FILE*, which gives us buffering but makes dealing
-     * with files >2GB awkward.  Until we support Zip64, we're fine.
-     */
-    FILE*           mZipFp;             // Zip file pointer
-
-    /* one of these per file */
-    EndOfCentralDir mEOCD;
-
-    /* did we open this read-only? */
-    bool            mReadOnly;
-
-    /* set this when we trash the central dir */
-    bool            mNeedCDRewrite;
-
-    /*
-     * One ZipEntry per entry in the zip file.  I'm using pointers instead
-     * of objects because it's easier than making operator= work for the
-     * classes and sub-classes.
-     */
-    std::vector<ZipEntry*>   mEntries;
-};
-
-}; // namespace aapt
-
-#endif // __LIBS_ZIPFILE_H
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
new file mode 100644
index 0000000..498bc9c
--- /dev/null
+++ b/tools/aapt2/compile/Compile.cpp
@@ -0,0 +1,419 @@
+/*
+ * 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 "ConfigDescription.h"
+#include "Diagnostics.h"
+#include "Flags.h"
+#include "ResourceParser.h"
+#include "ResourceTable.h"
+#include "XmlDom.h"
+#include "XmlPullParser.h"
+
+#include "compile/IdAssigner.h"
+#include "compile/Png.h"
+#include "compile/XmlIdCollector.h"
+#include "flatten/FileExportWriter.h"
+#include "flatten/TableFlattener.h"
+#include "flatten/XmlFlattener.h"
+#include "util/Files.h"
+#include "util/Maybe.h"
+#include "util/Util.h"
+
+#include <fstream>
+#include <string>
+
+namespace aapt {
+
+struct ResourcePathData {
+    Source source;
+    std::u16string resourceDir;
+    std::u16string name;
+    std::string extension;
+
+    // Original config str. We keep this because when we parse the config, we may add on
+    // version qualifiers. We want to preserve the original input so the output is easily
+    // computed before hand.
+    std::string configStr;
+    ConfigDescription config;
+};
+
+/**
+ * Resource file paths are expected to look like:
+ * [--/res/]type[-config]/name
+ */
+static Maybe<ResourcePathData> extractResourcePathData(const std::string& path,
+                                                       std::string* outError) {
+    std::vector<std::string> parts = util::split(path, file::sDirSep);
+    if (parts.size() < 2) {
+        if (outError) *outError = "bad resource path";
+        return {};
+    }
+
+    std::string& dir = parts[parts.size() - 2];
+    StringPiece dirStr = dir;
+
+    StringPiece configStr;
+    ConfigDescription config;
+    size_t dashPos = dir.find('-');
+    if (dashPos != std::string::npos) {
+        configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
+        if (!ConfigDescription::parse(configStr, &config)) {
+            if (outError) {
+                std::stringstream errStr;
+                errStr << "invalid configuration '" << configStr << "'";
+                *outError = errStr.str();
+            }
+            return {};
+        }
+        dirStr = dirStr.substr(0, dashPos);
+    }
+
+    std::string& filename = parts[parts.size() - 1];
+    StringPiece name = filename;
+    StringPiece extension;
+    size_t dotPos = filename.find('.');
+    if (dotPos != std::string::npos) {
+        extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
+        name = name.substr(0, dotPos);
+    }
+
+    return ResourcePathData{
+            Source{ path },
+            util::utf8ToUtf16(dirStr),
+            util::utf8ToUtf16(name),
+            extension.toString(),
+            configStr.toString(),
+            config
+    };
+}
+
+struct CompileOptions {
+    std::string outputPath;
+    bool verbose = false;
+};
+
+static std::string buildIntermediateFilename(const std::string outDir,
+                                             const ResourcePathData& data) {
+    std::stringstream name;
+    name << data.resourceDir;
+    if (!data.configStr.empty()) {
+        name << "-" << data.configStr;
+    }
+    name << "_" << data.name << "." << data.extension << ".flat";
+    std::string outPath = outDir;
+    file::appendPath(&outPath, name.str());
+    return outPath;
+}
+
+static bool compileTable(IAaptContext* context, const CompileOptions& options,
+                         const ResourcePathData& pathData, const std::string& outputPath) {
+    ResourceTable table;
+    table.createPackage(u"", 0x7f);
+
+    {
+        std::ifstream fin(pathData.source.path, std::ifstream::binary);
+        if (!fin) {
+            context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
+            return false;
+        }
+
+
+        // Parse the values file from XML.
+        XmlPullParser xmlParser(fin);
+        ResourceParser resParser(context->getDiagnostics(), &table, pathData.source,
+                                 pathData.config);
+        if (!resParser.parse(&xmlParser)) {
+            return false;
+        }
+
+        fin.close();
+    }
+
+    // Assign IDs to prepare the table for flattening.
+    IdAssigner idAssigner;
+    if (!idAssigner.consume(context, &table)) {
+        return false;
+    }
+
+    // Flatten the table.
+    BigBuffer buffer(1024);
+    TableFlattenerOptions tableFlattenerOptions;
+    tableFlattenerOptions.useExtendedChunks = true;
+    TableFlattener flattener(&buffer, tableFlattenerOptions);
+    if (!flattener.consume(context, &table)) {
+        return false;
+    }
+
+    // Build the output filename.
+    std::ofstream fout(outputPath, std::ofstream::binary);
+    if (!fout) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+
+    // Write it to disk.
+    if (!util::writeAll(fout, buffer)) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+    return true;
+}
+
+static bool compileXml(IAaptContext* context, const CompileOptions& options,
+                       const ResourcePathData& pathData, const std::string& outputPath) {
+
+    std::unique_ptr<XmlResource> xmlRes;
+
+    {
+        std::ifstream fin(pathData.source.path, std::ifstream::binary);
+        if (!fin) {
+            context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
+            return false;
+        }
+
+        xmlRes = xml::inflate(&fin, context->getDiagnostics(), pathData.source);
+
+        fin.close();
+    }
+
+    if (!xmlRes) {
+        return false;
+    }
+
+    // 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);
+    ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &xmlRes->file);
+
+    XmlFlattenerOptions xmlFlattenerOptions;
+    xmlFlattenerOptions.keepRawValues = true;
+    XmlFlattener flattener(fileExportWriter.getBuffer(), xmlFlattenerOptions);
+    if (!flattener.consume(context, xmlRes.get())) {
+        return false;
+    }
+
+    fileExportWriter.finish();
+
+    std::ofstream fout(outputPath, std::ofstream::binary);
+    if (!fout) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+
+    // Write it to disk.
+    if (!util::writeAll(fout, buffer)) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+    return true;
+}
+
+static bool compilePng(IAaptContext* context, const CompileOptions& options,
+                       const ResourcePathData& pathData, const std::string& outputPath) {
+    BigBuffer buffer(4096);
+    ResourceFile resFile;
+    resFile.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
+    resFile.config = pathData.config;
+    resFile.source = pathData.source;
+
+    ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &resFile);
+
+    {
+        std::ifstream fin(pathData.source.path, std::ifstream::binary);
+        if (!fin) {
+            context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
+            return false;
+        }
+
+        Png png(context->getDiagnostics());
+        if (!png.process(pathData.source, &fin, fileExportWriter.getBuffer(), {})) {
+            return false;
+        }
+    }
+
+    fileExportWriter.finish();
+
+    std::ofstream fout(outputPath, std::ofstream::binary);
+    if (!fout) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+
+    if (!util::writeAll(fout, buffer)) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+    return true;
+}
+
+static bool compileFile(IAaptContext* context, const CompileOptions& options,
+                        const ResourcePathData& pathData, const std::string& outputPath) {
+    BigBuffer buffer(256);
+    ResourceFile resFile;
+    resFile.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
+    resFile.config = pathData.config;
+    resFile.source = pathData.source;
+
+    ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &resFile);
+
+    std::string errorStr;
+    Maybe<android::FileMap> f = file::mmapPath(pathData.source.path, &errorStr);
+    if (!f) {
+        context->getDiagnostics()->error(DiagMessage(pathData.source) << errorStr);
+        return false;
+    }
+
+    std::ofstream fout(outputPath, std::ofstream::binary);
+    if (!fout) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+
+    // Manually set the size and don't call finish(). This is because we are not copying from
+    // the buffer the entire file.
+    fileExportWriter.getChunkHeader()->size =
+            util::hostToDevice32(buffer.size() + f.value().getDataLength());
+    if (!util::writeAll(fout, buffer)) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+
+    if (!fout.write((const char*) f.value().getDataPtr(), f.value().getDataLength())) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+    return true;
+}
+
+class CompileContext : public IAaptContext {
+private:
+    StdErrDiagnostics mDiagnostics;
+
+public:
+    IDiagnostics* getDiagnostics() override {
+       return &mDiagnostics;
+    }
+
+    NameMangler* getNameMangler() override {
+       abort();
+       return nullptr;
+    }
+
+    StringPiece16 getCompilationPackage() override {
+       return {};
+    }
+
+    uint8_t getPackageId() override {
+       return 0x7f;
+    }
+
+    ISymbolTable* getExternalSymbols() override {
+       abort();
+       return nullptr;
+    }
+};
+
+/**
+ * Entry point for compilation phase. Parses arguments and dispatches to the correct steps.
+ */
+int compile(const std::vector<StringPiece>& args) {
+    CompileOptions options;
+
+    Flags flags = Flags()
+            .requiredFlag("-o", "Output path", &options.outputPath)
+            .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
+    if (!flags.parse("aapt2 compile", args, &std::cerr)) {
+        return 1;
+    }
+
+    CompileContext context;
+
+    std::vector<ResourcePathData> inputData;
+    inputData.reserve(flags.getArgs().size());
+
+    // Collect data from the path for each input file.
+    for (const std::string& arg : flags.getArgs()) {
+        std::string errorStr;
+        if (Maybe<ResourcePathData> pathData = extractResourcePathData(arg, &errorStr)) {
+            inputData.push_back(std::move(pathData.value()));
+        } else {
+            context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg << ")");
+            return 1;
+        }
+    }
+
+    bool error = false;
+    for (ResourcePathData& pathData : inputData) {
+        if (options.verbose) {
+            context.getDiagnostics()->note(DiagMessage(pathData.source) << "processing");
+        }
+
+        if (pathData.resourceDir == u"values") {
+            // Overwrite the extension.
+            pathData.extension = "arsc";
+
+            const std::string outputFilename = buildIntermediateFilename(
+                    options.outputPath, pathData);
+            if (!compileTable(&context, options, pathData, outputFilename)) {
+                error = true;
+            }
+
+        } else {
+            const std::string outputFilename = buildIntermediateFilename(options.outputPath,
+                                                                         pathData);
+            if (const ResourceType* type = parseResourceType(pathData.resourceDir)) {
+                if (*type != ResourceType::kRaw) {
+                    if (pathData.extension == "xml") {
+                        if (!compileXml(&context, options, pathData, outputFilename)) {
+                            error = true;
+                        }
+                    } else if (pathData.extension == "png" || pathData.extension == "9.png") {
+                        if (!compilePng(&context, options, pathData, outputFilename)) {
+                            error = true;
+                        }
+                    } else {
+                        if (!compileFile(&context, options, pathData, outputFilename)) {
+                            error = true;
+                        }
+                    }
+                } else {
+                    if (!compileFile(&context, options, pathData, outputFilename)) {
+                        error = true;
+                    }
+                }
+            } else {
+                context.getDiagnostics()->error(
+                        DiagMessage() << "invalid file path '" << pathData.source << "'");
+                error = true;
+            }
+        }
+    }
+
+    if (error) {
+        return 1;
+    }
+    return 0;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp
new file mode 100644
index 0000000..80c6bbc
--- /dev/null
+++ b/tools/aapt2/compile/IdAssigner.cpp
@@ -0,0 +1,112 @@
+/*
+ * 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 "ResourceTable.h"
+
+#include "compile/IdAssigner.h"
+#include "process/IResourceTableConsumer.h"
+#include "util/Util.h"
+
+#include <bitset>
+#include <cassert>
+#include <set>
+
+namespace aapt {
+
+bool IdAssigner::consume(IAaptContext* context, ResourceTable* table) {
+    std::bitset<256> usedTypeIds;
+    std::set<uint16_t> usedEntryIds;
+
+    for (auto& package : table->packages) {
+        assert(package->id && "packages must have manually assigned IDs");
+
+        usedTypeIds.reset();
+
+        // Type ID 0 is invalid, reserve it.
+        usedTypeIds.set(0);
+
+        // Collect used type IDs.
+        for (auto& type : package->types) {
+            if (type->id) {
+                usedEntryIds.clear();
+
+                if (usedTypeIds[type->id.value()]) {
+                    // This ID is already taken!
+                    context->getDiagnostics()->error(DiagMessage()
+                                                     << "type '" << type->type << "' in "
+                                                     << "package '" << package->name << "' has "
+                                                     << "duplicate ID "
+                                                     << std::hex << (int) type->id.value()
+                                                     << std::dec);
+                    return false;
+                }
+
+                // Mark the type ID as taken.
+                usedTypeIds.set(type->id.value());
+            }
+
+            // Collect used entry IDs.
+            for (auto& entry : type->entries) {
+                if (entry->id) {
+                    // Mark entry ID as taken.
+                    if (!usedEntryIds.insert(entry->id.value()).second) {
+                        // This ID existed before!
+                        ResourceNameRef nameRef =
+                                { package->name, type->type, entry->name };
+                        ResourceId takenId(package->id.value(), type->id.value(),
+                                           entry->id.value());
+                        context->getDiagnostics()->error(DiagMessage()
+                                                         << "resource '" << nameRef << "' "
+                                                         << "has duplicate ID '"
+                                                         << takenId << "'");
+                        return false;
+                    }
+                }
+            }
+
+            // Assign unused entry IDs.
+            const auto endUsedEntryIter = usedEntryIds.end();
+            auto nextUsedEntryIter = usedEntryIds.begin();
+            uint16_t nextId = 0;
+            for (auto& entry : type->entries) {
+                if (!entry->id) {
+                    // Assign the next available entryID.
+                    while (nextUsedEntryIter != endUsedEntryIter &&
+                            nextId == *nextUsedEntryIter) {
+                        nextId++;
+                        ++nextUsedEntryIter;
+                    }
+                    entry->id = nextId++;
+                }
+            }
+        }
+
+        // Assign unused type IDs.
+        size_t nextTypeId = 0;
+        for (auto& type : package->types) {
+            if (!type->id) {
+                while (nextTypeId < usedTypeIds.size() && usedTypeIds[nextTypeId]) {
+                    nextTypeId++;
+                }
+                type->id = static_cast<uint8_t>(nextTypeId);
+                nextTypeId++;
+            }
+        }
+    }
+    return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Compat_test.cpp b/tools/aapt2/compile/IdAssigner.h
similarity index 61%
copy from tools/aapt2/Compat_test.cpp
copy to tools/aapt2/compile/IdAssigner.h
index 96aee44..514df3a 100644
--- a/tools/aapt2/Compat_test.cpp
+++ b/tools/aapt2/compile/IdAssigner.h
@@ -14,20 +14,21 @@
  * limitations under the License.
  */
 
-#include <gtest/gtest.h>
+#ifndef AAPT_COMPILE_IDASSIGNER_H
+#define AAPT_COMPILE_IDASSIGNER_H
+
+#include "process/IResourceTableConsumer.h"
 
 namespace aapt {
 
-TEST(CompatTest, VersionAttributesInStyle) {
-}
-
-TEST(CompatTest, VersionAttributesInXML) {
-}
-
-TEST(CompatTest, DoNotOverrideExistingVersionedFiles) {
-}
-
-TEST(CompatTest, VersionAttributesInStyleWithCorrectPrecedence) {
-}
+/**
+ * Assigns IDs to each resource in the table, respecting existing IDs and filling in gaps
+ * in between fixed ID assignments.
+ */
+struct IdAssigner : public IResourceTableConsumer {
+    bool consume(IAaptContext* context, ResourceTable* table) override;
+};
 
 } // namespace aapt
+
+#endif /* AAPT_COMPILE_IDASSIGNER_H */
diff --git a/tools/aapt2/compile/IdAssigner_test.cpp b/tools/aapt2/compile/IdAssigner_test.cpp
new file mode 100644
index 0000000..e25a17a
--- /dev/null
+++ b/tools/aapt2/compile/IdAssigner_test.cpp
@@ -0,0 +1,123 @@
+/*
+ * 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 "compile/IdAssigner.h"
+
+#include "test/Context.h"
+#include "test/Builders.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+::testing::AssertionResult verifyIds(ResourceTable* table);
+
+TEST(IdAssignerTest, AssignIds) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .addSimple(u"@android:attr/foo")
+            .addSimple(u"@android:attr/bar")
+            .addSimple(u"@android:id/foo")
+            .setPackageId(u"android", 0x01)
+            .build();
+
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+    IdAssigner assigner;
+
+    ASSERT_TRUE(assigner.consume(context.get(), table.get()));
+    ASSERT_TRUE(verifyIds(table.get()));
+}
+
+TEST(IdAssignerTest, AssignIdsWithReservedIds) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .addSimple(u"@android:attr/foo", ResourceId(0x01040006))
+            .addSimple(u"@android:attr/bar")
+            .addSimple(u"@android:id/foo")
+            .addSimple(u"@app:id/biz")
+            .setPackageId(u"android", 0x01)
+            .setPackageId(u"app", 0x7f)
+            .build();
+
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+    IdAssigner assigner;
+
+    ASSERT_TRUE(assigner.consume(context.get(), table.get()));
+    ASSERT_TRUE(verifyIds(table.get()));
+}
+
+TEST(IdAssignerTest, FailWhenNonUniqueIdsAssigned) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .addSimple(u"@android:attr/foo", ResourceId(0x01040006))
+            .addSimple(u"@android:attr/bar", ResourceId(0x01040006))
+            .setPackageId(u"android", 0x01)
+            .setPackageId(u"app", 0x7f)
+            .build();
+
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+    IdAssigner assigner;
+
+    ASSERT_FALSE(assigner.consume(context.get(), table.get()));
+}
+
+::testing::AssertionResult verifyIds(ResourceTable* table) {
+    std::set<uint8_t> packageIds;
+    for (auto& package : table->packages) {
+        if (!package->id) {
+            return ::testing::AssertionFailure() << "package " << package->name << " has no ID";
+        }
+
+        if (!packageIds.insert(package->id.value()).second) {
+            return ::testing::AssertionFailure() << "package " << package->name
+                    << " has non-unique ID " << std::hex << (int) package->id.value() << std::dec;
+        }
+    }
+
+    for (auto& package : table->packages) {
+        std::set<uint8_t> typeIds;
+        for (auto& type : package->types) {
+            if (!type->id) {
+                return ::testing::AssertionFailure() << "type " << type->type << " of package "
+                        << package->name << " has no ID";
+            }
+
+            if (!typeIds.insert(type->id.value()).second) {
+                return ::testing::AssertionFailure() << "type " << type->type
+                        << " of package " << package->name << " has non-unique ID "
+                        << std::hex << (int) type->id.value() << std::dec;
+            }
+        }
+
+
+        for (auto& type : package->types) {
+            std::set<uint16_t> entryIds;
+            for (auto& entry : type->entries) {
+                if (!entry->id) {
+                    return ::testing::AssertionFailure() << "entry " << entry->name << " of type "
+                            << type->type << " of package " << package->name << " has no ID";
+                }
+
+                if (!entryIds.insert(entry->id.value()).second) {
+                    return ::testing::AssertionFailure() << "entry " << entry->name
+                            << " of type " << type->type << " of package " << package->name
+                            << " has non-unique ID "
+                            << std::hex << (int) entry->id.value() << std::dec;
+                }
+            }
+        }
+    }
+    return ::testing::AssertionSuccess() << "all IDs are unique and assigned";
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Png.cpp b/tools/aapt2/compile/Png.cpp
similarity index 92%
rename from tools/aapt2/Png.cpp
rename to tools/aapt2/compile/Png.cpp
index 4e9b68e..9837c4e 100644
--- a/tools/aapt2/Png.cpp
+++ b/tools/aapt2/compile/Png.cpp
@@ -14,11 +14,10 @@
  * limitations under the License.
  */
 
-#include "BigBuffer.h"
-#include "Logger.h"
+#include "util/BigBuffer.h"
 #include "Png.h"
 #include "Source.h"
-#include "Util.h"
+#include "util/Util.h"
 
 #include <androidfw/ResourceTypes.h>
 #include <iostream>
@@ -95,15 +94,14 @@
 }
 
 static void logWarning(png_structp readPtr, png_const_charp warningMessage) {
-    SourceLogger* logger = reinterpret_cast<SourceLogger*>(png_get_error_ptr(readPtr));
-    logger->warn() << warningMessage << "." << std::endl;
+    IDiagnostics* diag = reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr));
+    diag->warn(DiagMessage() << warningMessage);
 }
 
 
-static bool readPng(png_structp readPtr, png_infop infoPtr, PngInfo* outInfo,
-                    std::string* outError) {
+static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr, PngInfo* outInfo) {
     if (setjmp(png_jmpbuf(readPtr))) {
-        *outError = "failed reading png";
+        diag->error(DiagMessage() << "failed reading png");
         return false;
     }
 
@@ -229,7 +227,7 @@
 #define MAX(a,b) ((a)>(b)?(a):(b))
 #define ABS(a)   ((a)<0?-(a):(a))
 
-static void analyze_image(SourceLogger* logger, const PngInfo& imageInfo, int grayscaleTolerance,
+static void analyze_image(IDiagnostics* diag, const PngInfo& imageInfo, int grayscaleTolerance,
                           png_colorp rgbPalette, png_bytep alphaPalette,
                           int *paletteEntries, bool *hasTransparency, int *colorType,
                           png_bytepp outRows) {
@@ -363,9 +361,9 @@
         *colorType = PNG_COLOR_TYPE_PALETTE;
     } else {
         if (maxGrayDeviation <= grayscaleTolerance) {
-            logger->note() << "forcing image to gray (max deviation = " << maxGrayDeviation
-                           << ")."
-                           << std::endl;
+            diag->note(DiagMessage()
+                       << "forcing image to gray (max deviation = "
+                       << maxGrayDeviation << ")");
             *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA;
         } else {
             *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
@@ -411,10 +409,10 @@
     }
 }
 
-static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info,
-                     int grayScaleTolerance, SourceLogger* logger, std::string* outError) {
+static bool writePng(IDiagnostics* diag, png_structp writePtr, png_infop infoPtr, PngInfo* info,
+                     int grayScaleTolerance) {
     if (setjmp(png_jmpbuf(writePtr))) {
-        *outError = "failed to write png";
+        diag->error(DiagMessage() << "failed to write png");
         return false;
     }
 
@@ -442,9 +440,9 @@
     png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
 
     if (kDebug) {
-        logger->note() << "writing image: w = " << info->width
-                       << ", h = " << info->height
-                       << std::endl;
+        diag->note(DiagMessage()
+                   << "writing image: w = " << info->width
+                   << ", h = " << info->height);
     }
 
     png_color rgbPalette[256];
@@ -452,7 +450,7 @@
     bool hasTransparency;
     int paletteEntries;
 
-    analyze_image(logger, *info, grayScaleTolerance, rgbPalette, alphaPalette,
+    analyze_image(diag, *info, grayScaleTolerance, rgbPalette, alphaPalette,
                   &paletteEntries, &hasTransparency, &colorType, outRows);
 
     // If the image is a 9-patch, we need to preserve it as a ARGB file to make
@@ -465,22 +463,22 @@
     if (kDebug) {
         switch (colorType) {
         case PNG_COLOR_TYPE_PALETTE:
-            logger->note() << "has " << paletteEntries
-                           << " colors" << (hasTransparency ? " (with alpha)" : "")
-                           << ", using PNG_COLOR_TYPE_PALLETTE."
-                           << std::endl;
+            diag->note(DiagMessage()
+                       << "has " << paletteEntries
+                       << " colors" << (hasTransparency ? " (with alpha)" : "")
+                       << ", using PNG_COLOR_TYPE_PALLETTE");
             break;
         case PNG_COLOR_TYPE_GRAY:
-            logger->note() << "is opaque gray, using PNG_COLOR_TYPE_GRAY." << std::endl;
+            diag->note(DiagMessage() << "is opaque gray, using PNG_COLOR_TYPE_GRAY");
             break;
         case PNG_COLOR_TYPE_GRAY_ALPHA:
-            logger->note() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA." << std::endl;
+            diag->note(DiagMessage() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA");
             break;
         case PNG_COLOR_TYPE_RGB:
-            logger->note() << "is opaque RGB, using PNG_COLOR_TYPE_RGB." << std::endl;
+            diag->note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB");
             break;
         case PNG_COLOR_TYPE_RGB_ALPHA:
-            logger->note() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA." << std::endl;
+            diag->note(DiagMessage() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA");
             break;
         }
     }
@@ -511,7 +509,7 @@
 
         // base 9 patch data
         if (kDebug) {
-            logger->note() << "adding 9-patch info..." << std::endl;
+            diag->note(DiagMessage() << "adding 9-patch info..");
         }
         strcpy((char*)unknowns[pIndex].name, "npTc");
         unknowns[pIndex].data = (png_byte*) info->serialize9Patch();
@@ -587,10 +585,10 @@
                  &compressionType, nullptr);
 
     if (kDebug) {
-        logger->note() << "image written: w = " << width << ", h = " << height
-                       << ", d = " << bitDepth << ", colors = " << colorType
-                       << ", inter = " << interlaceType << ", comp = " << compressionType
-                       << std::endl;
+        diag->note(DiagMessage()
+                   << "image written: w = " << width << ", h = " << height
+                   << ", d = " << bitDepth << ", colors = " << colorType
+                   << ", inter = " << interlaceType << ", comp = " << compressionType);
     }
     return true;
 }
@@ -1192,23 +1190,22 @@
 }
 
 
-bool Png::process(const Source& source, std::istream& input, BigBuffer* outBuffer,
-                  const Options& options, std::string* outError) {
+bool Png::process(const Source& source, std::istream* input, BigBuffer* outBuffer,
+                  const PngOptions& options) {
     png_byte signature[kPngSignatureSize];
 
     // Read the PNG signature first.
-    if (!input.read(reinterpret_cast<char*>(signature), kPngSignatureSize)) {
-        *outError = strerror(errno);
+    if (!input->read(reinterpret_cast<char*>(signature), kPngSignatureSize)) {
+        mDiag->error(DiagMessage() << strerror(errno));
         return false;
     }
 
     // If the PNG signature doesn't match, bail early.
     if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
-        *outError = "not a valid png file";
+        mDiag->error(DiagMessage() << "not a valid png file");
         return false;
     }
 
-    SourceLogger logger(source);
     bool result = false;
     png_structp readPtr = nullptr;
     png_infop infoPtr = nullptr;
@@ -1218,40 +1215,42 @@
 
     readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
     if (!readPtr) {
-        *outError = "failed to allocate read ptr";
+        mDiag->error(DiagMessage() << "failed to allocate read ptr");
         goto bail;
     }
 
     infoPtr = png_create_info_struct(readPtr);
     if (!infoPtr) {
-        *outError = "failed to allocate info ptr";
+        mDiag->error(DiagMessage() << "failed to allocate info ptr");
         goto bail;
     }
 
-    png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(&logger), nullptr, logWarning);
+    png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(mDiag), nullptr, logWarning);
 
     // Set the read function to read from std::istream.
-    png_set_read_fn(readPtr, (png_voidp)&input, readDataFromStream);
+    png_set_read_fn(readPtr, (png_voidp) input, readDataFromStream);
 
-    if (!readPng(readPtr, infoPtr, &pngInfo, outError)) {
+    if (!readPng(mDiag, readPtr, infoPtr, &pngInfo)) {
         goto bail;
     }
 
     if (util::stringEndsWith<char>(source.path, ".9.png")) {
-        if (!do9Patch(&pngInfo, outError)) {
+        std::string errorMsg;
+        if (!do9Patch(&pngInfo, &errorMsg)) {
+            mDiag->error(DiagMessage() << errorMsg);
             goto bail;
         }
     }
 
     writePtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
     if (!writePtr) {
-        *outError = "failed to allocate write ptr";
+        mDiag->error(DiagMessage() << "failed to allocate write ptr");
         goto bail;
     }
 
     writeInfoPtr = png_create_info_struct(writePtr);
     if (!writeInfoPtr) {
-        *outError = "failed to allocate write info ptr";
+        mDiag->error(DiagMessage() << "failed to allocate write info ptr");
         goto bail;
     }
 
@@ -1260,8 +1259,7 @@
     // Set the write function to write to std::ostream.
     png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream, flushDataToStream);
 
-    if (!writePng(writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance, &logger,
-                  outError)) {
+    if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance)) {
         goto bail;
     }
 
diff --git a/tools/aapt2/Png.h b/tools/aapt2/compile/Png.h
similarity index 71%
rename from tools/aapt2/Png.h
rename to tools/aapt2/compile/Png.h
index 4577ab8..345ff6c 100644
--- a/tools/aapt2/Png.h
+++ b/tools/aapt2/compile/Png.h
@@ -17,7 +17,8 @@
 #ifndef AAPT_PNG_H
 #define AAPT_PNG_H
 
-#include "BigBuffer.h"
+#include "util/BigBuffer.h"
+#include "Diagnostics.h"
 #include "Source.h"
 
 #include <iostream>
@@ -25,13 +26,20 @@
 
 namespace aapt {
 
-struct Png {
-    struct Options {
-        int grayScaleTolerance = 0;
-    };
+struct PngOptions {
+    int grayScaleTolerance = 0;
+};
 
-    bool process(const Source& source, std::istream& input, BigBuffer* outBuffer,
-                 const Options& options, std::string* outError);
+class Png {
+public:
+    Png(IDiagnostics* diag) : mDiag(diag) {
+    }
+
+    bool process(const Source& source, std::istream* input, BigBuffer* outBuffer,
+                 const PngOptions& options);
+
+private:
+    IDiagnostics* mDiag;
 };
 
 } // namespace aapt
diff --git a/tools/aapt2/compile/XmlIdCollector.cpp b/tools/aapt2/compile/XmlIdCollector.cpp
new file mode 100644
index 0000000..dfdf710
--- /dev/null
+++ b/tools/aapt2/compile/XmlIdCollector.cpp
@@ -0,0 +1,71 @@
+/*
+ * 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 "ResourceUtils.h"
+#include "ResourceValues.h"
+#include "XmlDom.h"
+
+#include "compile/XmlIdCollector.h"
+
+#include <algorithm>
+#include <vector>
+
+namespace aapt {
+
+namespace {
+
+static bool cmpName(const SourcedResourceName& a, const ResourceNameRef& b) {
+    return a.name < b;
+}
+
+struct IdCollector : public xml::Visitor {
+    using xml::Visitor::visit;
+
+    std::vector<SourcedResourceName>* mOutSymbols;
+
+    IdCollector(std::vector<SourcedResourceName>* outSymbols) : mOutSymbols(outSymbols) {
+    }
+
+    void visit(xml::Element* element) override {
+        for (xml::Attribute& attr : element->attributes) {
+            ResourceNameRef name;
+            bool create = false;
+            if (ResourceUtils::tryParseReference(attr.value, &name, &create, nullptr)) {
+                if (create && name.type == ResourceType::kId) {
+                    auto iter = std::lower_bound(mOutSymbols->begin(), mOutSymbols->end(),
+                                                 name, cmpName);
+                    if (iter == mOutSymbols->end() || iter->name != name) {
+                        mOutSymbols->insert(iter, SourcedResourceName{ name.toResourceName(),
+                                                                       element->lineNumber });
+                    }
+                }
+            }
+        }
+
+        xml::Visitor::visit(element);
+    }
+};
+
+} // namespace
+
+bool XmlIdCollector::consume(IAaptContext* context, XmlResource* xmlRes) {
+    xmlRes->file.exportedSymbols.clear();
+    IdCollector collector(&xmlRes->file.exportedSymbols);
+    xmlRes->root->accept(&collector);
+    return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Compat_test.cpp b/tools/aapt2/compile/XmlIdCollector.h
similarity index 70%
rename from tools/aapt2/Compat_test.cpp
rename to tools/aapt2/compile/XmlIdCollector.h
index 96aee44..96a58f2 100644
--- a/tools/aapt2/Compat_test.cpp
+++ b/tools/aapt2/compile/XmlIdCollector.h
@@ -14,20 +14,17 @@
  * limitations under the License.
  */
 
-#include <gtest/gtest.h>
+#ifndef AAPT_XMLIDCOLLECTOR_H
+#define AAPT_XMLIDCOLLECTOR_H
+
+#include "process/IResourceTableConsumer.h"
 
 namespace aapt {
 
-TEST(CompatTest, VersionAttributesInStyle) {
-}
-
-TEST(CompatTest, VersionAttributesInXML) {
-}
-
-TEST(CompatTest, DoNotOverrideExistingVersionedFiles) {
-}
-
-TEST(CompatTest, VersionAttributesInStyleWithCorrectPrecedence) {
-}
+struct XmlIdCollector : public IXmlResourceConsumer {
+    bool consume(IAaptContext* context, XmlResource* xmlRes) override;
+};
 
 } // namespace aapt
+
+#endif /* AAPT_XMLIDCOLLECTOR_H */
diff --git a/tools/aapt2/compile/XmlIdCollector_test.cpp b/tools/aapt2/compile/XmlIdCollector_test.cpp
new file mode 100644
index 0000000..c703f451
--- /dev/null
+++ b/tools/aapt2/compile/XmlIdCollector_test.cpp
@@ -0,0 +1,61 @@
+/*
+ * 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 "compile/XmlIdCollector.h"
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <algorithm>
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(XmlIdCollectorTest, CollectsIds) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+            <View xmlns:android="http://schemas.android.com/apk/res/android"
+                  android:id="@+id/foo"
+                  text="@+id/bar">
+              <SubView android:id="@+id/car"
+                       class="@+id/bar"/>
+            </View>)EOF");
+
+    XmlIdCollector collector;
+    ASSERT_TRUE(collector.consume(context.get(), doc.get()));
+
+    EXPECT_EQ(1u, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
+                             SourcedResourceName{ test::parseNameOrDie(u"@id/foo"), 3u }));
+
+    EXPECT_EQ(1u, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
+                             SourcedResourceName{ test::parseNameOrDie(u"@id/bar"), 3u }));
+
+    EXPECT_EQ(1u, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
+                             SourcedResourceName{ test::parseNameOrDie(u"@id/car"), 6u }));
+}
+
+TEST(XmlIdCollectorTest, DontCollectNonIds) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom("<View foo=\"@+string/foo\"/>");
+
+    XmlIdCollector collector;
+    ASSERT_TRUE(collector.consume(context.get(), doc.get()));
+
+    EXPECT_TRUE(doc->file.exportedSymbols.empty());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/data/AndroidManifest.xml b/tools/aapt2/data/AndroidManifest.xml
index 8533c28..d3b2fbe 100644
--- a/tools/aapt2/data/AndroidManifest.xml
+++ b/tools/aapt2/data/AndroidManifest.xml
@@ -2,6 +2,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.app">
     <application
-        android:name=".Activity">
+        android:name=".ActivityMain">
     </application>
 </manifest>
diff --git a/tools/aapt2/data/Makefile b/tools/aapt2/data/Makefile
index 91ff5fe..37012de 100644
--- a/tools/aapt2/data/Makefile
+++ b/tools/aapt2/data/Makefile
@@ -21,63 +21,41 @@
 # AAPT2 custom rules.
 ##
 
-PRIVATE_APK_UNALIGNED := $(LOCAL_OUT)/package-unaligned.apk
-PRIVATE_APK_ALIGNED := $(LOCAL_OUT)/package.apk
+PRIVATE_R_FILE := $(LOCAL_GEN)/$(subst .,/,$(LOCAL_PACKAGE))/R.java
+$(info PRIVATE_R_FILE = $(PRIVATE_R_FILE))
 
 # Eg: framework.apk, etc.
 PRIVATE_INCLUDES := $(FRAMEWORK)
 $(info PRIVATE_INCLUDES = $(PRIVATE_INCLUDES))
 
-# Eg: gen/com/android/app/R.java
-PRIVATE_R_JAVA := $(LOCAL_GEN)/$(subst .,/,$(LOCAL_PACKAGE))/R.java
-$(info PRIVATE_R_JAVA = $(PRIVATE_R_JAVA))
-
 # Eg: res/drawable/icon.png, res/values/styles.xml
 PRIVATE_RESOURCES := $(shell find $(LOCAL_RESOURCE_DIR) -mindepth 1 -maxdepth 2 -type f)
 $(info PRIVATE_RESOURCES = $(PRIVATE_RESOURCES))
 
-# Eg: drawable, values, layouts
-PRIVATE_RESOURCE_TYPES := \
-	$(patsubst $(LOCAL_RESOURCE_DIR)/%/,%,$(sort $(dir $(PRIVATE_RESOURCES))))
-$(info PRIVATE_RESOURCE_TYPES = $(PRIVATE_RESOURCE_TYPES))
+PRIVATE_RESOURCE_OBJECTS := $(subst /,_,$(patsubst $(LOCAL_RESOURCE_DIR)/%,%,$(filter $(LOCAL_RESOURCE_DIR)/values%,$(PRIVATE_RESOURCES))))
+PRIVATE_RESOURCE_OBJECTS := $(addprefix $(LOCAL_OUT)/,$(PRIVATE_RESOURCE_OBJECTS:.xml=.arsc.flat))
+$(info PRIVATE_RESOURCE_OBJECTS = $(PRIVATE_RESOURCE_OBJECTS))
 
-# Eg: out/values-v4.apk, out/drawable-xhdpi.apk
-PRIVATE_INTERMEDIATE_TABLES := $(patsubst %,$(LOCAL_OUT)/%.apk,$(PRIVATE_RESOURCE_TYPES))
-$(info PRIVATE_INTERMEDIATE_TABLES = $(PRIVATE_INTERMEDIATE_TABLES))
+PRIVATE_FILE_OBJECTS := $(subst /,_,$(patsubst $(LOCAL_RESOURCE_DIR)/%,%,$(filter-out $(LOCAL_RESOURCE_DIR)/values%,$(PRIVATE_RESOURCES))))
+PRIVATE_FILE_OBJECTS := $(addprefix $(LOCAL_OUT)/,$(addsuffix .flat,$(PRIVATE_FILE_OBJECTS)))
+$(info PRIVATE_FILE_OBJECTS = $(PRIVATE_FILE_OBJECTS))
 
-# Generates rules for collect phase.
-# $1: Resource type (values-v4)
-# returns: out/values-v4.apk: res/values-v4/styles.xml res/values-v4/colors.xml
-define make-collect-rule
-$(LOCAL_OUT)/$1.apk: $(filter $(LOCAL_RESOURCE_DIR)/$1/%,$(PRIVATE_RESOURCES))
-	$(AAPT) compile -o $$@ $$^
-endef
+.SECONDEXPANSION:
 
-# Collect: out/values-v4.apk <- res/values-v4/styles.xml res/values-v4/colors.xml
-$(foreach d,$(PRIVATE_RESOURCE_TYPES),$(eval $(call make-collect-rule,$d)))
+$(LOCAL_OUT)/%.arsc.flat: $(LOCAL_RESOURCE_DIR)/$$(subst _,/,%).xml
+	$(AAPT) compile -o $(LOCAL_OUT) $<
 
-# Link: out/package-unaligned.apk <- out/values-v4.apk out/drawable-v4.apk
-$(PRIVATE_APK_UNALIGNED): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_INCLUDES) $(LOCAL_LIBS) AndroidManifest.xml
-	$(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_INCLUDES)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) $(LOCAL_LIBS) --proguard $(LOCAL_PROGUARD) -v
+$(LOCAL_OUT)/%.flat: $(LOCAL_RESOURCE_DIR)/$$(subst _,/,%)
+	$(AAPT) compile -o $(LOCAL_OUT) $<
 
-# R.java: gen/com/android/app/R.java <- out/resources.arsc
-# No action since R.java is generated when out/resources.arsc is.
-$(PRIVATE_R_JAVA): $(PRIVATE_APK_UNALIGNED)
-
-# Assemble: zip out/resources.arsc AndroidManifest.xml and res/**/*
-$(PRIVATE_APK_ALIGNED): $(PRIVATE_APK_UNALIGNED)
-	$(ZIPALIGN) $< $@
+$(LOCAL_PROGUARD) $(LOCAL_OUT)/package.apk: AndroidManifest.xml
+$(PRIVATE_R_FILE) $(LOCAL_PROGUARD) $(LOCAL_OUT)/package.apk: $(PRIVATE_FILE_OBJECTS) $(PRIVATE_RESOURCE_OBJECTS)
+	$(AAPT) link -o $(LOCAL_OUT)/package.apk --manifest AndroidManifest.xml --java $(LOCAL_GEN) --proguard $(LOCAL_PROGUARD) -I $(PRIVATE_INCLUDES) $(filter-out AndroidManifest.xml,$^) -v
 
 # Create the out directory if needed.
 dummy := $(shell test -d $(LOCAL_OUT) || mkdir -p $(LOCAL_OUT))
 
-.PHONY: java
-java: $(PRIVATE_R_JAVA)
-
-.PHONY: assemble
-assemble: $(PRIVATE_APK_ALIGNED)
-
 .PHONY: all
-all: assemble java
+all: $(LOCAL_OUT)/package.apk $(LOCAL_PROGUARD) $(PRIVATE_R_FILE)
 
 .DEFAULT_GOAL := all
diff --git a/tools/aapt2/data/res/layout-v21/main.xml b/tools/aapt2/data/res/layout-v21/main.xml
new file mode 100644
index 0000000..959b349
--- /dev/null
+++ b/tools/aapt2/data/res/layout-v21/main.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              xmlns:support="http://schemas.android.com/apk/res/android.appcompat"
+    android:id="@+id/view"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+</LinearLayout>
diff --git a/tools/aapt2/data/res/layout/main.xml b/tools/aapt2/data/res/layout/main.xml
index 50a51d9..8a5e9e8 100644
--- a/tools/aapt2/data/res/layout/main.xml
+++ b/tools/aapt2/data/res/layout/main.xml
@@ -14,8 +14,8 @@
         android:layout_width="1dp"
         android:onClick="doClick"
         android:text="@{user.name}"
+        android:background="#ffffff"
         android:layout_height="match_parent"
-        app:layout_width="@support:bool/allow"
         app:flags="complex|weak"
         android:colorAccent="#ffffff"/>
 </LinearLayout>
diff --git a/tools/aapt2/data/res/raw/test.txt b/tools/aapt2/data/res/raw/test.txt
new file mode 100644
index 0000000..b14df64
--- /dev/null
+++ b/tools/aapt2/data/res/raw/test.txt
@@ -0,0 +1 @@
+Hi
diff --git a/tools/aapt2/data/res/values/styles.xml b/tools/aapt2/data/res/values/styles.xml
index d0b19a3..2bbdad1 100644
--- a/tools/aapt2/data/res/values/styles.xml
+++ b/tools/aapt2/data/res/values/styles.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources xmlns:lib="http://schemas.android.com/apk/res/android.appcompat">
-    <style name="App" parent="android.appcompat:Platform.AppCompat">
+    <style name="App">
         <item name="android:background">@color/primary</item>
         <item name="android:colorPrimary">@color/primary</item>
         <item name="android:colorPrimaryDark">@color/primary_dark</item>
@@ -8,8 +8,8 @@
     </style>
     <attr name="custom" format="reference" />
     <style name="Pop">
-        <item name="custom">@drawable/image</item>
-        <item name="android:focusable">@lib:bool/allow</item>
+        <item name="custom">@android:drawable/btn_default</item>
+        <item name="android:focusable">true</item>
     </style>
     <string name="yo">@string/wow</string>
 
diff --git a/tools/aapt2/data/res/values/test.xml b/tools/aapt2/data/res/values/test.xml
index d3ead34..d7ab1c8 100644
--- a/tools/aapt2/data/res/values/test.xml
+++ b/tools/aapt2/data/res/values/test.xml
@@ -3,7 +3,7 @@
     <string name="hooha"><font bgcolor="#ffffff">Hey guys!</font> <xliff:g>My</xliff:g> name is <b>Adam</b>. How <b><i>are</i></b> you?</string>
     <public name="hooha" type="string" id="0x7f020001"/>
     <string name="wow">@android:string/ok</string>
-    <public name="image" type="drawable" id="0x7f060000" />
+    <public name="layout_width" type="attr" />
     <attr name="layout_width" format="boolean" />
     <attr name="flags">
         <flag name="complex" value="1" />
diff --git a/tools/aapt2/flatten/Archive.cpp b/tools/aapt2/flatten/Archive.cpp
new file mode 100644
index 0000000..6db13b8
--- /dev/null
+++ b/tools/aapt2/flatten/Archive.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 "flatten/Archive.h"
+#include "util/Files.h"
+#include "util/StringPiece.h"
+
+#include <fstream>
+#include <memory>
+#include <string>
+#include <vector>
+#include <ziparchive/zip_writer.h>
+
+namespace aapt {
+
+namespace {
+
+struct DirectoryWriter : public IArchiveWriter {
+    std::string mOutDir;
+    std::vector<std::unique_ptr<ArchiveEntry>> mEntries;
+
+    explicit DirectoryWriter(const StringPiece& outDir) : mOutDir(outDir.toString()) {
+    }
+
+    ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
+                             const BigBuffer& buffer) override {
+        std::string fullPath = mOutDir;
+        file::appendPath(&fullPath, path);
+        file::mkdirs(file::getStem(fullPath));
+
+        std::ofstream fout(fullPath, std::ofstream::binary);
+        if (!fout) {
+            return nullptr;
+        }
+
+        if (!util::writeAll(fout, buffer)) {
+            return nullptr;
+        }
+
+        mEntries.push_back(util::make_unique<ArchiveEntry>(fullPath, flags, buffer.size()));
+        return mEntries.back().get();
+    }
+
+    ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, android::FileMap* fileMap,
+                             size_t offset, size_t len) override {
+        std::string fullPath = mOutDir;
+        file::appendPath(&fullPath, path);
+        file::mkdirs(file::getStem(fullPath));
+
+        std::ofstream fout(fullPath, std::ofstream::binary);
+        if (!fout) {
+            return nullptr;
+        }
+
+        if (!fout.write((const char*) fileMap->getDataPtr() + offset, len)) {
+            return nullptr;
+        }
+
+        mEntries.push_back(util::make_unique<ArchiveEntry>(fullPath, flags, len));
+        return mEntries.back().get();
+    }
+
+    virtual ~DirectoryWriter() {
+
+    }
+};
+
+struct ZipFileWriter : public IArchiveWriter {
+    FILE* mFile;
+    std::unique_ptr<ZipWriter> mWriter;
+    std::vector<std::unique_ptr<ArchiveEntry>> mEntries;
+
+    explicit ZipFileWriter(const StringPiece& path) {
+        mFile = fopen(path.data(), "w+b");
+        if (mFile) {
+            mWriter = util::make_unique<ZipWriter>(mFile);
+        }
+    }
+
+    ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
+                             const BigBuffer& buffer) override {
+        if (!mWriter) {
+            return nullptr;
+        }
+
+        size_t zipFlags = 0;
+        if (flags & ArchiveEntry::kCompress) {
+            zipFlags |= ZipWriter::kCompress;
+        }
+
+        if (flags & ArchiveEntry::kAlign) {
+            zipFlags |= ZipWriter::kAlign32;
+        }
+
+        int32_t result = mWriter->StartEntry(path.data(), zipFlags);
+        if (result != 0) {
+            return nullptr;
+        }
+
+        for (const BigBuffer::Block& b : buffer) {
+            result = mWriter->WriteBytes(reinterpret_cast<const uint8_t*>(b.buffer.get()), b.size);
+            if (result != 0) {
+                return nullptr;
+            }
+        }
+
+        result = mWriter->FinishEntry();
+        if (result != 0) {
+            return nullptr;
+        }
+
+        mEntries.push_back(util::make_unique<ArchiveEntry>(path.toString(), flags, buffer.size()));
+        return mEntries.back().get();
+    }
+
+    ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, android::FileMap* fileMap,
+                             size_t offset, size_t len) override {
+        if (!mWriter) {
+            return nullptr;
+        }
+
+        size_t zipFlags = 0;
+        if (flags & ArchiveEntry::kCompress) {
+            zipFlags |= ZipWriter::kCompress;
+        }
+
+        if (flags & ArchiveEntry::kAlign) {
+            zipFlags |= ZipWriter::kAlign32;
+        }
+
+        int32_t result = mWriter->StartEntry(path.data(), zipFlags);
+        if (result != 0) {
+            return nullptr;
+        }
+
+        result = mWriter->WriteBytes((const char*) fileMap->getDataPtr() + offset, len);
+        if (result != 0) {
+            return nullptr;
+        }
+
+        result = mWriter->FinishEntry();
+        if (result != 0) {
+            return nullptr;
+        }
+
+        mEntries.push_back(util::make_unique<ArchiveEntry>(path.toString(), flags, len));
+        return mEntries.back().get();
+    }
+
+    virtual ~ZipFileWriter() {
+        if (mWriter) {
+            mWriter->Finish();
+            fclose(mFile);
+        }
+    }
+};
+
+} // namespace
+
+std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(const StringPiece& path) {
+    return util::make_unique<DirectoryWriter>(path);
+}
+
+std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(const StringPiece& path) {
+    return util::make_unique<ZipFileWriter>(path);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/flatten/Archive.h
new file mode 100644
index 0000000..c4ddeb3
--- /dev/null
+++ b/tools/aapt2/flatten/Archive.h
@@ -0,0 +1,57 @@
+/*
+ * 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_FLATTEN_ARCHIVE_H
+#define AAPT_FLATTEN_ARCHIVE_H
+
+#include "util/BigBuffer.h"
+#include "util/Files.h"
+#include "util/StringPiece.h"
+
+#include <fstream>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace aapt {
+
+struct ArchiveEntry {
+    enum : uint32_t {
+        kCompress = 0x01,
+        kAlign    = 0x02,
+    };
+
+    std::string path;
+    uint32_t flags;
+    size_t uncompressedSize;
+};
+
+struct IArchiveWriter {
+    virtual ~IArchiveWriter() = default;
+
+    virtual ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
+                                     const BigBuffer& buffer) = 0;
+    virtual ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
+                                     android::FileMap* fileMap, size_t offset, size_t len) = 0;
+};
+
+std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(const StringPiece& path);
+
+std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(const StringPiece& path);
+
+} // namespace aapt
+
+#endif /* AAPT_FLATTEN_ARCHIVE_H */
diff --git a/tools/aapt2/flatten/ChunkWriter.h b/tools/aapt2/flatten/ChunkWriter.h
new file mode 100644
index 0000000..de1d87a
--- /dev/null
+++ b/tools/aapt2/flatten/ChunkWriter.h
@@ -0,0 +1,87 @@
+/*
+ * 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_FLATTEN_CHUNKWRITER_H
+#define AAPT_FLATTEN_CHUNKWRITER_H
+
+#include "util/BigBuffer.h"
+#include "util/Util.h"
+
+#include <androidfw/ResourceTypes.h>
+
+namespace aapt {
+
+class ChunkWriter {
+private:
+    BigBuffer* mBuffer;
+    size_t mStartSize = 0;
+    android::ResChunk_header* mHeader = nullptr;
+
+public:
+    explicit inline ChunkWriter(BigBuffer* buffer) : mBuffer(buffer) {
+    }
+
+    ChunkWriter(const ChunkWriter&) = delete;
+    ChunkWriter& operator=(const ChunkWriter&) = delete;
+    ChunkWriter(ChunkWriter&&) = default;
+    ChunkWriter& operator=(ChunkWriter&&) = default;
+
+    template <typename T>
+    inline T* startChunk(uint16_t type) {
+        mStartSize = mBuffer->size();
+        T* chunk = mBuffer->nextBlock<T>();
+        mHeader = &chunk->header;
+        mHeader->type = util::hostToDevice16(type);
+        mHeader->headerSize = util::hostToDevice16(sizeof(T));
+        return chunk;
+    }
+
+    template <typename T>
+    inline T* nextBlock(size_t count = 1) {
+        return mBuffer->nextBlock<T>(count);
+    }
+
+    inline BigBuffer* getBuffer() {
+        return mBuffer;
+    }
+
+    inline android::ResChunk_header* getChunkHeader() {
+        return mHeader;
+    }
+
+    inline size_t size() {
+        return mBuffer->size() - mStartSize;
+    }
+
+    inline android::ResChunk_header* finish() {
+        mBuffer->align4();
+        mHeader->size = util::hostToDevice32(mBuffer->size() - mStartSize);
+        return mHeader;
+    }
+};
+
+template <>
+inline android::ResChunk_header* ChunkWriter::startChunk(uint16_t type) {
+    mStartSize = mBuffer->size();
+    mHeader = mBuffer->nextBlock<android::ResChunk_header>();
+    mHeader->type = util::hostToDevice16(type);
+    mHeader->headerSize = util::hostToDevice16(sizeof(android::ResChunk_header));
+    return mHeader;
+}
+
+} // namespace aapt
+
+#endif /* AAPT_FLATTEN_CHUNKWRITER_H */
diff --git a/tools/aapt2/flatten/FileExportWriter.h b/tools/aapt2/flatten/FileExportWriter.h
new file mode 100644
index 0000000..7688fa7
--- /dev/null
+++ b/tools/aapt2/flatten/FileExportWriter.h
@@ -0,0 +1,67 @@
+/*
+ * 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_FLATTEN_FILEEXPORTWRITER_H
+#define AAPT_FLATTEN_FILEEXPORTWRITER_H
+
+#include "StringPool.h"
+
+#include "flatten/ResourceTypeExtensions.h"
+#include "flatten/ChunkWriter.h"
+#include "process/IResourceTableConsumer.h"
+#include "util/BigBuffer.h"
+#include "util/Util.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <utils/misc.h>
+
+namespace aapt {
+
+static ChunkWriter wrapBufferWithFileExportHeader(BigBuffer* buffer, ResourceFile* res) {
+    ChunkWriter fileExportWriter(buffer);
+    FileExport_header* fileExport = fileExportWriter.startChunk<FileExport_header>(
+            RES_FILE_EXPORT_TYPE);
+
+    ExportedSymbol* symbolRefs = nullptr;
+    if (!res->exportedSymbols.empty()) {
+        symbolRefs = fileExportWriter.nextBlock<ExportedSymbol>(
+                res->exportedSymbols.size());
+    }
+    fileExport->exportedSymbolCount = util::hostToDevice32(res->exportedSymbols.size());
+
+    StringPool symbolExportPool;
+    memcpy(fileExport->magic, "AAPT", NELEM(fileExport->magic));
+    fileExport->config = res->config;
+    fileExport->config.swapHtoD();
+    fileExport->name.index = util::hostToDevice32(symbolExportPool.makeRef(res->name.toString())
+                                                  .getIndex());
+    fileExport->source.index = util::hostToDevice32(symbolExportPool.makeRef(util::utf8ToUtf16(
+            res->source.path)).getIndex());
+
+    for (const SourcedResourceName& name : res->exportedSymbols) {
+        symbolRefs->name.index = util::hostToDevice32(symbolExportPool.makeRef(name.name.toString())
+                                                      .getIndex());
+        symbolRefs->line = util::hostToDevice32(name.line);
+        symbolRefs++;
+    }
+
+    StringPool::flattenUtf16(fileExportWriter.getBuffer(), symbolExportPool);
+    return fileExportWriter;
+}
+
+} // namespace aapt
+
+#endif /* AAPT_FLATTEN_FILEEXPORTWRITER_H */
diff --git a/tools/aapt2/flatten/FileExportWriter_test.cpp b/tools/aapt2/flatten/FileExportWriter_test.cpp
new file mode 100644
index 0000000..32fc203
--- /dev/null
+++ b/tools/aapt2/flatten/FileExportWriter_test.cpp
@@ -0,0 +1,51 @@
+/*
+ * 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 "Resource.h"
+
+#include "flatten/FileExportWriter.h"
+#include "util/BigBuffer.h"
+#include "util/Util.h"
+
+#include "test/Common.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(FileExportWriterTest, FlattenResourceFileDataWithNoExports) {
+    ResourceFile resFile = {
+            test::parseNameOrDie(u"@android:layout/main.xml"),
+            test::parseConfigOrDie("sw600dp-v4"),
+            Source{ "res/layout/main.xml" },
+    };
+
+    BigBuffer buffer(1024);
+    ChunkWriter writer = wrapBufferWithFileExportHeader(&buffer, &resFile);
+    *writer.getBuffer()->nextBlock<uint32_t>() = 42u;
+    writer.finish();
+
+    std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+
+    // There should be more data (string pool) besides the header and our data.
+    ASSERT_GT(buffer.size(), sizeof(FileExport_header) + sizeof(uint32_t));
+
+    // Write at the end of this chunk is our data.
+    uint32_t* val = (uint32_t*)(data.get() + buffer.size()) - 1;
+    EXPECT_EQ(*val, 42u);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ResourceTypeExtensions.h b/tools/aapt2/flatten/ResourceTypeExtensions.h
similarity index 75%
rename from tools/aapt2/ResourceTypeExtensions.h
rename to tools/aapt2/flatten/ResourceTypeExtensions.h
index dcbe923..af0afef 100644
--- a/tools/aapt2/ResourceTypeExtensions.h
+++ b/tools/aapt2/flatten/ResourceTypeExtensions.h
@@ -30,6 +30,12 @@
  * future collisions.
  */
 enum {
+    /**
+     * A chunk that contains an entire file that
+     * has been compiled.
+     */
+    RES_FILE_EXPORT_TYPE = 0x000c,
+
     RES_TABLE_PUBLIC_TYPE = 0x000d,
 
     /**
@@ -60,6 +66,48 @@
     };
 };
 
+/**
+ * Followed by exportedSymbolCount ExportedSymbol structs, followed by the string pool.
+ */
+struct FileExport_header {
+    android::ResChunk_header header;
+
+    /**
+     * MAGIC value. Must be 'AAPT' (0x41415054)
+     */
+    uint8_t magic[4];
+
+    /**
+     * Version of AAPT that built this file.
+     */
+    uint32_t version;
+
+    /**
+     * The resource name.
+     */
+    android::ResStringPool_ref name;
+
+    /**
+     * Configuration of this file.
+     */
+    android::ResTable_config config;
+
+    /**
+     * Original source path of this file.
+     */
+    android::ResStringPool_ref source;
+
+    /**
+     * Number of symbols exported by this file.
+     */
+    uint32_t exportedSymbolCount;
+};
+
+struct ExportedSymbol {
+    android::ResStringPool_ref name;
+    uint32_t line;
+};
+
 struct Public_header {
     android::ResChunk_header header;
 
@@ -142,6 +190,16 @@
     uint32_t line;
 };
 
+/**
+ * An alternative struct to use instead of ResTable_map_entry. This one is a standard_layout
+ * struct.
+ */
+struct ResTable_entry_ext {
+    android::ResTable_entry entry;
+    android::ResTable_ref parent;
+    uint32_t count;
+};
+
 } // namespace aapt
 
 #endif // AAPT_RESOURCE_TYPE_EXTENSIONS_H
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
new file mode 100644
index 0000000..427ab18
--- /dev/null
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -0,0 +1,644 @@
+/*
+ * 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 "ResourceTable.h"
+#include "ResourceValues.h"
+#include "ValueVisitor.h"
+
+#include "flatten/ChunkWriter.h"
+#include "flatten/ResourceTypeExtensions.h"
+#include "flatten/TableFlattener.h"
+#include "util/BigBuffer.h"
+
+#include <type_traits>
+#include <numeric>
+#include <utils/misc.h>
+
+using namespace android;
+
+namespace aapt {
+
+namespace {
+
+template <typename T>
+static bool cmpIds(const T* a, const T* b) {
+    return a->id.value() < b->id.value();
+}
+
+static void strcpy16_htod(uint16_t* dst, size_t len, const StringPiece16& src) {
+    if (len == 0) {
+        return;
+    }
+
+    size_t i;
+    const char16_t* srcData = src.data();
+    for (i = 0; i < len - 1 && i < src.size(); i++) {
+        dst[i] = util::hostToDevice16((uint16_t) srcData[i]);
+    }
+    dst[i] = 0;
+}
+
+struct FlatEntry {
+    ResourceEntry* entry;
+    Value* value;
+    uint32_t entryKey;
+    uint32_t sourcePathKey;
+    uint32_t sourceLine;
+};
+
+struct SymbolWriter {
+    struct Entry {
+        StringPool::Ref name;
+        size_t offset;
+    };
+
+    StringPool pool;
+    std::vector<Entry> symbols;
+
+    void addSymbol(const ResourceNameRef& name, size_t offset) {
+        symbols.push_back(Entry{ pool.makeRef(name.package.toString() + u":" +
+                                              toString(name.type).toString() + u"/" +
+                                              name.entry.toString()), offset });
+    }
+};
+
+struct MapFlattenVisitor : public RawValueVisitor {
+    using RawValueVisitor::visit;
+
+    SymbolWriter* mSymbols;
+    FlatEntry* mEntry;
+    BigBuffer* mBuffer;
+    size_t mEntryCount = 0;
+    Maybe<uint32_t> mParentIdent;
+    Maybe<ResourceNameRef> mParentName;
+
+    MapFlattenVisitor(SymbolWriter* symbols, FlatEntry* entry, BigBuffer* buffer) :
+            mSymbols(symbols), mEntry(entry), mBuffer(buffer) {
+    }
+
+    void flattenKey(Reference* key, ResTable_map* outEntry) {
+        if (!key->id) {
+            assert(key->name && "reference must have a name");
+
+            outEntry->name.ident = util::hostToDevice32(0);
+            mSymbols->addSymbol(key->name.value(), (mBuffer->size() - sizeof(ResTable_map)) +
+                                    offsetof(ResTable_map, name));
+        } else {
+            outEntry->name.ident = util::hostToDevice32(key->id.value().id);
+        }
+    }
+
+    void flattenValue(Item* value, ResTable_map* outEntry) {
+        if (Reference* ref = valueCast<Reference>(value)) {
+            if (!ref->id) {
+                assert(ref->name && "reference must have a name");
+
+                mSymbols->addSymbol(ref->name.value(), (mBuffer->size() - sizeof(ResTable_map)) +
+                                        offsetof(ResTable_map, value) + offsetof(Res_value, data));
+            }
+        }
+
+        bool result = value->flatten(&outEntry->value);
+        assert(result && "flatten failed");
+    }
+
+    void flattenEntry(Reference* key, Item* value) {
+        ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>();
+        flattenKey(key, outEntry);
+        flattenValue(value, outEntry);
+        outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
+        mEntryCount++;
+    }
+
+    void visit(Attribute* attr) override {
+        {
+            Reference key(ResourceId{ ResTable_map::ATTR_TYPE });
+            BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->typeMask);
+            flattenEntry(&key, &val);
+        }
+
+        for (Attribute::Symbol& s : attr->symbols) {
+            BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value);
+            flattenEntry(&s.symbol, &val);
+        }
+    }
+
+    static bool cmpStyleEntries(const Style::Entry& a, const Style::Entry& b) {
+        if (a.key.id) {
+            if (b.key.id) {
+                return a.key.id.value() < b.key.id.value();
+            }
+            return true;
+        } else if (!b.key.id) {
+            return a.key.name.value() < b.key.name.value();
+        }
+        return false;
+    }
+
+    void visit(Style* style) override {
+        if (style->parent) {
+            if (!style->parent.value().id) {
+                assert(style->parent.value().name && "reference must have a name");
+                mParentName = style->parent.value().name;
+            } else {
+                mParentIdent = style->parent.value().id.value().id;
+            }
+        }
+
+        // Sort the style.
+        std::sort(style->entries.begin(), style->entries.end(), cmpStyleEntries);
+
+        for (Style::Entry& entry : style->entries) {
+            flattenEntry(&entry.key, entry.value.get());
+        }
+    }
+
+    void visit(Styleable* styleable) override {
+        for (auto& attrRef : styleable->entries) {
+            BinaryPrimitive val(Res_value{});
+            flattenEntry(&attrRef, &val);
+        }
+    }
+
+    void visit(Array* array) override {
+        for (auto& item : array->items) {
+            ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>();
+            flattenValue(item.get(), outEntry);
+            outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
+            mEntryCount++;
+        }
+    }
+
+    void visit(Plural* plural) override {
+        const size_t count = plural->values.size();
+        for (size_t i = 0; i < count; i++) {
+            if (!plural->values[i]) {
+                continue;
+            }
+
+            ResourceId q;
+            switch (i) {
+            case Plural::Zero:
+                q.id = android::ResTable_map::ATTR_ZERO;
+                break;
+
+            case Plural::One:
+                q.id = android::ResTable_map::ATTR_ONE;
+                break;
+
+            case Plural::Two:
+                q.id = android::ResTable_map::ATTR_TWO;
+                break;
+
+            case Plural::Few:
+                q.id = android::ResTable_map::ATTR_FEW;
+                break;
+
+            case Plural::Many:
+                q.id = android::ResTable_map::ATTR_MANY;
+                break;
+
+            case Plural::Other:
+                q.id = android::ResTable_map::ATTR_OTHER;
+                break;
+
+            default:
+                assert(false);
+                break;
+            }
+
+            Reference key(q);
+            flattenEntry(&key, plural->values[i].get());
+        }
+    }
+};
+
+struct PackageFlattener {
+    IDiagnostics* mDiag;
+    TableFlattenerOptions mOptions;
+    ResourceTable* mTable;
+    ResourceTablePackage* mPackage;
+    SymbolWriter mSymbols;
+    StringPool mTypePool;
+    StringPool mKeyPool;
+    StringPool mSourcePool;
+
+    template <typename T>
+    T* writeEntry(FlatEntry* entry, BigBuffer* buffer) {
+        static_assert(std::is_same<ResTable_entry, T>::value ||
+                      std::is_same<ResTable_entry_ext, T>::value,
+                      "T must be ResTable_entry or ResTable_entry_ext");
+
+        T* result = buffer->nextBlock<T>();
+        ResTable_entry* outEntry = (ResTable_entry*)(result);
+        if (entry->entry->publicStatus.isPublic) {
+            outEntry->flags |= ResTable_entry::FLAG_PUBLIC;
+        }
+
+        if (entry->value->isWeak()) {
+            outEntry->flags |= ResTable_entry::FLAG_WEAK;
+        }
+
+        if (!entry->value->isItem()) {
+            outEntry->flags |= ResTable_entry::FLAG_COMPLEX;
+        }
+
+        outEntry->key.index = util::hostToDevice32(entry->entryKey);
+        outEntry->size = sizeof(T);
+
+        if (mOptions.useExtendedChunks) {
+            // Write the extra source block. This will be ignored by the Android runtime.
+            ResTable_entry_source* sourceBlock = buffer->nextBlock<ResTable_entry_source>();
+            sourceBlock->pathIndex = util::hostToDevice32(entry->sourcePathKey);
+            sourceBlock->line = util::hostToDevice32(entry->sourceLine);
+            outEntry->size += sizeof(*sourceBlock);
+        }
+
+        outEntry->flags = util::hostToDevice16(outEntry->flags);
+        outEntry->size = util::hostToDevice16(outEntry->size);
+        return result;
+    }
+
+    bool flattenValue(FlatEntry* entry, BigBuffer* buffer) {
+        if (entry->value->isItem()) {
+            writeEntry<ResTable_entry>(entry, buffer);
+            if (Reference* ref = valueCast<Reference>(entry->value)) {
+                if (!ref->id) {
+                    assert(ref->name && "reference must have at least a name");
+                    mSymbols.addSymbol(ref->name.value(),
+                                       buffer->size() + offsetof(Res_value, data));
+                }
+            }
+            Res_value* outValue = buffer->nextBlock<Res_value>();
+            bool result = static_cast<Item*>(entry->value)->flatten(outValue);
+            assert(result && "flatten failed");
+            outValue->size = util::hostToDevice16(sizeof(*outValue));
+        } else {
+            const size_t beforeEntry = buffer->size();
+            ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext>(entry, buffer);
+            MapFlattenVisitor visitor(&mSymbols, entry, buffer);
+            entry->value->accept(&visitor);
+            outEntry->count = util::hostToDevice32(visitor.mEntryCount);
+            if (visitor.mParentName) {
+                mSymbols.addSymbol(visitor.mParentName.value(),
+                                   beforeEntry + offsetof(ResTable_entry_ext, parent));
+            } else if (visitor.mParentIdent) {
+                outEntry->parent.ident = util::hostToDevice32(visitor.mParentIdent.value());
+            }
+        }
+        return true;
+    }
+
+    bool flattenConfig(const ResourceTableType* type, const ConfigDescription& config,
+                       std::vector<FlatEntry>* entries, BigBuffer* buffer) {
+        ChunkWriter typeWriter(buffer);
+        ResTable_type* typeHeader = typeWriter.startChunk<ResTable_type>(RES_TABLE_TYPE_TYPE);
+        typeHeader->id = type->id.value();
+        typeHeader->config = config;
+        typeHeader->config.swapHtoD();
+
+        auto maxAccum = [](uint32_t max, const std::unique_ptr<ResourceEntry>& a) -> uint32_t {
+            return std::max(max, (uint32_t) a->id.value());
+        };
+
+        // Find the largest entry ID. That is how many entries we will have.
+        const uint32_t entryCount =
+                std::accumulate(type->entries.begin(), type->entries.end(), 0, maxAccum) + 1;
+
+        typeHeader->entryCount = util::hostToDevice32(entryCount);
+        uint32_t* indices = typeWriter.nextBlock<uint32_t>(entryCount);
+
+        assert((size_t) entryCount <= std::numeric_limits<uint16_t>::max() + 1);
+        memset(indices, 0xff, entryCount * sizeof(uint32_t));
+
+        typeHeader->entriesStart = util::hostToDevice32(typeWriter.size());
+
+        const size_t entryStart = typeWriter.getBuffer()->size();
+        for (FlatEntry& flatEntry : *entries) {
+            assert(flatEntry.entry->id.value() < entryCount);
+            indices[flatEntry.entry->id.value()] = util::hostToDevice32(
+                    typeWriter.getBuffer()->size() - entryStart);
+            if (!flattenValue(&flatEntry, typeWriter.getBuffer())) {
+                mDiag->error(DiagMessage()
+                             << "failed to flatten resource '"
+                             << ResourceNameRef(mPackage->name, type->type, flatEntry.entry->name)
+                             << "' for configuration '" << config << "'");
+                return false;
+            }
+        }
+        typeWriter.finish();
+        return true;
+    }
+
+    std::vector<ResourceTableType*> collectAndSortTypes() {
+        std::vector<ResourceTableType*> sortedTypes;
+        for (auto& type : mPackage->types) {
+            if (type->type == ResourceType::kStyleable && !mOptions.useExtendedChunks) {
+                // Styleables aren't real Resource Types, they are represented in the R.java
+                // file.
+                continue;
+            }
+
+            assert(type->id && "type must have an ID set");
+
+            sortedTypes.push_back(type.get());
+        }
+        std::sort(sortedTypes.begin(), sortedTypes.end(), cmpIds<ResourceTableType>);
+        return sortedTypes;
+    }
+
+    std::vector<ResourceEntry*> collectAndSortEntries(ResourceTableType* type) {
+        // Sort the entries by entry ID.
+        std::vector<ResourceEntry*> sortedEntries;
+        for (auto& entry : type->entries) {
+            assert(entry->id && "entry must have an ID set");
+            sortedEntries.push_back(entry.get());
+        }
+        std::sort(sortedEntries.begin(), sortedEntries.end(), cmpIds<ResourceEntry>);
+        return sortedEntries;
+    }
+
+    bool flattenTypeSpec(ResourceTableType* type, std::vector<ResourceEntry*>* sortedEntries,
+                         BigBuffer* buffer) {
+        ChunkWriter typeSpecWriter(buffer);
+        ResTable_typeSpec* specHeader = typeSpecWriter.startChunk<ResTable_typeSpec>(
+                RES_TABLE_TYPE_SPEC_TYPE);
+        specHeader->id = type->id.value();
+
+        if (sortedEntries->empty()) {
+            typeSpecWriter.finish();
+            return true;
+        }
+
+        // We can't just take the size of the vector. There may be holes in the entry ID space.
+        // Since the entries are sorted by ID, the last one will be the biggest.
+        const size_t numEntries = sortedEntries->back()->id.value() + 1;
+
+        specHeader->entryCount = util::hostToDevice32(numEntries);
+
+        // Reserve space for the masks of each resource in this type. These
+        // show for which configuration axis the resource changes.
+        uint32_t* configMasks = typeSpecWriter.nextBlock<uint32_t>(numEntries);
+
+        const size_t actualNumEntries = sortedEntries->size();
+        for (size_t entryIndex = 0; entryIndex < actualNumEntries; entryIndex++) {
+            ResourceEntry* entry = sortedEntries->at(entryIndex);
+
+            // Populate the config masks for this entry.
+
+            if (entry->publicStatus.isPublic) {
+                configMasks[entry->id.value()] |=
+                        util::hostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
+            }
+
+            const size_t configCount = entry->values.size();
+            for (size_t i = 0; i < configCount; i++) {
+                const ConfigDescription& config = entry->values[i].config;
+                for (size_t j = i + 1; j < configCount; j++) {
+                    configMasks[entry->id.value()] |= util::hostToDevice32(
+                            config.diff(entry->values[j].config));
+                }
+            }
+        }
+        typeSpecWriter.finish();
+        return true;
+    }
+
+    bool flattenPublic(ResourceTableType* type, std::vector<ResourceEntry*>* sortedEntries,
+                       BigBuffer* buffer) {
+        ChunkWriter publicWriter(buffer);
+        Public_header* publicHeader = publicWriter.startChunk<Public_header>(RES_TABLE_PUBLIC_TYPE);
+        publicHeader->typeId = type->id.value();
+
+        for (ResourceEntry* entry : *sortedEntries) {
+            if (entry->publicStatus.isPublic) {
+                // Write the public status of this entry.
+                Public_entry* publicEntry = publicWriter.nextBlock<Public_entry>();
+                publicEntry->entryId = util::hostToDevice32(entry->id.value());
+                publicEntry->key.index = util::hostToDevice32(mKeyPool.makeRef(
+                        entry->name).getIndex());
+                publicEntry->source.index = util::hostToDevice32(mSourcePool.makeRef(
+                        util::utf8ToUtf16(entry->publicStatus.source.path)).getIndex());
+                if (entry->publicStatus.source.line) {
+                    publicEntry->sourceLine = util::hostToDevice32(
+                            entry->publicStatus.source.line.value());
+                }
+
+                // Don't hostToDevice until the last step.
+                publicHeader->count += 1;
+            }
+        }
+
+        publicHeader->count = util::hostToDevice32(publicHeader->count);
+        publicWriter.finish();
+        return true;
+    }
+
+    bool flattenTypes(BigBuffer* buffer) {
+        // Sort the types by their IDs. They will be inserted into the StringPool in this order.
+        std::vector<ResourceTableType*> sortedTypes = collectAndSortTypes();
+
+        size_t expectedTypeId = 1;
+        for (ResourceTableType* type : sortedTypes) {
+            // If there is a gap in the type IDs, fill in the StringPool
+            // with empty values until we reach the ID we expect.
+            while (type->id.value() > expectedTypeId) {
+                std::u16string typeName(u"?");
+                typeName += expectedTypeId;
+                mTypePool.makeRef(typeName);
+                expectedTypeId++;
+            }
+            expectedTypeId++;
+            mTypePool.makeRef(toString(type->type));
+
+            std::vector<ResourceEntry*> sortedEntries = collectAndSortEntries(type);
+
+            if (!flattenTypeSpec(type, &sortedEntries, buffer)) {
+                return false;
+            }
+
+            if (mOptions.useExtendedChunks) {
+                if (!flattenPublic(type, &sortedEntries, buffer)) {
+                    return false;
+                }
+            }
+
+            // The binary resource table lists resource entries for each configuration.
+            // We store them inverted, where a resource entry lists the values for each
+            // configuration available. Here we reverse this to match the binary table.
+            std::map<ConfigDescription, std::vector<FlatEntry>> configToEntryListMap;
+            for (ResourceEntry* entry : sortedEntries) {
+                const size_t keyIndex = mKeyPool.makeRef(entry->name).getIndex();
+
+                // Group values by configuration.
+                for (auto& configValue : entry->values) {
+                   configToEntryListMap[configValue.config].push_back(FlatEntry{
+                            entry, configValue.value.get(), (uint32_t) keyIndex,
+                            (uint32_t)(mSourcePool.makeRef(util::utf8ToUtf16(
+                                    configValue.source.path)).getIndex()),
+                            (uint32_t)(configValue.source.line
+                                    ? configValue.source.line.value() : 0)
+                   });
+                }
+            }
+
+            // Flatten a configuration value.
+            for (auto& entry : configToEntryListMap) {
+                if (!flattenConfig(type, entry.first, &entry.second, buffer)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    bool flattenPackage(BigBuffer* buffer) {
+        // We must do this before writing the resources, since the string pool IDs may change.
+        mTable->stringPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
+            int diff = a.context.priority - b.context.priority;
+            if (diff < 0) return true;
+            if (diff > 0) return false;
+            diff = a.context.config.compare(b.context.config);
+            if (diff < 0) return true;
+            if (diff > 0) return false;
+            return a.value < b.value;
+        });
+        mTable->stringPool.prune();
+
+        const size_t beginningIndex = buffer->size();
+
+        BigBuffer typeBuffer(1024);
+        if (!flattenTypes(&typeBuffer)) {
+            return false;
+        }
+
+        ChunkWriter tableWriter(buffer);
+        ResTable_header* tableHeader = tableWriter.startChunk<ResTable_header>(RES_TABLE_TYPE);
+        tableHeader->packageCount = util::hostToDevice32(1);
+
+        SymbolTable_entry* symbolEntryData = nullptr;
+        if (mOptions.useExtendedChunks && !mSymbols.symbols.empty()) {
+            // Sort the offsets so we can scan them linearly.
+            std::sort(mSymbols.symbols.begin(), mSymbols.symbols.end(),
+                      [](const SymbolWriter::Entry& a, const SymbolWriter::Entry& b) -> bool {
+                          return a.offset < b.offset;
+                      });
+
+            ChunkWriter symbolWriter(tableWriter.getBuffer());
+            SymbolTable_header* symbolHeader = symbolWriter.startChunk<SymbolTable_header>(
+                    RES_TABLE_SYMBOL_TABLE_TYPE);
+            symbolHeader->count = util::hostToDevice32(mSymbols.symbols.size());
+
+            symbolEntryData = symbolWriter.nextBlock<SymbolTable_entry>(mSymbols.symbols.size());
+            StringPool::flattenUtf8(symbolWriter.getBuffer(), mSymbols.pool);
+            symbolWriter.finish();
+        }
+
+        if (mOptions.useExtendedChunks && mSourcePool.size() > 0) {
+            // Write out source pool.
+            ChunkWriter srcWriter(tableWriter.getBuffer());
+            srcWriter.startChunk<ResChunk_header>(RES_TABLE_SOURCE_POOL_TYPE);
+            StringPool::flattenUtf8(srcWriter.getBuffer(), mSourcePool);
+            srcWriter.finish();
+        }
+
+        StringPool::flattenUtf8(tableWriter.getBuffer(), mTable->stringPool);
+
+        ChunkWriter pkgWriter(tableWriter.getBuffer());
+        ResTable_package* pkgHeader = pkgWriter.startChunk<ResTable_package>(
+                RES_TABLE_PACKAGE_TYPE);
+        pkgHeader->id = util::hostToDevice32(mPackage->id.value());
+
+        if (mPackage->name.size() >= NELEM(pkgHeader->name)) {
+            mDiag->error(DiagMessage() <<
+                         "package name '" << mPackage->name << "' is too long");
+            return false;
+        }
+
+        strcpy16_htod(pkgHeader->name, NELEM(pkgHeader->name), mPackage->name);
+
+        pkgHeader->typeStrings = util::hostToDevice32(pkgWriter.size());
+        StringPool::flattenUtf16(pkgWriter.getBuffer(), mTypePool);
+
+        pkgHeader->keyStrings = util::hostToDevice32(pkgWriter.size());
+        StringPool::flattenUtf16(pkgWriter.getBuffer(), mKeyPool);
+
+        // Actually write out the symbol entries if we have symbols.
+        if (symbolEntryData) {
+            for (auto& entry : mSymbols.symbols) {
+                symbolEntryData->stringIndex = util::hostToDevice32(entry.name.getIndex());
+
+                // The symbols were all calculated with the typeBuffer offset. We need to
+                // add the beginning of the output buffer.
+                symbolEntryData->offset = util::hostToDevice32(
+                        (pkgWriter.getBuffer()->size() - beginningIndex) + entry.offset);
+
+                symbolEntryData++;
+            }
+        }
+
+        // Write out the types and entries.
+        pkgWriter.getBuffer()->appendBuffer(std::move(typeBuffer));
+
+        pkgWriter.finish();
+        tableWriter.finish();
+        return true;
+    }
+};
+
+} // namespace
+
+bool TableFlattener::consume(IAaptContext* context, ResourceTable* table) {
+    for (auto& package : table->packages) {
+        // Only support flattening one package. Since the StringPool is shared between packages
+        // in ResourceTable, we must fail if other packages are present, since their strings
+        // will be included in the final ResourceTable.
+        if (context->getCompilationPackage() != package->name) {
+            context->getDiagnostics()->error(DiagMessage()
+                                             << "resources for package '" << package->name
+                                             << "' can't be flattened when compiling package '"
+                                             << context->getCompilationPackage() << "'");
+            return false;
+        }
+
+        if (!package->id || package->id.value() != context->getPackageId()) {
+            context->getDiagnostics()->error(DiagMessage()
+                                             << "package '" << package->name << "' must have "
+                                             << "package id "
+                                             << std::hex << context->getPackageId() << std::dec);
+            return false;
+        }
+
+        PackageFlattener flattener = {
+                context->getDiagnostics(),
+                mOptions,
+                table,
+                package.get()
+        };
+
+        if (!flattener.flattenPackage(mBuffer)) {
+            return false;
+        }
+        return true;
+    }
+
+    context->getDiagnostics()->error(DiagMessage()
+                                     << "compilation package '" << context->getCompilationPackage()
+                                     << "' not found");
+    return false;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/flatten/TableFlattener.h b/tools/aapt2/flatten/TableFlattener.h
new file mode 100644
index 0000000..901b129
--- /dev/null
+++ b/tools/aapt2/flatten/TableFlattener.h
@@ -0,0 +1,53 @@
+/*
+ * 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_FLATTEN_TABLEFLATTENER_H
+#define AAPT_FLATTEN_TABLEFLATTENER_H
+
+#include "process/IResourceTableConsumer.h"
+
+namespace aapt {
+
+class BigBuffer;
+class ResourceTable;
+
+struct TableFlattenerOptions {
+    /**
+     * Specifies whether to output extended chunks, like
+     * source information and missing symbol entries. Default
+     * is false.
+     *
+     * Set this to true when emitting intermediate resource table.
+     */
+    bool useExtendedChunks = false;
+};
+
+class TableFlattener : public IResourceTableConsumer {
+public:
+    TableFlattener(BigBuffer* buffer, TableFlattenerOptions options) :
+            mBuffer(buffer), mOptions(options) {
+    }
+
+    bool consume(IAaptContext* context, ResourceTable* table) override;
+
+private:
+    BigBuffer* mBuffer;
+    TableFlattenerOptions mOptions;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_FLATTEN_TABLEFLATTENER_H */
diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp
new file mode 100644
index 0000000..68a1f47
--- /dev/null
+++ b/tools/aapt2/flatten/TableFlattener_test.cpp
@@ -0,0 +1,265 @@
+/*
+ * 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 "flatten/TableFlattener.h"
+#include "unflatten/BinaryResourceParser.h"
+#include "util/Util.h"
+
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+using namespace android;
+
+namespace aapt {
+
+class TableFlattenerTest : public ::testing::Test {
+public:
+    void SetUp() override {
+        mContext = test::ContextBuilder()
+                .setCompilationPackage(u"com.app.test")
+                .setPackageId(0x7f)
+                .build();
+    }
+
+    ::testing::AssertionResult flatten(ResourceTable* table, ResTable* outTable) {
+        BigBuffer buffer(1024);
+        TableFlattenerOptions options = {};
+        options.useExtendedChunks = true;
+        TableFlattener flattener(&buffer, options);
+        if (!flattener.consume(mContext.get(), table)) {
+            return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
+        }
+
+        std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+        if (outTable->add(data.get(), buffer.size(), -1, true) != NO_ERROR) {
+            return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
+        }
+        return ::testing::AssertionSuccess();
+    }
+
+    ::testing::AssertionResult flatten(ResourceTable* table, ResourceTable* outTable) {
+        BigBuffer buffer(1024);
+        TableFlattenerOptions options = {};
+        options.useExtendedChunks = true;
+        TableFlattener flattener(&buffer, options);
+        if (!flattener.consume(mContext.get(), table)) {
+            return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
+        }
+
+        std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+        BinaryResourceParser parser(mContext.get(), outTable, {}, data.get(), buffer.size());
+        if (!parser.parse()) {
+            return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
+        }
+        return ::testing::AssertionSuccess();
+    }
+
+    ::testing::AssertionResult exists(ResTable* table,
+                                      const StringPiece16& expectedName,
+                                      const ResourceId expectedId,
+                                      const ConfigDescription& expectedConfig,
+                                      const uint8_t expectedDataType, const uint32_t expectedData,
+                                      const uint32_t expectedSpecFlags) {
+        const ResourceName expectedResName = test::parseNameOrDie(expectedName);
+
+        table->setParameters(&expectedConfig);
+
+        ResTable_config config;
+        Res_value val;
+        uint32_t specFlags;
+        if (table->getResource(expectedId.id, &val, false, 0, &specFlags, &config) < 0) {
+            return ::testing::AssertionFailure() << "could not find resource with";
+        }
+
+        if (expectedDataType != val.dataType) {
+            return ::testing::AssertionFailure()
+                    << "expected data type "
+                    << std::hex << (int) expectedDataType << " but got data type "
+                    << (int) val.dataType << std::dec << " instead";
+        }
+
+        if (expectedData != val.data) {
+            return ::testing::AssertionFailure()
+                    << "expected data "
+                    << std::hex << expectedData << " but got data "
+                    << val.data << std::dec << " instead";
+        }
+
+        if (expectedSpecFlags != specFlags) {
+            return ::testing::AssertionFailure()
+                    << "expected specFlags "
+                    << std::hex << expectedSpecFlags << " but got specFlags "
+                    << specFlags << std::dec << " instead";
+        }
+
+        ResTable::resource_name actualName;
+        if (!table->getResourceName(expectedId.id, false, &actualName)) {
+            return ::testing::AssertionFailure() << "failed to find resource name";
+        }
+
+        StringPiece16 package16(actualName.package, actualName.packageLen);
+        if (package16 != expectedResName.package) {
+            return ::testing::AssertionFailure()
+                    << "expected package '" << expectedResName.package << "' but got '"
+                    << package16 << "'";
+        }
+
+        StringPiece16 type16(actualName.type, actualName.typeLen);
+        if (type16 != toString(expectedResName.type)) {
+            return ::testing::AssertionFailure()
+                    << "expected type '" << expectedResName.type
+                    << "' but got '" << type16 << "'";
+        }
+
+        StringPiece16 name16(actualName.name, actualName.nameLen);
+        if (name16 != expectedResName.entry) {
+            return ::testing::AssertionFailure()
+                    << "expected name '" << expectedResName.entry
+                    << "' but got '" << name16 << "'";
+        }
+
+        if (expectedConfig != config) {
+            return ::testing::AssertionFailure()
+                    << "expected config '" << expectedConfig << "' but got '"
+                    << ConfigDescription(config) << "'";
+        }
+        return ::testing::AssertionSuccess();
+    }
+
+private:
+    std::unique_ptr<IAaptContext> mContext;
+};
+
+TEST_F(TableFlattenerTest, FlattenFullyLinkedTable) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.test", 0x7f)
+            .addSimple(u"@com.app.test:id/one", ResourceId(0x7f020000))
+            .addSimple(u"@com.app.test:id/two", ResourceId(0x7f020001))
+            .addValue(u"@com.app.test:id/three", ResourceId(0x7f020002),
+                      test::buildReference(u"@com.app.test:id/one", ResourceId(0x7f020000)))
+            .addValue(u"@com.app.test:integer/one", ResourceId(0x7f030000),
+                      util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u))
+            .addValue(u"@com.app.test:integer/one", ResourceId(0x7f030000),
+                      test::parseConfigOrDie("v1"),
+                      util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u))
+            .addString(u"@com.app.test:string/test", ResourceId(0x7f040000), u"foo")
+            .addString(u"@com.app.test:layout/bar", ResourceId(0x7f050000), u"res/layout/bar.xml")
+            .build();
+
+    ResTable resTable;
+    ASSERT_TRUE(flatten(table.get(), &resTable));
+
+    EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/one", ResourceId(0x7f020000), {},
+                       Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+
+    EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/two", ResourceId(0x7f020001), {},
+                       Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+
+    EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/three", ResourceId(0x7f020002), {},
+                       Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
+
+    EXPECT_TRUE(exists(&resTable, u"@com.app.test:integer/one", ResourceId(0x7f030000),
+                       {}, Res_value::TYPE_INT_DEC, 1u,
+                       ResTable_config::CONFIG_VERSION));
+
+    EXPECT_TRUE(exists(&resTable, u"@com.app.test:integer/one", ResourceId(0x7f030000),
+                       test::parseConfigOrDie("v1"), Res_value::TYPE_INT_DEC, 2u,
+                       ResTable_config::CONFIG_VERSION));
+
+    StringPiece16 fooStr = u"foo";
+    ssize_t idx = resTable.getTableStringBlock(0)->indexOfString(fooStr.data(), fooStr.size());
+    ASSERT_GE(idx, 0);
+    EXPECT_TRUE(exists(&resTable, u"@com.app.test:string/test", ResourceId(0x7f040000),
+                       {}, Res_value::TYPE_STRING, (uint32_t) idx, 0u));
+
+    StringPiece16 barPath = u"res/layout/bar.xml";
+    idx = resTable.getTableStringBlock(0)->indexOfString(barPath.data(), barPath.size());
+    ASSERT_GE(idx, 0);
+    EXPECT_TRUE(exists(&resTable, u"@com.app.test:layout/bar", ResourceId(0x7f050000), {},
+                       Res_value::TYPE_STRING, (uint32_t) idx, 0u));
+}
+
+TEST_F(TableFlattenerTest, FlattenEntriesWithGapsInIds) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.test", 0x7f)
+            .addSimple(u"@com.app.test:id/one", ResourceId(0x7f020001))
+            .addSimple(u"@com.app.test:id/three", ResourceId(0x7f020003))
+            .build();
+
+    ResTable resTable;
+    ASSERT_TRUE(flatten(table.get(), &resTable));
+
+    EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/one", ResourceId(0x7f020001), {},
+                       Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+    EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/three", ResourceId(0x7f020003), {},
+                           Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+}
+
+TEST_F(TableFlattenerTest, FlattenUnlinkedTable) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.test", 0x7f)
+            .addValue(u"@com.app.test:integer/one", ResourceId(0x7f020000),
+                      test::buildReference(u"@android:integer/foo"))
+            .addValue(u"@com.app.test:style/Theme", ResourceId(0x7f030000), test::StyleBuilder()
+                    .setParent(u"@android:style/Theme.Material")
+                    .addItem(u"@android:attr/background", {})
+                    .addItem(u"@android:attr/colorAccent",
+                             test::buildReference(u"@com.app.test:color/green"))
+                    .build())
+            .build();
+
+    {
+        // Need access to stringPool to make RawString.
+        Style* style = test::getValue<Style>(table.get(), u"@com.app.test:style/Theme");
+        style->entries[0].value = util::make_unique<RawString>(table->stringPool.makeRef(u"foo"));
+    }
+
+    ResourceTable finalTable;
+    ASSERT_TRUE(flatten(table.get(), &finalTable));
+
+    Reference* ref = test::getValue<Reference>(&finalTable, u"@com.app.test:integer/one");
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->name);
+    EXPECT_EQ(ref->name.value(), test::parseNameOrDie(u"@android:integer/foo"));
+
+    Style* style = test::getValue<Style>(&finalTable, u"@com.app.test:style/Theme");
+    ASSERT_NE(style, nullptr);
+    AAPT_ASSERT_TRUE(style->parent);
+    AAPT_ASSERT_TRUE(style->parent.value().name);
+    EXPECT_EQ(style->parent.value().name.value(),
+              test::parseNameOrDie(u"@android:style/Theme.Material"));
+
+    ASSERT_EQ(2u, style->entries.size());
+
+    AAPT_ASSERT_TRUE(style->entries[0].key.name);
+    EXPECT_EQ(style->entries[0].key.name.value(),
+              test::parseNameOrDie(u"@android:attr/background"));
+    RawString* raw = valueCast<RawString>(style->entries[0].value.get());
+    ASSERT_NE(raw, nullptr);
+    EXPECT_EQ(*raw->value, u"foo");
+
+    AAPT_ASSERT_TRUE(style->entries[1].key.name);
+    EXPECT_EQ(style->entries[1].key.name.value(),
+              test::parseNameOrDie(u"@android:attr/colorAccent"));
+    ref = valueCast<Reference>(style->entries[1].value.get());
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->name);
+    EXPECT_EQ(ref->name.value(), test::parseNameOrDie(u"@com.app.test:color/green"));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp
new file mode 100644
index 0000000..4efb08bfe
--- /dev/null
+++ b/tools/aapt2/flatten/XmlFlattener.cpp
@@ -0,0 +1,316 @@
+/*
+ * 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 "SdkConstants.h"
+#include "XmlDom.h"
+
+#include "flatten/ChunkWriter.h"
+#include "flatten/ResourceTypeExtensions.h"
+#include "flatten/XmlFlattener.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <vector>
+#include <utils/misc.h>
+
+using namespace android;
+
+namespace aapt {
+
+namespace {
+
+constexpr uint32_t kLowPriority = 0xffffffffu;
+
+struct XmlFlattenerVisitor : public xml::Visitor {
+    using xml::Visitor::visit;
+
+    BigBuffer* mBuffer;
+    XmlFlattenerOptions mOptions;
+    StringPool mPool;
+    std::map<uint8_t, StringPool> mPackagePools;
+
+    struct StringFlattenDest {
+        StringPool::Ref ref;
+        ResStringPool_ref* dest;
+    };
+    std::vector<StringFlattenDest> mStringRefs;
+
+    // Scratch vector to filter attributes. We avoid allocations
+    // making this a member.
+    std::vector<xml::Attribute*> mFilteredAttrs;
+
+
+    XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options) :
+            mBuffer(buffer), mOptions(options) {
+    }
+
+    void addString(const StringPiece16& str, uint32_t priority, android::ResStringPool_ref* dest) {
+        if (!str.empty()) {
+            mStringRefs.push_back(StringFlattenDest{
+                    mPool.makeRef(str, StringPool::Context{ priority }),
+                    dest });
+        } else {
+            // The device doesn't think a string of size 0 is the same as null.
+            dest->index = util::deviceToHost32(-1);
+        }
+    }
+
+    void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
+        mStringRefs.push_back(StringFlattenDest{ ref, dest });
+    }
+
+    void writeNamespace(xml::Namespace* node, uint16_t type) {
+        ChunkWriter writer(mBuffer);
+
+        ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(type);
+        flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
+        flatNode->comment.index = util::hostToDevice32(-1);
+
+        ResXMLTree_namespaceExt* flatNs = writer.nextBlock<ResXMLTree_namespaceExt>();
+        addString(node->namespacePrefix, kLowPriority, &flatNs->prefix);
+        addString(node->namespaceUri, kLowPriority, &flatNs->uri);
+
+        writer.finish();
+    }
+
+    void visit(xml::Namespace* node) override {
+        writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE);
+        xml::Visitor::visit(node);
+        writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE);
+    }
+
+    void visit(xml::Text* node) override {
+        if (util::trimWhitespace(node->text).empty()) {
+            // Skip whitespace only text nodes.
+            return;
+        }
+
+        ChunkWriter writer(mBuffer);
+        ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE);
+        flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
+        flatNode->comment.index = util::hostToDevice32(-1);
+
+        ResXMLTree_cdataExt* flatText = writer.nextBlock<ResXMLTree_cdataExt>();
+        addString(node->text, kLowPriority, &flatText->data);
+
+        writer.finish();
+    }
+
+    void visit(xml::Element* node) override {
+        {
+            ChunkWriter startWriter(mBuffer);
+            ResXMLTree_node* flatNode = startWriter.startChunk<ResXMLTree_node>(
+                    RES_XML_START_ELEMENT_TYPE);
+            flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
+            flatNode->comment.index = util::hostToDevice32(-1);
+
+            ResXMLTree_attrExt* flatElem = startWriter.nextBlock<ResXMLTree_attrExt>();
+            addString(node->namespaceUri, kLowPriority, &flatElem->ns);
+            addString(node->name, kLowPriority, &flatElem->name);
+            flatElem->attributeStart = util::hostToDevice16(sizeof(*flatElem));
+            flatElem->attributeSize = util::hostToDevice16(sizeof(ResXMLTree_attribute));
+
+            writeAttributes(node, flatElem, &startWriter);
+
+            startWriter.finish();
+        }
+
+        xml::Visitor::visit(node);
+
+        {
+            ChunkWriter endWriter(mBuffer);
+            ResXMLTree_node* flatEndNode = endWriter.startChunk<ResXMLTree_node>(
+                    RES_XML_END_ELEMENT_TYPE);
+            flatEndNode->lineNumber = util::hostToDevice32(node->lineNumber);
+            flatEndNode->comment.index = util::hostToDevice32(-1);
+
+            ResXMLTree_endElementExt* flatEndElem = endWriter.nextBlock<ResXMLTree_endElementExt>();
+            addString(node->namespaceUri, kLowPriority, &flatEndElem->ns);
+            addString(node->name, kLowPriority, &flatEndElem->name);
+
+            endWriter.finish();
+        }
+    }
+
+    static bool cmpXmlAttributeById(const xml::Attribute* a, const xml::Attribute* b) {
+        if (a->compiledAttribute) {
+            if (b->compiledAttribute) {
+                return a->compiledAttribute.value().id < b->compiledAttribute.value().id;
+            }
+            return true;
+        } else if (!b->compiledAttribute) {
+            int diff = a->namespaceUri.compare(b->namespaceUri);
+            if (diff < 0) {
+                return true;
+            } else if (diff > 0) {
+                return false;
+            }
+            return a->name < b->name;
+        }
+        return false;
+    }
+
+    void writeAttributes(xml::Element* node, ResXMLTree_attrExt* flatElem, ChunkWriter* writer) {
+        mFilteredAttrs.clear();
+        mFilteredAttrs.reserve(node->attributes.size());
+
+        // Filter the attributes.
+        for (xml::Attribute& attr : node->attributes) {
+            if (mOptions.maxSdkLevel && attr.compiledAttribute) {
+                size_t sdkLevel = findAttributeSdkLevel(attr.compiledAttribute.value().id);
+                if (sdkLevel > mOptions.maxSdkLevel.value()) {
+                    continue;
+                }
+            }
+            mFilteredAttrs.push_back(&attr);
+        }
+
+        if (mFilteredAttrs.empty()) {
+            return;
+        }
+
+        const ResourceId kIdAttr(0x010100d0);
+
+        std::sort(mFilteredAttrs.begin(), mFilteredAttrs.end(), cmpXmlAttributeById);
+
+        flatElem->attributeCount = util::hostToDevice16(mFilteredAttrs.size());
+
+        ResXMLTree_attribute* flatAttr = writer->nextBlock<ResXMLTree_attribute>(
+                mFilteredAttrs.size());
+        uint16_t attributeIndex = 1;
+        for (const xml::Attribute* xmlAttr : mFilteredAttrs) {
+            // Assign the indices for specific attributes.
+            if (xmlAttr->compiledAttribute &&
+                    xmlAttr->compiledAttribute.value().id == kIdAttr) {
+                flatElem->idIndex = util::hostToDevice16(attributeIndex);
+            } else if (xmlAttr->namespaceUri.empty()) {
+                if (xmlAttr->name == u"class") {
+                    flatElem->classIndex = util::hostToDevice16(attributeIndex);
+                } else if (xmlAttr->name == u"style") {
+                    flatElem->styleIndex = util::hostToDevice16(attributeIndex);
+                }
+            }
+            attributeIndex++;
+
+            // Add the namespaceUri to the list of StringRefs to encode.
+            addString(xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns);
+
+            flatAttr->rawValue.index = util::hostToDevice32(-1);
+
+            if (!xmlAttr->compiledAttribute) {
+                // The attribute has no associated ResourceID, so the string order doesn't matter.
+                addString(xmlAttr->name, kLowPriority, &flatAttr->name);
+            } else {
+                // Attribute names are stored without packages, but we use
+                // their StringPool index to lookup their resource IDs.
+                // This will cause collisions, so we can't dedupe
+                // attribute names from different packages. We use separate
+                // pools that we later combine.
+                //
+                // Lookup the StringPool for this package and make the reference there.
+                const xml::AaptAttribute& aaptAttr = xmlAttr->compiledAttribute.value();
+
+                StringPool::Ref nameRef = mPackagePools[aaptAttr.id.packageId()].makeRef(
+                        xmlAttr->name, StringPool::Context{ aaptAttr.id.id });
+
+                // Add it to the list of strings to flatten.
+                addString(nameRef, &flatAttr->name);
+
+                if (mOptions.keepRawValues) {
+                    // Keep raw values (this is for static libraries).
+                    // TODO(with a smarter inflater for binary XML, we can do without this).
+                    addString(xmlAttr->value, kLowPriority, &flatAttr->rawValue);
+                }
+            }
+
+            if (xmlAttr->compiledValue) {
+                bool result = xmlAttr->compiledValue->flatten(&flatAttr->typedValue);
+                assert(result);
+            } else {
+                // Flatten as a regular string type.
+                flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
+                addString(xmlAttr->value, kLowPriority, &flatAttr->rawValue);
+                addString(xmlAttr->value, kLowPriority,
+                          (ResStringPool_ref*) &flatAttr->typedValue.data);
+            }
+
+            flatAttr->typedValue.size = util::hostToDevice16(sizeof(flatAttr->typedValue));
+            flatAttr++;
+        }
+    }
+};
+
+} // namespace
+
+bool XmlFlattener::flatten(IAaptContext* context, xml::Node* node) {
+    BigBuffer nodeBuffer(1024);
+    XmlFlattenerVisitor visitor(&nodeBuffer, mOptions);
+    node->accept(&visitor);
+
+    // Merge the package pools into the main pool.
+    for (auto& packagePoolEntry : visitor.mPackagePools) {
+        visitor.mPool.merge(std::move(packagePoolEntry.second));
+    }
+
+    // Sort the string pool so that attribute resource IDs show up first.
+    visitor.mPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
+        return a.context.priority < b.context.priority;
+    });
+
+    // Now we flatten the string pool references into the correct places.
+    for (const auto& refEntry : visitor.mStringRefs) {
+        refEntry.dest->index = util::hostToDevice32(refEntry.ref.getIndex());
+    }
+
+    // Write the XML header.
+    ChunkWriter xmlHeaderWriter(mBuffer);
+    xmlHeaderWriter.startChunk<ResXMLTree_header>(RES_XML_TYPE);
+
+    // Flatten the StringPool.
+    StringPool::flattenUtf16(mBuffer, visitor.mPool);
+
+    {
+        // Write the array of resource IDs, indexed by StringPool order.
+        ChunkWriter resIdMapWriter(mBuffer);
+        resIdMapWriter.startChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE);
+        for (const auto& str : visitor.mPool) {
+            ResourceId id = { str->context.priority };
+            if (id.id == kLowPriority || !id.isValid()) {
+                // When we see the first non-resource ID,
+                // we're done.
+                break;
+            }
+
+            *resIdMapWriter.nextBlock<uint32_t>() = id.id;
+        }
+        resIdMapWriter.finish();
+    }
+
+    // Move the nodeBuffer and append it to the out buffer.
+    mBuffer->appendBuffer(std::move(nodeBuffer));
+
+    // Finish the xml header.
+    xmlHeaderWriter.finish();
+    return true;
+}
+
+bool XmlFlattener::consume(IAaptContext* context, XmlResource* resource) {
+    if (!resource->root) {
+        return false;
+    }
+    return flatten(context, resource->root.get());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/flatten/XmlFlattener.h b/tools/aapt2/flatten/XmlFlattener.h
new file mode 100644
index 0000000..b1fb3a7
--- /dev/null
+++ b/tools/aapt2/flatten/XmlFlattener.h
@@ -0,0 +1,59 @@
+/*
+ * 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_FLATTEN_XMLFLATTENER_H
+#define AAPT_FLATTEN_XMLFLATTENER_H
+
+#include "util/BigBuffer.h"
+
+#include "process/IResourceTableConsumer.h"
+
+namespace aapt {
+
+namespace xml {
+struct Node;
+}
+
+struct XmlFlattenerOptions {
+    /**
+     * Keep attribute raw string values along with typed values.
+     */
+    bool keepRawValues = false;
+
+    /**
+     * If set, the max SDK level of attribute to flatten. All others are ignored.
+     */
+    Maybe<size_t> maxSdkLevel;
+};
+
+class XmlFlattener : public IXmlResourceConsumer {
+public:
+    XmlFlattener(BigBuffer* buffer, XmlFlattenerOptions options) :
+            mBuffer(buffer), mOptions(options) {
+    }
+
+    bool consume(IAaptContext* context, XmlResource* resource) override;
+
+private:
+    BigBuffer* mBuffer;
+    XmlFlattenerOptions mOptions;
+
+    bool flatten(IAaptContext* context, xml::Node* node);
+};
+
+} // namespace aapt
+
+#endif /* AAPT_FLATTEN_XMLFLATTENER_H */
diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp
new file mode 100644
index 0000000..318bcdd
--- /dev/null
+++ b/tools/aapt2/flatten/XmlFlattener_test.cpp
@@ -0,0 +1,209 @@
+/*
+ * 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 "flatten/XmlFlattener.h"
+#include "link/Linkers.h"
+#include "util/BigBuffer.h"
+#include "util/Util.h"
+
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+class XmlFlattenerTest : public ::testing::Test {
+public:
+    void SetUp() override {
+        mContext = test::ContextBuilder()
+                .setCompilationPackage(u"com.app.test")
+                .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" })
+                .setSymbolTable(test::StaticSymbolTableBuilder()
+                        .addSymbol(u"@android:attr/id", ResourceId(0x010100d0),
+                                   test::AttributeBuilder().build())
+                        .addSymbol(u"@com.app.test:id/id", ResourceId(0x7f020000))
+                        .addSymbol(u"@android:attr/paddingStart", ResourceId(0x010103b3),
+                                   test::AttributeBuilder().build())
+                        .addSymbol(u"@android:attr/colorAccent", ResourceId(0x01010435),
+                                   test::AttributeBuilder().build())
+                        .build())
+                .build();
+    }
+
+    ::testing::AssertionResult flatten(XmlResource* doc, android::ResXMLTree* outTree,
+                                       XmlFlattenerOptions options = {}) {
+        BigBuffer buffer(1024);
+        XmlFlattener flattener(&buffer, options);
+        if (!flattener.consume(mContext.get(), doc)) {
+            return ::testing::AssertionFailure() << "failed to flatten XML Tree";
+        }
+
+        std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+        if (outTree->setTo(data.get(), buffer.size(), true) != android::NO_ERROR) {
+            return ::testing::AssertionFailure() << "flattened XML is corrupt";
+        }
+        return ::testing::AssertionSuccess();
+    }
+
+protected:
+    std::unique_ptr<IAaptContext> mContext;
+};
+
+TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+            <View xmlns:test="http://com.test"
+                  attr="hey">
+              <Layout test:hello="hi" />
+              <Layout>Some text</Layout>
+            </View>)EOF");
+
+
+    android::ResXMLTree tree;
+    ASSERT_TRUE(flatten(doc.get(), &tree));
+
+    ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE);
+
+    size_t len;
+    const char16_t* namespacePrefix = tree.getNamespacePrefix(&len);
+    EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test");
+
+    const char16_t* namespaceUri = tree.getNamespaceUri(&len);
+    ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test");
+
+    ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
+
+    ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
+    const char16_t* tagName = tree.getElementName(&len);
+    EXPECT_EQ(StringPiece16(tagName, len), u"View");
+
+    ASSERT_EQ(1u, tree.getAttributeCount());
+    ASSERT_EQ(tree.getAttributeNamespace(0, &len), nullptr);
+    const char16_t* attrName = tree.getAttributeName(0, &len);
+    EXPECT_EQ(StringPiece16(attrName, len), u"attr");
+
+    EXPECT_EQ(0, tree.indexOfAttribute(nullptr, 0, u"attr", StringPiece16(u"attr").size()));
+
+    ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
+
+    ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
+    tagName = tree.getElementName(&len);
+    EXPECT_EQ(StringPiece16(tagName, len), u"Layout");
+
+    ASSERT_EQ(1u, tree.getAttributeCount());
+    const char16_t* attrNamespace = tree.getAttributeNamespace(0, &len);
+    EXPECT_EQ(StringPiece16(attrNamespace, len), u"http://com.test");
+
+    attrName = tree.getAttributeName(0, &len);
+    EXPECT_EQ(StringPiece16(attrName, len), u"hello");
+
+    ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
+    ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
+
+    ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
+    tagName = tree.getElementName(&len);
+    EXPECT_EQ(StringPiece16(tagName, len), u"Layout");
+    ASSERT_EQ(0u, tree.getAttributeCount());
+
+    ASSERT_EQ(tree.next(), android::ResXMLTree::TEXT);
+    const char16_t* text = tree.getText(&len);
+    EXPECT_EQ(StringPiece16(text, len), u"Some text");
+
+    ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
+    ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
+    tagName = tree.getElementName(&len);
+    EXPECT_EQ(StringPiece16(tagName, len), u"Layout");
+
+    ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
+    ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
+    tagName = tree.getElementName(&len);
+    EXPECT_EQ(StringPiece16(tagName, len), u"View");
+
+    ASSERT_EQ(tree.next(), android::ResXMLTree::END_NAMESPACE);
+    namespacePrefix = tree.getNamespacePrefix(&len);
+    EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test");
+
+    namespaceUri = tree.getNamespaceUri(&len);
+    ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test");
+
+    ASSERT_EQ(tree.next(), android::ResXMLTree::END_DOCUMENT);
+}
+
+TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripSdk21) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+            <View xmlns:android="http://schemas.android.com/apk/res/android"
+                android:paddingStart="1dp"
+                android:colorAccent="#ffffff"/>)EOF");
+
+    XmlReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+    ASSERT_TRUE(linker.getSdkLevels().count(17) == 1);
+    ASSERT_TRUE(linker.getSdkLevels().count(21) == 1);
+
+    android::ResXMLTree tree;
+    XmlFlattenerOptions options;
+    options.maxSdkLevel = 17;
+    ASSERT_TRUE(flatten(doc.get(), &tree, options));
+
+    while (tree.next() != android::ResXMLTree::START_TAG) {
+        ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
+        ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
+    }
+
+    ASSERT_EQ(1u, tree.getAttributeCount());
+    EXPECT_EQ(uint32_t(0x010103b3), tree.getAttributeNameResID(0));
+}
+
+TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+            <View xmlns:android="http://schemas.android.com/apk/res/android"
+                  android:id="@id/id"
+                  class="str"
+                  style="@id/id"/>)EOF");
+
+    android::ResXMLTree tree;
+    ASSERT_TRUE(flatten(doc.get(), &tree));
+
+    while (tree.next() != android::ResXMLTree::START_TAG) {
+        ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
+        ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
+    }
+
+    EXPECT_EQ(tree.indexOfClass(), 0);
+    EXPECT_EQ(tree.indexOfStyle(), 1);
+}
+
+/*
+ * The device ResXMLParser in libandroidfw differentiates between empty namespace and null
+ * namespace.
+ */
+TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom("<View package=\"android\"/>");
+
+    android::ResXMLTree tree;
+    ASSERT_TRUE(flatten(doc.get(), &tree));
+
+    while (tree.next() != android::ResXMLTree::START_TAG) {
+        ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
+        ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
+    }
+
+    const StringPiece16 kPackage = u"package";
+    EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/AutoVersioner.cpp b/tools/aapt2/link/AutoVersioner.cpp
new file mode 100644
index 0000000..0ccafc2
--- /dev/null
+++ b/tools/aapt2/link/AutoVersioner.cpp
@@ -0,0 +1,146 @@
+/*
+ * 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 "ConfigDescription.h"
+#include "ResourceTable.h"
+#include "SdkConstants.h"
+#include "ValueVisitor.h"
+
+#include "link/Linkers.h"
+
+#include <algorithm>
+#include <cassert>
+
+namespace aapt {
+
+static bool cmpConfigValue(const ResourceConfigValue& lhs, const ConfigDescription& config) {
+    return lhs.config < config;
+}
+
+bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config,
+                                     const int sdkVersionToGenerate) {
+    assert(sdkVersionToGenerate > config.sdkVersion);
+    const auto endIter = entry->values.end();
+    auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmpConfigValue);
+
+    // The source config came from this list, so it should be here.
+    assert(iter != entry->values.end());
+    ++iter;
+
+    // The next configuration either only varies in sdkVersion, or it is completely different
+    // and therefore incompatible. If it is incompatible, we must generate the versioned resource.
+
+    // NOTE: The ordering of configurations takes sdkVersion as higher precedence than other
+    // qualifiers, so we need to iterate through the entire list to be sure there
+    // are no higher sdk level versions of this resource.
+    ConfigDescription tempConfig(config);
+    for (; iter != endIter; ++iter) {
+        tempConfig.sdkVersion = iter->config.sdkVersion;
+        if (tempConfig == iter->config) {
+            // The two configs are the same, check the sdk version.
+            return sdkVersionToGenerate < iter->config.sdkVersion;
+        }
+    }
+
+    // No match was found, so we should generate the versioned resource.
+    return true;
+}
+
+bool AutoVersioner::consume(IAaptContext* context, ResourceTable* table) {
+    for (auto& package : table->packages) {
+        for (auto& type : package->types) {
+            if (type->type != ResourceType::kStyle) {
+                continue;
+            }
+
+            for (auto& entry : type->entries) {
+                for (size_t i = 0; i < entry->values.size(); i++) {
+                    ResourceConfigValue& configValue = entry->values[i];
+                    if (configValue.config.sdkVersion >= SDK_LOLLIPOP_MR1) {
+                        // If this configuration is only used on L-MR1 then we don't need
+                        // to do anything since we use private attributes since that version.
+                        continue;
+                    }
+
+                    if (Style* style = valueCast<Style>(configValue.value.get())) {
+                        Maybe<size_t> minSdkStripped;
+                        std::vector<Style::Entry> stripped;
+
+                        auto iter = style->entries.begin();
+                        while (iter != style->entries.end()) {
+                            assert(iter->key.id && "IDs must be assigned and linked");
+
+                            // Find the SDK level that is higher than the configuration allows.
+                            const size_t sdkLevel = findAttributeSdkLevel(iter->key.id.value());
+                            if (sdkLevel > std::max<size_t>(configValue.config.sdkVersion, 1)) {
+                                // Record that we are about to strip this.
+                                stripped.emplace_back(std::move(*iter));
+
+                                // We use the smallest SDK level to generate the new style.
+                                if (minSdkStripped) {
+                                    minSdkStripped = std::min(minSdkStripped.value(), sdkLevel);
+                                } else {
+                                    minSdkStripped = sdkLevel;
+                                }
+
+                                // Erase this from this style.
+                                iter = style->entries.erase(iter);
+                                continue;
+                            }
+                            ++iter;
+                        }
+
+                        if (minSdkStripped && !stripped.empty()) {
+                            // We found attributes from a higher SDK level. Check that
+                            // there is no other defined resource for the version we want to
+                            // generate.
+                            if (shouldGenerateVersionedResource(entry.get(), configValue.config,
+                                                                minSdkStripped.value())) {
+                                // Let's create a new Style for this versioned resource.
+                                ConfigDescription newConfig(configValue.config);
+                                newConfig.sdkVersion = minSdkStripped.value();
+
+                                ResourceConfigValue newValue = {
+                                        newConfig,
+                                        configValue.source,
+                                        configValue.comment,
+                                        std::unique_ptr<Value>(configValue.value->clone(
+                                                &table->stringPool))
+                                };
+
+                                Style* newStyle = static_cast<Style*>(newValue.value.get());
+
+                                // Move the previously stripped attributes into this style.
+                                newStyle->entries.insert(newStyle->entries.end(),
+                                                         std::make_move_iterator(stripped.begin()),
+                                                         std::make_move_iterator(stripped.end()));
+
+                                // Insert the new Resource into the correct place.
+                                auto iter = std::lower_bound(entry->values.begin(),
+                                                             entry->values.end(), newConfig,
+                                                             cmpConfigValue);
+                                entry->values.insert(iter, std::move(newValue));
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/AutoVersioner_test.cpp b/tools/aapt2/link/AutoVersioner_test.cpp
new file mode 100644
index 0000000..29bcc93
--- /dev/null
+++ b/tools/aapt2/link/AutoVersioner_test.cpp
@@ -0,0 +1,127 @@
+/*
+ * 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 "ConfigDescription.h"
+
+#include "link/Linkers.h"
+
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(AutoVersionerTest, GenerateVersionedResources) {
+    const ConfigDescription defaultConfig = {};
+    const ConfigDescription landConfig = test::parseConfigOrDie("land");
+    const ConfigDescription sw600dpLandConfig = test::parseConfigOrDie("sw600dp-land");
+
+    ResourceEntry entry(u"foo");
+    entry.values.push_back(ResourceConfigValue{ defaultConfig });
+    entry.values.push_back(ResourceConfigValue{ landConfig });
+    entry.values.push_back(ResourceConfigValue{ sw600dpLandConfig });
+
+    EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17));
+    EXPECT_TRUE(shouldGenerateVersionedResource(&entry, landConfig, 17));
+}
+
+TEST(AutoVersionerTest, GenerateVersionedResourceWhenHigherVersionExists) {
+    const ConfigDescription defaultConfig = {};
+    const ConfigDescription sw600dpV13Config = test::parseConfigOrDie("sw600dp-v13");
+    const ConfigDescription v21Config = test::parseConfigOrDie("v21");
+
+    ResourceEntry entry(u"foo");
+    entry.values.push_back(ResourceConfigValue{ defaultConfig });
+    entry.values.push_back(ResourceConfigValue{ sw600dpV13Config });
+    entry.values.push_back(ResourceConfigValue{ v21Config });
+
+    EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17));
+    EXPECT_FALSE(shouldGenerateVersionedResource(&entry, defaultConfig, 22));
+}
+
+TEST(AutoVersionerTest, VersionStylesForTable) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"app", 0x7f)
+            .addValue(u"@app:style/Foo", ResourceId(0x7f020000), test::parseConfigOrDie("v4"),
+                      test::StyleBuilder()
+                            .addItem(u"@android:attr/onClick", ResourceId(0x0101026f),
+                                     util::make_unique<Id>())
+                            .addItem(u"@android:attr/paddingStart", ResourceId(0x010103b3),
+                                     util::make_unique<Id>())
+                            .addItem(u"@android:attr/requiresSmallestWidthDp",
+                                     ResourceId(0x01010364), util::make_unique<Id>())
+                            .addItem(u"@android:attr/colorAccent", ResourceId(0x01010435),
+                                     util::make_unique<Id>())
+                            .build())
+            .addValue(u"@app:style/Foo", ResourceId(0x7f020000), test::parseConfigOrDie("v21"),
+                      test::StyleBuilder()
+                            .addItem(u"@android:attr/paddingEnd", ResourceId(0x010103b4),
+                                     util::make_unique<Id>())
+                            .build())
+            .build();
+
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+            .setCompilationPackage(u"app")
+            .setPackageId(0x7f)
+            .build();
+
+    AutoVersioner versioner;
+    ASSERT_TRUE(versioner.consume(context.get(), table.get()));
+
+    Style* style = test::getValueForConfig<Style>(table.get(), u"@app:style/Foo",
+                                                  test::parseConfigOrDie("v4"));
+    ASSERT_NE(style, nullptr);
+    ASSERT_EQ(style->entries.size(), 1u);
+    AAPT_ASSERT_TRUE(style->entries.front().key.name);
+    EXPECT_EQ(style->entries.front().key.name.value(),
+              test::parseNameOrDie(u"@android:attr/onClick"));
+
+    style = test::getValueForConfig<Style>(table.get(), u"@app:style/Foo",
+                                           test::parseConfigOrDie("v13"));
+    ASSERT_NE(style, nullptr);
+    ASSERT_EQ(style->entries.size(), 2u);
+    AAPT_ASSERT_TRUE(style->entries[0].key.name);
+    EXPECT_EQ(style->entries[0].key.name.value(),
+              test::parseNameOrDie(u"@android:attr/onClick"));
+    AAPT_ASSERT_TRUE(style->entries[1].key.name);
+    EXPECT_EQ(style->entries[1].key.name.value(),
+                  test::parseNameOrDie(u"@android:attr/requiresSmallestWidthDp"));
+
+    style = test::getValueForConfig<Style>(table.get(), u"@app:style/Foo",
+                                           test::parseConfigOrDie("v17"));
+    ASSERT_NE(style, nullptr);
+    ASSERT_EQ(style->entries.size(), 3u);
+    AAPT_ASSERT_TRUE(style->entries[0].key.name);
+    EXPECT_EQ(style->entries[0].key.name.value(),
+                  test::parseNameOrDie(u"@android:attr/onClick"));
+    AAPT_ASSERT_TRUE(style->entries[1].key.name);
+    EXPECT_EQ(style->entries[1].key.name.value(),
+                  test::parseNameOrDie(u"@android:attr/requiresSmallestWidthDp"));
+    AAPT_ASSERT_TRUE(style->entries[2].key.name);
+    EXPECT_EQ(style->entries[2].key.name.value(),
+                  test::parseNameOrDie(u"@android:attr/paddingStart"));
+
+    style = test::getValueForConfig<Style>(table.get(), u"@app:style/Foo",
+                                           test::parseConfigOrDie("v21"));
+    ASSERT_NE(style, nullptr);
+    ASSERT_EQ(style->entries.size(), 1u);
+    AAPT_ASSERT_TRUE(style->entries.front().key.name);
+    EXPECT_EQ(style->entries.front().key.name.value(),
+              test::parseNameOrDie(u"@android:attr/paddingEnd"));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
new file mode 100644
index 0000000..2b4c4d2
--- /dev/null
+++ b/tools/aapt2/link/Link.cpp
@@ -0,0 +1,702 @@
+/*
+ * 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 "AppInfo.h"
+#include "Debug.h"
+#include "Flags.h"
+#include "JavaClassGenerator.h"
+#include "NameMangler.h"
+#include "ProguardRules.h"
+#include "XmlDom.h"
+
+#include "compile/IdAssigner.h"
+#include "flatten/Archive.h"
+#include "flatten/TableFlattener.h"
+#include "flatten/XmlFlattener.h"
+#include "link/Linkers.h"
+#include "link/TableMerger.h"
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+#include "unflatten/BinaryResourceParser.h"
+#include "unflatten/FileExportHeaderReader.h"
+#include "util/Files.h"
+#include "util/StringPiece.h"
+
+#include <fstream>
+#include <sys/stat.h>
+#include <utils/FileMap.h>
+#include <vector>
+
+namespace aapt {
+
+struct LinkOptions {
+    std::string outputPath;
+    std::string manifestPath;
+    std::vector<std::string> includePaths;
+    Maybe<std::string> generateJavaClassPath;
+    Maybe<std::string> generateProguardRulesPath;
+    bool noAutoVersion = false;
+    bool staticLib = false;
+    bool verbose = false;
+    bool outputToDirectory = false;
+};
+
+struct LinkContext : public IAaptContext {
+    StdErrDiagnostics mDiagnostics;
+    std::unique_ptr<NameMangler> mNameMangler;
+    std::u16string mCompilationPackage;
+    uint8_t mPackageId;
+    std::unique_ptr<ISymbolTable> mSymbols;
+
+    IDiagnostics* getDiagnostics() override {
+        return &mDiagnostics;
+    }
+
+    NameMangler* getNameMangler() override {
+        return mNameMangler.get();
+    }
+
+    StringPiece16 getCompilationPackage() override {
+        return mCompilationPackage;
+    }
+
+    uint8_t getPackageId() override {
+        return mPackageId;
+    }
+
+    ISymbolTable* getExternalSymbols() override {
+        return mSymbols.get();
+    }
+};
+
+struct LinkCommand {
+    LinkOptions mOptions;
+    LinkContext mContext;
+
+    std::string buildResourceFileName(const ResourceFile& resFile) {
+        std::stringstream out;
+        out << "res/" << resFile.name.type;
+        if (resFile.config != ConfigDescription{}) {
+            out << "-" << resFile.config;
+        }
+        out << "/";
+
+        if (mContext.getNameMangler()->shouldMangle(resFile.name.package)) {
+            out << NameMangler::mangleEntry(resFile.name.package, resFile.name.entry);
+        } else {
+            out << resFile.name.entry;
+        }
+        out << file::getExtension(resFile.source.path);
+        return out.str();
+    }
+
+    /**
+     * Creates a SymbolTable that loads symbols from the various APKs and caches the
+     * results for faster lookup.
+     */
+    std::unique_ptr<ISymbolTable> createSymbolTableFromIncludePaths() {
+        AssetManagerSymbolTableBuilder builder;
+        for (const std::string& path : mOptions.includePaths) {
+            if (mOptions.verbose) {
+                mContext.getDiagnostics()->note(
+                        DiagMessage(Source{ path }) << "loading include path");
+            }
+
+            std::unique_ptr<android::AssetManager> assetManager =
+                    util::make_unique<android::AssetManager>();
+            int32_t cookie = 0;
+            if (!assetManager->addAssetPath(android::String8(path.data(), path.size()), &cookie)) {
+                mContext.getDiagnostics()->error(
+                        DiagMessage(Source{ path }) << "failed to load include path");
+                return {};
+            }
+            builder.add(std::move(assetManager));
+        }
+        return builder.build();
+    }
+
+    /**
+     * Loads the resource table (not inside an apk) at the given path.
+     */
+    std::unique_ptr<ResourceTable> loadTable(const std::string& input) {
+        std::string errorStr;
+        Maybe<android::FileMap> map = file::mmapPath(input, &errorStr);
+        if (!map) {
+            mContext.getDiagnostics()->error(DiagMessage(Source{ input }) << errorStr);
+            return {};
+        }
+
+        std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
+        BinaryResourceParser parser(&mContext, table.get(), Source{ input },
+                                    map.value().getDataPtr(), map.value().getDataLength());
+        if (!parser.parse()) {
+            return {};
+        }
+        return table;
+    }
+
+    /**
+     * Inflates an XML file from the source path.
+     */
+    std::unique_ptr<XmlResource> loadXml(const std::string& path) {
+        std::ifstream fin(path, std::ifstream::binary);
+        if (!fin) {
+            mContext.getDiagnostics()->error(DiagMessage(Source{ path }) << strerror(errno));
+            return {};
+        }
+
+        return xml::inflate(&fin, mContext.getDiagnostics(), Source{ path });
+    }
+
+    /**
+     * Inflates a binary XML file from the source path.
+     */
+    std::unique_ptr<XmlResource> loadBinaryXmlSkipFileExport(const std::string& path) {
+        // Read header for symbol info and export info.
+        std::string errorStr;
+        Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
+        if (!maybeF) {
+            mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+            return {};
+        }
+
+        ssize_t offset = getWrappedDataOffset(maybeF.value().getDataPtr(),
+                                              maybeF.value().getDataLength(), &errorStr);
+        if (offset < 0) {
+            mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+            return {};
+        }
+
+        std::unique_ptr<XmlResource> xmlRes = xml::inflate(
+                (const uint8_t*) maybeF.value().getDataPtr() + (size_t) offset,
+                maybeF.value().getDataLength() - offset,
+                mContext.getDiagnostics(), Source(path));
+        if (!xmlRes) {
+            return {};
+        }
+        return xmlRes;
+    }
+
+    Maybe<ResourceFile> loadFileExportHeader(const std::string& path) {
+        // Read header for symbol info and export info.
+        std::string errorStr;
+        Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
+        if (!maybeF) {
+            mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+            return {};
+        }
+
+        ResourceFile resFile;
+        ssize_t offset = unwrapFileExportHeader(maybeF.value().getDataPtr(),
+                                                maybeF.value().getDataLength(),
+                                                &resFile, &errorStr);
+        if (offset < 0) {
+            mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+            return {};
+        }
+        return std::move(resFile);
+    }
+
+    bool copyFileToArchive(const std::string& path, const std::string& outPath, uint32_t flags,
+                           IArchiveWriter* writer) {
+        std::string errorStr;
+        Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
+        if (!maybeF) {
+            mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+            return false;
+        }
+
+        ssize_t offset = getWrappedDataOffset(maybeF.value().getDataPtr(),
+                                              maybeF.value().getDataLength(),
+                                              &errorStr);
+        if (offset < 0) {
+            mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+            return false;
+        }
+
+        ArchiveEntry* entry = writer->writeEntry(outPath, flags, &maybeF.value(),
+                                                 offset, maybeF.value().getDataLength() - offset);
+        if (!entry) {
+            mContext.getDiagnostics()->error(
+                    DiagMessage(mOptions.outputPath) << "failed to write file " << outPath);
+            return false;
+        }
+        return true;
+    }
+
+    Maybe<AppInfo> extractAppInfoFromManifest(XmlResource* xmlRes) {
+        xml::Node* node = xmlRes->root.get();
+
+        // Find the first xml::Element.
+        while (node && !xml::nodeCast<xml::Element>(node)) {
+            node = !node->children.empty() ? node->children.front().get() : nullptr;
+        }
+
+        // Make sure the first element is <manifest> with package attribute.
+        if (xml::Element* manifestEl = xml::nodeCast<xml::Element>(node)) {
+            if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") {
+                if (xml::Attribute* packageAttr = manifestEl->findAttribute({}, u"package")) {
+                    return AppInfo{ packageAttr->value };
+                }
+            }
+        }
+        return {};
+    }
+
+    bool verifyNoExternalPackages(ResourceTable* table) {
+        bool error = false;
+        for (const auto& package : table->packages) {
+            if (mContext.getCompilationPackage() != package->name ||
+                    !package->id || package->id.value() != mContext.getPackageId()) {
+                // We have a package that is not related to the one we're building!
+                for (const auto& type : package->types) {
+                    for (const auto& entry : type->entries) {
+                        for (const auto& configValue : entry->values) {
+                            mContext.getDiagnostics()->error(DiagMessage(configValue.source)
+                                                             << "defined resource '"
+                                                             << ResourceNameRef(package->name,
+                                                                                type->type,
+                                                                                entry->name)
+                                                             << "' for external package '"
+                                                             << package->name << "'");
+                            error = true;
+                        }
+                    }
+                }
+            }
+        }
+        return !error;
+    }
+
+    std::unique_ptr<IArchiveWriter> makeArchiveWriter() {
+        if (mOptions.outputToDirectory) {
+            return createDirectoryArchiveWriter(mOptions.outputPath);
+        } else {
+            return createZipFileArchiveWriter(mOptions.outputPath);
+        }
+    }
+
+    bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
+        BigBuffer buffer(1024);
+        TableFlattenerOptions options = {};
+        options.useExtendedChunks = mOptions.staticLib;
+        TableFlattener flattener(&buffer, options);
+        if (!flattener.consume(&mContext, table)) {
+            return false;
+        }
+
+        ArchiveEntry* entry = writer->writeEntry("resources.arsc", ArchiveEntry::kAlign, buffer);
+        if (!entry) {
+            mContext.getDiagnostics()->error(
+                    DiagMessage() << "failed to write resources.arsc to archive");
+            return false;
+        }
+        return true;
+    }
+
+    bool flattenXml(XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
+                    IArchiveWriter* writer) {
+        BigBuffer buffer(1024);
+        XmlFlattenerOptions options = {};
+        options.keepRawValues = mOptions.staticLib;
+        options.maxSdkLevel = maxSdkLevel;
+        XmlFlattener flattener(&buffer, options);
+        if (!flattener.consume(&mContext, xmlRes)) {
+            return false;
+        }
+
+        ArchiveEntry* entry = writer->writeEntry(path, ArchiveEntry::kCompress, buffer);
+        if (!entry) {
+            mContext.getDiagnostics()->error(
+                    DiagMessage() << "failed to write " << path << " to archive");
+            return false;
+        }
+        return true;
+    }
+
+    bool writeJavaFile(ResourceTable* table, const StringPiece16& package) {
+        if (!mOptions.generateJavaClassPath) {
+            return true;
+        }
+
+        std::string outPath = mOptions.generateJavaClassPath.value();
+        file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(package)));
+        file::mkdirs(outPath);
+        file::appendPath(&outPath, "R.java");
+
+        std::ofstream fout(outPath, std::ofstream::binary);
+        if (!fout) {
+            mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
+            return false;
+        }
+
+        JavaClassGeneratorOptions javaOptions;
+        if (mOptions.staticLib) {
+            javaOptions.useFinal = false;
+        }
+
+        JavaClassGenerator generator(table, javaOptions);
+        if (!generator.generate(mContext.getCompilationPackage(), &fout)) {
+            mContext.getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
+            return false;
+        }
+        return true;
+    }
+
+    bool writeProguardFile(const proguard::KeepSet& keepSet) {
+        if (!mOptions.generateProguardRulesPath) {
+            return true;
+        }
+
+        std::ofstream fout(mOptions.generateProguardRulesPath.value(), std::ofstream::binary);
+        if (!fout) {
+            mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
+            return false;
+        }
+
+        proguard::writeKeepSet(&fout, keepSet);
+        if (!fout) {
+            mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
+            return false;
+        }
+        return true;
+    }
+
+    int run(const std::vector<std::string>& inputFiles) {
+        // Load the AndroidManifest.xml
+        std::unique_ptr<XmlResource> manifestXml = loadXml(mOptions.manifestPath);
+        if (!manifestXml) {
+            return 1;
+        }
+
+        if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get())) {
+            mContext.mCompilationPackage = maybeAppInfo.value().package;
+        } else {
+            mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
+                                             << "no package specified in <manifest> tag");
+            return 1;
+        }
+
+        if (!util::isJavaPackageName(mContext.mCompilationPackage)) {
+            mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
+                                             << "invalid package name '"
+                                             << mContext.mCompilationPackage
+                                             << "'");
+            return 1;
+        }
+
+        mContext.mNameMangler = util::make_unique<NameMangler>(
+                NameManglerPolicy{ mContext.mCompilationPackage });
+        mContext.mPackageId = 0x7f;
+        mContext.mSymbols = createSymbolTableFromIncludePaths();
+        if (!mContext.mSymbols) {
+            return 1;
+        }
+
+        if (mOptions.verbose) {
+            mContext.getDiagnostics()->note(
+                    DiagMessage() << "linking package '" << mContext.mCompilationPackage << "' "
+                                  << "with package ID " << std::hex << (int) mContext.mPackageId);
+        }
+
+        ResourceTable mergedTable;
+        TableMerger tableMerger(&mContext, &mergedTable);
+
+        struct FilesToProcess {
+            Source source;
+            ResourceFile file;
+        };
+
+        bool error = false;
+        std::queue<FilesToProcess> filesToProcess;
+        for (const std::string& input : inputFiles) {
+            if (util::stringEndsWith<char>(input, ".apk")) {
+                // TODO(adamlesinski): Load resources from a static library APK
+                //                     Merge the table into TableMerger.
+
+            } else if (util::stringEndsWith<char>(input, ".arsc.flat")) {
+                if (mOptions.verbose) {
+                    mContext.getDiagnostics()->note(DiagMessage() << "linking " << input);
+                }
+
+                std::unique_ptr<ResourceTable> table = loadTable(input);
+                if (!table) {
+                    return 1;
+                }
+
+                if (!tableMerger.merge(Source(input), table.get())) {
+                    return 1;
+                }
+
+            } else {
+                // Extract the exported IDs here so we can build the resource table.
+                if (Maybe<ResourceFile> maybeF = loadFileExportHeader(input)) {
+                    ResourceFile& f = maybeF.value();
+
+                    if (f.name.package.empty()) {
+                        f.name.package = mContext.getCompilationPackage().toString();
+                    }
+
+                    Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(f.name);
+
+                    // Add this file to the table.
+                    if (!mergedTable.addFileReference(mangledName ? mangledName.value() : f.name,
+                                                      f.config, f.source,
+                                                      util::utf8ToUtf16(buildResourceFileName(f)),
+                                                      mContext.getDiagnostics())) {
+                        error = true;
+                    }
+
+                    // Add the exports of this file to the table.
+                    for (SourcedResourceName& exportedSymbol : f.exportedSymbols) {
+                        if (exportedSymbol.name.package.empty()) {
+                            exportedSymbol.name.package = mContext.getCompilationPackage()
+                                    .toString();
+                        }
+
+                        Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(
+                                exportedSymbol.name);
+                        if (!mergedTable.addResource(
+                                mangledName ? mangledName.value() : exportedSymbol.name,
+                                {}, {}, f.source.withLine(exportedSymbol.line),
+                                util::make_unique<Id>(), mContext.getDiagnostics())) {
+                            error = true;
+                        }
+                    }
+
+                    filesToProcess.push(FilesToProcess{ Source(input), std::move(f) });
+                } else {
+                    return 1;
+                }
+            }
+        }
+
+        if (error) {
+            mContext.getDiagnostics()->error(DiagMessage() << "failed parsing input");
+            return 1;
+        }
+
+        if (!verifyNoExternalPackages(&mergedTable)) {
+            return 1;
+        }
+
+        if (!mOptions.staticLib) {
+            PrivateAttributeMover mover;
+            if (!mover.consume(&mContext, &mergedTable)) {
+                mContext.getDiagnostics()->error(
+                        DiagMessage() << "failed moving private attributes");
+                return 1;
+            }
+        }
+
+        {
+            IdAssigner idAssigner;
+            if (!idAssigner.consume(&mContext, &mergedTable)) {
+                mContext.getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
+                return 1;
+            }
+        }
+
+        mContext.mNameMangler = util::make_unique<NameMangler>(
+                NameManglerPolicy{ mContext.mCompilationPackage, tableMerger.getMergedPackages() });
+        mContext.mSymbols = JoinedSymbolTableBuilder()
+                .addSymbolTable(util::make_unique<SymbolTableWrapper>(&mergedTable))
+                .addSymbolTable(std::move(mContext.mSymbols))
+                .build();
+
+        {
+            ReferenceLinker linker;
+            if (!linker.consume(&mContext, &mergedTable)) {
+                mContext.getDiagnostics()->error(DiagMessage() << "failed linking references");
+                return 1;
+            }
+        }
+
+        proguard::KeepSet proguardKeepSet;
+
+        std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter();
+        if (!archiveWriter) {
+            mContext.getDiagnostics()->error(DiagMessage() << "failed to create archive");
+            return 1;
+        }
+
+        {
+            XmlReferenceLinker manifestLinker;
+            if (manifestLinker.consume(&mContext, manifestXml.get())) {
+
+                if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
+                                                               manifestXml.get(),
+                                                               &proguardKeepSet)) {
+                    error = true;
+                }
+
+                if (!flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
+                                archiveWriter.get())) {
+                    error = true;
+                }
+            } else {
+                error = true;
+            }
+        }
+
+        for (; !filesToProcess.empty(); filesToProcess.pop()) {
+            FilesToProcess& f = filesToProcess.front();
+            if (f.file.name.type != ResourceType::kRaw &&
+                    util::stringEndsWith<char>(f.source.path, ".xml.flat")) {
+                if (mOptions.verbose) {
+                    mContext.getDiagnostics()->note(DiagMessage() << "linking " << f.source.path);
+                }
+
+                std::unique_ptr<XmlResource> xmlRes = loadBinaryXmlSkipFileExport(f.source.path);
+                if (!xmlRes) {
+                    return 1;
+                }
+
+                xmlRes->file = std::move(f.file);
+
+                XmlReferenceLinker xmlLinker;
+                if (xmlLinker.consume(&mContext, xmlRes.get())) {
+                    if (!proguard::collectProguardRules(xmlRes->file.source, xmlRes.get(),
+                                                        &proguardKeepSet)) {
+                        error = true;
+                    }
+
+                    Maybe<size_t> maxSdkLevel;
+                    if (!mOptions.noAutoVersion) {
+                        maxSdkLevel = std::max<size_t>(xmlRes->file.config.sdkVersion, 1u);
+                    }
+
+                    if (!flattenXml(xmlRes.get(), buildResourceFileName(xmlRes->file), maxSdkLevel,
+                                    archiveWriter.get())) {
+                        error = true;
+                    }
+
+                    if (!mOptions.noAutoVersion) {
+                        Maybe<ResourceTable::SearchResult> result = mergedTable.findResource(
+                                xmlRes->file.name);
+                        for (int sdkLevel : xmlLinker.getSdkLevels()) {
+                            if (sdkLevel > xmlRes->file.config.sdkVersion &&
+                                    shouldGenerateVersionedResource(result.value().entry,
+                                                                    xmlRes->file.config,
+                                                                    sdkLevel)) {
+                                xmlRes->file.config.sdkVersion = sdkLevel;
+                                if (!mergedTable.addFileReference(xmlRes->file.name,
+                                                                  xmlRes->file.config,
+                                                                  xmlRes->file.source,
+                                                                  util::utf8ToUtf16(
+                                                                     buildResourceFileName(xmlRes->file)),
+                                                             mContext.getDiagnostics())) {
+                                    error = true;
+                                    continue;
+                                }
+
+                                if (!flattenXml(xmlRes.get(), buildResourceFileName(xmlRes->file),
+                                                sdkLevel, archiveWriter.get())) {
+                                    error = true;
+                                }
+                            }
+                        }
+                    }
+
+                } else {
+                    error = true;
+                }
+            } else {
+                if (mOptions.verbose) {
+                    mContext.getDiagnostics()->note(DiagMessage() << "copying " << f.source.path);
+                }
+
+                if (!copyFileToArchive(f.source.path, buildResourceFileName(f.file), 0,
+                                       archiveWriter.get())) {
+                    error = true;
+                }
+            }
+        }
+
+        if (error) {
+            mContext.getDiagnostics()->error(DiagMessage() << "failed linking file resources");
+            return 1;
+        }
+
+        if (!mOptions.noAutoVersion) {
+            AutoVersioner versioner;
+            if (!versioner.consume(&mContext, &mergedTable)) {
+                mContext.getDiagnostics()->error(DiagMessage() << "failed versioning styles");
+                return 1;
+            }
+        }
+
+        if (!flattenTable(&mergedTable, archiveWriter.get())) {
+            mContext.getDiagnostics()->error(DiagMessage() << "failed to write resources.arsc");
+            return 1;
+        }
+
+        if (mOptions.generateJavaClassPath) {
+            if (!writeJavaFile(&mergedTable, mContext.getCompilationPackage())) {
+                return 1;
+            }
+        }
+
+        if (mOptions.generateProguardRulesPath) {
+            if (!writeProguardFile(proguardKeepSet)) {
+                return 1;
+            }
+        }
+
+        if (mOptions.verbose) {
+            Debug::printTable(&mergedTable);
+            for (; !tableMerger.getFileMergeQueue()->empty();
+                    tableMerger.getFileMergeQueue()->pop()) {
+                const FileToMerge& f = tableMerger.getFileMergeQueue()->front();
+                mContext.getDiagnostics()->note(
+                        DiagMessage() << f.srcPath << " -> " << f.dstPath << " from (0x"
+                                      << std::hex << (uintptr_t) f.srcTable << std::dec);
+            }
+        }
+
+        return 0;
+    }
+};
+
+int link(const std::vector<StringPiece>& args) {
+    LinkOptions options;
+    Flags flags = Flags()
+            .requiredFlag("-o", "Output path", &options.outputPath)
+            .requiredFlag("--manifest", "Path to the Android manifest to build",
+                          &options.manifestPath)
+            .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths)
+            .optionalFlag("--java", "Directory in which to generate R.java",
+                          &options.generateJavaClassPath)
+            .optionalFlag("--proguard", "Output file for generated Proguard rules",
+                          &options.generateProguardRulesPath)
+            .optionalSwitch("--no-auto-version",
+                            "Disables automatic style and layout SDK versioning",
+                            &options.noAutoVersion)
+            .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
+                            "by -o",
+                            &options.outputToDirectory)
+            .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib)
+            .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
+
+    if (!flags.parse("aapt2 link", args, &std::cerr)) {
+        return 1;
+    }
+
+    LinkCommand cmd = { options };
+    return cmd.run(flags.getArgs());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h
new file mode 100644
index 0000000..2cc8d9f
--- /dev/null
+++ b/tools/aapt2/link/Linkers.h
@@ -0,0 +1,67 @@
+/*
+ * 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_LINKER_LINKERS_H
+#define AAPT_LINKER_LINKERS_H
+
+#include "process/IResourceTableConsumer.h"
+
+#include <set>
+
+namespace aapt {
+
+class ResourceTable;
+struct ResourceEntry;
+struct ConfigDescription;
+
+/**
+ * Determines whether a versioned resource should be created. If a versioned resource already
+ * exists, it takes precedence.
+ */
+bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config,
+                                     const int sdkVersionToGenerate);
+
+struct AutoVersioner : public IResourceTableConsumer {
+    bool consume(IAaptContext* context, ResourceTable* table) override;
+};
+
+struct PrivateAttributeMover : public IResourceTableConsumer {
+    bool consume(IAaptContext* context, ResourceTable* table) override;
+};
+
+struct XmlAutoVersioner : public IXmlResourceConsumer {
+    bool consume(IAaptContext* context, XmlResource* resource) override;
+};
+
+struct ReferenceLinker : public IResourceTableConsumer {
+    bool consume(IAaptContext* context, ResourceTable* table) override;
+};
+
+class XmlReferenceLinker : IXmlResourceConsumer {
+private:
+    std::set<int> mSdkLevelsFound;
+
+public:
+    bool consume(IAaptContext* context, XmlResource* resource) override;
+
+    const std::set<int>& getSdkLevels() const {
+        return mSdkLevelsFound;
+    }
+};
+
+} // namespace aapt
+
+#endif /* AAPT_LINKER_LINKERS_H */
diff --git a/tools/aapt2/link/PrivateAttributeMover.cpp b/tools/aapt2/link/PrivateAttributeMover.cpp
new file mode 100644
index 0000000..db20bcb
--- /dev/null
+++ b/tools/aapt2/link/PrivateAttributeMover.cpp
@@ -0,0 +1,81 @@
+/*
+ * 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 "ResourceTable.h"
+
+#include "link/Linkers.h"
+
+#include <algorithm>
+#include <iterator>
+
+namespace aapt {
+
+template <typename InputContainer, typename OutputIterator, typename Predicate>
+OutputIterator moveIf(InputContainer& inputContainer, OutputIterator result,
+                      Predicate pred) {
+    const auto last = inputContainer.end();
+    auto newEnd = std::find_if(inputContainer.begin(), inputContainer.end(), pred);
+    if (newEnd == last) {
+        return result;
+    }
+
+    *result = std::move(*newEnd);
+
+    auto first = newEnd;
+    ++first;
+
+    for (; first != last; ++first) {
+        if (bool(pred(*first))) {
+            // We want to move this guy
+            *result = std::move(*first);
+            ++result;
+        } else {
+            // We want to keep this guy, but we will need to move it up the list to replace
+            // missing items.
+            *newEnd = std::move(*first);
+            ++newEnd;
+        }
+    }
+
+    inputContainer.erase(newEnd, last);
+    return result;
+}
+
+bool PrivateAttributeMover::consume(IAaptContext* context, ResourceTable* table) {
+    for (auto& package : table->packages) {
+        ResourceTableType* type = package->findType(ResourceType::kAttr);
+        if (!type) {
+            continue;
+        }
+
+        if (!type->publicStatus.isPublic) {
+            // No public attributes, so we can safely leave these private attributes where they are.
+            return true;
+        }
+
+        ResourceTableType* privAttrType = package->findOrCreateType(ResourceType::kAttrPrivate);
+        assert(privAttrType->entries.empty());
+
+        moveIf(type->entries, std::back_inserter(privAttrType->entries),
+               [](const std::unique_ptr<ResourceEntry>& entry) -> bool {
+                   return !entry->publicStatus.isPublic;
+               });
+        break;
+    }
+    return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/PrivateAttributeMover_test.cpp b/tools/aapt2/link/PrivateAttributeMover_test.cpp
new file mode 100644
index 0000000..8173c30
--- /dev/null
+++ b/tools/aapt2/link/PrivateAttributeMover_test.cpp
@@ -0,0 +1,80 @@
+/*
+ * 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 "link/Linkers.h"
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(PrivateAttributeMoverTest, MovePrivateAttributes) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .addSimple(u"@android:attr/publicA")
+            .addSimple(u"@android:attr/privateA")
+            .addSimple(u"@android:attr/publicB")
+            .addSimple(u"@android:attr/privateB")
+            .build();
+    ASSERT_TRUE(table->markPublic(test::parseNameOrDie(u"@android:attr/publicA"),
+                                  ResourceId(0x01010000), {}, context->getDiagnostics()));
+    ASSERT_TRUE(table->markPublic(test::parseNameOrDie(u"@android:attr/publicB"),
+                                      ResourceId(0x01010002), {}, context->getDiagnostics()));
+
+    PrivateAttributeMover mover;
+    ASSERT_TRUE(mover.consume(context.get(), table.get()));
+
+    ResourceTablePackage* package = table->findPackage(u"android");
+    ASSERT_NE(package, nullptr);
+
+    ResourceTableType* type = package->findType(ResourceType::kAttr);
+    ASSERT_NE(type, nullptr);
+    ASSERT_EQ(type->entries.size(), 2u);
+    EXPECT_NE(type->findEntry(u"publicA"), nullptr);
+    EXPECT_NE(type->findEntry(u"publicB"), nullptr);
+
+    type = package->findType(ResourceType::kAttrPrivate);
+    ASSERT_NE(type, nullptr);
+    ASSERT_EQ(type->entries.size(), 2u);
+    EXPECT_NE(type->findEntry(u"privateA"), nullptr);
+    EXPECT_NE(type->findEntry(u"privateB"), nullptr);
+}
+
+TEST(PrivateAttributeMoverTest, LeavePrivateAttributesWhenNoPublicAttributesDefined) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .addSimple(u"@android:attr/privateA")
+            .addSimple(u"@android:attr/privateB")
+            .build();
+
+    PrivateAttributeMover mover;
+    ASSERT_TRUE(mover.consume(context.get(), table.get()));
+
+    ResourceTablePackage* package = table->findPackage(u"android");
+    ASSERT_NE(package, nullptr);
+
+    ResourceTableType* type = package->findType(ResourceType::kAttr);
+    ASSERT_NE(type, nullptr);
+    ASSERT_EQ(type->entries.size(), 2u);
+
+    type = package->findType(ResourceType::kAttrPrivate);
+    ASSERT_EQ(type, nullptr);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
new file mode 100644
index 0000000..c0356e5
--- /dev/null
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -0,0 +1,274 @@
+/*
+ * 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 "Diagnostics.h"
+#include "ResourceTable.h"
+#include "ResourceUtils.h"
+#include "ResourceValues.h"
+#include "util/Util.h"
+#include "ValueVisitor.h"
+
+#include "link/Linkers.h"
+#include "link/ReferenceLinkerVisitor.h"
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <cassert>
+
+namespace aapt {
+
+namespace {
+
+/**
+ * The ReferenceLinkerVisitor will follow all references and make sure they point
+ * to resources that actually exist, either in the local resource table, or as external
+ * symbols. Once the target resource has been found, the ID of the resource will be assigned
+ * to the reference object.
+ *
+ * NOTE: All of the entries in the ResourceTable must be assigned IDs.
+ */
+class StyleAndReferenceLinkerVisitor : public ValueVisitor {
+private:
+    ReferenceLinkerVisitor mReferenceVisitor;
+    IAaptContext* mContext;
+    ISymbolTable* mSymbols;
+    IPackageDeclStack* mPackageDecls;
+    StringPool* mStringPool;
+    bool mError = false;
+
+    const ISymbolTable::Symbol* findAttributeSymbol(Reference* reference) {
+        assert(reference);
+        assert(reference->name || reference->id);
+
+        if (reference->name) {
+            // Transform the package name if it is an alias.
+            Maybe<ResourceName> realName = mPackageDecls->transformPackage(
+                    reference->name.value(), mContext->getCompilationPackage());
+
+            // Mangle the reference name if it should be mangled.
+            Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
+                    realName ? realName.value() : reference->name.value());
+
+            const ISymbolTable::Symbol* s = nullptr;
+            if (mangledName) {
+                s = mSymbols->findByName(mangledName.value());
+            } else if (realName) {
+                s = mSymbols->findByName(realName.value());
+            } else {
+                s = mSymbols->findByName(reference->name.value());
+            }
+
+            if (s && s->attribute) {
+                return s;
+            }
+        }
+
+        if (reference->id) {
+            if (const ISymbolTable::Symbol* s = mSymbols->findById(reference->id.value())) {
+                if (s->attribute) {
+                    return s;
+                }
+            }
+        }
+        return nullptr;
+    }
+
+    /**
+     * Transform a RawString value into a more specific, appropriate value, based on the
+     * Attribute. If a non RawString value is passed in, this is an identity transform.
+     */
+    std::unique_ptr<Item> parseValueWithAttribute(std::unique_ptr<Item> value,
+                                                  const Attribute* attr) {
+        if (RawString* rawString = valueCast<RawString>(value.get())) {
+            std::unique_ptr<Item> transformed = ResourceUtils::parseItemForAttribute(
+                    *rawString->value, attr);
+
+            // If we could not parse as any specific type, try a basic STRING.
+            if (!transformed && (attr->typeMask & android::ResTable_map::TYPE_STRING)) {
+                util::StringBuilder stringBuilder;
+                stringBuilder.append(*rawString->value);
+                if (stringBuilder) {
+                    transformed = util::make_unique<String>(
+                            mStringPool->makeRef(stringBuilder.str()));
+                }
+            }
+
+            if (transformed) {
+                return transformed;
+            }
+        };
+        return value;
+    }
+
+    void buildAttributeMismatchMessage(DiagMessage* msg, const Attribute* attr,
+                                       const Item* value) {
+        *msg << "expected";
+        if (attr->typeMask & android::ResTable_map::TYPE_BOOLEAN) {
+            *msg << " boolean";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_COLOR) {
+            *msg << " color";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_DIMENSION) {
+            *msg << " dimension";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_ENUM) {
+            *msg << " enum";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_FLAGS) {
+            *msg << " flags";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_FLOAT) {
+            *msg << " float";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_FRACTION) {
+            *msg << " fraction";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_INTEGER) {
+            *msg << " integer";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_REFERENCE) {
+            *msg << " reference";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_STRING) {
+            *msg << " string";
+        }
+
+        *msg << " but got " << *value;
+    }
+
+public:
+    using ValueVisitor::visit;
+
+    StyleAndReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols,
+                                   StringPool* stringPool, IPackageDeclStack* decl) :
+            mReferenceVisitor(context, symbols, decl), mContext(context), mSymbols(symbols),
+            mPackageDecls(decl), mStringPool(stringPool) {
+    }
+
+    void visit(Reference* reference) override {
+        mReferenceVisitor.visit(reference);
+    }
+
+    /**
+     * We visit the Style specially because during this phase, values of attributes are
+     * all RawString values. Now that we are expected to resolve all symbols, we can
+     * lookup the attributes to find out which types are allowed for the attributes' values.
+     */
+    void visit(Style* style) override {
+        if (style->parent) {
+            visit(&style->parent.value());
+        }
+
+        for (Style::Entry& entry : style->entries) {
+            if (const ISymbolTable::Symbol* s = findAttributeSymbol(&entry.key)) {
+                // Assign our style key the correct ID.
+                entry.key.id = s->id;
+
+                // Try to convert the value to a more specific, typed value based on the
+                // attribute it is set to.
+                entry.value = parseValueWithAttribute(std::move(entry.value), s->attribute.get());
+
+                // Link/resolve the final value (mostly if it's a reference).
+                entry.value->accept(this);
+
+                // Now verify that the type of this item is compatible with the attribute it
+                // is defined for.
+                android::Res_value val = {};
+                entry.value->flatten(&val);
+
+                // Always allow references.
+                const uint32_t typeMask = s->attribute->typeMask |
+                        android::ResTable_map::TYPE_REFERENCE;
+
+                if (!(typeMask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) {
+                    // The actual type of this item is incompatible with the attribute.
+                    DiagMessage msg;
+                    buildAttributeMismatchMessage(&msg, s->attribute.get(), entry.value.get());
+                    mContext->getDiagnostics()->error(msg);
+                    mError = true;
+                }
+            } else {
+                DiagMessage msg;
+                msg << "style attribute '";
+                if (entry.key.name) {
+                    msg << entry.key.name.value().package << ":" << entry.key.name.value().entry;
+                } else {
+                    msg << entry.key.id.value();
+                }
+                msg << "' not found";
+                mContext->getDiagnostics()->error(msg);
+                mError = true;
+            }
+        }
+    }
+
+    inline bool hasError() {
+        return mError || mReferenceVisitor.hasError();
+    }
+};
+
+struct EmptyDeclStack : public IPackageDeclStack {
+    Maybe<ResourceName> transformPackage(const ResourceName& name,
+                                         const StringPiece16& localPackage) const override {
+        if (name.package.empty()) {
+            return ResourceName{ localPackage.toString(), name.type, name.entry };
+        }
+        return {};
+    }
+};
+
+} // namespace
+
+bool ReferenceLinker::consume(IAaptContext* context, ResourceTable* table) {
+    EmptyDeclStack declStack;
+    bool error = false;
+    for (auto& package : table->packages) {
+        for (auto& type : package->types) {
+            for (auto& entry : type->entries) {
+                // A public entry with no values will not be encoded properly.
+                if (entry->publicStatus.isPublic && entry->values.empty()) {
+                    context->getDiagnostics()->error(DiagMessage(entry->publicStatus.source)
+                                                     << "No value for public resource");
+                    error = true;
+                }
+
+                for (auto& configValue : entry->values) {
+                    StyleAndReferenceLinkerVisitor visitor(context,
+                                                           context->getExternalSymbols(),
+                                                           &table->stringPool, &declStack);
+                    configValue.value->accept(&visitor);
+                    if (visitor.hasError()) {
+                        error = true;
+                    }
+                }
+            }
+        }
+    }
+    return !error;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/ReferenceLinkerVisitor.h b/tools/aapt2/link/ReferenceLinkerVisitor.h
new file mode 100644
index 0000000..c70531b
--- /dev/null
+++ b/tools/aapt2/link/ReferenceLinkerVisitor.h
@@ -0,0 +1,109 @@
+/*
+ * 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_LINKER_REFERENCELINKERVISITOR_H
+#define AAPT_LINKER_REFERENCELINKERVISITOR_H
+
+#include "Resource.h"
+#include "ResourceValues.h"
+#include "ValueVisitor.h"
+
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+
+#include <cassert>
+
+namespace aapt {
+
+/**
+ * The ReferenceLinkerVisitor will follow all references and make sure they point
+ * to resources that actually exist in the given ISymbolTable.
+ * Once the target resource has been found, the ID of the resource will be assigned
+ * to the reference object.
+ */
+class ReferenceLinkerVisitor : public ValueVisitor {
+    using ValueVisitor::visit;
+private:
+    IAaptContext* mContext;
+    ISymbolTable* mSymbols;
+    IPackageDeclStack* mPackageDecls;
+    bool mError = false;
+
+public:
+    ReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols, IPackageDeclStack* decls) :
+            mContext(context), mSymbols(symbols), mPackageDecls(decls) {
+    }
+
+    /**
+     * Lookup a reference and ensure it exists, either in our local table, or as an external
+     * symbol. Once found, assign the ID of the target resource to this reference object.
+     */
+    void visit(Reference* reference) override {
+        assert(reference);
+        assert(reference->name || reference->id);
+
+        // We prefer to lookup by name if the name is set. Otherwise it could be
+        // an out-of-date ID.
+        if (reference->name) {
+            // Transform the package name if it is an alias.
+            Maybe<ResourceName> realName = mPackageDecls->transformPackage(
+                    reference->name.value(), mContext->getCompilationPackage());
+
+            // Mangle the reference name if it should be mangled.
+            Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
+                    realName ? realName.value() : reference->name.value());
+
+            const ISymbolTable::Symbol* s = nullptr;
+            if (mangledName) {
+                s = mSymbols->findByName(mangledName.value());
+            } else if (realName) {
+                s = mSymbols->findByName(realName.value());
+            } else {
+                s = mSymbols->findByName(reference->name.value());
+            }
+
+            if (s) {
+                reference->id = s->id;
+                return;
+            }
+
+            DiagMessage errorMsg;
+            errorMsg << "reference to " << reference->name.value();
+            if (realName) {
+                errorMsg << " (aka " << realName.value() << ")";
+            }
+            errorMsg << " was not found";
+            mContext->getDiagnostics()->error(errorMsg);
+            mError = true;
+            return;
+        }
+
+        if (!mSymbols->findById(reference->id.value())) {
+            mContext->getDiagnostics()->error(DiagMessage()
+                                              << "reference to " << reference->id.value()
+                                              << " was not found");
+            mError = true;
+        }
+    }
+
+    inline bool hasError() {
+        return mError;
+    }
+};
+
+} // namespace aapt
+
+#endif /* AAPT_LINKER_REFERENCELINKERVISITOR_H */
diff --git a/tools/aapt2/link/ReferenceLinker_test.cpp b/tools/aapt2/link/ReferenceLinker_test.cpp
new file mode 100644
index 0000000..5e7641a
--- /dev/null
+++ b/tools/aapt2/link/ReferenceLinker_test.cpp
@@ -0,0 +1,159 @@
+/*
+ * 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 "link/Linkers.h"
+#include "process/SymbolTable.h"
+
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(ReferenceLinkerTest, LinkSimpleReferences) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.test", 0x7f)
+            .addReference(u"@com.app.test:string/foo", ResourceId(0x7f020000),
+                          u"@com.app.test:string/bar")
+
+            // Test use of local reference (w/o package name).
+            .addReference(u"@com.app.test:string/bar", ResourceId(0x7f020001), u"@string/baz")
+
+            .addReference(u"@com.app.test:string/baz", ResourceId(0x7f020002),
+                          u"@android:string/ok")
+            .build();
+
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+            .setCompilationPackage(u"com.app.test")
+            .setPackageId(0x7f)
+            .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" })
+            .setSymbolTable(JoinedSymbolTableBuilder()
+                            .addSymbolTable(util::make_unique<SymbolTableWrapper>(table.get()))
+                            .addSymbolTable(test::StaticSymbolTableBuilder()
+                                    .addSymbol(u"@android:string/ok", ResourceId(0x01040034))
+                                    .build())
+                            .build())
+            .build();
+
+    ReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(context.get(), table.get()));
+
+    Reference* ref = test::getValue<Reference>(table.get(), u"@com.app.test:string/foo");
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->id);
+    EXPECT_EQ(ref->id.value(), ResourceId(0x7f020001));
+
+    ref = test::getValue<Reference>(table.get(), u"@com.app.test:string/bar");
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->id);
+    EXPECT_EQ(ref->id.value(), ResourceId(0x7f020002));
+
+    ref = test::getValue<Reference>(table.get(), u"@com.app.test:string/baz");
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->id);
+    EXPECT_EQ(ref->id.value(), ResourceId(0x01040034));
+}
+
+TEST(ReferenceLinkerTest, LinkStyleAttributes) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.test", 0x7f)
+            .addValue(u"@com.app.test:style/Theme", test::StyleBuilder()
+                    .setParent(u"@android:style/Theme.Material")
+                    .addItem(u"@android:attr/foo", ResourceUtils::tryParseColor(u"#ff00ff"))
+                    .addItem(u"@android:attr/bar", {} /* placeholder */)
+                    .build())
+            .build();
+
+    {
+        // We need to fill in the value for the attribute android:attr/bar after we build the
+        // table, because we need access to the string pool.
+        Style* style = test::getValue<Style>(table.get(), u"@com.app.test:style/Theme");
+        ASSERT_NE(style, nullptr);
+        style->entries.back().value = util::make_unique<RawString>(
+                table->stringPool.makeRef(u"one|two"));
+    }
+
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+            .setCompilationPackage(u"com.app.test")
+            .setPackageId(0x7f)
+            .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" })
+            .setSymbolTable(test::StaticSymbolTableBuilder()
+                    .addSymbol(u"@android:style/Theme.Material", ResourceId(0x01060000))
+                    .addSymbol(u"@android:attr/foo", ResourceId(0x01010001),
+                               test::AttributeBuilder()
+                                    .setTypeMask(android::ResTable_map::TYPE_COLOR)
+                                    .build())
+                    .addSymbol(u"@android:attr/bar", ResourceId(0x01010002),
+                               test::AttributeBuilder()
+                                    .setTypeMask(android::ResTable_map::TYPE_FLAGS)
+                                    .addItem(u"one", 0x01)
+                                    .addItem(u"two", 0x02)
+                                    .build())
+                    .build())
+            .build();
+
+    ReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(context.get(), table.get()));
+
+    Style* style = test::getValue<Style>(table.get(), u"@com.app.test:style/Theme");
+    ASSERT_NE(style, nullptr);
+    AAPT_ASSERT_TRUE(style->parent);
+    AAPT_ASSERT_TRUE(style->parent.value().id);
+    EXPECT_EQ(style->parent.value().id.value(), ResourceId(0x01060000));
+
+    ASSERT_EQ(2u, style->entries.size());
+
+    AAPT_ASSERT_TRUE(style->entries[0].key.id);
+    EXPECT_EQ(style->entries[0].key.id.value(), ResourceId(0x01010001));
+    ASSERT_NE(valueCast<BinaryPrimitive>(style->entries[0].value.get()), nullptr);
+
+    AAPT_ASSERT_TRUE(style->entries[1].key.id);
+    EXPECT_EQ(style->entries[1].key.id.value(), ResourceId(0x01010002));
+    ASSERT_NE(valueCast<BinaryPrimitive>(style->entries[1].value.get()), nullptr);
+}
+
+TEST(ReferenceLinkerTest, LinkMangledReferencesAndAttributes) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+            .setCompilationPackage(u"com.app.test")
+            .setPackageId(0x7f)
+            .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test", { u"com.android.support" } })
+            .setSymbolTable(test::StaticSymbolTableBuilder()
+                    .addSymbol(u"@com.app.test:attr/com.android.support$foo",
+                               ResourceId(0x7f010000), test::AttributeBuilder()
+                                        .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
+                    .build())
+            .build();
+
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.test", 0x7f)
+            .addValue(u"@com.app.test:style/Theme", ResourceId(0x7f020000),
+                      test::StyleBuilder().addItem(u"@com.android.support:attr/foo",
+                                                   ResourceUtils::tryParseColor(u"#ff0000"))
+                                          .build())
+            .build();
+
+    ReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(context.get(), table.get()));
+
+    Style* style = test::getValue<Style>(table.get(), u"@com.app.test:style/Theme");
+    ASSERT_NE(style, nullptr);
+    ASSERT_EQ(1u, style->entries.size());
+    AAPT_ASSERT_TRUE(style->entries.front().key.id);
+    EXPECT_EQ(style->entries.front().key.id.value(), ResourceId(0x7f010000));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
new file mode 100644
index 0000000..d5fd1fc
--- /dev/null
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -0,0 +1,183 @@
+/*
+ * 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 "ResourceTable.h"
+#include "ResourceValues.h"
+#include "ValueVisitor.h"
+
+#include "link/TableMerger.h"
+#include "util/Util.h"
+
+#include <cassert>
+
+namespace aapt {
+
+TableMerger::TableMerger(IAaptContext* context, ResourceTable* outTable) :
+        mContext(context), mMasterTable(outTable) {
+    // Create the desired package that all tables will be merged into.
+    mMasterPackage = mMasterTable->createPackage(
+            mContext->getCompilationPackage(), mContext->getPackageId());
+    assert(mMasterPackage && "package name or ID already taken");
+}
+
+bool TableMerger::merge(const Source& src, ResourceTable* table) {
+    const uint8_t desiredPackageId = mContext->getPackageId();
+
+    bool error = false;
+    for (auto& package : table->packages) {
+        // Warn of packages with an unrelated ID.
+        if (package->id && package->id.value() != desiredPackageId) {
+            mContext->getDiagnostics()->warn(DiagMessage(src)
+                                             << "ignoring package " << package->name);
+            continue;
+        }
+
+        bool manglePackage = false;
+        if (!package->name.empty() && mContext->getCompilationPackage() != package->name) {
+            manglePackage = true;
+            mMergedPackages.insert(package->name);
+        }
+
+        // Merge here. Once the entries are merged and mangled, any references to
+        // them are still valid. This is because un-mangled references are
+        // mangled, then looked up at resolution time.
+        // Also, when linking, we convert references with no package name to use
+        // the compilation package name.
+        if (!doMerge(src, table, package.get(), manglePackage)) {
+            error = true;
+        }
+    }
+    return !error;
+}
+
+bool TableMerger::doMerge(const Source& src, ResourceTable* srcTable,
+                          ResourceTablePackage* srcPackage, const bool manglePackage) {
+    bool error = false;
+
+    for (auto& srcType : srcPackage->types) {
+        ResourceTableType* dstType = mMasterPackage->findOrCreateType(srcType->type);
+        if (srcType->publicStatus.isPublic) {
+            if (dstType->publicStatus.isPublic && dstType->id && srcType->id
+                    && dstType->id.value() == srcType->id.value()) {
+                // Both types are public and have different IDs.
+                mContext->getDiagnostics()->error(DiagMessage(src)
+                                                  << "can not merge type '"
+                                                  << srcType->type
+                                                  << "': conflicting public IDs");
+                error = true;
+                continue;
+            }
+
+            dstType->publicStatus = std::move(srcType->publicStatus);
+            dstType->id = srcType->id;
+        }
+
+        for (auto& srcEntry : srcType->entries) {
+            ResourceEntry* dstEntry;
+            if (manglePackage) {
+                dstEntry = dstType->findOrCreateEntry(NameMangler::mangleEntry(
+                        srcPackage->name, srcEntry->name));
+            } else {
+                dstEntry = dstType->findOrCreateEntry(srcEntry->name);
+            }
+
+            if (srcEntry->publicStatus.isPublic) {
+                if (dstEntry->publicStatus.isPublic && dstEntry->id && srcEntry->id
+                        && dstEntry->id.value() != srcEntry->id.value()) {
+                    // Both entries are public and have different IDs.
+                    mContext->getDiagnostics()->error(DiagMessage(src)
+                                                      << "can not merge entry '"
+                                                      << srcEntry->name
+                                                      << "': conflicting public IDs");
+                    error = true;
+                    continue;
+                }
+
+                dstEntry->publicStatus = std::move(srcEntry->publicStatus);
+                dstEntry->id = srcEntry->id;
+            }
+
+            for (ResourceConfigValue& srcValue : srcEntry->values) {
+                auto cmp = [](const ResourceConfigValue& a,
+                              const ConfigDescription& b) -> bool {
+                    return a.config < b;
+                };
+
+                auto iter = std::lower_bound(dstEntry->values.begin(), dstEntry->values.end(),
+                                             srcValue.config, cmp);
+
+                if (iter != dstEntry->values.end() && iter->config == srcValue.config) {
+                    const int collisionResult = ResourceTable::resolveValueCollision(
+                            iter->value.get(), srcValue.value.get());
+                    if (collisionResult == 0) {
+                        // Error!
+                        ResourceNameRef resourceName =
+                                { srcPackage->name, srcType->type, srcEntry->name };
+                        mContext->getDiagnostics()->error(DiagMessage(srcValue.source)
+                                                          << "resource '" << resourceName
+                                                          << "' has a conflicting value for "
+                                                          << "configuration ("
+                                                          << srcValue.config << ")");
+                        mContext->getDiagnostics()->note(DiagMessage(iter->source)
+                                                         << "originally defined here");
+                        error = true;
+                        continue;
+                    } else if (collisionResult < 0) {
+                        // Keep our existing value.
+                        continue;
+                    }
+
+                } else {
+                    // Insert a new value.
+                    iter = dstEntry->values.insert(iter,
+                                                   ResourceConfigValue{ srcValue.config });
+                }
+
+                iter->source = std::move(srcValue.source);
+                iter->comment = std::move(srcValue.comment);
+                if (manglePackage) {
+                    iter->value = cloneAndMangle(srcTable, srcPackage->name,
+                                                 srcValue.value.get());
+                } else {
+                    iter->value = clone(srcValue.value.get());
+                }
+            }
+        }
+    }
+    return !error;
+}
+
+std::unique_ptr<Value> TableMerger::cloneAndMangle(ResourceTable* table,
+                                                   const std::u16string& package,
+                                                   Value* value) {
+    if (FileReference* f = valueCast<FileReference>(value)) {
+        // Mangle the path.
+        StringPiece16 prefix, entry, suffix;
+        if (util::extractResFilePathParts(*f->path, &prefix, &entry, &suffix)) {
+            std::u16string mangledEntry = NameMangler::mangleEntry(package, entry.toString());
+            std::u16string newPath = prefix.toString() + mangledEntry + suffix.toString();
+            mFilesToMerge.push(FileToMerge{ table, *f->path, newPath });
+            return util::make_unique<FileReference>(mMasterTable->stringPool.makeRef(newPath));
+        }
+    }
+    return clone(value);
+}
+
+std::unique_ptr<Value> TableMerger::clone(Value* value) {
+    return std::unique_ptr<Value>(value->clone(&mMasterTable->stringPool));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
new file mode 100644
index 0000000..157c16e
--- /dev/null
+++ b/tools/aapt2/link/TableMerger.h
@@ -0,0 +1,83 @@
+/*
+ * 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_TABLEMERGER_H
+#define AAPT_TABLEMERGER_H
+
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "util/Util.h"
+
+#include "process/IResourceTableConsumer.h"
+
+#include <queue>
+#include <set>
+
+namespace aapt {
+
+struct FileToMerge {
+    ResourceTable* srcTable;
+    std::u16string srcPath;
+    std::u16string dstPath;
+};
+
+/**
+ * TableMerger takes resource tables and merges all packages within the tables that have the same
+ * package ID.
+ *
+ * If a package has a different name, all the entries in that table have their names mangled
+ * to include the package name. This way there are no collisions. In order to do this correctly,
+ * the TableMerger needs to also mangle any FileReference paths. Once these are mangled,
+ * the original source path of the file, along with the new destination path is recorded in the
+ * queue returned from getFileMergeQueue().
+ *
+ * Once the merging is complete, a separate process can go collect the files from the various
+ * source APKs and either copy or process their XML and put them in the correct location in
+ * the final APK.
+ */
+class TableMerger {
+public:
+    TableMerger(IAaptContext* context, ResourceTable* outTable);
+
+    inline std::queue<FileToMerge>* getFileMergeQueue() {
+        return &mFilesToMerge;
+    }
+
+    inline const std::set<std::u16string>& getMergedPackages() const {
+        return mMergedPackages;
+    }
+
+    bool merge(const Source& src, ResourceTable* table);
+
+private:
+    IAaptContext* mContext;
+    ResourceTable* mMasterTable;
+    ResourceTablePackage* mMasterPackage;
+
+    std::set<std::u16string> mMergedPackages;
+    std::queue<FileToMerge> mFilesToMerge;
+
+    bool doMerge(const Source& src, ResourceTable* srcTable, ResourceTablePackage* srcPackage,
+                 const bool manglePackage);
+
+    std::unique_ptr<Value> cloneAndMangle(ResourceTable* table, const std::u16string& package,
+                                          Value* value);
+    std::unique_ptr<Value> clone(Value* value);
+};
+
+} // namespace aapt
+
+#endif /* AAPT_TABLEMERGER_H */
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
new file mode 100644
index 0000000..fa7ce86
--- /dev/null
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -0,0 +1,112 @@
+/*
+ * 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 "link/TableMerger.h"
+
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+struct TableMergerTest : public ::testing::Test {
+    std::unique_ptr<IAaptContext> mContext;
+
+    void SetUp() override {
+        mContext = test::ContextBuilder()
+                // We are compiling this package.
+                .setCompilationPackage(u"com.app.a")
+
+                // Merge all packages that have this package ID.
+                .setPackageId(0x7f)
+
+                // Mangle all packages that do not have this package name.
+                .setNameManglerPolicy(NameManglerPolicy{ u"com.app.a", { u"com.app.b" } })
+
+                .build();
+    }
+};
+
+TEST_F(TableMergerTest, SimpleMerge) {
+    std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.a", 0x7f)
+            .addReference(u"@com.app.a:id/foo", u"@com.app.a:id/bar")
+            .addReference(u"@com.app.a:id/bar", u"@com.app.b:id/foo")
+            .addValue(u"@com.app.a:styleable/view", test::StyleableBuilder()
+                    .addItem(u"@com.app.b:id/foo")
+                    .build())
+            .build();
+
+    std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.b", 0x7f)
+            .addSimple(u"@com.app.b:id/foo")
+            .build();
+
+    ResourceTable finalTable;
+    TableMerger merger(mContext.get(), &finalTable);
+
+    ASSERT_TRUE(merger.merge({}, tableA.get()));
+    ASSERT_TRUE(merger.merge({}, tableB.get()));
+
+    EXPECT_TRUE(merger.getMergedPackages().count(u"com.app.b") != 0);
+
+    // Entries from com.app.a should not be mangled.
+    AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:id/foo")));
+    AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:id/bar")));
+    AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:styleable/view")));
+
+    // The unmangled name should not be present.
+    AAPT_EXPECT_FALSE(finalTable.findResource(test::parseNameOrDie(u"@com.app.b:id/foo")));
+
+    // Look for the mangled name.
+    AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:id/com.app.b$foo")));
+}
+
+TEST_F(TableMergerTest, MergeFileReferences) {
+    std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.a", 0x7f)
+            .addFileReference(u"@com.app.a:xml/file", u"res/xml/file.xml")
+            .build();
+    std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.b", 0x7f)
+            .addFileReference(u"@com.app.b:xml/file", u"res/xml/file.xml")
+            .build();
+
+    ResourceTable finalTable;
+    TableMerger merger(mContext.get(), &finalTable);
+
+    ASSERT_TRUE(merger.merge({}, tableA.get()));
+    ASSERT_TRUE(merger.merge({}, tableB.get()));
+
+    FileReference* f = test::getValue<FileReference>(&finalTable, u"@com.app.a:xml/file");
+    ASSERT_NE(f, nullptr);
+    EXPECT_EQ(std::u16string(u"res/xml/file.xml"), *f->path);
+
+    f = test::getValue<FileReference>(&finalTable, u"@com.app.a:xml/com.app.b$file");
+    ASSERT_NE(f, nullptr);
+    EXPECT_EQ(std::u16string(u"res/xml/com.app.b$file.xml"), *f->path);
+
+    std::queue<FileToMerge>* filesToMerge = merger.getFileMergeQueue();
+    ASSERT_FALSE(filesToMerge->empty());
+
+    FileToMerge& fileToMerge = filesToMerge->front();
+    EXPECT_EQ(fileToMerge.srcTable, tableB.get());
+    EXPECT_EQ(fileToMerge.srcPath, u"res/xml/file.xml");
+    EXPECT_EQ(fileToMerge.dstPath, u"res/xml/com.app.b$file.xml");
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp
new file mode 100644
index 0000000..147b9bf
--- /dev/null
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -0,0 +1,134 @@
+/*
+ * 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 "Diagnostics.h"
+#include "ResourceUtils.h"
+#include "SdkConstants.h"
+#include "XmlDom.h"
+
+#include "link/Linkers.h"
+#include "link/ReferenceLinkerVisitor.h"
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+#include "util/Util.h"
+
+namespace aapt {
+
+namespace {
+
+class XmlReferenceLinkerVisitor : public xml::PackageAwareVisitor {
+private:
+    IAaptContext* mContext;
+    ISymbolTable* mSymbols;
+    std::set<int>* mSdkLevelsFound;
+    ReferenceLinkerVisitor mReferenceLinkerVisitor;
+    bool mError = false;
+
+public:
+    using xml::PackageAwareVisitor::visit;
+
+    XmlReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols,
+                              std::set<int>* sdkLevelsFound) :
+            mContext(context), mSymbols(symbols), mSdkLevelsFound(sdkLevelsFound),
+            mReferenceLinkerVisitor(context, symbols, this) {
+    }
+
+    void visit(xml::Element* el) override {
+        for (xml::Attribute& attr : el->attributes) {
+            Maybe<std::u16string> maybePackage =
+                    util::extractPackageFromNamespace(attr.namespaceUri);
+            if (maybePackage) {
+                // There is a valid package name for this attribute. We will look this up.
+                StringPiece16 package = maybePackage.value();
+                if (package.empty()) {
+                    // Empty package means the 'current' or 'local' package.
+                    package = mContext->getCompilationPackage();
+                }
+
+                attr.compiledAttribute = compileAttribute(
+                        ResourceName{ package.toString(), ResourceType::kAttr, attr.name });
+
+                // Convert the string value into a compiled Value if this is a valid attribute.
+                if (attr.compiledAttribute) {
+                    // Record all SDK levels from which the attributes were defined.
+                    const int sdkLevel = findAttributeSdkLevel(attr.compiledAttribute.value().id);
+                    if (sdkLevel > 1) {
+                        mSdkLevelsFound->insert(sdkLevel);
+                    }
+
+                    const Attribute* attribute = &attr.compiledAttribute.value().attribute;
+                    attr.compiledValue = ResourceUtils::parseItemForAttribute(attr.value,
+                                                                              attribute);
+                    if (!attr.compiledValue &&
+                            !(attribute->typeMask & android::ResTable_map::TYPE_STRING)) {
+                        // We won't be able to encode this as a string.
+                        mContext->getDiagnostics()->error(
+                                DiagMessage() << "'" << attr.value << "' "
+                                              << "is incompatible with attribute "
+                                              << package << ":" << attr.name << " " << *attribute);
+                        mError = true;
+                    }
+                } else {
+                    mContext->getDiagnostics()->error(
+                            DiagMessage() << "attribute '" << package << ":" << attr.name
+                                          << "' was not found");
+                    mError = true;
+
+                }
+            } else {
+                // We still encode references.
+                attr.compiledValue = ResourceUtils::tryParseReference(attr.value);
+            }
+
+            if (attr.compiledValue) {
+                // With a compiledValue, we must resolve the reference and assign it an ID.
+                attr.compiledValue->accept(&mReferenceLinkerVisitor);
+            }
+        }
+
+        // Call the super implementation.
+        xml::PackageAwareVisitor::visit(el);
+    }
+
+    Maybe<xml::AaptAttribute> compileAttribute(const ResourceName& name) {
+        Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(name);
+        if (const ISymbolTable::Symbol* symbol = mSymbols->findByName(
+                mangledName ? mangledName.value() : name)) {
+            if (symbol->attribute) {
+                return xml::AaptAttribute{ symbol->id, *symbol->attribute };
+            }
+        }
+        return {};
+    }
+
+    inline bool hasError() {
+        return mError || mReferenceLinkerVisitor.hasError();
+    }
+};
+
+} // namespace
+
+bool XmlReferenceLinker::consume(IAaptContext* context, XmlResource* resource) {
+    mSdkLevelsFound.clear();
+    XmlReferenceLinkerVisitor visitor(context, context->getExternalSymbols(), &mSdkLevelsFound);
+    if (resource->root) {
+        resource->root->accept(&visitor);
+        return !visitor.hasError();
+    }
+    return false;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp
new file mode 100644
index 0000000..7f91ec3
--- /dev/null
+++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp
@@ -0,0 +1,250 @@
+/*
+ * 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 "link/Linkers.h"
+
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+class XmlReferenceLinkerTest : public ::testing::Test {
+public:
+    void SetUp() override {
+        mContext = test::ContextBuilder()
+                .setCompilationPackage(u"com.app.test")
+                .setNameManglerPolicy(
+                        NameManglerPolicy{ u"com.app.test", { u"com.android.support" } })
+                .setSymbolTable(test::StaticSymbolTableBuilder()
+                        .addSymbol(u"@android:attr/layout_width", ResourceId(0x01010000),
+                                   test::AttributeBuilder()
+                                        .setTypeMask(android::ResTable_map::TYPE_ENUM |
+                                                     android::ResTable_map::TYPE_DIMENSION)
+                                        .addItem(u"match_parent", 0xffffffff)
+                                        .build())
+                        .addSymbol(u"@android:attr/background", ResourceId(0x01010001),
+                                   test::AttributeBuilder()
+                                        .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
+                        .addSymbol(u"@android:attr/attr", ResourceId(0x01010002),
+                                   test::AttributeBuilder().build())
+                        .addSymbol(u"@android:attr/text", ResourceId(0x01010003),
+                                   test::AttributeBuilder()
+                                        .setTypeMask(android::ResTable_map::TYPE_STRING)
+                                        .build())
+
+                         // Add one real symbol that was introduces in v21
+                        .addSymbol(u"@android:attr/colorAccent", ResourceId(0x01010435),
+                                   test::AttributeBuilder().build())
+
+                        .addSymbol(u"@android:id/id", ResourceId(0x01030000))
+                        .addSymbol(u"@com.app.test:id/id", ResourceId(0x7f030000))
+                        .addSymbol(u"@com.app.test:color/green", ResourceId(0x7f020000))
+                        .addSymbol(u"@com.app.test:color/red", ResourceId(0x7f020001))
+                        .addSymbol(u"@com.app.test:attr/colorAccent", ResourceId(0x7f010000),
+                                   test::AttributeBuilder()
+                                       .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
+                        .addSymbol(u"@com.app.test:attr/com.android.support$colorAccent",
+                                   ResourceId(0x7f010001), test::AttributeBuilder()
+                                       .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
+                        .addSymbol(u"@com.app.test:attr/attr", ResourceId(0x7f010002),
+                                   test::AttributeBuilder().build())
+                        .build())
+                .build();
+    }
+
+protected:
+    std::unique_ptr<IAaptContext> mContext;
+};
+
+static xml::Element* getRootElement(XmlResource* doc) {
+    xml::Node* node = doc->root.get();
+    while (xml::nodeCast<xml::Namespace>(node)) {
+        if (node->children.empty()) {
+            return nullptr;
+        }
+        node = node->children.front().get();
+    }
+
+    if (xml::Element* el = xml::nodeCast<xml::Element>(node)) {
+        return el;
+    }
+    return nullptr;
+}
+
+TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+        <View xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:background="@color/green"
+              android:text="hello"
+              class="hello" />)EOF");
+
+    XmlReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+
+    xml::Element* viewEl = getRootElement(doc.get());
+    ASSERT_NE(viewEl, nullptr);
+
+    xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android",
+                                                    u"layout_width");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+    EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x01010000));
+    ASSERT_NE(xmlAttr->compiledValue, nullptr);
+    ASSERT_NE(valueCast<BinaryPrimitive>(xmlAttr->compiledValue.get()), nullptr);
+
+    xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android", u"background");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+    EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x01010001));
+    ASSERT_NE(xmlAttr->compiledValue, nullptr);
+    Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get());
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->name);
+    EXPECT_EQ(ref->name.value(), test::parseNameOrDie(u"@color/green")); // Make sure the name
+                                                                         // didn't change.
+    AAPT_ASSERT_TRUE(ref->id);
+    EXPECT_EQ(ref->id.value(), ResourceId(0x7f020000));
+
+    xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android", u"text");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+    ASSERT_FALSE(xmlAttr->compiledValue);   // Strings don't get compiled for memory sake.
+
+    xmlAttr = viewEl->findAttribute(u"", u"class");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_FALSE(xmlAttr->compiledAttribute);
+    ASSERT_EQ(xmlAttr->compiledValue, nullptr);
+}
+
+TEST_F(XmlReferenceLinkerTest, SdkLevelsAreRecorded) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+        <View xmlns:android="http://schemas.android.com/apk/res/android"
+              android:colorAccent="#ffffff" />)EOF");
+
+    XmlReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+    EXPECT_TRUE(linker.getSdkLevels().count(21) == 1);
+}
+
+TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+            <View xmlns:support="http://schemas.android.com/apk/res/com.android.support"
+                  support:colorAccent="#ff0000" />)EOF");
+
+    XmlReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+
+    xml::Element* viewEl = getRootElement(doc.get());
+    ASSERT_NE(viewEl, nullptr);
+
+    xml::Attribute* xmlAttr = viewEl->findAttribute(
+            u"http://schemas.android.com/apk/res/com.android.support", u"colorAccent");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+    EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x7f010001));
+    ASSERT_NE(valueCast<BinaryPrimitive>(xmlAttr->compiledValue.get()), nullptr);
+}
+
+TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+            <View xmlns:app="http://schemas.android.com/apk/res-auto"
+                  app:colorAccent="@app:color/red" />)EOF");
+
+    XmlReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+
+    xml::Element* viewEl = getRootElement(doc.get());
+    ASSERT_NE(viewEl, nullptr);
+
+    xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res-auto",
+                                                    u"colorAccent");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+    EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x7f010000));
+    Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get());
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->name);
+    AAPT_ASSERT_TRUE(ref->id);
+    EXPECT_EQ(ref->id.value(), ResourceId(0x7f020001));
+}
+
+TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+            <View xmlns:app="http://schemas.android.com/apk/res/android"
+                  app:attr="@app:id/id">
+              <View xmlns:app="http://schemas.android.com/apk/res/com.app.test"
+                    app:attr="@app:id/id"/>
+            </View>)EOF");
+
+    XmlReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+
+    xml::Element* viewEl = getRootElement(doc.get());
+    ASSERT_NE(viewEl, nullptr);
+
+    // All attributes and references in this element should be referring to "android" (0x01).
+    xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android",
+                                                    u"attr");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+    EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x01010002));
+    Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get());
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->id);
+    EXPECT_EQ(ref->id.value(), ResourceId(0x01030000));
+
+    ASSERT_FALSE(viewEl->getChildElements().empty());
+    viewEl = viewEl->getChildElements().front();
+    ASSERT_NE(viewEl, nullptr);
+
+    // All attributes and references in this element should be referring to "com.app.test" (0x7f).
+    xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/com.app.test", u"attr");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+    EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x7f010002));
+    ref = valueCast<Reference>(xmlAttr->compiledValue.get());
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->id);
+    EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000));
+}
+
+TEST_F(XmlReferenceLinkerTest, LinkViewWithLocalPackageAndAliasOfTheSameName) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+            <View xmlns:android="http://schemas.android.com/apk/res/com.app.test"
+                  android:attr="@id/id"/>)EOF");
+
+    XmlReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+
+    xml::Element* viewEl = getRootElement(doc.get());
+    ASSERT_NE(viewEl, nullptr);
+
+    // All attributes and references in this element should be referring to "com.app.test" (0x7f).
+    xml::Attribute* xmlAttr = viewEl->findAttribute(
+            u"http://schemas.android.com/apk/res/com.app.test", u"attr");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+    EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x7f010002));
+    Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get());
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->id);
+    EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/process.dot b/tools/aapt2/process.dot
deleted file mode 100644
index 4741952..0000000
--- a/tools/aapt2/process.dot
+++ /dev/null
@@ -1,108 +0,0 @@
-digraph aapt {
-    out_package [label="out/default/package.apk"];
-    out_fr_package [label="out/fr/package.apk"];
-    out_table_aligned [label="out/default/resources-aligned.arsc"];
-    out_table_fr_aligned [label="out/fr/resources-aligned.arsc"];
-    out_res_layout_main_xml [label="out/res/layout/main.xml"];
-    out_res_layout_v21_main_xml [color=red,label="out/res/layout-v21/main.xml"];
-    out_res_layout_fr_main_xml [label="out/res/layout-fr/main.xml"];
-    out_res_layout_fr_v21_main_xml [color=red,label="out/res/layout-fr-v21/main.xml"];
-    out_table [label="out/default/resources.arsc"];
-    out_fr_table [label="out/fr/resources.arsc"];
-    out_values_table [label="out/values/resources.arsc"];
-    out_layout_table [label="out/layout/resources.arsc"];
-    out_values_fr_table [label="out/values-fr/resources.arsc"];
-    out_layout_fr_table [label="out/layout-fr/resources.arsc"];
-    res_values_strings_xml [label="res/values/strings.xml"];
-    res_values_attrs_xml [label="res/values/attrs.xml"];
-    res_layout_main_xml [label="res/layout/main.xml"];
-    res_layout_fr_main_xml [label="res/layout-fr/main.xml"];
-    res_values_fr_strings_xml [label="res/values-fr/strings.xml"];
-
-    lib_apk_resources_arsc [label="lib.apk:resources.arsc",color=green];
-    lib_apk_res_layout_main_xml [label="lib.apk:res/layout/main.xml",color=green];
-    lib_apk_res_drawable_icon_png [label="lib.apk:res/drawable/icon.png",color=green];
-    lib_apk_fr_res_layout_main_xml [label="lib.apk:res/layout-fr/main.xml",color=green];
-    lib_apk_fr_res_drawable_icon_png [label="lib.apk:res/drawable-fr/icon.png",color=green];
-    out_res_layout_lib_main_xml [label="out/res/layout/lib-main.xml"];
-
-    out_package -> package_default;
-    out_fr_package -> package_fr;
-
-    package_default [shape=box,label="Assemble",color=blue];
-    package_default -> out_table_aligned;
-    package_default -> out_res_layout_main_xml;
-    package_default -> out_res_layout_v21_main_xml [color=red];
-    package_default -> out_res_layout_lib_main_xml;
-
-    package_fr [shape=box,label="Assemble",color=blue];
-    package_fr -> out_table_fr_aligned;
-    package_fr -> out_res_layout_fr_main_xml;
-    package_fr -> out_res_layout_fr_v21_main_xml [color=red];
-
-    out_table_aligned -> align_tables;
-    out_table_fr_aligned -> align_tables;
-
-    align_tables [shape=box,label="Align",color=blue];
-    align_tables -> out_table;
-    align_tables -> out_fr_table;
-
-    out_table -> link_tables;
-
-    link_tables [shape=box,label="Link",color=blue];
-    link_tables -> out_values_table;
-    link_tables -> out_layout_table;
-    link_tables -> lib_apk_resources_arsc;
-
-    out_values_table -> compile_values;
-
-    compile_values [shape=box,label="Collect",color=blue];
-    compile_values -> res_values_strings_xml;
-    compile_values -> res_values_attrs_xml;
-
-    out_layout_table -> collect_xml;
-
-    collect_xml [shape=box,label="Collect",color=blue];
-    collect_xml -> res_layout_main_xml;
-
-    out_fr_table -> link_fr_tables;
-
-    link_fr_tables [shape=box,label="Link",color=blue];
-    link_fr_tables -> out_values_fr_table;
-    link_fr_tables -> out_layout_fr_table;
-    link_fr_tables -> lib_apk_resources_arsc;
-
-    out_values_fr_table -> compile_values_fr;
-
-    compile_values_fr [shape=box,label="Collect",color=blue];
-    compile_values_fr -> res_values_fr_strings_xml;
-
-    out_layout_fr_table -> collect_xml_fr;
-
-    collect_xml_fr [shape=box,label="Collect",color=blue];
-    collect_xml_fr -> res_layout_fr_main_xml;
-
-    compile_res_layout_main_xml [shape=box,label="Compile",color=blue];
-
-    out_res_layout_main_xml -> compile_res_layout_main_xml;
-
-    out_res_layout_v21_main_xml -> compile_res_layout_main_xml [color=red];
-
-    compile_res_layout_main_xml -> res_layout_main_xml;
-    compile_res_layout_main_xml -> out_table_aligned;
-
-    compile_res_layout_fr_main_xml [shape=box,label="Compile",color=blue];
-
-    out_res_layout_fr_main_xml -> compile_res_layout_fr_main_xml;
-
-    out_res_layout_fr_v21_main_xml -> compile_res_layout_fr_main_xml [color=red];
-
-    compile_res_layout_fr_main_xml -> res_layout_fr_main_xml;
-    compile_res_layout_fr_main_xml -> out_table_fr_aligned;
-
-    out_res_layout_lib_main_xml -> compile_res_layout_lib_main_xml;
-
-    compile_res_layout_lib_main_xml [shape=box,label="Compile",color=blue];
-    compile_res_layout_lib_main_xml -> out_table_aligned;
-    compile_res_layout_lib_main_xml -> lib_apk_res_layout_main_xml;
-}
diff --git a/tools/aapt2/process/IResourceTableConsumer.h b/tools/aapt2/process/IResourceTableConsumer.h
new file mode 100644
index 0000000..24ad05d
--- /dev/null
+++ b/tools/aapt2/process/IResourceTableConsumer.h
@@ -0,0 +1,75 @@
+/*
+ * 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_PROCESS_IRESOURCETABLECONSUMER_H
+#define AAPT_PROCESS_IRESOURCETABLECONSUMER_H
+
+#include "Diagnostics.h"
+#include "NameMangler.h"
+#include "Resource.h"
+#include "ResourceValues.h"
+#include "Source.h"
+
+#include <iostream>
+#include <list>
+#include <sstream>
+
+namespace aapt {
+
+class ResourceTable;
+struct ISymbolTable;
+
+struct IAaptContext {
+    virtual ~IAaptContext() = default;
+
+    virtual ISymbolTable* getExternalSymbols() = 0;
+    virtual IDiagnostics* getDiagnostics() = 0;
+    virtual StringPiece16 getCompilationPackage() = 0;
+    virtual uint8_t getPackageId() = 0;
+    virtual NameMangler* getNameMangler() = 0;
+};
+
+struct IResourceTableConsumer {
+    virtual ~IResourceTableConsumer() = default;
+
+    virtual bool consume(IAaptContext* context, ResourceTable* table) = 0;
+};
+
+namespace xml {
+struct Node;
+}
+
+struct XmlResource {
+    ResourceFile file;
+    std::unique_ptr<xml::Node> root;
+};
+
+struct IXmlResourceConsumer {
+    virtual ~IXmlResourceConsumer() = default;
+
+    virtual bool consume(IAaptContext* context, XmlResource* resource) = 0;
+};
+
+struct IPackageDeclStack {
+    virtual ~IPackageDeclStack() = default;
+
+    virtual Maybe<ResourceName> transformPackage(const ResourceName& name,
+                                                 const StringPiece16& localPackage) const = 0;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_PROCESS_IRESOURCETABLECONSUMER_H */
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
new file mode 100644
index 0000000..c96b080
--- /dev/null
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -0,0 +1,204 @@
+/*
+ * 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 "ConfigDescription.h"
+#include "Resource.h"
+#include "util/Util.h"
+
+#include "process/SymbolTable.h"
+
+#include <androidfw/AssetManager.h>
+#include <androidfw/ResourceTypes.h>
+
+namespace aapt {
+
+const ISymbolTable::Symbol* SymbolTableWrapper::findByName(const ResourceName& name) {
+    if (const std::shared_ptr<Symbol>& s = mCache.get(name)) {
+        return s.get();
+    }
+
+    Maybe<ResourceTable::SearchResult> result = mTable->findResource(name);
+    if (!result) {
+        if (name.type == ResourceType::kAttr) {
+            // Recurse and try looking up a private attribute.
+            return findByName(ResourceName{ name.package, ResourceType::kAttrPrivate, name.entry });
+        }
+        return {};
+    }
+
+    ResourceTable::SearchResult sr = result.value();
+
+    // If no ID exists, we treat the symbol as missing. SymbolTables are used to
+    // find symbols to link.
+    if (!sr.package->id || !sr.type->id || !sr.entry->id) {
+        return {};
+    }
+
+    std::shared_ptr<Symbol> symbol = std::make_shared<Symbol>();
+    symbol->id = ResourceId{
+            sr.package->id.value(), sr.type->id.value(), sr.entry->id.value() };
+
+    if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) {
+        auto lt = [](ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool {
+            return lhs.config < rhs;
+        };
+
+        const ConfigDescription kDefaultConfig;
+        auto iter = std::lower_bound(sr.entry->values.begin(), sr.entry->values.end(),
+                                     kDefaultConfig, lt);
+
+        if (iter != sr.entry->values.end() && iter->config == kDefaultConfig) {
+            // This resource has an Attribute.
+            symbol->attribute = util::make_unique<Attribute>(
+                    *static_cast<Attribute*>(iter->value.get()));
+        }
+    }
+
+    if (name.type == ResourceType::kAttrPrivate) {
+        // Masquerade this entry as kAttr.
+        mCache.put(ResourceName{ name.package, ResourceType::kAttr, name.entry }, symbol);
+    } else {
+        mCache.put(name, symbol);
+    }
+    return symbol.get();
+}
+
+
+static std::shared_ptr<ISymbolTable::Symbol> lookupIdInTable(const android::ResTable& table,
+                                                             ResourceId id) {
+    android::Res_value val = {};
+    ssize_t block = table.getResource(id.id, &val, true);
+    if (block >= 0) {
+        std::shared_ptr<ISymbolTable::Symbol> s = std::make_shared<ISymbolTable::Symbol>();
+        s->id = id;
+        return s;
+    }
+
+    // Try as a bag.
+    const android::ResTable::bag_entry* entry;
+    ssize_t count = table.lockBag(id.id, &entry);
+    if (count < 0) {
+        table.unlockBag(entry);
+        return nullptr;
+    }
+
+    // We found a resource.
+    std::shared_ptr<ISymbolTable::Symbol> s = std::make_shared<ISymbolTable::Symbol>();
+    s->id = id;
+
+    // Check to see if it is an attribute.
+    for (size_t i = 0; i < (size_t) count; i++) {
+        if (entry[i].map.name.ident == android::ResTable_map::ATTR_TYPE) {
+            s->attribute = util::make_unique<Attribute>(false);
+            s->attribute->typeMask = entry[i].map.value.data;
+            break;
+        }
+    }
+
+    if (s->attribute) {
+        for (size_t i = 0; i < (size_t) count; i++) {
+            if (!Res_INTERNALID(entry[i].map.name.ident)) {
+                android::ResTable::resource_name entryName;
+                if (!table.getResourceName(entry[i].map.name.ident, false, &entryName)) {
+                    table.unlockBag(entry);
+                    return nullptr;
+                }
+
+                const ResourceType* parsedType = parseResourceType(
+                        StringPiece16(entryName.type, entryName.typeLen));
+                if (!parsedType) {
+                    table.unlockBag(entry);
+                    return nullptr;
+                }
+
+                Attribute::Symbol symbol;
+                symbol.symbol.name = ResourceNameRef(
+                        StringPiece16(entryName.package, entryName.packageLen),
+                        *parsedType,
+                        StringPiece16(entryName.name, entryName.nameLen)).toResourceName();
+                symbol.symbol.id = ResourceId(entry[i].map.name.ident);
+                symbol.value = entry[i].map.value.data;
+                s->attribute->symbols.push_back(std::move(symbol));
+            }
+        }
+    }
+    table.unlockBag(entry);
+    return s;
+}
+
+const ISymbolTable::Symbol* AssetManagerSymbolTableBuilder::AssetManagerSymbolTable::findByName(
+        const ResourceName& name) {
+    if (const std::shared_ptr<Symbol>& s = mCache.get(name)) {
+        return s.get();
+    }
+
+    for (const auto& asset : mAssets) {
+        const android::ResTable& table = asset->getResources(false);
+        StringPiece16 typeStr = toString(name.type);
+        ResourceId resId = table.identifierForName(name.entry.data(), name.entry.size(),
+                                                   typeStr.data(), typeStr.size(),
+                                                   name.package.data(), name.package.size());
+        if (!resId.isValid()) {
+            continue;
+        }
+
+        std::shared_ptr<Symbol> s = lookupIdInTable(table, resId);
+        if (s) {
+            mCache.put(name, s);
+            return s.get();
+        }
+    }
+    return nullptr;
+}
+
+const ISymbolTable::Symbol* AssetManagerSymbolTableBuilder::AssetManagerSymbolTable::findById(
+        ResourceId id) {
+    if (const std::shared_ptr<Symbol>& s = mIdCache.get(id)) {
+        return s.get();
+    }
+
+    for (const auto& asset : mAssets) {
+        const android::ResTable& table = asset->getResources(false);
+
+        std::shared_ptr<Symbol> s = lookupIdInTable(table, id);
+        if (s) {
+            mIdCache.put(id, s);
+            return s.get();
+        }
+    }
+    return nullptr;
+}
+
+const ISymbolTable::Symbol* JoinedSymbolTableBuilder::JoinedSymbolTable::findByName(
+        const ResourceName& name) {
+    for (auto& symbolTable : mSymbolTables) {
+        if (const Symbol* s = symbolTable->findByName(name)) {
+            return s;
+        }
+    }
+    return {};
+}
+
+const ISymbolTable::Symbol* JoinedSymbolTableBuilder::JoinedSymbolTable::findById(ResourceId id) {
+    for (auto& symbolTable : mSymbolTables) {
+        if (const Symbol* s = symbolTable->findById(id)) {
+            return s;
+        }
+    }
+    return {};
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
new file mode 100644
index 0000000..22096ed
--- /dev/null
+++ b/tools/aapt2/process/SymbolTable.h
@@ -0,0 +1,152 @@
+/*
+ * 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_PROCESS_SYMBOLTABLE_H
+#define AAPT_PROCESS_SYMBOLTABLE_H
+
+#include "Resource.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "util/Util.h"
+
+#include <utils/JenkinsHash.h>
+#include <utils/LruCache.h>
+
+#include <androidfw/AssetManager.h>
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <vector>
+
+namespace aapt {
+
+struct ISymbolTable {
+    virtual ~ISymbolTable() = default;
+
+    struct Symbol {
+        ResourceId id;
+        std::unique_ptr<Attribute> attribute;
+        bool isPublic;
+    };
+
+    /**
+     * Never hold on to the result between calls to findByName or findById. The results
+     * are typically stored in a cache which may evict entries.
+     */
+    virtual const Symbol* findByName(const ResourceName& name) = 0;
+    virtual const Symbol* findById(ResourceId id) = 0;
+};
+
+inline android::hash_t hash_type(const ResourceName& name) {
+    std::hash<std::u16string> strHash;
+    android::hash_t hash = 0;
+    hash = android::JenkinsHashMix(hash, strHash(name.package));
+    hash = android::JenkinsHashMix(hash, (uint32_t) name.type);
+    hash = android::JenkinsHashMix(hash, strHash(name.entry));
+    return hash;
+}
+
+inline android::hash_t hash_type(const ResourceId& id) {
+    return android::hash_type(id.id);
+}
+
+/**
+ * Presents a ResourceTable as an ISymbolTable, caching results.
+ * Instances of this class must outlive the encompassed ResourceTable.
+ * Since symbols are cached, the ResourceTable should not change during the
+ * lifetime of this SymbolTableWrapper.
+ *
+ * If a resource in the ResourceTable does not have a ResourceID assigned to it,
+ * it is ignored.
+ *
+ * Lookups by ID are ignored.
+ */
+class SymbolTableWrapper : public ISymbolTable {
+private:
+    ResourceTable* mTable;
+
+    // We use shared_ptr because unique_ptr is not supported and
+    // we need automatic deletion.
+    android::LruCache<ResourceName, std::shared_ptr<Symbol>> mCache;
+
+public:
+    SymbolTableWrapper(ResourceTable* table) : mTable(table), mCache(200) {
+    }
+
+    const Symbol* findByName(const ResourceName& name) override;
+
+    // Unsupported, all queries to ResourceTable should be done by name.
+    const Symbol* findById(ResourceId id) override {
+        return {};
+    }
+};
+
+class AssetManagerSymbolTableBuilder {
+private:
+    struct AssetManagerSymbolTable : public ISymbolTable {
+        std::vector<std::unique_ptr<android::AssetManager>> mAssets;
+
+        // We use shared_ptr because unique_ptr is not supported and
+        // we need automatic deletion.
+        android::LruCache<ResourceName, std::shared_ptr<Symbol>> mCache;
+        android::LruCache<ResourceId, std::shared_ptr<Symbol>> mIdCache;
+
+        AssetManagerSymbolTable() : mCache(200), mIdCache(200) {
+        }
+
+        const Symbol* findByName(const ResourceName& name) override;
+        const Symbol* findById(ResourceId id) override;
+    };
+
+    std::unique_ptr<AssetManagerSymbolTable> mSymbolTable =
+            util::make_unique<AssetManagerSymbolTable>();
+
+public:
+    AssetManagerSymbolTableBuilder& add(std::unique_ptr<android::AssetManager> assetManager) {
+        mSymbolTable->mAssets.push_back(std::move(assetManager));
+        return *this;
+    }
+
+    std::unique_ptr<ISymbolTable> build() {
+        return std::move(mSymbolTable);
+    }
+};
+
+class JoinedSymbolTableBuilder {
+private:
+    struct JoinedSymbolTable : public ISymbolTable {
+        std::vector<std::unique_ptr<ISymbolTable>> mSymbolTables;
+
+        const Symbol* findByName(const ResourceName& name) override;
+        const Symbol* findById(ResourceId id) override;
+    };
+
+    std::unique_ptr<JoinedSymbolTable> mSymbolTable = util::make_unique<JoinedSymbolTable>();
+
+public:
+    JoinedSymbolTableBuilder& addSymbolTable(std::unique_ptr<ISymbolTable> table) {
+        mSymbolTable->mSymbolTables.push_back(std::move(table));
+        return *this;
+    }
+
+    std::unique_ptr<ISymbolTable> build() {
+        return std::move(mSymbolTable);
+    }
+};
+
+} // namespace aapt
+
+#endif /* AAPT_PROCESS_SYMBOLTABLE_H */
diff --git a/tools/aapt2/process/SymbolTable_test.cpp b/tools/aapt2/process/SymbolTable_test.cpp
new file mode 100644
index 0000000..1dc3b4f
--- /dev/null
+++ b/tools/aapt2/process/SymbolTable_test.cpp
@@ -0,0 +1,56 @@
+/*
+ * 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 "process/SymbolTable.h"
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(SymbolTableWrapperTest, FindSymbolsWithIds) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .addSimple(u"@android:id/foo", ResourceId(0x01020000))
+            .addSimple(u"@android:id/bar")
+            .addValue(u"@android:attr/foo", ResourceId(0x01010000),
+                      test::AttributeBuilder().build())
+            .build();
+
+    SymbolTableWrapper symbolTable(table.get());
+    EXPECT_NE(symbolTable.findByName(test::parseNameOrDie(u"@android:id/foo")), nullptr);
+    EXPECT_EQ(symbolTable.findByName(test::parseNameOrDie(u"@android:id/bar")), nullptr);
+
+    const ISymbolTable::Symbol* s = symbolTable.findByName(
+            test::parseNameOrDie(u"@android:attr/foo"));
+    ASSERT_NE(s, nullptr);
+    EXPECT_NE(s->attribute, nullptr);
+}
+
+TEST(SymbolTableWrapperTest, FindPrivateAttrSymbol) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .addValue(u"@android:^attr-private/foo", ResourceId(0x01010000),
+                      test::AttributeBuilder().build())
+            .build();
+
+    SymbolTableWrapper symbolTable(table.get());
+    const ISymbolTable::Symbol* s = symbolTable.findByName(
+                test::parseNameOrDie(u"@android:attr/foo"));
+    ASSERT_NE(s, nullptr);
+    EXPECT_NE(s->attribute, nullptr);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
new file mode 100644
index 0000000..0d8d8b5
--- /dev/null
+++ b/tools/aapt2/test/Builders.h
@@ -0,0 +1,188 @@
+/*
+ * 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_TEST_BUILDERS_H
+#define AAPT_TEST_BUILDERS_H
+
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "util/Util.h"
+#include "XmlDom.h"
+
+#include "test/Common.h"
+
+#include <memory>
+
+namespace aapt {
+namespace test {
+
+class ResourceTableBuilder {
+private:
+    DummyDiagnosticsImpl mDiagnostics;
+    std::unique_ptr<ResourceTable> mTable = util::make_unique<ResourceTable>();
+
+public:
+    ResourceTableBuilder() = default;
+
+    ResourceTableBuilder& setPackageId(const StringPiece16& packageName, uint8_t id) {
+        ResourceTablePackage* package = mTable->createPackage(packageName, id);
+        assert(package);
+        return *this;
+    }
+
+    ResourceTableBuilder& addSimple(const StringPiece16& name, ResourceId id = {}) {
+        return addValue(name, id, util::make_unique<Id>());
+    }
+
+    ResourceTableBuilder& addReference(const StringPiece16& name, const StringPiece16& ref) {
+        return addReference(name, {}, ref);
+    }
+
+    ResourceTableBuilder& addReference(const StringPiece16& name, ResourceId id,
+                                       const StringPiece16& ref) {
+        return addValue(name, id, util::make_unique<Reference>(parseNameOrDie(ref)));
+    }
+
+    ResourceTableBuilder& addString(const StringPiece16& name, const StringPiece16& str) {
+        return addString(name, {}, str);
+    }
+
+    ResourceTableBuilder& addString(const StringPiece16& name, ResourceId id,
+                                    const StringPiece16& str) {
+        return addValue(name, id, util::make_unique<String>(mTable->stringPool.makeRef(str)));
+    }
+
+    ResourceTableBuilder& addFileReference(const StringPiece16& name, const StringPiece16& path) {
+        return addFileReference(name, {}, path);
+    }
+
+    ResourceTableBuilder& addFileReference(const StringPiece16& name, ResourceId id,
+                                           const StringPiece16& path) {
+        return addValue(name, id,
+                        util::make_unique<FileReference>(mTable->stringPool.makeRef(path)));
+    }
+
+
+    ResourceTableBuilder& addValue(const StringPiece16& name, std::unique_ptr<Value> value) {
+        return addValue(name, {}, std::move(value));
+    }
+
+    ResourceTableBuilder& addValue(const StringPiece16& name, ResourceId id,
+                                       std::unique_ptr<Value> value) {
+        return addValue(name, id, {}, std::move(value));
+    }
+
+    ResourceTableBuilder& addValue(const StringPiece16& name, ResourceId id,
+                                   const ConfigDescription& config, std::unique_ptr<Value> value) {
+        bool result = mTable->addResource(parseNameOrDie(name), id, config, {}, std::move(value),
+                                          &mDiagnostics);
+        assert(result);
+        return *this;
+    }
+
+    std::unique_ptr<ResourceTable> build() {
+        return std::move(mTable);
+    }
+};
+
+inline std::unique_ptr<Reference> buildReference(const StringPiece16& ref,
+                                                 Maybe<ResourceId> id = {}) {
+    std::unique_ptr<Reference> reference = util::make_unique<Reference>(parseNameOrDie(ref));
+    reference->id = id;
+    return reference;
+}
+
+class AttributeBuilder {
+private:
+    std::unique_ptr<Attribute> mAttr;
+
+public:
+    AttributeBuilder(bool weak = false) : mAttr(util::make_unique<Attribute>(weak)) {
+        mAttr->typeMask = android::ResTable_map::TYPE_ANY;
+    }
+
+    AttributeBuilder& setTypeMask(uint32_t typeMask) {
+        mAttr->typeMask = typeMask;
+        return *this;
+    }
+
+    AttributeBuilder& addItem(const StringPiece16& name, uint32_t value) {
+        mAttr->symbols.push_back(Attribute::Symbol{
+                Reference(ResourceName{ {}, ResourceType::kId, name.toString()}),
+                value});
+        return *this;
+    }
+
+    std::unique_ptr<Attribute> build() {
+        return std::move(mAttr);
+    }
+};
+
+class StyleBuilder {
+private:
+    std::unique_ptr<Style> mStyle = util::make_unique<Style>();
+
+public:
+    StyleBuilder& setParent(const StringPiece16& str) {
+        mStyle->parent = Reference(parseNameOrDie(str));
+        return *this;
+    }
+
+    StyleBuilder& addItem(const StringPiece16& str, std::unique_ptr<Item> value) {
+        mStyle->entries.push_back(Style::Entry{ Reference(parseNameOrDie(str)), std::move(value) });
+        return *this;
+    }
+
+    StyleBuilder& addItem(const StringPiece16& str, ResourceId id, std::unique_ptr<Item> value) {
+        addItem(str, std::move(value));
+        mStyle->entries.back().key.id = id;
+        return *this;
+    }
+
+    std::unique_ptr<Style> build() {
+        return std::move(mStyle);
+    }
+};
+
+class StyleableBuilder {
+private:
+    std::unique_ptr<Styleable> mStyleable = util::make_unique<Styleable>();
+
+public:
+    StyleableBuilder& addItem(const StringPiece16& str, Maybe<ResourceId> id = {}) {
+        mStyleable->entries.push_back(Reference(parseNameOrDie(str)));
+        mStyleable->entries.back().id = id;
+        return *this;
+    }
+
+    std::unique_ptr<Styleable> build() {
+        return std::move(mStyleable);
+    }
+};
+
+inline std::unique_ptr<XmlResource> buildXmlDom(const StringPiece& str) {
+    std::stringstream in;
+    in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str;
+    StdErrDiagnostics diag;
+    std::unique_ptr<XmlResource> doc = xml::inflate(&in, &diag, {});
+    assert(doc);
+    return doc;
+}
+
+} // namespace test
+} // namespace aapt
+
+#endif /* AAPT_TEST_BUILDERS_H */
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
new file mode 100644
index 0000000..b41c568
--- /dev/null
+++ b/tools/aapt2/test/Common.h
@@ -0,0 +1,92 @@
+/*
+ * 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_TEST_COMMON_H
+#define AAPT_TEST_COMMON_H
+
+#include "ConfigDescription.h"
+#include "ResourceTable.h"
+#include "ResourceUtils.h"
+#include "ValueVisitor.h"
+
+#include "process/IResourceTableConsumer.h"
+#include "util/StringPiece.h"
+
+#include <gtest/gtest.h>
+#include <iostream>
+
+//
+// GTEST 1.7 doesn't explicitly cast to bool, which causes explicit operators to fail to compile.
+//
+#define AAPT_ASSERT_TRUE(v) ASSERT_TRUE(bool(v))
+#define AAPT_ASSERT_FALSE(v) ASSERT_FALSE(bool(v))
+#define AAPT_EXPECT_TRUE(v) EXPECT_TRUE(bool(v))
+#define AAPT_EXPECT_FALSE(v) EXPECT_FALSE(bool(v))
+
+namespace aapt {
+namespace test {
+
+struct DummyDiagnosticsImpl : public IDiagnostics {
+    void error(const DiagMessage& message) override {
+        DiagMessageActual actual = message.build();
+        std::cerr << actual.source << ": error: " << actual.message << "." << std::endl;
+    }
+    void warn(const DiagMessage& message) override {
+        DiagMessageActual actual = message.build();
+        std::cerr << actual.source << ": warn: " << actual.message << "." << std::endl;
+    }
+    void note(const DiagMessage& message) override {}
+};
+
+inline ResourceName parseNameOrDie(const StringPiece16& str) {
+    ResourceNameRef ref;
+    bool result = ResourceUtils::tryParseReference(str, &ref);
+    assert(result && "invalid resource name");
+    return ref.toResourceName();
+}
+
+inline ConfigDescription parseConfigOrDie(const StringPiece& str) {
+    ConfigDescription config;
+    bool result = ConfigDescription::parse(str, &config);
+    assert(result && "invalid configuration");
+    return config;
+}
+
+template <typename T> T* getValueForConfig(ResourceTable* table, const StringPiece16& resName,
+                                           const ConfigDescription& config) {
+    Maybe<ResourceTable::SearchResult> result = table->findResource(parseNameOrDie(resName));
+    if (result) {
+        ResourceEntry* entry = result.value().entry;
+        auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), config,
+                                     [](const ResourceConfigValue& a, const ConfigDescription& b)
+                                             -> bool {
+                                         return a.config < b;
+                                     });
+        if (iter != entry->values.end() && iter->config == config) {
+            return valueCast<T>(iter->value.get());
+        }
+    }
+    return nullptr;
+}
+
+template <typename T> T* getValue(ResourceTable* table, const StringPiece16& resName) {
+    return getValueForConfig<T>(table, resName, {});
+}
+
+} // namespace test
+} // namespace aapt
+
+#endif /* AAPT_TEST_COMMON_H */
diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h
new file mode 100644
index 0000000..4fa4918
--- /dev/null
+++ b/tools/aapt2/test/Context.h
@@ -0,0 +1,156 @@
+/*
+ * 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_TEST_CONTEXT_H
+#define AAPT_TEST_CONTEXT_H
+
+#include "NameMangler.h"
+#include "util/Util.h"
+
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+#include "test/Common.h"
+
+#include <cassert>
+#include <list>
+
+namespace aapt {
+namespace test {
+
+class Context : public IAaptContext {
+private:
+    friend class ContextBuilder;
+
+    Context() = default;
+
+    Maybe<std::u16string> mCompilationPackage;
+    Maybe<uint8_t> mPackageId;
+    std::unique_ptr<IDiagnostics> mDiagnostics = util::make_unique<StdErrDiagnostics>();
+    std::unique_ptr<ISymbolTable> mSymbols;
+    std::unique_ptr<NameMangler> mNameMangler;
+
+public:
+    ISymbolTable* getExternalSymbols() override {
+        assert(mSymbols && "test symbols not set");
+        return mSymbols.get();
+    }
+
+    void setSymbolTable(std::unique_ptr<ISymbolTable> symbols) {
+        mSymbols = std::move(symbols);
+    }
+
+    IDiagnostics* getDiagnostics() override {
+        assert(mDiagnostics && "test diagnostics not set");
+        return mDiagnostics.get();
+    }
+
+    StringPiece16 getCompilationPackage() override {
+        assert(mCompilationPackage && "package name not set");
+        return mCompilationPackage.value();
+    }
+
+    uint8_t getPackageId() override {
+        assert(mPackageId && "package ID not set");
+        return mPackageId.value();
+    }
+
+    NameMangler* getNameMangler() override {
+        assert(mNameMangler && "test name mangler not set");
+        return mNameMangler.get();
+    }
+};
+
+class ContextBuilder {
+private:
+    std::unique_ptr<Context> mContext = std::unique_ptr<Context>(new Context());
+
+public:
+    ContextBuilder& setCompilationPackage(const StringPiece16& package) {
+        mContext->mCompilationPackage = package.toString();
+        return *this;
+    }
+
+    ContextBuilder& setPackageId(uint8_t id) {
+        mContext->mPackageId = id;
+        return *this;
+    }
+
+    ContextBuilder& setSymbolTable(std::unique_ptr<ISymbolTable> symbols) {
+        mContext->mSymbols = std::move(symbols);
+        return *this;
+    }
+
+    ContextBuilder& setDiagnostics(std::unique_ptr<IDiagnostics> diag) {
+        mContext->mDiagnostics = std::move(diag);
+        return *this;
+    }
+
+    ContextBuilder& setNameManglerPolicy(NameManglerPolicy policy) {
+        mContext->mNameMangler = util::make_unique<NameMangler>(policy);
+        return *this;
+    }
+
+    std::unique_ptr<Context> build() {
+        return std::move(mContext);
+    }
+};
+
+class StaticSymbolTableBuilder {
+private:
+    struct SymbolTable : public ISymbolTable {
+        std::list<std::unique_ptr<Symbol>> mSymbols;
+        std::map<ResourceName, Symbol*> mNameMap;
+        std::map<ResourceId, Symbol*> mIdMap;
+
+        const Symbol* findByName(const ResourceName& name) override {
+            auto iter = mNameMap.find(name);
+            if (iter != mNameMap.end()) {
+                return iter->second;
+            }
+            return nullptr;
+        }
+
+        const Symbol* findById(ResourceId id) override {
+            auto iter = mIdMap.find(id);
+            if (iter != mIdMap.end()) {
+                return iter->second;
+            }
+            return nullptr;
+        }
+    };
+
+    std::unique_ptr<SymbolTable> mSymbolTable = util::make_unique<SymbolTable>();
+
+public:
+    StaticSymbolTableBuilder& addSymbol(const StringPiece16& name, ResourceId id,
+                                  std::unique_ptr<Attribute> attr = {}) {
+        std::unique_ptr<ISymbolTable::Symbol> symbol = util::make_unique<ISymbolTable::Symbol>(
+                id, std::move(attr));
+        mSymbolTable->mNameMap[parseNameOrDie(name)] = symbol.get();
+        mSymbolTable->mIdMap[id] = symbol.get();
+        mSymbolTable->mSymbols.push_back(std::move(symbol));
+        return *this;
+    }
+
+    std::unique_ptr<ISymbolTable> build() {
+        return std::move(mSymbolTable);
+    }
+};
+
+} // namespace test
+} // namespace aapt
+
+#endif /* AAPT_TEST_CONTEXT_H */
diff --git a/tools/aapt2/todo.txt b/tools/aapt2/todo.txt
deleted file mode 100644
index acc8bfb..0000000
--- a/tools/aapt2/todo.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-XML Files
-X Collect declared IDs
-X Build StringPool
-X Flatten
-
-Resource Table Operations
-X Build Resource Table (with StringPool) from XML.
-X Modify Resource Table.
-X - Copy and transform resources.
-X   - Pre-17/21 attr correction.
-X Perform analysis of types.
-X Flatten.
-X Assign resource IDs.
-X Assign public resource IDs.
-X Merge resource tables
-- Assign private attributes to different typespace.
-- Align resource tables
-
-Splits
-- Collect all resources (ids from layouts).
-- Generate resource table from base resources.
-- Generate resource table from individual resources of the required type.
-- Align resource tables (same type/name = same ID).
-
-Fat Apk
-X Collect all resources (ids from layouts).
-X Generate resource tables for all configurations.
-- Align individual resource tables.
-- Merge resource tables.
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
new file mode 100644
index 0000000..992a45c
--- /dev/null
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -0,0 +1,822 @@
+/*
+ * 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 "ResourceTable.h"
+#include "ResourceUtils.h"
+#include "ResourceValues.h"
+#include "Source.h"
+#include "ValueVisitor.h"
+
+#include "flatten/ResourceTypeExtensions.h"
+#include "unflatten/BinaryResourceParser.h"
+#include "unflatten/ResChunkPullParser.h"
+#include "util/Util.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <androidfw/TypeWrappers.h>
+#include <utils/misc.h>
+
+#include <map>
+#include <string>
+
+namespace aapt {
+
+using namespace android;
+
+/*
+ * Visitor that converts a reference's resource ID to a resource name,
+ * given a mapping from resource ID to resource name.
+ */
+class ReferenceIdToNameVisitor : public ValueVisitor {
+private:
+    const std::map<ResourceId, ResourceName>* mMapping;
+
+public:
+    using ValueVisitor::visit;
+
+    ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceName>* mapping) :
+            mMapping(mapping) {
+        assert(mMapping);
+    }
+
+    void visit(Reference* reference) override {
+        if (!reference->id || !reference->id.value().isValid()) {
+            return;
+        }
+
+        ResourceId id = reference->id.value();
+        auto cacheIter = mMapping->find(id);
+        if (cacheIter != mMapping->end()) {
+            reference->name = cacheIter->second;
+            reference->id = {};
+        }
+    }
+};
+
+BinaryResourceParser::BinaryResourceParser(IAaptContext* context, ResourceTable* table,
+                                           const Source& source, const void* data, size_t len) :
+        mContext(context), mTable(table), mSource(source), mData(data), mDataLen(len) {
+}
+
+bool BinaryResourceParser::parse() {
+    ResChunkPullParser parser(mData, mDataLen);
+
+    bool error = false;
+    while(ResChunkPullParser::isGoodEvent(parser.next())) {
+        if (parser.getChunk()->type != android::RES_TABLE_TYPE) {
+            mContext->getDiagnostics()->warn(DiagMessage(mSource)
+                                             << "unknown chunk of type '"
+                                             << (int) parser.getChunk()->type << "'");
+            continue;
+        }
+
+        if (!parseTable(parser.getChunk())) {
+            error = true;
+        }
+    }
+
+    if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "corrupt resource table: "
+                                          << parser.getLastError());
+        return false;
+    }
+    return !error;
+}
+
+bool BinaryResourceParser::getSymbol(const void* data, ResourceNameRef* outSymbol) {
+    if (!mSymbolEntries || mSymbolEntryCount == 0) {
+        return false;
+    }
+
+    if ((uintptr_t) data < (uintptr_t) mData) {
+        return false;
+    }
+
+    // We only support 32 bit offsets right now.
+    const uintptr_t offset = (uintptr_t) data - (uintptr_t) mData;
+    if (offset > std::numeric_limits<uint32_t>::max()) {
+        return false;
+    }
+
+    for (size_t i = 0; i < mSymbolEntryCount; i++) {
+        if (util::deviceToHost32(mSymbolEntries[i].offset) == offset) {
+            // This offset is a symbol!
+            const StringPiece16 str = util::getString(
+                    mSymbolPool, util::deviceToHost32(mSymbolEntries[i].stringIndex));
+
+            StringPiece16 typeStr;
+            ResourceUtils::extractResourceName(str, &outSymbol->package, &typeStr,
+                                               &outSymbol->entry);
+            const ResourceType* type = parseResourceType(typeStr);
+            if (!type) {
+                return false;
+            }
+
+            outSymbol->type = *type;
+
+            // Since we scan the symbol table in order, we can start looking for the
+            // next symbol from this point.
+            mSymbolEntryCount -= i + 1;
+            mSymbolEntries += i + 1;
+            return true;
+        }
+    }
+    return false;
+}
+
+/**
+ * Parses the SymbolTable_header, which is present on non-final resource tables
+ * after the compile phase.
+ *
+ * | SymbolTable_header |
+ * |--------------------|
+ * |SymbolTable_entry 0 |
+ * |SymbolTable_entry 1 |
+ * | ...                |
+ * |SymbolTable_entry n |
+ * |--------------------|
+ *
+ */
+bool BinaryResourceParser::parseSymbolTable(const ResChunk_header* chunk) {
+    const SymbolTable_header* header = convertTo<SymbolTable_header>(chunk);
+    if (!header) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "corrupt SymbolTable_header");
+        return false;
+    }
+
+    const uint32_t entrySizeBytes =
+            util::deviceToHost32(header->count) * sizeof(SymbolTable_entry);
+    if (entrySizeBytes > getChunkDataLen(&header->header)) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "SymbolTable_header data section too long");
+        return false;
+    }
+
+    mSymbolEntries = (const SymbolTable_entry*) getChunkData(&header->header);
+    mSymbolEntryCount = util::deviceToHost32(header->count);
+
+    // Skip over the symbol entries and parse the StringPool chunk that should be next.
+    ResChunkPullParser parser(getChunkData(&header->header) + entrySizeBytes,
+                              getChunkDataLen(&header->header) - entrySizeBytes);
+    if (!ResChunkPullParser::isGoodEvent(parser.next())) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "failed to parse chunk in SymbolTable: "
+                                          << parser.getLastError());
+        return false;
+    }
+
+    const ResChunk_header* nextChunk = parser.getChunk();
+    if (util::deviceToHost16(nextChunk->type) != android::RES_STRING_POOL_TYPE) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "expected string pool in SymbolTable but got "
+                                          << "chunk of type "
+                                          << (int) util::deviceToHost16(nextChunk->type));
+        return false;
+    }
+
+    if (mSymbolPool.setTo(nextChunk, util::deviceToHost32(nextChunk->size)) != NO_ERROR) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "corrupt string pool in SymbolTable: "
+                                          << mSymbolPool.getError());
+        return false;
+    }
+    return true;
+}
+
+/**
+ * Parses the resource table, which contains all the packages, types, and entries.
+ */
+bool BinaryResourceParser::parseTable(const ResChunk_header* chunk) {
+    const ResTable_header* tableHeader = convertTo<ResTable_header>(chunk);
+    if (!tableHeader) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource) << "corrupt ResTable_header chunk");
+        return false;
+    }
+
+    ResChunkPullParser parser(getChunkData(&tableHeader->header),
+                              getChunkDataLen(&tableHeader->header));
+    while (ResChunkPullParser::isGoodEvent(parser.next())) {
+        switch (util::deviceToHost16(parser.getChunk()->type)) {
+        case android::RES_STRING_POOL_TYPE:
+            if (mValuePool.getError() == NO_INIT) {
+                status_t err = mValuePool.setTo(parser.getChunk(),
+                                                util::deviceToHost32(parser.getChunk()->size));
+                if (err != NO_ERROR) {
+                    mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                                      << "corrupt string pool in ResTable: "
+                                                      << mValuePool.getError());
+                    return false;
+                }
+
+                // Reserve some space for the strings we are going to add.
+                mTable->stringPool.hintWillAdd(mValuePool.size(), mValuePool.styleCount());
+            } else {
+                mContext->getDiagnostics()->warn(DiagMessage(mSource)
+                                                 << "unexpected string pool in ResTable");
+            }
+            break;
+
+        case RES_TABLE_SYMBOL_TABLE_TYPE:
+            if (!parseSymbolTable(parser.getChunk())) {
+                return false;
+            }
+            break;
+
+        case RES_TABLE_SOURCE_POOL_TYPE: {
+            status_t err = mSourcePool.setTo(getChunkData(parser.getChunk()),
+                                             getChunkDataLen(parser.getChunk()));
+            if (err != NO_ERROR) {
+                mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                                  << "corrupt source string pool in ResTable: "
+                                                  << mSourcePool.getError());
+                return false;
+            }
+            break;
+        }
+
+        case android::RES_TABLE_PACKAGE_TYPE:
+            if (!parsePackage(parser.getChunk())) {
+                return false;
+            }
+            break;
+
+        default:
+            mContext->getDiagnostics()
+                    ->warn(DiagMessage(mSource)
+                           << "unexpected chunk type "
+                           << (int) util::deviceToHost16(parser.getChunk()->type));
+            break;
+        }
+    }
+
+    if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "corrupt resource table: " << parser.getLastError());
+        return false;
+    }
+    return true;
+}
+
+
+bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) {
+    const ResTable_package* packageHeader = convertTo<ResTable_package>(chunk);
+    if (!packageHeader) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "corrupt ResTable_package chunk");
+        return false;
+    }
+
+    uint32_t packageId = util::deviceToHost32(packageHeader->id);
+    if (packageId > std::numeric_limits<uint8_t>::max()) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "package ID is too big (" << packageId << ")");
+        return false;
+    }
+
+    // Extract the package name.
+    size_t len = strnlen16((const char16_t*) packageHeader->name, NELEM(packageHeader->name));
+    std::u16string packageName;
+    packageName.resize(len);
+    for (size_t i = 0; i < len; i++) {
+        packageName[i] = util::deviceToHost16(packageHeader->name[i]);
+    }
+
+    ResourceTablePackage* package = mTable->createPackage(packageName, (uint8_t) packageId);
+    if (!package) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "incompatible package '" << packageName
+                                          << "' with ID " << packageId);
+        return false;
+    }
+
+    ResChunkPullParser parser(getChunkData(&packageHeader->header),
+                              getChunkDataLen(&packageHeader->header));
+    while (ResChunkPullParser::isGoodEvent(parser.next())) {
+        switch (util::deviceToHost16(parser.getChunk()->type)) {
+        case android::RES_STRING_POOL_TYPE:
+            if (mTypePool.getError() == NO_INIT) {
+                status_t err = mTypePool.setTo(parser.getChunk(),
+                                               util::deviceToHost32(parser.getChunk()->size));
+                if (err != NO_ERROR) {
+                    mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                                      << "corrupt type string pool in "
+                                                      << "ResTable_package: "
+                                                      << mTypePool.getError());
+                    return false;
+                }
+            } else if (mKeyPool.getError() == NO_INIT) {
+                status_t err = mKeyPool.setTo(parser.getChunk(),
+                                              util::deviceToHost32(parser.getChunk()->size));
+                if (err != NO_ERROR) {
+                    mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                                      << "corrupt key string pool in "
+                                                      << "ResTable_package: "
+                                                      << mKeyPool.getError());
+                    return false;
+                }
+            } else {
+                mContext->getDiagnostics()->warn(DiagMessage(mSource) << "unexpected string pool");
+            }
+            break;
+
+        case android::RES_TABLE_TYPE_SPEC_TYPE:
+            if (!parseTypeSpec(parser.getChunk())) {
+                return false;
+            }
+            break;
+
+        case android::RES_TABLE_TYPE_TYPE:
+            if (!parseType(package, parser.getChunk())) {
+                return false;
+            }
+            break;
+
+        case RES_TABLE_PUBLIC_TYPE:
+            if (!parsePublic(package, parser.getChunk())) {
+                return false;
+            }
+            break;
+
+        default:
+            mContext->getDiagnostics()
+                    ->warn(DiagMessage(mSource)
+                           << "unexpected chunk type "
+                           << (int) util::deviceToHost16(parser.getChunk()->type));
+            break;
+        }
+    }
+
+    if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "corrupt ResTable_package: "
+                                          << parser.getLastError());
+        return false;
+    }
+
+    // Now go through the table and change local resource ID references to
+    // symbolic references.
+    ReferenceIdToNameVisitor visitor(&mIdIndex);
+    for (auto& package : mTable->packages) {
+        for (auto& type : package->types) {
+            for (auto& entry : type->entries) {
+                for (auto& configValue : entry->values) {
+                    configValue.value->accept(&visitor);
+                }
+            }
+        }
+    }
+    return true;
+}
+
+bool BinaryResourceParser::parsePublic(const ResourceTablePackage* package,
+                                       const ResChunk_header* chunk) {
+    const Public_header* header = convertTo<Public_header>(chunk);
+    if (!header) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "corrupt Public_header chunk");
+        return false;
+    }
+
+    if (header->typeId == 0) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "invalid type ID "
+                                          << (int) header->typeId);
+        return false;
+    }
+
+    StringPiece16 typeStr16 = util::getString(mTypePool, header->typeId - 1);
+    const ResourceType* parsedType = parseResourceType(typeStr16);
+    if (!parsedType) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "invalid type '" << typeStr16 << "'");
+        return false;
+    }
+
+    const uintptr_t chunkEnd = (uintptr_t) chunk + util::deviceToHost32(chunk->size);
+    const Public_entry* entry = (const Public_entry*) getChunkData(&header->header);
+    for (uint32_t i = 0; i < util::deviceToHost32(header->count); i++) {
+        if ((uintptr_t) entry + sizeof(*entry) > chunkEnd) {
+            mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                              << "Public_entry data section is too long");
+            return false;
+        }
+
+        const ResourceId resId = {
+                package->id.value(), header->typeId, util::deviceToHost16(entry->entryId) };
+
+        const ResourceName name = {
+                package->name,
+                *parsedType,
+                util::getString(mKeyPool, entry->key.index).toString() };
+
+        Source source;
+        if (mSourcePool.getError() == NO_ERROR) {
+            source.path = util::utf16ToUtf8(util::getString(
+                    mSourcePool, util::deviceToHost32(entry->source.index)));
+            source.line = util::deviceToHost32(entry->sourceLine);
+        }
+
+        if (!mTable->markPublicAllowMangled(name, resId, source, mContext->getDiagnostics())) {
+            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;
+}
+
+bool BinaryResourceParser::parseTypeSpec(const ResChunk_header* chunk) {
+    if (mTypePool.getError() != NO_ERROR) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "missing type string pool");
+        return false;
+    }
+
+    const ResTable_typeSpec* typeSpec = convertTo<ResTable_typeSpec>(chunk);
+    if (!typeSpec) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "corrupt ResTable_typeSpec chunk");
+        return false;
+    }
+
+    if (typeSpec->id == 0) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "ResTable_typeSpec has invalid id: " << typeSpec->id);
+        return false;
+    }
+    return true;
+}
+
+bool BinaryResourceParser::parseType(const ResourceTablePackage* package,
+                                     const ResChunk_header* chunk) {
+    if (mTypePool.getError() != NO_ERROR) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "missing type string pool");
+        return false;
+    }
+
+    if (mKeyPool.getError() != NO_ERROR) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "missing key string pool");
+        return false;
+    }
+
+    const ResTable_type* type = convertTo<ResTable_type>(chunk);
+    if (!type) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "corrupt ResTable_type chunk");
+        return false;
+    }
+
+    if (type->id == 0) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "ResTable_type has invalid id: " << (int) type->id);
+        return false;
+    }
+
+    ConfigDescription config;
+    config.copyFromDtoH(type->config);
+
+    StringPiece16 typeStr16 = util::getString(mTypePool, type->id - 1);
+
+    const ResourceType* parsedType = parseResourceType(typeStr16);
+    if (!parsedType) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "invalid type name '" << typeStr16
+                                          << "' for type with ID " << (int) type->id);
+        return false;
+    }
+
+    TypeVariant tv(type);
+    for (auto it = tv.beginEntries(); it != tv.endEntries(); ++it) {
+        const ResTable_entry* entry = *it;
+        if (!entry) {
+            continue;
+        }
+
+        const ResourceName name = {
+                package->name,
+                *parsedType,
+                util::getString(mKeyPool, util::deviceToHost32(entry->key.index)).toString() };
+
+        const ResourceId resId =
+                { package->id.value(), type->id, static_cast<uint16_t>(it.index()) };
+
+        std::unique_ptr<Value> resourceValue;
+        const ResTable_entry_source* sourceBlock = nullptr;
+
+        if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
+            const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry);
+            if (util::deviceToHost32(mapEntry->size) - sizeof(*mapEntry) == sizeof(*sourceBlock)) {
+                const uint8_t* data = (const uint8_t*) mapEntry;
+                data += util::deviceToHost32(mapEntry->size) - sizeof(*sourceBlock);
+                sourceBlock = (const ResTable_entry_source*) data;
+            }
+
+            // TODO(adamlesinski): Check that the entry count is valid.
+            resourceValue = parseMapEntry(name, config, mapEntry);
+        } else {
+            if (util::deviceToHost32(entry->size) - sizeof(*entry) == sizeof(*sourceBlock)) {
+                const uint8_t* data = (const uint8_t*) entry;
+                data += util::deviceToHost32(entry->size) - sizeof(*sourceBlock);
+                sourceBlock = (const ResTable_entry_source*) data;
+            }
+
+            const Res_value* value = (const Res_value*)(
+                    (const uint8_t*) entry + util::deviceToHost32(entry->size));
+            resourceValue = parseValue(name, config, value, entry->flags);
+        }
+
+        assert(resourceValue && "failed to interpret valid resource");
+
+        Source source = mSource;
+        if (sourceBlock) {
+            size_t len;
+            const char* str = mSourcePool.string8At(util::deviceToHost32(sourceBlock->pathIndex),
+                                                    &len);
+            if (str) {
+                source.path.assign(str, len);
+            }
+            source.line = util::deviceToHost32(sourceBlock->line);
+        }
+
+        if (!mTable->addResourceAllowMangled(name, config, source, std::move(resourceValue),
+                                             mContext->getDiagnostics())) {
+            return false;
+        }
+
+        if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
+            if (!mTable->markPublicAllowMangled(name, resId, mSource.withLine(0),
+                                                mContext->getDiagnostics())) {
+                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 });
+        }
+    }
+    return true;
+}
+
+std::unique_ptr<Item> BinaryResourceParser::parseValue(const ResourceNameRef& name,
+                                                       const ConfigDescription& config,
+                                                       const Res_value* value,
+                                                       uint16_t flags) {
+    if (name.type == ResourceType::kId) {
+        return util::make_unique<Id>();
+    }
+
+    const uint32_t data = util::deviceToHost32(value->data);
+
+    if (value->dataType == Res_value::TYPE_STRING) {
+        StringPiece16 str = util::getString(mValuePool, data);
+
+        const ResStringPool_span* spans = mValuePool.styleAt(data);
+        if (spans != nullptr) {
+            StyleString styleStr = { str.toString() };
+            while (spans->name.index != ResStringPool_span::END) {
+                styleStr.spans.push_back(Span{
+                        util::getString(mValuePool, spans->name.index).toString(),
+                        spans->firstChar,
+                        spans->lastChar
+                });
+                spans++;
+            }
+            return util::make_unique<StyledString>(mTable->stringPool.makeRef(
+                    styleStr, StringPool::Context{1, config}));
+        } else {
+            if (name.type != ResourceType::kString &&
+                    util::stringStartsWith<char16_t>(str, u"res/")) {
+                // This must be a FileReference.
+                return util::make_unique<FileReference>(mTable->stringPool.makeRef(
+                            str, StringPool::Context{ 0, config }));
+            }
+
+            // There are no styles associated with this string, so treat it as
+            // a simple string.
+            return util::make_unique<String>(mTable->stringPool.makeRef(
+                    str, StringPool::Context{1, config}));
+        }
+    }
+
+    if (value->dataType == Res_value::TYPE_REFERENCE ||
+            value->dataType == Res_value::TYPE_ATTRIBUTE) {
+        const Reference::Type type = (value->dataType == Res_value::TYPE_REFERENCE) ?
+                    Reference::Type::kResource : Reference::Type::kAttribute;
+
+        if (data != 0) {
+            // This is a normal reference.
+            return util::make_unique<Reference>(data, type);
+        }
+
+        // This reference has an invalid ID. Check if it is an unresolved symbol.
+        ResourceNameRef symbol;
+        if (getSymbol(&value->data, &symbol)) {
+            return util::make_unique<Reference>(symbol, type);
+        }
+
+        // This is not an unresolved symbol, so it must be the magic @null reference.
+        Res_value nullType = {};
+        nullType.dataType = Res_value::TYPE_REFERENCE;
+        return util::make_unique<BinaryPrimitive>(nullType);
+    }
+
+    if (value->dataType == ExtendedTypes::TYPE_RAW_STRING) {
+        return util::make_unique<RawString>(mTable->stringPool.makeRef(
+                util::getString(mValuePool, data), StringPool::Context{ 1, config }));
+    }
+
+    // Treat this as a raw binary primitive.
+    return util::make_unique<BinaryPrimitive>(*value);
+}
+
+std::unique_ptr<Value> BinaryResourceParser::parseMapEntry(const ResourceNameRef& name,
+                                                           const ConfigDescription& config,
+                                                           const ResTable_map_entry* map) {
+    switch (name.type) {
+        case ResourceType::kStyle:
+            return parseStyle(name, config, map);
+        case ResourceType::kAttr:
+            return parseAttr(name, config, map);
+        case ResourceType::kArray:
+            return parseArray(name, config, map);
+        case ResourceType::kStyleable:
+            return parseStyleable(name, config, map);
+        case ResourceType::kPlurals:
+            return parsePlural(name, config, map);
+        default:
+            break;
+    }
+    return {};
+}
+
+std::unique_ptr<Style> BinaryResourceParser::parseStyle(const ResourceNameRef& name,
+                                                        const ConfigDescription& config,
+                                                        const ResTable_map_entry* map) {
+    std::unique_ptr<Style> style = util::make_unique<Style>();
+    if (util::deviceToHost32(map->parent.ident) == 0) {
+        // The parent is either not set or it is an unresolved symbol.
+        // Check to see if it is a symbol.
+        ResourceNameRef symbol;
+        if (getSymbol(&map->parent.ident, &symbol)) {
+            style->parent = Reference(symbol.toResourceName());
+        }
+    } else {
+         // The parent is a regular reference to a resource.
+        style->parent = Reference(util::deviceToHost32(map->parent.ident));
+    }
+
+    for (const ResTable_map& mapEntry : map) {
+        style->entries.emplace_back();
+        Style::Entry& styleEntry = style->entries.back();
+
+        if (util::deviceToHost32(mapEntry.name.ident) == 0) {
+            // The map entry's key (attribute) is not set. This must be
+            // a symbol reference, so resolve it.
+            ResourceNameRef symbol;
+            bool result = getSymbol(&mapEntry.name.ident, &symbol);
+            assert(result);
+            styleEntry.key.name = symbol.toResourceName();
+        } else {
+            // The map entry's key (attribute) is a regular reference.
+            styleEntry.key.id = ResourceId(util::deviceToHost32(mapEntry.name.ident));
+        }
+
+        // Parse the attribute's value.
+        styleEntry.value = parseValue(name, config, &mapEntry.value, 0);
+        assert(styleEntry.value);
+    }
+    return style;
+}
+
+std::unique_ptr<Attribute> BinaryResourceParser::parseAttr(const ResourceNameRef& name,
+                                                           const ConfigDescription& config,
+                                                           const ResTable_map_entry* map) {
+    const bool isWeak = (util::deviceToHost16(map->flags) & ResTable_entry::FLAG_WEAK) != 0;
+    std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(isWeak);
+
+    // First we must discover what type of attribute this is. Find the type mask.
+    auto typeMaskIter = std::find_if(begin(map), end(map), [](const ResTable_map& entry) -> bool {
+        return util::deviceToHost32(entry.name.ident) == ResTable_map::ATTR_TYPE;
+    });
+
+    if (typeMaskIter != end(map)) {
+        attr->typeMask = util::deviceToHost32(typeMaskIter->value.data);
+    }
+
+    if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
+        for (const ResTable_map& mapEntry : map) {
+            if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) {
+                continue;
+            }
+
+            Attribute::Symbol symbol;
+            symbol.value = util::deviceToHost32(mapEntry.value.data);
+            if (util::deviceToHost32(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 = ResourceId(util::deviceToHost32(mapEntry.name.ident));
+            }
+
+            attr->symbols.push_back(std::move(symbol));
+        }
+    }
+
+    // TODO(adamlesinski): Find min, max, i80n, etc attributes.
+    return attr;
+}
+
+std::unique_ptr<Array> BinaryResourceParser::parseArray(const ResourceNameRef& name,
+                                                        const ConfigDescription& config,
+                                                        const ResTable_map_entry* map) {
+    std::unique_ptr<Array> array = util::make_unique<Array>();
+    for (const ResTable_map& mapEntry : map) {
+        array->items.push_back(parseValue(name, config, &mapEntry.value, 0));
+    }
+    return array;
+}
+
+std::unique_ptr<Styleable> BinaryResourceParser::parseStyleable(const ResourceNameRef& name,
+                                                                const ConfigDescription& config,
+                                                                const ResTable_map_entry* map) {
+    std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
+    for (const ResTable_map& mapEntry : map) {
+        if (util::deviceToHost32(mapEntry.name.ident) == 0) {
+            // The map entry's key (attribute) is not set. This must be
+            // a symbol reference, so resolve it.
+            ResourceNameRef symbol;
+            bool result = getSymbol(&mapEntry.name.ident, &symbol);
+            assert(result);
+            styleable->entries.emplace_back(symbol);
+        } else {
+            // The map entry's key (attribute) is a regular reference.
+            styleable->entries.emplace_back(util::deviceToHost32(mapEntry.name.ident));
+        }
+    }
+    return styleable;
+}
+
+std::unique_ptr<Plural> BinaryResourceParser::parsePlural(const ResourceNameRef& name,
+                                                          const ConfigDescription& config,
+                                                          const ResTable_map_entry* map) {
+    std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+    for (const ResTable_map& mapEntry : map) {
+        std::unique_ptr<Item> item = parseValue(name, config, &mapEntry.value, 0);
+
+        switch (util::deviceToHost32(mapEntry.name.ident)) {
+            case android::ResTable_map::ATTR_ZERO:
+                plural->values[Plural::Zero] = std::move(item);
+                break;
+            case android::ResTable_map::ATTR_ONE:
+                plural->values[Plural::One] = std::move(item);
+                break;
+            case android::ResTable_map::ATTR_TWO:
+                plural->values[Plural::Two] = std::move(item);
+                break;
+            case android::ResTable_map::ATTR_FEW:
+                plural->values[Plural::Few] = std::move(item);
+                break;
+            case android::ResTable_map::ATTR_MANY:
+                plural->values[Plural::Many] = std::move(item);
+                break;
+            case android::ResTable_map::ATTR_OTHER:
+                plural->values[Plural::Other] = std::move(item);
+                break;
+        }
+    }
+    return plural;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/BinaryResourceParser.h b/tools/aapt2/unflatten/BinaryResourceParser.h
similarity index 82%
rename from tools/aapt2/BinaryResourceParser.h
rename to tools/aapt2/unflatten/BinaryResourceParser.h
index 3aab301..4dbef5d 100644
--- a/tools/aapt2/BinaryResourceParser.h
+++ b/tools/aapt2/unflatten/BinaryResourceParser.h
@@ -17,11 +17,13 @@
 #ifndef AAPT_BINARY_RESOURCE_PARSER_H
 #define AAPT_BINARY_RESOURCE_PARSER_H
 
-#include "Resolver.h"
 #include "ResourceTable.h"
 #include "ResourceValues.h"
 #include "Source.h"
 
+#include "process/IResourceTableConsumer.h"
+#include "util/Util.h"
+
 #include <androidfw/ResourceTypes.h>
 #include <string>
 
@@ -42,11 +44,8 @@
      * Creates a parser, which will read `len` bytes from `data`, and
      * add any resources parsed to `table`. `source` is for logging purposes.
      */
-    BinaryResourceParser(const std::shared_ptr<ResourceTable>& table,
-                         const std::shared_ptr<IResolver>& resolver,
-                         const Source& source,
-                         const std::u16string& defaultPackage,
-                         const void* data, size_t len);
+    BinaryResourceParser(IAaptContext* context, ResourceTable* table, const Source& source,
+                         const void* data, size_t dataLen);
 
     BinaryResourceParser(const BinaryResourceParser&) = delete; // No copy.
 
@@ -62,14 +61,10 @@
 
     bool parseTable(const android::ResChunk_header* chunk);
     bool parseSymbolTable(const android::ResChunk_header* chunk);
-
-    // Looks up the resource ID in the reference and converts it to a name if available.
-    bool idToName(Reference* reference);
-
     bool parsePackage(const android::ResChunk_header* chunk);
-    bool parsePublic(const android::ResChunk_header* chunk);
+    bool parsePublic(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
     bool parseTypeSpec(const android::ResChunk_header* chunk);
-    bool parseType(const android::ResChunk_header* chunk);
+    bool parseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
 
     std::unique_ptr<Item> parseValue(const ResourceNameRef& name,
             const ConfigDescription& config, const android::Res_value* value, uint16_t flags);
@@ -92,15 +87,11 @@
     std::unique_ptr<Styleable> parseStyleable(const ResourceNameRef& name,
             const ConfigDescription& config, const android::ResTable_map_entry* map);
 
-    std::shared_ptr<ResourceTable> mTable;
-
-    std::shared_ptr<IResolver> mResolver;
+    IAaptContext* mContext;
+    ResourceTable* mTable;
 
     const Source mSource;
 
-    // The package name of the resource table.
-    std::u16string mDefaultPackage;
-
     const void* mData;
     const size_t mDataLen;
 
@@ -146,13 +137,11 @@
  */
 
 inline const ResTable_map* begin(const ResTable_map_entry* map) {
-    return reinterpret_cast<const ResTable_map*>(
-            reinterpret_cast<const uint8_t*>(map) + map->size);
+    return (const ResTable_map*)((const uint8_t*) map + aapt::util::deviceToHost32(map->size));
 }
 
 inline const ResTable_map* end(const ResTable_map_entry* map) {
-    return reinterpret_cast<const ResTable_map*>(
-            reinterpret_cast<const uint8_t*>(map) + map->size) + map->count;
+    return begin(map) + aapt::util::deviceToHost32(map->count);
 }
 
 } // namespace android
diff --git a/tools/aapt2/unflatten/FileExportHeaderReader.h b/tools/aapt2/unflatten/FileExportHeaderReader.h
new file mode 100644
index 0000000..e552ea1
--- /dev/null
+++ b/tools/aapt2/unflatten/FileExportHeaderReader.h
@@ -0,0 +1,159 @@
+/*
+ * 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_UNFLATTEN_FILEEXPORTHEADERREADER_H
+#define AAPT_UNFLATTEN_FILEEXPORTHEADERREADER_H
+
+#include "ResChunkPullParser.h"
+#include "Resource.h"
+#include "ResourceUtils.h"
+
+#include "flatten/ResourceTypeExtensions.h"
+#include "util/StringPiece.h"
+#include "util/Util.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <sstream>
+#include <string>
+
+namespace aapt {
+
+static ssize_t parseFileExportHeaderImpl(const void* data, const size_t len,
+                                         const FileExport_header** outFileExport,
+                                         const ExportedSymbol** outExportedSymbolIndices,
+                                         android::ResStringPool* outStringPool,
+                                         std::string* outError) {
+    ResChunkPullParser parser(data, len);
+    if (!ResChunkPullParser::isGoodEvent(parser.next())) {
+        if (outError) *outError = parser.getLastError();
+        return -1;
+    }
+
+    if (util::deviceToHost16(parser.getChunk()->type) != RES_FILE_EXPORT_TYPE) {
+        if (outError) *outError = "no FileExport_header found";
+        return -1;
+    }
+
+    const FileExport_header* fileExport = convertTo<FileExport_header>(parser.getChunk());
+    if (!fileExport) {
+        if (outError) *outError = "corrupt FileExport_header";
+        return -1;
+    }
+
+    if (memcmp(fileExport->magic, "AAPT", sizeof(fileExport->magic)) != 0) {
+        if (outError) *outError = "invalid magic value";
+        return -1;
+    }
+
+    const size_t exportedSymbolCount = util::deviceToHost32(fileExport->exportedSymbolCount);
+
+    // Verify that we have enough space for all those symbols.
+    size_t dataLen = getChunkDataLen(&fileExport->header);
+    if (exportedSymbolCount > dataLen / sizeof(ExportedSymbol)) {
+        if (outError) *outError = "too many symbols";
+        return -1;
+    }
+
+    const size_t symbolIndicesSize = exportedSymbolCount * sizeof(ExportedSymbol);
+
+    const void* strPoolData = getChunkData(&fileExport->header) + symbolIndicesSize;
+    const size_t strPoolDataLen = dataLen - symbolIndicesSize;
+    if (outStringPool->setTo(strPoolData, strPoolDataLen, false) != android::NO_ERROR) {
+        if (outError) *outError = "corrupt string pool";
+        return -1;
+    }
+
+    *outFileExport = fileExport;
+    *outExportedSymbolIndices = (const ExportedSymbol*) getChunkData(
+            &fileExport->header);
+    return util::deviceToHost16(fileExport->header.headerSize) + symbolIndicesSize +
+            outStringPool->bytes();
+}
+
+static ssize_t getWrappedDataOffset(const void* data, size_t len, std::string* outError) {
+    const FileExport_header* header = nullptr;
+    const ExportedSymbol* entries = nullptr;
+    android::ResStringPool pool;
+    return parseFileExportHeaderImpl(data, len, &header, &entries, &pool, outError);
+}
+
+/**
+ * Reads the FileExport_header and populates outRes with the values in that header.
+ */
+static ssize_t unwrapFileExportHeader(const void* data, size_t len, ResourceFile* outRes,
+                                      std::string* outError) {
+
+    const FileExport_header* fileExport = nullptr;
+    const ExportedSymbol* entries = nullptr;
+    android::ResStringPool symbolPool;
+    const ssize_t offset = parseFileExportHeaderImpl(data, len, &fileExport, &entries, &symbolPool,
+                                                     outError);
+    if (offset < 0) {
+        return offset;
+    }
+
+    const size_t exportedSymbolCount = util::deviceToHost32(fileExport->exportedSymbolCount);
+    outRes->exportedSymbols.clear();
+    outRes->exportedSymbols.reserve(exportedSymbolCount);
+
+    for (size_t i = 0; i < exportedSymbolCount; i++) {
+        const StringPiece16 str = util::getString(symbolPool,
+                                                  util::deviceToHost32(entries[i].name.index));
+        StringPiece16 packageStr, typeStr, entryStr;
+        ResourceUtils::extractResourceName(str, &packageStr, &typeStr, &entryStr);
+        const ResourceType* resType = parseResourceType(typeStr);
+        if (!resType || entryStr.empty()) {
+            if (outError) {
+                std::stringstream errorStr;
+                errorStr << "invalid exported symbol at index="
+                         << util::deviceToHost32(entries[i].name.index)
+                         << " (" << str << ")";
+                *outError = errorStr.str();
+            }
+            return -1;
+        }
+
+        outRes->exportedSymbols.push_back(SourcedResourceName{
+                ResourceName{ packageStr.toString(), *resType, entryStr.toString() },
+                util::deviceToHost32(entries[i].line) });
+    }
+
+    const StringPiece16 str = util::getString(symbolPool,
+                                              util::deviceToHost32(fileExport->name.index));
+    StringPiece16 packageStr, typeStr, entryStr;
+    ResourceUtils::extractResourceName(str, &packageStr, &typeStr, &entryStr);
+    const ResourceType* resType = parseResourceType(typeStr);
+    if (!resType || entryStr.empty()) {
+        if (outError) {
+            std::stringstream errorStr;
+            errorStr << "invalid resource name at index="
+                     << util::deviceToHost32(fileExport->name.index)
+                     << " (" << str << ")";
+            *outError = errorStr.str();
+        }
+        return -1;
+    }
+
+    outRes->name = ResourceName{ packageStr.toString(), *resType, entryStr.toString() };
+    outRes->source.path = util::utf16ToUtf8(
+            util::getString(symbolPool, util::deviceToHost32(fileExport->source.index)));
+    outRes->config.copyFromDtoH(fileExport->config);
+    return offset;
+}
+
+} // namespace aapt
+
+#endif /* AAPT_UNFLATTEN_FILEEXPORTHEADERREADER_H */
diff --git a/tools/aapt2/unflatten/FileExportHeaderReader_test.cpp b/tools/aapt2/unflatten/FileExportHeaderReader_test.cpp
new file mode 100644
index 0000000..a76c83b
--- /dev/null
+++ b/tools/aapt2/unflatten/FileExportHeaderReader_test.cpp
@@ -0,0 +1,58 @@
+/*
+ * 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 "Resource.h"
+
+#include "flatten/FileExportWriter.h"
+#include "unflatten/FileExportHeaderReader.h"
+#include "util/BigBuffer.h"
+#include "util/Util.h"
+
+#include "test/Common.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(FileExportHeaderReaderTest, ReadHeaderWithNoSymbolExports) {
+    ResourceFile resFile = {
+            test::parseNameOrDie(u"@android:layout/main.xml"),
+            test::parseConfigOrDie("sw600dp-v4"),
+            Source{ "res/layout/main.xml" },
+    };
+
+    BigBuffer buffer(1024);
+    ChunkWriter writer = wrapBufferWithFileExportHeader(&buffer, &resFile);
+    *writer.getBuffer()->nextBlock<uint32_t>() = 42u;
+    writer.finish();
+
+    std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+
+    ResourceFile actualResFile;
+
+    ssize_t offset = unwrapFileExportHeader(data.get(), buffer.size(), &actualResFile, nullptr);
+    ASSERT_GT(offset, 0);
+
+    EXPECT_EQ(offset, getWrappedDataOffset(data.get(), buffer.size(), nullptr));
+
+    EXPECT_EQ(actualResFile.config, test::parseConfigOrDie("sw600dp-v4"));
+    EXPECT_EQ(actualResFile.name, test::parseNameOrDie(u"@android:layout/main.xml"));
+    EXPECT_EQ(actualResFile.source.path, "res/layout/main.xml");
+
+    EXPECT_EQ(*(uint32_t*)(data.get() + offset), 42u);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ResChunkPullParser.cpp b/tools/aapt2/unflatten/ResChunkPullParser.cpp
similarity index 76%
rename from tools/aapt2/ResChunkPullParser.cpp
rename to tools/aapt2/unflatten/ResChunkPullParser.cpp
index 78ea60e..6f8bb1b 100644
--- a/tools/aapt2/ResChunkPullParser.cpp
+++ b/tools/aapt2/unflatten/ResChunkPullParser.cpp
@@ -14,7 +14,8 @@
  * limitations under the License.
  */
 
-#include "ResChunkPullParser.h"
+#include "unflatten/ResChunkPullParser.h"
+#include "util/Util.h"
 
 #include <androidfw/ResourceTypes.h>
 #include <cstddef>
@@ -31,12 +32,11 @@
     if (mEvent == Event::StartDocument) {
         mCurrentChunk = mData;
     } else {
-        mCurrentChunk = reinterpret_cast<const ResChunk_header*>(
-                reinterpret_cast<const char*>(mCurrentChunk) + mCurrentChunk->size);
+        mCurrentChunk = (const ResChunk_header*)
+                (((const char*) mCurrentChunk) + util::deviceToHost32(mCurrentChunk->size));
     }
 
-    const std::ptrdiff_t diff = reinterpret_cast<const char*>(mCurrentChunk)
-            - reinterpret_cast<const char*>(mData);
+    const std::ptrdiff_t diff = (const char*) mCurrentChunk - (const char*) mData;
     assert(diff >= 0 && "diff is negative");
     const size_t offset = static_cast<const size_t>(diff);
 
@@ -49,15 +49,16 @@
         return (mEvent = Event::BadDocument);
     }
 
-    if (mCurrentChunk->headerSize < sizeof(ResChunk_header)) {
+    if (util::deviceToHost16(mCurrentChunk->headerSize) < sizeof(ResChunk_header)) {
         mLastError = "chunk has too small header";
         mCurrentChunk = nullptr;
         return (mEvent = Event::BadDocument);
-    } else if (mCurrentChunk->size < mCurrentChunk->headerSize) {
+    } else if (util::deviceToHost32(mCurrentChunk->size) <
+            util::deviceToHost16(mCurrentChunk->headerSize)) {
         mLastError = "chunk's total size is smaller than header";
         mCurrentChunk = nullptr;
         return (mEvent = Event::BadDocument);
-    } else if (offset + mCurrentChunk->size > mLen) {
+    } else if (offset + util::deviceToHost32(mCurrentChunk->size) > mLen) {
         mLastError = "chunk's data extends past the end of the document";
         mCurrentChunk = nullptr;
         return (mEvent = Event::BadDocument);
diff --git a/tools/aapt2/ResChunkPullParser.h b/tools/aapt2/unflatten/ResChunkPullParser.h
similarity index 89%
rename from tools/aapt2/ResChunkPullParser.h
rename to tools/aapt2/unflatten/ResChunkPullParser.h
index 1426ed2..a51d5bf 100644
--- a/tools/aapt2/ResChunkPullParser.h
+++ b/tools/aapt2/unflatten/ResChunkPullParser.h
@@ -17,6 +17,8 @@
 #ifndef AAPT_RES_CHUNK_PULL_PARSER_H
 #define AAPT_RES_CHUNK_PULL_PARSER_H
 
+#include "util/Util.h"
+
 #include <androidfw/ResourceTypes.h>
 #include <string>
 
@@ -76,18 +78,18 @@
 
 template <typename T>
 inline static const T* convertTo(const android::ResChunk_header* chunk) {
-    if (chunk->headerSize < sizeof(T)) {
+    if (util::deviceToHost16(chunk->headerSize) < sizeof(T)) {
         return nullptr;
     }
     return reinterpret_cast<const T*>(chunk);
 }
 
-inline static const uint8_t* getChunkData(const android::ResChunk_header& chunk) {
-    return reinterpret_cast<const uint8_t*>(&chunk) + chunk.headerSize;
+inline static const uint8_t* getChunkData(const android::ResChunk_header* chunk) {
+    return reinterpret_cast<const uint8_t*>(chunk) + util::deviceToHost16(chunk->headerSize);
 }
 
-inline static size_t getChunkDataLen(const android::ResChunk_header& chunk) {
-    return chunk.size - chunk.headerSize;
+inline static uint32_t getChunkDataLen(const android::ResChunk_header* chunk) {
+    return util::deviceToHost32(chunk->size) - util::deviceToHost16(chunk->headerSize);
 }
 
 //
diff --git a/tools/aapt2/BigBuffer.cpp b/tools/aapt2/util/BigBuffer.cpp
similarity index 97%
rename from tools/aapt2/BigBuffer.cpp
rename to tools/aapt2/util/BigBuffer.cpp
index 8f57172..c88e3c1 100644
--- a/tools/aapt2/BigBuffer.cpp
+++ b/tools/aapt2/util/BigBuffer.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "BigBuffer.h"
+#include "util/BigBuffer.h"
 
 #include <algorithm>
 #include <memory>
diff --git a/tools/aapt2/BigBuffer.h b/tools/aapt2/util/BigBuffer.h
similarity index 96%
rename from tools/aapt2/BigBuffer.h
rename to tools/aapt2/util/BigBuffer.h
index 8b6569c..cad2a2e 100644
--- a/tools/aapt2/BigBuffer.h
+++ b/tools/aapt2/util/BigBuffer.h
@@ -20,6 +20,7 @@
 #include <cassert>
 #include <cstring>
 #include <memory>
+#include <type_traits>
 #include <vector>
 
 namespace aapt {
@@ -124,6 +125,7 @@
 
 template <typename T>
 inline T* BigBuffer::nextBlock(size_t count) {
+    static_assert(std::is_standard_layout<T>::value, "T must be standard_layout type");
     assert(count != 0);
     return reinterpret_cast<T*>(nextBlockImpl(sizeof(T) * count));
 }
diff --git a/tools/aapt2/BigBuffer_test.cpp b/tools/aapt2/util/BigBuffer_test.cpp
similarity index 98%
rename from tools/aapt2/BigBuffer_test.cpp
rename to tools/aapt2/util/BigBuffer_test.cpp
index 01ee8d7..2a24f12 100644
--- a/tools/aapt2/BigBuffer_test.cpp
+++ b/tools/aapt2/util/BigBuffer_test.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "BigBuffer.h"
+#include "util/BigBuffer.h"
 
 #include <gtest/gtest.h>
 
diff --git a/tools/aapt2/Files.cpp b/tools/aapt2/util/Files.cpp
similarity index 67%
rename from tools/aapt2/Files.cpp
rename to tools/aapt2/util/Files.cpp
index b24ff6b..a81dc7b 100644
--- a/tools/aapt2/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-#include "Files.h"
-#include "Util.h"
+#include "util/Files.h"
+#include "util/Util.h"
 
 #include <cerrno>
+#include <cstdio>
 #include <dirent.h>
 #include <string>
 #include <sys/stat.h>
@@ -28,6 +29,7 @@
 #endif
 
 namespace aapt {
+namespace file {
 
 FileType getFileType(const StringPiece& path) {
     struct stat sb;
@@ -61,15 +63,15 @@
     }
 }
 
-std::vector<std::string> listFiles(const StringPiece& root) {
+std::vector<std::string> listFiles(const StringPiece& root, std::string* outError) {
     DIR* dir = opendir(root.data());
     if (dir == nullptr) {
-        Logger::error(Source{ root.toString() })
-            << "unable to open file: "
-            << strerror(errno)
-            << "."
-            << std::endl;
-        return {};
+        if (outError) {
+            std::stringstream errorStr;
+            errorStr << "unable to open file: " << strerror(errno);
+            *outError = errorStr.str();
+            return {};
+        }
     }
 
     std::vector<std::string> files;
@@ -105,17 +107,69 @@
     return mkdirImpl(path) == 0 || errno == EEXIST;
 }
 
-std::string getStem(const StringPiece& path) {
+StringPiece getStem(const StringPiece& path) {
     const char* start = path.begin();
     const char* end = path.end();
     for (const char* current = end - 1; current != start - 1; --current) {
         if (*current == sDirSep) {
-            return std::string(start, current - start);
+            return StringPiece(start, current - start);
         }
     }
     return {};
 }
 
+StringPiece getFilename(const StringPiece& path) {
+    const char* end = path.end();
+    const char* lastDirSep = path.begin();
+    for (const char* c = path.begin(); c != end; ++c) {
+        if (*c == sDirSep) {
+            lastDirSep = c + 1;
+        }
+    }
+    return StringPiece(lastDirSep, end - lastDirSep);
+}
+
+StringPiece getExtension(const StringPiece& path) {
+    StringPiece filename = getFilename(path);
+    const char* const end = filename.end();
+    const char* c = std::find(filename.begin(), end, '.');
+    if (c != end) {
+        return StringPiece(c, end - c);
+    }
+    return {};
+}
+
+std::string packageToPath(const StringPiece& package) {
+    std::string outPath;
+    for (StringPiece part : util::tokenize<char>(package, '.')) {
+        appendPath(&outPath, part);
+    }
+    return outPath;
+}
+
+Maybe<android::FileMap> mmapPath(const StringPiece& path, std::string* outError) {
+    std::unique_ptr<FILE, decltype(fclose)*> f = { fopen(path.data(), "rb"), fclose };
+    if (!f) {
+        if (outError) *outError = strerror(errno);
+        return {};
+    }
+
+    int fd = fileno(f.get());
+
+    struct stat fileStats = {};
+    if (fstat(fd, &fileStats) != 0) {
+        if (outError) *outError = strerror(errno);
+        return {};
+    }
+
+    android::FileMap fileMap;
+    if (!fileMap.create(path.data(), fd, 0, fileStats.st_size, true)) {
+        if (outError) *outError = strerror(errno);
+        return {};
+    }
+    return std::move(fileMap);
+}
+
 bool FileFilter::setPattern(const StringPiece& pattern) {
     mPatternTokens = util::splitAndLowercase(pattern, ':');
     return true;
@@ -169,14 +223,10 @@
 
         if (ignore) {
             if (chatty) {
-                Logger::warn()
-                    << "skipping " <<
-                    (type == FileType::kDirectory ? "dir '" : "file '")
-                    << filename
-                    << "' due to ignore pattern '"
-                    << token
-                    << "'."
-                    << std::endl;
+                mDiag->warn(DiagMessage() << "skipping "
+                            << (type == FileType::kDirectory ? "dir '" : "file '")
+                            << filename << "' due to ignore pattern '"
+                            << token << "'");
             }
             return false;
         }
@@ -184,5 +234,5 @@
     return true;
 }
 
-
+} // namespace file
 } // namespace aapt
diff --git a/tools/aapt2/Files.h b/tools/aapt2/util/Files.h
similarity index 79%
rename from tools/aapt2/Files.h
rename to tools/aapt2/util/Files.h
index 844fd2b..c58ba5d 100644
--- a/tools/aapt2/Files.h
+++ b/tools/aapt2/util/Files.h
@@ -17,15 +17,20 @@
 #ifndef AAPT_FILES_H
 #define AAPT_FILES_H
 
-#include "Logger.h"
+#include "Diagnostics.h"
+#include "Maybe.h"
 #include "Source.h"
-#include "StringPiece.h"
 
+#include "util/StringPiece.h"
+
+#include <utils/FileMap.h>
 #include <cassert>
+#include <memory>
 #include <string>
 #include <vector>
 
 namespace aapt {
+namespace file {
 
 #ifdef _WIN32
 constexpr const char sDirSep = '\\';
@@ -74,7 +79,28 @@
 /**
  * Returns all but the last part of the path.
  */
-std::string getStem(const StringPiece& path);
+StringPiece getStem(const StringPiece& path);
+
+/**
+ * Returns the last part of the path with extension.
+ */
+StringPiece getFilename(const StringPiece& path);
+
+/**
+ * Returns the extension of the path. This is the entire string after
+ * the first '.' of the last part of the path.
+ */
+StringPiece getExtension(const StringPiece& path);
+
+/**
+ * Converts a package name (com.android.app) to a path: com/android/app
+ */
+std::string packageToPath(const StringPiece& package);
+
+/**
+ * Creates a FileMap for the file at path.
+ */
+Maybe<android::FileMap> mmapPath(const StringPiece& path, std::string* outError);
 
 /*
  * Filter that determines which resource files/directories are
@@ -84,6 +110,9 @@
  */
 class FileFilter {
 public:
+    FileFilter(IDiagnostics* diag) : mDiag(diag) {
+    }
+
     /*
      * Patterns syntax:
      * - Delimiter is :
@@ -106,6 +135,7 @@
     bool operator()(const std::string& filename, FileType type) const;
 
 private:
+    IDiagnostics* mDiag;
     std::vector<std::string> mPatternTokens;
 };
 
@@ -123,6 +153,7 @@
     appendPath(base, parts...);
 }
 
+} // namespace file
 } // namespace aapt
 
 #endif // AAPT_FILES_H
diff --git a/tools/aapt2/Maybe.h b/tools/aapt2/util/Maybe.h
similarity index 99%
rename from tools/aapt2/Maybe.h
rename to tools/aapt2/util/Maybe.h
index ff6625f..1f7d5ce 100644
--- a/tools/aapt2/Maybe.h
+++ b/tools/aapt2/util/Maybe.h
@@ -72,7 +72,7 @@
      * True if this holds a value, false if
      * it holds Nothing.
      */
-    operator bool() const;
+    explicit operator bool() const;
 
     /**
      * Gets the value if one exists, or else
diff --git a/tools/aapt2/Maybe_test.cpp b/tools/aapt2/util/Maybe_test.cpp
similarity index 93%
rename from tools/aapt2/Maybe_test.cpp
rename to tools/aapt2/util/Maybe_test.cpp
index 71bbb94..d2c33ca 100644
--- a/tools/aapt2/Maybe_test.cpp
+++ b/tools/aapt2/util/Maybe_test.cpp
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
+#include "test/Common.h"
+#include "util/Maybe.h"
+
 #include <gtest/gtest.h>
 #include <string>
 
-#include "Maybe.h"
-
 namespace aapt {
 
 struct Dummy {
@@ -85,22 +86,22 @@
 
 TEST(MaybeTest, MakeNothing) {
     Maybe<int> val = make_nothing<int>();
-    EXPECT_FALSE(val);
+    AAPT_EXPECT_FALSE(val);
 
     Maybe<std::string> val2 = make_nothing<std::string>();
-    EXPECT_FALSE(val2);
+    AAPT_EXPECT_FALSE(val2);
 
     val2 = make_nothing<std::string>();
-    EXPECT_FALSE(val2);
+    AAPT_EXPECT_FALSE(val2);
 }
 
 TEST(MaybeTest, MakeSomething) {
     Maybe<int> val = make_value(23);
-    ASSERT_TRUE(val);
+    AAPT_ASSERT_TRUE(val);
     EXPECT_EQ(23, val.value());
 
     Maybe<std::string> val2 = make_value(std::string("hey"));
-    ASSERT_TRUE(val2);
+    AAPT_ASSERT_TRUE(val2);
     EXPECT_EQ(std::string("hey"), val2.value());
 }
 
diff --git a/tools/aapt2/StringPiece.h b/tools/aapt2/util/StringPiece.h
similarity index 100%
rename from tools/aapt2/StringPiece.h
rename to tools/aapt2/util/StringPiece.h
diff --git a/tools/aapt2/StringPiece_test.cpp b/tools/aapt2/util/StringPiece_test.cpp
similarity index 98%
rename from tools/aapt2/StringPiece_test.cpp
rename to tools/aapt2/util/StringPiece_test.cpp
index 43f7a37..d49b67f 100644
--- a/tools/aapt2/StringPiece_test.cpp
+++ b/tools/aapt2/util/StringPiece_test.cpp
@@ -19,7 +19,7 @@
 #include <string>
 #include <vector>
 
-#include "StringPiece.h"
+#include "util/StringPiece.h"
 
 namespace aapt {
 
diff --git a/tools/aapt2/Util.cpp b/tools/aapt2/util/Util.cpp
similarity index 87%
rename from tools/aapt2/Util.cpp
rename to tools/aapt2/util/Util.cpp
index ca352e0..f219b65 100644
--- a/tools/aapt2/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-#include "BigBuffer.h"
-#include "Maybe.h"
-#include "StringPiece.h"
-#include "Util.h"
+#include "util/BigBuffer.h"
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
+#include "util/Util.h"
 
 #include <algorithm>
 #include <ostream>
@@ -122,6 +122,29 @@
     return pieces >= 2;
 }
 
+bool isJavaPackageName(const StringPiece16& str) {
+    if (str.empty()) {
+        return false;
+    }
+
+    size_t pieces = 0;
+    for (const StringPiece16& piece : tokenize(str, u'.')) {
+        pieces++;
+        if (piece.empty()) {
+            return false;
+        }
+
+        if (piece.data()[0] == u'_' || piece.data()[piece.size() - 1] == u'_') {
+            return false;
+        }
+
+        if (findNonAlphaNumericAndNotInSet(piece, u"_") != piece.end()) {
+            return false;
+        }
+    }
+    return pieces >= 1;
+}
+
 Maybe<std::u16string> getFullyQualifiedClassName(const StringPiece16& package,
                                                  const StringPiece16& className) {
     if (className.empty()) {
@@ -338,5 +361,29 @@
     return {};
 }
 
+bool extractResFilePathParts(const StringPiece16& path, StringPiece16* outPrefix,
+                             StringPiece16* outEntry, StringPiece16* outSuffix) {
+    if (!stringStartsWith<char16_t>(path, u"res/")) {
+        return false;
+    }
+
+    StringPiece16::const_iterator lastOccurence = path.end();
+    for (auto iter = path.begin() + StringPiece16(u"res/").size(); iter != path.end(); ++iter) {
+        if (*iter == u'/') {
+            lastOccurence = iter;
+        }
+    }
+
+    if (lastOccurence == path.end()) {
+        return false;
+    }
+
+    auto iter = std::find(lastOccurence, path.end(), u'.');
+    *outSuffix = StringPiece16(iter, path.end() - iter);
+    *outEntry = StringPiece16(lastOccurence + 1, iter - lastOccurence - 1);
+    *outPrefix = StringPiece16(path.begin(), lastOccurence - path.begin() + 1);
+    return true;
+}
+
 } // namespace util
 } // namespace aapt
diff --git a/tools/aapt2/Util.h b/tools/aapt2/util/Util.h
similarity index 90%
rename from tools/aapt2/Util.h
rename to tools/aapt2/util/Util.h
index 7ec6b03..402147d 100644
--- a/tools/aapt2/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -17,9 +17,9 @@
 #ifndef AAPT_UTIL_H
 #define AAPT_UTIL_H
 
-#include "BigBuffer.h"
-#include "Maybe.h"
-#include "StringPiece.h"
+#include "util/BigBuffer.h"
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
 
 #include <androidfw/ResourceTypes.h>
 #include <functional>
@@ -83,6 +83,11 @@
 bool isJavaClassName(const StringPiece16& str);
 
 /**
+ * Tests that the string is a valid Java package name.
+ */
+bool isJavaPackageName(const StringPiece16& str);
+
+/**
  * Converts the class name to a fully qualified class name from the given `package`. Ex:
  *
  * asdf         --> package.asdf
@@ -296,6 +301,22 @@
         mEnd(str, sep, BasicStringPiece<Char>(str.end(), 0)) {
 }
 
+inline uint16_t hostToDevice16(uint16_t value) {
+    return htods(value);
+}
+
+inline uint32_t hostToDevice32(uint32_t value) {
+    return htodl(value);
+}
+
+inline uint16_t deviceToHost16(uint16_t value) {
+    return dtohs(value);
+}
+
+inline uint32_t deviceToHost32(uint32_t value) {
+    return dtohl(value);
+}
+
 /**
  * Returns a package name if the namespace URI is of the form:
  * http://schemas.android.com/apk/res/<package>
@@ -305,6 +326,18 @@
  */
 Maybe<std::u16string> extractPackageFromNamespace(const std::u16string& namespaceUri);
 
+/**
+ * Given a path like: res/xml-sw600dp/foo.xml
+ *
+ * Extracts "res/xml-sw600dp/" into outPrefix.
+ * Extracts "foo" into outEntry.
+ * Extracts ".xml" into outSuffix.
+ *
+ * Returns true if successful.
+ */
+bool extractResFilePathParts(const StringPiece16& path, StringPiece16* outPrefix,
+                             StringPiece16* outEntry, StringPiece16* outSuffix);
+
 } // namespace util
 
 /**
diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp
new file mode 100644
index 0000000..cdba960
--- /dev/null
+++ b/tools/aapt2/util/Util_test.cpp
@@ -0,0 +1,188 @@
+/*
+ * 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 "test/Common.h"
+#include "util/StringPiece.h"
+#include "util/Util.h"
+
+#include <gtest/gtest.h>
+#include <string>
+
+namespace aapt {
+
+TEST(UtilTest, TrimOnlyWhitespace) {
+    const std::u16string full = u"\n        ";
+
+    StringPiece16 trimmed = util::trimWhitespace(full);
+    EXPECT_TRUE(trimmed.empty());
+    EXPECT_EQ(0u, trimmed.size());
+}
+
+TEST(UtilTest, StringEndsWith) {
+    EXPECT_TRUE(util::stringEndsWith<char>("hello.xml", ".xml"));
+}
+
+TEST(UtilTest, StringStartsWith) {
+    EXPECT_TRUE(util::stringStartsWith<char>("hello.xml", "he"));
+}
+
+TEST(UtilTest, StringBuilderSplitEscapeSequence) {
+    EXPECT_EQ(StringPiece16(u"this is a new\nline."),
+              util::StringBuilder().append(u"this is a new\\")
+                                   .append(u"nline.")
+                                   .str());
+}
+
+TEST(UtilTest, StringBuilderWhitespaceRemoval) {
+    EXPECT_EQ(StringPiece16(u"hey guys this is so cool"),
+              util::StringBuilder().append(u"    hey guys ")
+                                   .append(u" this is so cool ")
+                                   .str());
+
+    EXPECT_EQ(StringPiece16(u" wow,  so many \t spaces. what?"),
+              util::StringBuilder().append(u" \" wow,  so many \t ")
+                                   .append(u"spaces. \"what? ")
+                                   .str());
+
+    EXPECT_EQ(StringPiece16(u"where is the pie?"),
+              util::StringBuilder().append(u"  where \t ")
+                                   .append(u" \nis the "" pie?")
+                                   .str());
+}
+
+TEST(UtilTest, StringBuilderEscaping) {
+    EXPECT_EQ(StringPiece16(u"hey guys\n this \t is so\\ cool"),
+              util::StringBuilder().append(u"    hey guys\\n ")
+                                   .append(u" this \\t is so\\\\ cool ")
+                                   .str());
+
+    EXPECT_EQ(StringPiece16(u"@?#\\\'"),
+              util::StringBuilder().append(u"\\@\\?\\#\\\\\\'")
+                                   .str());
+}
+
+TEST(UtilTest, StringBuilderMisplacedQuote) {
+    util::StringBuilder builder{};
+    EXPECT_FALSE(builder.append(u"they're coming!"));
+}
+
+TEST(UtilTest, StringBuilderUnicodeCodes) {
+    EXPECT_EQ(StringPiece16(u"\u00AF\u0AF0 woah"),
+              util::StringBuilder().append(u"\\u00AF\\u0AF0 woah")
+                                   .str());
+
+    EXPECT_FALSE(util::StringBuilder().append(u"\\u00 yo"));
+}
+
+TEST(UtilTest, TokenizeInput) {
+    auto tokenizer = util::tokenize(StringPiece16(u"this| is|the|end"), u'|');
+    auto iter = tokenizer.begin();
+    ASSERT_EQ(*iter, StringPiece16(u"this"));
+    ++iter;
+    ASSERT_EQ(*iter, StringPiece16(u" is"));
+    ++iter;
+    ASSERT_EQ(*iter, StringPiece16(u"the"));
+    ++iter;
+    ASSERT_EQ(*iter, StringPiece16(u"end"));
+    ++iter;
+    ASSERT_EQ(tokenizer.end(), iter);
+}
+
+TEST(UtilTest, TokenizeAtEnd) {
+    auto tokenizer = util::tokenize(StringPiece16(u"one."), u'.');
+    auto iter = tokenizer.begin();
+    ASSERT_EQ(*iter, StringPiece16(u"one"));
+    ++iter;
+    ASSERT_NE(iter, tokenizer.end());
+    ASSERT_EQ(*iter, StringPiece16());
+}
+
+TEST(UtilTest, IsJavaClassName) {
+    EXPECT_TRUE(util::isJavaClassName(u"android.test.Class"));
+    EXPECT_TRUE(util::isJavaClassName(u"android.test.Class$Inner"));
+    EXPECT_TRUE(util::isJavaClassName(u"android_test.test.Class"));
+    EXPECT_TRUE(util::isJavaClassName(u"_android_.test._Class_"));
+    EXPECT_FALSE(util::isJavaClassName(u"android.test.$Inner"));
+    EXPECT_FALSE(util::isJavaClassName(u"android.test.Inner$"));
+    EXPECT_FALSE(util::isJavaClassName(u".test.Class"));
+    EXPECT_FALSE(util::isJavaClassName(u"android"));
+}
+
+TEST(UtilTest, IsJavaPackageName) {
+    EXPECT_TRUE(util::isJavaPackageName(u"android"));
+    EXPECT_TRUE(util::isJavaPackageName(u"android.test"));
+    EXPECT_TRUE(util::isJavaPackageName(u"android.test_thing"));
+    EXPECT_FALSE(util::isJavaPackageName(u"_android"));
+    EXPECT_FALSE(util::isJavaPackageName(u"android_"));
+    EXPECT_FALSE(util::isJavaPackageName(u"android."));
+    EXPECT_FALSE(util::isJavaPackageName(u".android"));
+    EXPECT_FALSE(util::isJavaPackageName(u"android._test"));
+    EXPECT_FALSE(util::isJavaPackageName(u".."));
+}
+
+TEST(UtilTest, FullyQualifiedClassName) {
+    Maybe<std::u16string> res = util::getFullyQualifiedClassName(u"android", u"asdf");
+    AAPT_ASSERT_TRUE(res);
+    EXPECT_EQ(res.value(), u"android.asdf");
+
+    res = util::getFullyQualifiedClassName(u"android", u".asdf");
+    AAPT_ASSERT_TRUE(res);
+    EXPECT_EQ(res.value(), u"android.asdf");
+
+    res = util::getFullyQualifiedClassName(u"android", u".a.b");
+    AAPT_ASSERT_TRUE(res);
+    EXPECT_EQ(res.value(), u"android.a.b");
+
+    res = util::getFullyQualifiedClassName(u"android", u"a.b");
+    AAPT_ASSERT_TRUE(res);
+    EXPECT_EQ(res.value(), u"a.b");
+
+    res = util::getFullyQualifiedClassName(u"", u"a.b");
+    AAPT_ASSERT_TRUE(res);
+    EXPECT_EQ(res.value(), u"a.b");
+
+    res = util::getFullyQualifiedClassName(u"", u"");
+    AAPT_ASSERT_FALSE(res);
+
+    res = util::getFullyQualifiedClassName(u"android", u"./Apple");
+    AAPT_ASSERT_FALSE(res);
+}
+
+TEST(UtilTest, ExtractResourcePathComponents) {
+    StringPiece16 prefix, entry, suffix;
+    ASSERT_TRUE(util::extractResFilePathParts(u"res/xml-sw600dp/entry.xml", &prefix, &entry,
+                                              &suffix));
+    EXPECT_EQ(prefix, u"res/xml-sw600dp/");
+    EXPECT_EQ(entry, u"entry");
+    EXPECT_EQ(suffix, u".xml");
+
+    ASSERT_TRUE(util::extractResFilePathParts(u"res/xml-sw600dp/entry.9.png", &prefix, &entry,
+                                              &suffix));
+
+    EXPECT_EQ(prefix, u"res/xml-sw600dp/");
+    EXPECT_EQ(entry, u"entry");
+    EXPECT_EQ(suffix, u".9.png");
+
+    EXPECT_FALSE(util::extractResFilePathParts(u"AndroidManifest.xml", &prefix, &entry, &suffix));
+    EXPECT_FALSE(util::extractResFilePathParts(u"res/.xml", &prefix, &entry, &suffix));
+
+    ASSERT_TRUE(util::extractResFilePathParts(u"res//.", &prefix, &entry, &suffix));
+    EXPECT_EQ(prefix, u"res//");
+    EXPECT_EQ(entry, u"");
+    EXPECT_EQ(suffix, u".");
+}
+
+} // namespace aapt