AAPT2

First checking of AAPT2. The individual phases of AAPT2 work, but there
are some missing pieces.

For early testing we are missing:
- Need to properly mark file references and include them in package
- Need to package into zip

Final AAPT for apps we are missing:
- Need to crush PNGs
- Need to parse 9-patches
- Need to validate all of AndroidManifest.xml
- Need to write align method to align resource tables for splits.

Final AAPT for apps + system we are missing:
- Need to handle overlays
- Need to store comments for R file
- Need to handle --shared-lib (dynamic references too).

New AAPT features coming:
- Need to import compiled libraries
    - Name mangling
    - R file generation for library code

Change-Id: I95f8a63581b81a1f424ae6fb2c373c883b72c18d
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
new file mode 100644
index 0000000..e61fd29
--- /dev/null
+++ b/tools/aapt2/Android.mk
@@ -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.
+#
+
+# This tool is prebuilt if we're doing an app-only build.
+ifeq ($(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)),)
+
+# ==========================================================
+# Setup some common variables for the different build
+# targets here.
+# ==========================================================
+LOCAL_PATH:= $(call my-dir)
+
+main := Main.cpp
+sources := \
+	BigBuffer.cpp \
+	BinaryResourceParser.cpp \
+	ConfigDescription.cpp \
+	Files.cpp \
+	JavaClassGenerator.cpp \
+	Linker.cpp \
+	Locale.cpp \
+	Logger.cpp \
+	ManifestParser.cpp \
+	ManifestValidator.cpp \
+	ResChunkPullParser.cpp \
+	Resolver.cpp \
+	Resource.cpp \
+	ResourceParser.cpp \
+	ResourceTable.cpp \
+	ResourceValues.cpp \
+	SdkConstants.cpp \
+	StringPool.cpp \
+	TableFlattener.cpp \
+	Util.cpp \
+	ScopedXmlPullParser.cpp \
+	SourceXmlPullParser.cpp \
+	XliffXmlPullParser.cpp \
+	XmlFlattener.cpp
+
+testSources := \
+	BigBuffer_test.cpp \
+	Compat_test.cpp \
+	ConfigDescription_test.cpp \
+	JavaClassGenerator_test.cpp \
+	Linker_test.cpp \
+	Locale_test.cpp \
+	ManifestParser_test.cpp \
+	Maybe_test.cpp \
+	ResourceParser_test.cpp \
+	Resource_test.cpp \
+	ResourceTable_test.cpp \
+	ScopedXmlPullParser_test.cpp \
+	StringPiece_test.cpp \
+	StringPool_test.cpp \
+	Util_test.cpp \
+	XliffXmlPullParser_test.cpp \
+	XmlFlattener_test.cpp
+
+cIncludes :=
+
+hostLdLibs := -lz
+hostStaticLibs := \
+	libandroidfw \
+	libutils \
+	liblog \
+	libcutils \
+	libexpat \
+	libziparchive-host
+
+cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG
+cppFlags := -std=c++11 -Wno-missing-field-initializers
+
+# ==========================================================
+# Build the host static library: libaapt2
+# ==========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libaapt2
+
+LOCAL_SRC_FILES := $(sources)
+LOCAL_C_INCLUDES += $(cIncludes)
+LOCAL_CFLAGS += $(cFlags)
+LOCAL_CPPFLAGS += $(cppFlags)
+
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+
+# ==========================================================
+# Build the host tests: libaapt2_tests
+# ==========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libaapt2_tests
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(testSources)
+
+LOCAL_C_INCLUDES += $(cIncludes)
+LOCAL_STATIC_LIBRARIES += libaapt2 $(hostStaticLibs)
+LOCAL_LDLIBS += $(hostLdLibs)
+LOCAL_CFLAGS += $(cFlags)
+LOCAL_CPPFLAGS += $(cppFlags)
+
+include $(BUILD_HOST_NATIVE_TEST)
+
+# ==========================================================
+# Build the host executable: aapt2
+# ==========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := aapt2
+
+LOCAL_SRC_FILES := $(main)
+
+LOCAL_C_INCLUDES += $(cIncludes)
+LOCAL_STATIC_LIBRARIES += libaapt2 $(hostStaticLibs)
+LOCAL_LDLIBS += $(hostLdLibs)
+LOCAL_CFLAGS += $(cFlags)
+LOCAL_CPPFLAGS += $(cppFlags)
+
+include $(BUILD_HOST_EXECUTABLE)
+
+endif # No TARGET_BUILD_APPS or TARGET_BUILD_PDK
diff --git a/tools/aapt2/AppInfo.h b/tools/aapt2/AppInfo.h
new file mode 100644
index 0000000..30047f7
--- /dev/null
+++ b/tools/aapt2/AppInfo.h
@@ -0,0 +1,37 @@
+/*
+ * 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_APP_INFO_H
+#define AAPT_APP_INFO_H
+
+#include <string>
+
+namespace aapt {
+
+/**
+ * Holds basic information about the app being built. Most of this information
+ * will come from the app's AndroidManifest.
+ */
+struct AppInfo {
+    /**
+     * App's package name.
+     */
+    std::u16string package;
+};
+
+} // namespace aapt
+
+#endif // AAPT_APP_INFO_H
diff --git a/tools/aapt2/BigBuffer.cpp b/tools/aapt2/BigBuffer.cpp
new file mode 100644
index 0000000..8f57172
--- /dev/null
+++ b/tools/aapt2/BigBuffer.cpp
@@ -0,0 +1,52 @@
+/*
+ * 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 <algorithm>
+#include <memory>
+#include <vector>
+
+namespace aapt {
+
+void* BigBuffer::nextBlockImpl(size_t size) {
+    if (!mBlocks.empty()) {
+        Block& block = mBlocks.back();
+        if (block.mBlockSize - block.size >= size) {
+            void* outBuffer = block.buffer.get() + block.size;
+            block.size += size;
+            mSize += size;
+            return outBuffer;
+        }
+    }
+
+    const size_t actualSize = std::max(mBlockSize, size);
+
+    Block block = {};
+
+    // Zero-allocate the block's buffer.
+    block.buffer = std::unique_ptr<uint8_t[]>(new uint8_t[actualSize]());
+    assert(block.buffer);
+
+    block.size = size;
+    block.mBlockSize = actualSize;
+
+    mBlocks.push_back(std::move(block));
+    mSize += size;
+    return mBlocks.back().buffer.get();
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/BigBuffer.h b/tools/aapt2/BigBuffer.h
new file mode 100644
index 0000000..025142b
--- /dev/null
+++ b/tools/aapt2/BigBuffer.h
@@ -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.
+ */
+
+#ifndef AAPT_BIG_BUFFER_H
+#define AAPT_BIG_BUFFER_H
+
+#include <cstring>
+#include <memory>
+#include <vector>
+
+namespace aapt {
+
+/**
+ * Inspired by protobuf's ZeroCopyOutputStream, offers blocks of memory
+ * in which to write without knowing the full size of the entire payload.
+ * This is essentially a list of memory blocks. As one fills up, another
+ * block is allocated and appended to the end of the list.
+ */
+class BigBuffer {
+public:
+    /**
+     * A contiguous block of allocated memory.
+     */
+    struct Block {
+        /**
+         * Pointer to the memory.
+         */
+        std::unique_ptr<uint8_t[]> buffer;
+
+        /**
+         * Size of memory that is currently occupied. The actual
+         * allocation may be larger.
+         */
+        size_t size;
+
+    private:
+        friend class BigBuffer;
+
+        /**
+         * The size of the memory block allocation.
+         */
+        size_t mBlockSize;
+    };
+
+    typedef std::vector<Block>::const_iterator const_iterator;
+
+    /**
+     * Create a BigBuffer with block allocation sizes
+     * of blockSize.
+     */
+    BigBuffer(size_t blockSize);
+
+    BigBuffer(const BigBuffer&) = delete; // No copying.
+
+    BigBuffer(BigBuffer&& rhs);
+
+    /**
+     * Number of occupied bytes in all the allocated blocks.
+     */
+    size_t size() const;
+
+    /**
+     * Returns a pointer to an array of T, where T is
+     * a POD type. The elements are zero-initialized.
+     */
+    template <typename T>
+    T* nextBlock(size_t count = 1);
+
+    /**
+     * Moves the specified BigBuffer into this one. When this method
+     * returns, buffer is empty.
+     */
+    void appendBuffer(BigBuffer&& buffer);
+
+    /**
+     * Pads the block with 'bytes' bytes of zero values.
+     */
+    void pad(size_t bytes);
+
+    /**
+     * Pads the block so that it aligns on a 4 byte boundary.
+     */
+    void align4();
+
+    const_iterator begin() const;
+    const_iterator end() const;
+
+private:
+    /**
+     * Returns a pointer to a buffer of the requested size.
+     * The buffer is zero-initialized.
+     */
+    void* nextBlockImpl(size_t size);
+
+    size_t mBlockSize;
+    size_t mSize;
+    std::vector<Block> mBlocks;
+};
+
+inline BigBuffer::BigBuffer(size_t blockSize) : mBlockSize(blockSize), mSize(0) {
+}
+
+inline BigBuffer::BigBuffer(BigBuffer&& rhs) :
+        mBlockSize(rhs.mBlockSize), mSize(rhs.mSize), mBlocks(std::move(rhs.mBlocks)) {
+}
+
+inline size_t BigBuffer::size() const {
+    return mSize;
+}
+
+template <typename T>
+inline T* BigBuffer::nextBlock(size_t count) {
+    assert(count != 0);
+    return reinterpret_cast<T*>(nextBlockImpl(sizeof(T) * count));
+}
+
+inline void BigBuffer::appendBuffer(BigBuffer&& buffer) {
+    std::move(buffer.mBlocks.begin(), buffer.mBlocks.end(), std::back_inserter(mBlocks));
+    mSize += buffer.mSize;
+    buffer.mBlocks.clear();
+    buffer.mSize = 0;
+}
+
+inline void BigBuffer::pad(size_t bytes) {
+    nextBlock<char>(bytes);
+}
+
+inline void BigBuffer::align4() {
+    const size_t unaligned = mSize % 4;
+    if (unaligned != 0) {
+        pad(4 - unaligned);
+    }
+}
+
+inline BigBuffer::const_iterator BigBuffer::begin() const {
+    return mBlocks.begin();
+}
+
+inline BigBuffer::const_iterator BigBuffer::end() const {
+    return mBlocks.end();
+}
+
+} // namespace aapt
+
+#endif // AAPT_BIG_BUFFER_H
diff --git a/tools/aapt2/BigBuffer_test.cpp b/tools/aapt2/BigBuffer_test.cpp
new file mode 100644
index 0000000..01ee8d7
--- /dev/null
+++ b/tools/aapt2/BigBuffer_test.cpp
@@ -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.
+ */
+
+#include "BigBuffer.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(BigBufferTest, AllocateSingleBlock) {
+    BigBuffer buffer(4);
+
+    EXPECT_NE(nullptr, buffer.nextBlock<char>(2));
+    EXPECT_EQ(2u, buffer.size());
+}
+
+TEST(BigBufferTest, ReturnSameBlockIfNextAllocationFits) {
+    BigBuffer buffer(16);
+
+    char* b1 = buffer.nextBlock<char>(8);
+    EXPECT_NE(nullptr, b1);
+
+    char* b2 = buffer.nextBlock<char>(4);
+    EXPECT_NE(nullptr, b2);
+
+    EXPECT_EQ(b1 + 8, b2);
+}
+
+TEST(BigBufferTest, AllocateExactSizeBlockIfLargerThanBlockSize) {
+    BigBuffer buffer(16);
+
+    EXPECT_NE(nullptr, buffer.nextBlock<char>(32));
+    EXPECT_EQ(32u, buffer.size());
+}
+
+TEST(BigBufferTest, AppendAndMoveBlock) {
+    BigBuffer buffer(16);
+
+    uint32_t* b1 = buffer.nextBlock<uint32_t>();
+    ASSERT_NE(nullptr, b1);
+    *b1 = 33;
+
+    {
+        BigBuffer buffer2(16);
+        b1 = buffer2.nextBlock<uint32_t>();
+        ASSERT_NE(nullptr, b1);
+        *b1 = 44;
+
+        buffer.appendBuffer(std::move(buffer2));
+        EXPECT_EQ(0u, buffer2.size());
+        EXPECT_EQ(buffer2.begin(), buffer2.end());
+    }
+
+    EXPECT_EQ(2 * sizeof(uint32_t), buffer.size());
+
+    auto b = buffer.begin();
+    ASSERT_NE(b, buffer.end());
+    ASSERT_EQ(sizeof(uint32_t), b->size);
+    ASSERT_EQ(33u, *reinterpret_cast<uint32_t*>(b->buffer.get()));
+    ++b;
+
+    ASSERT_NE(b, buffer.end());
+    ASSERT_EQ(sizeof(uint32_t), b->size);
+    ASSERT_EQ(44u, *reinterpret_cast<uint32_t*>(b->buffer.get()));
+    ++b;
+
+    ASSERT_EQ(b, buffer.end());
+}
+
+TEST(BigBufferTest, PadAndAlignProperly) {
+    BigBuffer buffer(16);
+
+    ASSERT_NE(buffer.nextBlock<char>(2), nullptr);
+    ASSERT_EQ(2u, buffer.size());
+    buffer.pad(2);
+    ASSERT_EQ(4u, buffer.size());
+    buffer.align4();
+    ASSERT_EQ(4u, buffer.size());
+    buffer.pad(2);
+    ASSERT_EQ(6u, buffer.size());
+    buffer.align4();
+    ASSERT_EQ(8u, buffer.size());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/BinaryResourceParser.cpp b/tools/aapt2/BinaryResourceParser.cpp
new file mode 100644
index 0000000..d58f05a
--- /dev/null
+++ b/tools/aapt2/BinaryResourceParser.cpp
@@ -0,0 +1,794 @@
+/*
+ * 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 "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;
+
+template <typename T>
+inline static const T* convertTo(const ResChunk_header* chunk) {
+    if (chunk->headerSize < sizeof(T)) {
+        return nullptr;
+    }
+    return reinterpret_cast<const T*>(chunk);
+}
+
+inline static const uint8_t* getChunkData(const ResChunk_header& chunk) {
+    return reinterpret_cast<const uint8_t*>(&chunk) + chunk.headerSize;
+}
+
+inline static size_t getChunkDataLen(const ResChunk_header& chunk) {
+    return chunk.size - chunk.headerSize;
+}
+
+/*
+ * 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::map<ResourceId, ResourceName>& cache) : 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 == std::end(mCache)) {
+            Logger::note() << "failed to find " << reference.id << std::endl;
+        } else {
+            reference.name = cacheIter->second;
+            reference.id = 0;
+        }
+    }
+
+    const std::map<ResourceId, ResourceName>& mCache;
+};
+
+
+BinaryResourceParser::BinaryResourceParser(std::shared_ptr<ResourceTable> table,
+                                           const Source& source,
+                                           const void* data,
+                                           size_t len) :
+        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) {
+            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;
+    }
+
+    // We only support 32 bit offsets right now.
+    const ptrdiff_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;
+            }
+            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) != android::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() == android::NO_INIT) {
+                if (mValuePool.setTo(parser.getChunk(), parser.getChunk()->size) !=
+                        android::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())) != android::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() != android::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]));
+    mTable->setPackage(StringPiece16(reinterpret_cast<const char16_t*>(packageHeader->name), len));
+
+    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() == android::NO_INIT) {
+                if (mTypePool.setTo(parser.getChunk(), parser.getChunk()->size) !=
+                        android::NO_ERROR) {
+                    Logger::error(mSource)
+                            << "failed to parse type string pool with code "
+                            << mTypePool.getError()
+                            << "."
+                            << std::endl;
+                    return false;
+                }
+            } else if (mKeyPool.getError() == android::NO_INIT) {
+                if (mKeyPool.setTo(parser.getChunk(), parser.getChunk()->size) !=
+                        android::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;
+
+        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(mIdIndex);
+    for (auto& type : *mTable) {
+        for (auto& entry : type->entries) {
+            for (auto& configValue : entry->values) {
+                configValue.value->accept(visitor, {});
+            }
+        }
+    }
+    return true;
+}
+
+bool BinaryResourceParser::parseTypeSpec(const ResChunk_header* chunk) {
+    if (mTypePool.getError() != android::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() != android::NO_ERROR) {
+        Logger::error(mSource)
+                << "no type string pool available for ResTable_typeSpec."
+                << std::endl;
+        return false;
+    }
+
+    if (mKeyPool.getError() != android::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->addResource(name, config, source, std::move(resourceValue))) {
+            return false;
+        }
+
+        if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
+            if (!mTable->markPublic(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 (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 {
+            // 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_NULL;
+        nullType.data = Res_value::DATA_NULL_UNDEFINED;
+        return util::make_unique<BinaryPrimitive>(nullType);
+    }
+
+    if (value->dataType == ExtendedTypes::TYPE_SENTINEL) {
+        return util::make_unique<Sentinel>();
+    }
+
+    if (value->dataType == ExtendedTypes::TYPE_RAW_STRING) {
+        return util::make_unique<RawString>(
+                mTable->getValueStringPool().makeRef(util::getString(mValuePool, value->data),
+                                                    StringPool::Context{ 1, config }));
+    }
+
+    if (name.type == ResourceType::kId ||
+            (value->dataType == Res_value::TYPE_NULL &&
+            value->data == Res_value::DATA_NULL_UNDEFINED &&
+            (flags & ResTable_entry::FLAG_WEAK) != 0)) {
+        return util::make_unique<Id>();
+    }
+
+    // Treat this as a raw binary primitive.
+    return util::make_unique<BinaryPrimitive>(*value);
+}
+
+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;
+            }
+
+            attr->symbols.push_back(Attribute::Symbol{
+                    Reference(mapEntry.name.ident),
+                    mapEntry.value.data
+            });
+        }
+    }
+
+    // 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) {
+        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/BinaryResourceParser.h b/tools/aapt2/BinaryResourceParser.h
new file mode 100644
index 0000000..9268078
--- /dev/null
+++ b/tools/aapt2/BinaryResourceParser.h
@@ -0,0 +1,153 @@
+/*
+ * 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_BINARY_RESOURCE_PARSER_H
+#define AAPT_BINARY_RESOURCE_PARSER_H
+
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "Source.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <string>
+
+namespace aapt {
+
+struct SymbolTable_entry;
+
+/*
+ * Parses a binary resource table (resources.arsc) and adds the entries
+ * to a ResourceTable. This is different than the libandroidfw ResTable
+ * in that it scans the table from top to bottom and doesn't require
+ * support for random access. It is also able to parse non-runtime
+ * chunks and types.
+ */
+class BinaryResourceParser {
+public:
+    /*
+     * Creates a parser, which will read `len` bytes from `data`, and
+     * add any resources parsed to `table`. `source` is for logging purposes.
+     */
+    BinaryResourceParser(std::shared_ptr<ResourceTable> table, const Source& source,
+                         const void* data, size_t len);
+
+    BinaryResourceParser(const BinaryResourceParser&) = delete; // No copy.
+
+    /*
+     * Parses the binary resource table and returns true if successful.
+     */
+    bool parse();
+
+private:
+    // Helper method to retrieve the symbol name for a given table offset specified
+    // as a pointer.
+    bool getSymbol(const void* data, ResourceNameRef* outSymbol);
+
+    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 parseTypeSpec(const android::ResChunk_header* chunk);
+    bool parseType(const android::ResChunk_header* chunk);
+
+    std::unique_ptr<Item> parseValue(const ResourceNameRef& name,
+            const ConfigDescription& config, const android::Res_value* value, uint16_t flags);
+
+    std::unique_ptr<Value> parseMapEntry(const ResourceNameRef& name,
+            const ConfigDescription& config, const android::ResTable_map_entry* map);
+
+    std::unique_ptr<Style> parseStyle(const ResourceNameRef& name,
+            const ConfigDescription& config, const android::ResTable_map_entry* map);
+
+    std::unique_ptr<Attribute> parseAttr(const ResourceNameRef& name,
+            const ConfigDescription& config, const android::ResTable_map_entry* map);
+
+    std::unique_ptr<Array> parseArray(const ResourceNameRef& name,
+            const ConfigDescription& config, const android::ResTable_map_entry* map);
+
+    std::unique_ptr<Plural> parsePlural(const ResourceNameRef& name,
+            const ConfigDescription& config, const android::ResTable_map_entry* map);
+
+    std::unique_ptr<Styleable> parseStyleable(const ResourceNameRef& name,
+            const ConfigDescription& config, const android::ResTable_map_entry* map);
+
+    std::shared_ptr<ResourceTable> mTable;
+
+    const Source mSource;
+
+    const void* mData;
+    const size_t mDataLen;
+
+    // The package name of the resource table.
+    std::u16string mPackage;
+
+    // The array of symbol entries. Each element points to an offset
+    // in the table and an index into the symbol table string pool.
+    const SymbolTable_entry* mSymbolEntries = nullptr;
+
+    // Number of symbol entries.
+    size_t mSymbolEntryCount = 0;
+
+    // The symbol table string pool. Holds the names of symbols
+    // referenced in this table but not defined nor resolved to an
+    // ID.
+    android::ResStringPool mSymbolPool;
+
+    // The source string pool. Resource entries may have an extra
+    // field that points into this string pool, which denotes where
+    // the resource was parsed from originally.
+    android::ResStringPool mSourcePool;
+
+    // The standard value string pool for resource values.
+    android::ResStringPool mValuePool;
+
+    // The string pool that holds the names of the types defined
+    // in this table.
+    android::ResStringPool mTypePool;
+
+    // The string pool that holds the names of the entries defined
+    // in this table.
+    android::ResStringPool mKeyPool;
+
+    // A mapping of resource ID to resource name. When we finish parsing
+    // we use this to convert all resource IDs to symbolic references.
+    std::map<ResourceId, ResourceName> mIdIndex;
+};
+
+} // namespace aapt
+
+namespace android {
+
+/**
+ * Iterator functionality for ResTable_map_entry.
+ */
+
+inline const ResTable_map* begin(const ResTable_map_entry* map) {
+    return reinterpret_cast<const ResTable_map*>(
+            reinterpret_cast<const uint8_t*>(map) + 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;
+}
+
+} // namespace android
+
+#endif // AAPT_BINARY_RESOURCE_PARSER_H
diff --git a/tools/aapt2/Compat_test.cpp b/tools/aapt2/Compat_test.cpp
new file mode 100644
index 0000000..96aee44
--- /dev/null
+++ b/tools/aapt2/Compat_test.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(CompatTest, VersionAttributesInStyle) {
+}
+
+TEST(CompatTest, VersionAttributesInXML) {
+}
+
+TEST(CompatTest, DoNotOverrideExistingVersionedFiles) {
+}
+
+TEST(CompatTest, VersionAttributesInStyleWithCorrectPrecedence) {
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp
new file mode 100644
index 0000000..6ddf94a
--- /dev/null
+++ b/tools/aapt2/ConfigDescription.cpp
@@ -0,0 +1,752 @@
+/*
+ * 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 "Locale.h"
+#include "SdkConstants.h"
+#include "StringPiece.h"
+#include "Util.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <string>
+#include <vector>
+
+namespace aapt {
+
+using android::ResTable_config;
+
+static const char* kWildcardName = "any";
+
+static bool parseMcc(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->mcc = 0;
+        return true;
+    }
+    const char* c = name;
+    if (tolower(*c) != 'm') return false;
+    c++;
+    if (tolower(*c) != 'c') return false;
+    c++;
+    if (tolower(*c) != 'c') return false;
+    c++;
+
+    const char* val = c;
+
+    while (*c >= '0' && *c <= '9') {
+        c++;
+    }
+    if (*c != 0) return false;
+    if (c-val != 3) return false;
+
+    int d = atoi(val);
+    if (d != 0) {
+        if (out) out->mcc = d;
+        return true;
+    }
+
+    return false;
+}
+
+static bool parseMnc(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->mcc = 0;
+        return true;
+    }
+    const char* c = name;
+    if (tolower(*c) != 'm') return false;
+    c++;
+    if (tolower(*c) != 'n') return false;
+    c++;
+    if (tolower(*c) != 'c') return false;
+    c++;
+
+    const char* val = c;
+
+    while (*c >= '0' && *c <= '9') {
+        c++;
+    }
+    if (*c != 0) return false;
+    if (c-val == 0 || c-val > 3) return false;
+
+    if (out) {
+        out->mnc = atoi(val);
+        if (out->mnc == 0) {
+            out->mnc = ACONFIGURATION_MNC_ZERO;
+        }
+    }
+
+    return true;
+}
+
+static bool parseLayoutDirection(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
+                | ResTable_config::LAYOUTDIR_ANY;
+        return true;
+    } else if (strcmp(name, "ldltr") == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
+                | ResTable_config::LAYOUTDIR_LTR;
+        return true;
+    } else if (strcmp(name, "ldrtl") == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
+                | ResTable_config::LAYOUTDIR_RTL;
+        return true;
+    }
+
+    return false;
+}
+
+static bool parseScreenLayoutSize(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
+                | ResTable_config::SCREENSIZE_ANY;
+        return true;
+    } else if (strcmp(name, "small") == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
+                | ResTable_config::SCREENSIZE_SMALL;
+        return true;
+    } else if (strcmp(name, "normal") == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
+                | ResTable_config::SCREENSIZE_NORMAL;
+        return true;
+    } else if (strcmp(name, "large") == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
+                | ResTable_config::SCREENSIZE_LARGE;
+        return true;
+    } else if (strcmp(name, "xlarge") == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
+                | ResTable_config::SCREENSIZE_XLARGE;
+        return true;
+    }
+
+    return false;
+}
+
+static bool parseScreenLayoutLong(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_SCREENLONG)
+                | ResTable_config::SCREENLONG_ANY;
+        return true;
+    } else if (strcmp(name, "long") == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_SCREENLONG)
+                | ResTable_config::SCREENLONG_YES;
+        return true;
+    } else if (strcmp(name, "notlong") == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_SCREENLONG)
+                | ResTable_config::SCREENLONG_NO;
+        return true;
+    }
+
+    return false;
+}
+
+static bool parseOrientation(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->orientation = out->ORIENTATION_ANY;
+        return true;
+    } else if (strcmp(name, "port") == 0) {
+        if (out) out->orientation = out->ORIENTATION_PORT;
+        return true;
+    } else if (strcmp(name, "land") == 0) {
+        if (out) out->orientation = out->ORIENTATION_LAND;
+        return true;
+    } else if (strcmp(name, "square") == 0) {
+        if (out) out->orientation = out->ORIENTATION_SQUARE;
+        return true;
+    }
+
+    return false;
+}
+
+static bool parseUiModeType(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->uiMode =
+                (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+                | ResTable_config::UI_MODE_TYPE_ANY;
+        return true;
+    } else if (strcmp(name, "desk") == 0) {
+      if (out) out->uiMode =
+              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+              | ResTable_config::UI_MODE_TYPE_DESK;
+        return true;
+    } else if (strcmp(name, "car") == 0) {
+      if (out) out->uiMode =
+              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+              | ResTable_config::UI_MODE_TYPE_CAR;
+        return true;
+    } else if (strcmp(name, "television") == 0) {
+      if (out) out->uiMode =
+              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+              | ResTable_config::UI_MODE_TYPE_TELEVISION;
+        return true;
+    } else if (strcmp(name, "appliance") == 0) {
+      if (out) out->uiMode =
+              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+              | ResTable_config::UI_MODE_TYPE_APPLIANCE;
+        return true;
+    } else if (strcmp(name, "watch") == 0) {
+      if (out) out->uiMode =
+              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+              | ResTable_config::UI_MODE_TYPE_WATCH;
+        return true;
+    }
+
+    return false;
+}
+
+static bool parseUiModeNight(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->uiMode =
+                (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
+                | ResTable_config::UI_MODE_NIGHT_ANY;
+        return true;
+    } else if (strcmp(name, "night") == 0) {
+        if (out) out->uiMode =
+                (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
+                | ResTable_config::UI_MODE_NIGHT_YES;
+        return true;
+    } else if (strcmp(name, "notnight") == 0) {
+      if (out) out->uiMode =
+              (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
+              | ResTable_config::UI_MODE_NIGHT_NO;
+        return true;
+    }
+
+    return false;
+}
+
+static bool parseDensity(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->density = ResTable_config::DENSITY_DEFAULT;
+        return true;
+    }
+
+    if (strcmp(name, "anydpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_ANY;
+        return true;
+    }
+
+    if (strcmp(name, "nodpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_NONE;
+        return true;
+    }
+
+    if (strcmp(name, "ldpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_LOW;
+        return true;
+    }
+
+    if (strcmp(name, "mdpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_MEDIUM;
+        return true;
+    }
+
+    if (strcmp(name, "tvdpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_TV;
+        return true;
+    }
+
+    if (strcmp(name, "hdpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_HIGH;
+        return true;
+    }
+
+    if (strcmp(name, "xhdpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_XHIGH;
+        return true;
+    }
+
+    if (strcmp(name, "xxhdpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_XXHIGH;
+        return true;
+    }
+
+    if (strcmp(name, "xxxhdpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_XXXHIGH;
+        return true;
+    }
+
+    char* c = (char*)name;
+    while (*c >= '0' && *c <= '9') {
+        c++;
+    }
+
+    // check that we have 'dpi' after the last digit.
+    if (toupper(c[0]) != 'D' ||
+            toupper(c[1]) != 'P' ||
+            toupper(c[2]) != 'I' ||
+            c[3] != 0) {
+        return false;
+    }
+
+    // temporarily replace the first letter with \0 to
+    // use atoi.
+    char tmp = c[0];
+    c[0] = '\0';
+
+    int d = atoi(name);
+    c[0] = tmp;
+
+    if (d != 0) {
+        if (out) out->density = d;
+        return true;
+    }
+
+    return false;
+}
+
+static bool parseTouchscreen(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->touchscreen = out->TOUCHSCREEN_ANY;
+        return true;
+    } else if (strcmp(name, "notouch") == 0) {
+        if (out) out->touchscreen = out->TOUCHSCREEN_NOTOUCH;
+        return true;
+    } else if (strcmp(name, "stylus") == 0) {
+        if (out) out->touchscreen = out->TOUCHSCREEN_STYLUS;
+        return true;
+    } else if (strcmp(name, "finger") == 0) {
+        if (out) out->touchscreen = out->TOUCHSCREEN_FINGER;
+        return true;
+    }
+
+    return false;
+}
+
+static bool parseKeysHidden(const char* name, ResTable_config* out) {
+    uint8_t mask = 0;
+    uint8_t value = 0;
+    if (strcmp(name, kWildcardName) == 0) {
+        mask = ResTable_config::MASK_KEYSHIDDEN;
+        value = ResTable_config::KEYSHIDDEN_ANY;
+    } else if (strcmp(name, "keysexposed") == 0) {
+        mask = ResTable_config::MASK_KEYSHIDDEN;
+        value = ResTable_config::KEYSHIDDEN_NO;
+    } else if (strcmp(name, "keyshidden") == 0) {
+        mask = ResTable_config::MASK_KEYSHIDDEN;
+        value = ResTable_config::KEYSHIDDEN_YES;
+    } else if (strcmp(name, "keyssoft") == 0) {
+        mask = ResTable_config::MASK_KEYSHIDDEN;
+        value = ResTable_config::KEYSHIDDEN_SOFT;
+    }
+
+    if (mask != 0) {
+        if (out) out->inputFlags = (out->inputFlags&~mask) | value;
+        return true;
+    }
+
+    return false;
+}
+
+static bool parseKeyboard(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->keyboard = out->KEYBOARD_ANY;
+        return true;
+    } else if (strcmp(name, "nokeys") == 0) {
+        if (out) out->keyboard = out->KEYBOARD_NOKEYS;
+        return true;
+    } else if (strcmp(name, "qwerty") == 0) {
+        if (out) out->keyboard = out->KEYBOARD_QWERTY;
+        return true;
+    } else if (strcmp(name, "12key") == 0) {
+        if (out) out->keyboard = out->KEYBOARD_12KEY;
+        return true;
+    }
+
+    return false;
+}
+
+static bool parseNavHidden(const char* name, ResTable_config* out) {
+    uint8_t mask = 0;
+    uint8_t value = 0;
+    if (strcmp(name, kWildcardName) == 0) {
+        mask = ResTable_config::MASK_NAVHIDDEN;
+        value = ResTable_config::NAVHIDDEN_ANY;
+    } else if (strcmp(name, "navexposed") == 0) {
+        mask = ResTable_config::MASK_NAVHIDDEN;
+        value = ResTable_config::NAVHIDDEN_NO;
+    } else if (strcmp(name, "navhidden") == 0) {
+        mask = ResTable_config::MASK_NAVHIDDEN;
+        value = ResTable_config::NAVHIDDEN_YES;
+    }
+
+    if (mask != 0) {
+        if (out) out->inputFlags = (out->inputFlags&~mask) | value;
+        return true;
+    }
+
+    return false;
+}
+
+static bool parseNavigation(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->navigation = out->NAVIGATION_ANY;
+        return true;
+    } else if (strcmp(name, "nonav") == 0) {
+        if (out) out->navigation = out->NAVIGATION_NONAV;
+        return true;
+    } else if (strcmp(name, "dpad") == 0) {
+        if (out) out->navigation = out->NAVIGATION_DPAD;
+        return true;
+    } else if (strcmp(name, "trackball") == 0) {
+        if (out) out->navigation = out->NAVIGATION_TRACKBALL;
+        return true;
+    } else if (strcmp(name, "wheel") == 0) {
+        if (out) out->navigation = out->NAVIGATION_WHEEL;
+        return true;
+    }
+
+    return false;
+}
+
+static bool parseScreenSize(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) {
+            out->screenWidth = out->SCREENWIDTH_ANY;
+            out->screenHeight = out->SCREENHEIGHT_ANY;
+        }
+        return true;
+    }
+
+    const char* x = name;
+    while (*x >= '0' && *x <= '9') x++;
+    if (x == name || *x != 'x') return false;
+    std::string xName(name, x-name);
+    x++;
+
+    const char* y = x;
+    while (*y >= '0' && *y <= '9') y++;
+    if (y == name || *y != 0) return false;
+    std::string yName(x, y-x);
+
+    uint16_t w = (uint16_t)atoi(xName.c_str());
+    uint16_t h = (uint16_t)atoi(yName.c_str());
+    if (w < h) {
+        return false;
+    }
+
+    if (out) {
+        out->screenWidth = w;
+        out->screenHeight = h;
+    }
+
+    return true;
+}
+
+static bool parseSmallestScreenWidthDp(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) {
+            out->smallestScreenWidthDp = out->SCREENWIDTH_ANY;
+        }
+        return true;
+    }
+
+    if (*name != 's') return false;
+    name++;
+    if (*name != 'w') return false;
+    name++;
+    const char* x = name;
+    while (*x >= '0' && *x <= '9') x++;
+    if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
+    std::string xName(name, x-name);
+
+    if (out) {
+        out->smallestScreenWidthDp = (uint16_t)atoi(xName.c_str());
+    }
+
+    return true;
+}
+
+static bool parseScreenWidthDp(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) {
+            out->screenWidthDp = out->SCREENWIDTH_ANY;
+        }
+        return true;
+    }
+
+    if (*name != 'w') return false;
+    name++;
+    const char* x = name;
+    while (*x >= '0' && *x <= '9') x++;
+    if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
+    std::string xName(name, x-name);
+
+    if (out) {
+        out->screenWidthDp = (uint16_t)atoi(xName.c_str());
+    }
+
+    return true;
+}
+
+static bool parseScreenHeightDp(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) {
+            out->screenHeightDp = out->SCREENWIDTH_ANY;
+        }
+        return true;
+    }
+
+    if (*name != 'h') return false;
+    name++;
+    const char* x = name;
+    while (*x >= '0' && *x <= '9') x++;
+    if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
+    std::string xName(name, x-name);
+
+    if (out) {
+        out->screenHeightDp = (uint16_t)atoi(xName.c_str());
+    }
+
+    return true;
+}
+
+static bool parseVersion(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) {
+            out->sdkVersion = out->SDKVERSION_ANY;
+            out->minorVersion = out->MINORVERSION_ANY;
+        }
+        return true;
+    }
+
+    if (*name != 'v') {
+        return false;
+    }
+
+    name++;
+    const char* s = name;
+    while (*s >= '0' && *s <= '9') s++;
+    if (s == name || *s != 0) return false;
+    std::string sdkName(name, s-name);
+
+    if (out) {
+        out->sdkVersion = (uint16_t)atoi(sdkName.c_str());
+        out->minorVersion = 0;
+    }
+
+    return true;
+}
+
+bool ConfigDescription::parse(const StringPiece& str, ConfigDescription* out) {
+    std::vector<std::string> parts = util::splitAndLowercase(str, '-');
+
+    ConfigDescription config;
+    ssize_t partsConsumed = 0;
+    LocaleValue locale;
+
+    const auto partsEnd = parts.end();
+    auto partIter = parts.begin();
+
+    if (str.size() == 0) {
+        goto success;
+    }
+
+    if (parseMcc(partIter->c_str(), &config)) {
+        ++partIter;
+        if (partIter == partsEnd) {
+            goto success;
+        }
+    }
+
+    if (parseMnc(partIter->c_str(), &config)) {
+        ++partIter;
+        if (partIter == partsEnd) {
+            goto success;
+        }
+    }
+
+    // Locale spans a few '-' separators, so we let it
+    // control the index.
+    partsConsumed = locale.initFromParts(partIter, partsEnd);
+    if (partsConsumed < 0) {
+        return false;
+    } else {
+        locale.writeTo(&config);
+        partIter += partsConsumed;
+        if (partIter == partsEnd) {
+            goto success;
+        }
+    }
+
+    if (parseLayoutDirection(partIter->c_str(), &config)) {
+        ++partIter;
+        if (partIter == partsEnd) {
+            goto success;
+        }
+    }
+
+    if (parseSmallestScreenWidthDp(partIter->c_str(), &config)) {
+        ++partIter;
+        if (partIter == partsEnd) {
+            goto success;
+        }
+    }
+
+    if (parseScreenWidthDp(partIter->c_str(), &config)) {
+        ++partIter;
+        if (partIter == partsEnd) {
+            goto success;
+        }
+    }
+
+    if (parseScreenHeightDp(partIter->c_str(), &config)) {
+        ++partIter;
+        if (partIter == partsEnd) {
+            goto success;
+        }
+    }
+
+    if (parseScreenLayoutSize(partIter->c_str(), &config)) {
+        ++partIter;
+        if (partIter == partsEnd) {
+            goto success;
+        }
+    }
+
+    if (parseScreenLayoutLong(partIter->c_str(), &config)) {
+        ++partIter;
+        if (partIter == partsEnd) {
+            goto success;
+        }
+    }
+
+    if (parseOrientation(partIter->c_str(), &config)) {
+        ++partIter;
+        if (partIter == partsEnd) {
+            goto success;
+        }
+    }
+
+    if (parseUiModeType(partIter->c_str(), &config)) {
+        ++partIter;
+        if (partIter == partsEnd) {
+            goto success;
+        }
+    }
+
+    if (parseUiModeNight(partIter->c_str(), &config)) {
+        ++partIter;
+        if (partIter == partsEnd) {
+            goto success;
+        }
+    }
+
+    if (parseDensity(partIter->c_str(), &config)) {
+        ++partIter;
+        if (partIter == partsEnd) {
+            goto success;
+        }
+    }
+
+    if (parseTouchscreen(partIter->c_str(), &config)) {
+        ++partIter;
+        if (partIter == partsEnd) {
+            goto success;
+        }
+    }
+
+    if (parseKeysHidden(partIter->c_str(), &config)) {
+        ++partIter;
+        if (partIter == partsEnd) {
+            goto success;
+        }
+    }
+
+    if (parseKeyboard(partIter->c_str(), &config)) {
+        ++partIter;
+        if (partIter == partsEnd) {
+            goto success;
+        }
+    }
+
+    if (parseNavHidden(partIter->c_str(), &config)) {
+        ++partIter;
+        if (partIter == partsEnd) {
+            goto success;
+        }
+    }
+
+    if (parseNavigation(partIter->c_str(), &config)) {
+        ++partIter;
+        if (partIter == partsEnd) {
+            goto success;
+        }
+    }
+
+    if (parseScreenSize(partIter->c_str(), &config)) {
+        ++partIter;
+        if (partIter == partsEnd) {
+            goto success;
+        }
+    }
+
+    if (parseVersion(partIter->c_str(), &config)) {
+        ++partIter;
+        if (partIter == partsEnd) {
+            goto success;
+        }
+    }
+
+    // Unrecognized.
+    return false;
+
+success:
+    if (out != NULL) {
+        applyVersionForCompatibility(&config);
+        *out = config;
+    }
+    return true;
+}
+
+void ConfigDescription::applyVersionForCompatibility(ConfigDescription* config) {
+    uint16_t minSdk = 0;
+    if (config->density == ResTable_config::DENSITY_ANY) {
+        minSdk = SDK_LOLLIPOP;
+    } else if (config->smallestScreenWidthDp != ResTable_config::SCREENWIDTH_ANY
+            || config->screenWidthDp != ResTable_config::SCREENWIDTH_ANY
+            || config->screenHeightDp != ResTable_config::SCREENHEIGHT_ANY) {
+        minSdk = SDK_HONEYCOMB_MR2;
+    } else if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE)
+                != ResTable_config::UI_MODE_TYPE_ANY
+            ||  (config->uiMode & ResTable_config::MASK_UI_MODE_NIGHT)
+                != ResTable_config::UI_MODE_NIGHT_ANY) {
+        minSdk = SDK_FROYO;
+    } else if ((config->screenLayout & ResTable_config::MASK_SCREENSIZE)
+                != ResTable_config::SCREENSIZE_ANY
+            ||  (config->screenLayout & ResTable_config::MASK_SCREENLONG)
+                != ResTable_config::SCREENLONG_ANY
+            || config->density != ResTable_config::DENSITY_DEFAULT) {
+        minSdk = SDK_DONUT;
+    }
+
+    if (minSdk > config->sdkVersion) {
+        config->sdkVersion = minSdk;
+    }
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ConfigDescription.h b/tools/aapt2/ConfigDescription.h
new file mode 100644
index 0000000..67b4b75
--- /dev/null
+++ b/tools/aapt2/ConfigDescription.h
@@ -0,0 +1,129 @@
+/*
+ * 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_CONFIG_DESCRIPTION_H
+#define AAPT_CONFIG_DESCRIPTION_H
+
+#include "StringPiece.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <ostream>
+
+namespace aapt {
+
+/*
+ * Subclass of ResTable_config that adds convenient
+ * initialization and comparison methods.
+ */
+struct ConfigDescription : public android::ResTable_config {
+    /*
+     * Parse a string of the form 'fr-sw600dp-land' and fill in the
+     * given ResTable_config with resulting configuration parameters.
+     *
+     * The resulting configuration has the appropriate sdkVersion defined
+     * for backwards compatibility.
+     */
+    static bool parse(const StringPiece& str, ConfigDescription* out = nullptr);
+
+    /**
+     * If the configuration uses an axis that was added after
+     * the original Android release, make sure the SDK version
+     * is set accordingly.
+     */
+    static void applyVersionForCompatibility(ConfigDescription* config);
+
+    ConfigDescription();
+    ConfigDescription(const android::ResTable_config& o);
+    ConfigDescription(const ConfigDescription& o);
+    ConfigDescription(ConfigDescription&& o);
+
+    ConfigDescription& operator=(const android::ResTable_config& o);
+    ConfigDescription& operator=(const ConfigDescription& o);
+    ConfigDescription& operator=(ConfigDescription&& o);
+
+    bool operator<(const ConfigDescription& o) const;
+    bool operator<=(const ConfigDescription& o) const;
+    bool operator==(const ConfigDescription& o) const;
+    bool operator!=(const ConfigDescription& o) const;
+    bool operator>=(const ConfigDescription& o) const;
+    bool operator>(const ConfigDescription& o) const;
+};
+
+inline ConfigDescription::ConfigDescription() {
+    memset(this, 0, sizeof(*this));
+    size = sizeof(android::ResTable_config);
+}
+
+inline ConfigDescription::ConfigDescription(const android::ResTable_config& o) {
+    *static_cast<android::ResTable_config*>(this) = o;
+    size = sizeof(android::ResTable_config);
+}
+
+inline ConfigDescription::ConfigDescription(const ConfigDescription& o) {
+    *static_cast<android::ResTable_config*>(this) = o;
+}
+
+inline ConfigDescription::ConfigDescription(ConfigDescription&& o) {
+    *this = o;
+}
+
+inline ConfigDescription& ConfigDescription::operator=(const android::ResTable_config& o) {
+    *static_cast<android::ResTable_config*>(this) = o;
+    size = sizeof(android::ResTable_config);
+    return *this;
+}
+
+inline ConfigDescription& ConfigDescription::operator=(const ConfigDescription& o) {
+    *static_cast<android::ResTable_config*>(this) = o;
+    return *this;
+}
+
+inline ConfigDescription& ConfigDescription::operator=(ConfigDescription&& o) {
+    *this = o;
+    return *this;
+}
+
+inline bool ConfigDescription::operator<(const ConfigDescription& o) const {
+    return compare(o) < 0;
+}
+
+inline bool ConfigDescription::operator<=(const ConfigDescription& o) const {
+    return compare(o) <= 0;
+}
+
+inline bool ConfigDescription::operator==(const ConfigDescription& o) const {
+    return compare(o) == 0;
+}
+
+inline bool ConfigDescription::operator!=(const ConfigDescription& o) const {
+    return compare(o) != 0;
+}
+
+inline bool ConfigDescription::operator>=(const ConfigDescription& o) const {
+    return compare(o) >= 0;
+}
+
+inline bool ConfigDescription::operator>(const ConfigDescription& o) const {
+    return compare(o) > 0;
+}
+
+inline ::std::ostream& operator<<(::std::ostream& out, const ConfigDescription& o) {
+    return out << o.toString().string();
+}
+
+} // namespace aapt
+
+#endif // AAPT_CONFIG_DESCRIPTION_H
diff --git a/tools/aapt2/ConfigDescription_test.cpp b/tools/aapt2/ConfigDescription_test.cpp
new file mode 100644
index 0000000..c57e351
--- /dev/null
+++ b/tools/aapt2/ConfigDescription_test.cpp
@@ -0,0 +1,82 @@
+/*
+ * 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 "StringPiece.h"
+
+#include <gtest/gtest.h>
+#include <string>
+
+namespace aapt {
+
+static ::testing::AssertionResult TestParse(const StringPiece& input,
+                                            ConfigDescription* config = nullptr) {
+    if (ConfigDescription::parse(input, config)) {
+        return ::testing::AssertionSuccess() << input << " was successfully parsed";
+    }
+    return ::testing::AssertionFailure() << input << " could not be parsed";
+}
+
+TEST(ConfigDescriptionTest, ParseFailWhenQualifiersAreOutOfOrder) {
+    EXPECT_FALSE(TestParse("en-sw600dp-ldrtl"));
+    EXPECT_FALSE(TestParse("land-en"));
+    EXPECT_FALSE(TestParse("hdpi-320dpi"));
+}
+
+TEST(ConfigDescriptionTest, ParseFailWhenQualifiersAreNotMatched) {
+    EXPECT_FALSE(TestParse("en-sw600dp-ILLEGAL"));
+}
+
+TEST(ConfigDescriptionTest, ParseFailWhenQualifiersHaveTrailingDash) {
+    EXPECT_FALSE(TestParse("en-sw600dp-land-"));
+}
+
+TEST(ConfigDescriptionTest, ParseBasicQualifiers) {
+    ConfigDescription config;
+    EXPECT_TRUE(TestParse("", &config));
+    EXPECT_EQ(std::string(""), config.toString().string());
+
+    EXPECT_TRUE(TestParse("fr-land", &config));
+    EXPECT_EQ(std::string("fr-land"), config.toString().string());
+
+    EXPECT_TRUE(TestParse("mcc310-pl-sw720dp-normal-long-port-night-"
+                "xhdpi-keyssoft-qwerty-navexposed-nonav", &config));
+    EXPECT_EQ(std::string("mcc310-pl-sw720dp-normal-long-port-night-"
+                "xhdpi-keyssoft-qwerty-navexposed-nonav-v13"), config.toString().string());
+}
+
+TEST(ConfigDescriptionTest, ParseLocales) {
+    ConfigDescription config;
+    EXPECT_TRUE(TestParse("en-rUS", &config));
+    EXPECT_EQ(std::string("en-rUS"), config.toString().string());
+}
+
+TEST(ConfigDescriptionTest, ParseQualifierAddedInApi13) {
+    ConfigDescription config;
+    EXPECT_TRUE(TestParse("sw600dp", &config));
+    EXPECT_EQ(std::string("sw600dp-v13"), config.toString().string());
+
+    EXPECT_TRUE(TestParse("sw600dp-v8", &config));
+    EXPECT_EQ(std::string("sw600dp-v13"), config.toString().string());
+}
+
+TEST(ConfigDescriptionTest, ParseCarAttribute) {
+    ConfigDescription config;
+    EXPECT_TRUE(TestParse("car", &config));
+    EXPECT_EQ(android::ResTable_config::UI_MODE_TYPE_CAR, config.uiMode);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Files.cpp b/tools/aapt2/Files.cpp
new file mode 100644
index 0000000..c910c81
--- /dev/null
+++ b/tools/aapt2/Files.cpp
@@ -0,0 +1,168 @@
+/*
+ * 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 "Files.h"
+#include "Util.h"
+
+#include <cerrno>
+#include <dirent.h>
+#include <string>
+#include <sys/stat.h>
+
+namespace aapt {
+
+FileType getFileType(const StringPiece& path) {
+    struct stat sb;
+    if (stat(path.data(), &sb) < 0) {
+        if (errno == ENOENT || errno == ENOTDIR) {
+            return FileType::kNonexistant;
+        }
+        return FileType::kUnknown;
+    }
+
+    if (S_ISREG(sb.st_mode)) {
+        return FileType::kRegular;
+    } else if (S_ISDIR(sb.st_mode)) {
+        return FileType::kDirectory;
+    } else if (S_ISCHR(sb.st_mode)) {
+        return FileType::kCharDev;
+    } else if (S_ISBLK(sb.st_mode)) {
+        return FileType::kBlockDev;
+    } else if (S_ISFIFO(sb.st_mode)) {
+        return FileType::kFifo;
+    } else if (S_ISLNK(sb.st_mode)) {
+        return FileType::kSymlink;
+    } else if (S_ISSOCK(sb.st_mode)) {
+        return FileType::kSocket;
+    } else {
+        return FileType::kUnknown;
+    }
+}
+
+std::vector<std::string> listFiles(const StringPiece& root) {
+    DIR* dir = opendir(root.data());
+    if (dir == nullptr) {
+        Logger::error(Source{ root.toString() })
+            << "unable to open file: "
+            << strerror(errno)
+            << "."
+            << std::endl;
+        return {};
+    }
+
+    std::vector<std::string> files;
+    dirent* entry;
+    while ((entry = readdir(dir))) {
+        files.emplace_back(entry->d_name);
+    }
+
+    closedir(dir);
+    return files;
+}
+
+inline static int mkdirImpl(const StringPiece& path) {
+#ifdef HAVE_MS_C_RUNTIME
+    return _mkdir(path.toString().c_str());
+#else
+    return mkdir(path.toString().c_str(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP);
+#endif
+}
+
+bool mkdirs(const StringPiece& path) {
+    const char* start = path.begin();
+    const char* end = path.end();
+    for (const char* current = start; current != end; ++current) {
+        if (*current == sDirSep) {
+            StringPiece parentPath(start, current - start);
+            int result = mkdirImpl(parentPath);
+            if (result < 0 && errno != EEXIST) {
+                return false;
+            }
+        }
+    }
+    return mkdirImpl(path) == 0 || errno == EEXIST;
+}
+
+bool FileFilter::setPattern(const StringPiece& pattern) {
+    mPatternTokens = util::splitAndLowercase(pattern, ':');
+    return true;
+}
+
+bool FileFilter::operator()(const std::string& filename, FileType type) const {
+    if (filename == "." || filename == "..") {
+        return false;
+    }
+
+    const char kDir[] = "dir";
+    const char kFile[] = "file";
+    const size_t filenameLen = filename.length();
+    bool chatty = true;
+    for (const std::string& token : mPatternTokens) {
+        const char* tokenStr = token.c_str();
+        if (*tokenStr == '!') {
+            chatty = false;
+            tokenStr++;
+        }
+
+        if (strncasecmp(tokenStr, kDir, sizeof(kDir)) == 0) {
+            if (type != FileType::kDirectory) {
+                continue;
+            }
+            tokenStr += sizeof(kDir);
+        }
+
+        if (strncasecmp(tokenStr, kFile, sizeof(kFile)) == 0) {
+            if (type != FileType::kRegular) {
+                continue;
+            }
+            tokenStr += sizeof(kFile);
+        }
+
+        bool ignore = false;
+        size_t n = strlen(tokenStr);
+        if (*tokenStr == '*') {
+            // Math suffix.
+            tokenStr++;
+            n--;
+            if (n <= filenameLen) {
+                ignore = strncasecmp(tokenStr, filename.c_str() + filenameLen - n, n) == 0;
+            }
+        } else if (n > 1 && tokenStr[n - 1] == '*') {
+            // Match prefix.
+            ignore = strncasecmp(tokenStr, filename.c_str(), n - 1) == 0;
+        } else {
+            ignore = strcasecmp(tokenStr, filename.c_str()) == 0;
+        }
+
+        if (ignore) {
+            if (chatty) {
+                Logger::warn()
+                    << "skipping " <<
+                    (type == FileType::kDirectory ? "dir '" : "file '")
+                    << filename
+                    << "' due to ignore pattern '"
+                    << token
+                    << "'."
+                    << std::endl;
+            }
+            return false;
+        }
+    }
+    return true;
+}
+
+
+} // namespace aapt
diff --git a/tools/aapt2/Files.h b/tools/aapt2/Files.h
new file mode 100644
index 0000000..e5e196e
--- /dev/null
+++ b/tools/aapt2/Files.h
@@ -0,0 +1,122 @@
+/*
+ * 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_FILES_H
+#define AAPT_FILES_H
+
+#include "Logger.h"
+#include "Source.h"
+#include "StringPiece.h"
+
+#include <string>
+#include <vector>
+
+namespace aapt {
+
+#ifdef _WIN32
+constexpr const char sDirSep = '\\';
+#else
+constexpr const char sDirSep = '/';
+#endif
+
+enum class FileType {
+    kUnknown = 0,
+    kNonexistant,
+    kRegular,
+    kDirectory,
+    kCharDev,
+    kBlockDev,
+    kFifo,
+    kSymlink,
+    kSocket,
+};
+
+FileType getFileType(const StringPiece& path);
+
+/*
+ * Lists files under the directory `root`. Files are listed
+ * with just their leaf (filename) names.
+ */
+std::vector<std::string> listFiles(const StringPiece& root);
+
+/*
+ * Appends a path to `base`, separated by the directory separator.
+ */
+void appendPath(std::string* base, const StringPiece& part);
+
+/*
+ * Appends a series of paths to `base`, separated by the
+ * system directory separator.
+ */
+template <typename... Ts >
+void appendPath(std::string* base, const StringPiece& part, const Ts&... parts);
+
+/*
+ * Makes all the directories in `path`. The last element in the path
+ * is interpreted as a directory.
+ */
+bool mkdirs(const StringPiece& path);
+
+/*
+ * Filter that determines which resource files/directories are
+ * processed by AAPT. Takes a pattern string supplied by the user.
+ * Pattern format is specified in the
+ * FileFilter::setPattern(const std::string&) method.
+ */
+class FileFilter {
+public:
+    /*
+     * Patterns syntax:
+     * - Delimiter is :
+     * - Entry can start with the flag ! to avoid printing a warning
+     *   about the file being ignored.
+     * - Entry can have the flag "<dir>" to match only directories
+     *   or <file> to match only files. Default is to match both.
+     * - Entry can be a simplified glob "<prefix>*" or "*<suffix>"
+     *   where prefix/suffix must have at least 1 character (so that
+     *   we don't match a '*' catch-all pattern.)
+     * - The special filenames "." and ".." are always ignored.
+     * - Otherwise the full string is matched.
+     * - match is not case-sensitive.
+     */
+    bool setPattern(const StringPiece& pattern);
+
+    /**
+     * Applies the filter, returning true for pass, false for fail.
+     */
+    bool operator()(const std::string& filename, FileType type) const;
+
+private:
+    std::vector<std::string> mPatternTokens;
+};
+
+inline void appendPath(std::string* base, const StringPiece& part) {
+    assert(base);
+    *base += sDirSep;
+    base->append(part.data(), part.size());
+}
+
+template <typename... Ts >
+void appendPath(std::string* base, const StringPiece& part, const Ts&... parts) {
+    assert(base);
+    *base += sDirSep;
+    base->append(part.data(), part.size());
+    appendPath(base, parts...);
+}
+
+} // namespace aapt
+
+#endif // AAPT_FILES_H
diff --git a/tools/aapt2/JavaClassGenerator.cpp b/tools/aapt2/JavaClassGenerator.cpp
new file mode 100644
index 0000000..7ec2848
--- /dev/null
+++ b/tools/aapt2/JavaClassGenerator.cpp
@@ -0,0 +1,189 @@
+/*
+ * 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 "JavaClassGenerator.h"
+#include "Resource.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "StringPiece.h"
+
+#include <ostream>
+#include <set>
+#include <sstream>
+#include <tuple>
+
+namespace aapt {
+
+// The number of attributes to emit per line in a Styleable array.
+constexpr size_t kAttribsPerLine = 4;
+
+JavaClassGenerator::JavaClassGenerator(std::shared_ptr<const ResourceTable> table,
+                                       Options 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 const std::set<StringPiece16> sJavaIdentifiers = {
+    u"abstract", u"assert", u"boolean", u"break", u"byte",
+    u"case", u"catch", u"char", u"class", u"const", u"continue",
+    u"default", u"do", u"double", u"else", u"enum", u"extends",
+    u"final", u"finally", u"float", u"for", u"goto", u"if",
+    u"implements", u"import", u"instanceof", u"int", u"interface",
+    u"long", u"native", u"new", u"package", u"private", u"protected",
+    u"public", u"return", u"short", u"static", u"strictfp", u"super",
+    u"switch", u"synchronized", u"this", u"throw", u"throws",
+    u"transient", u"try", u"void", u"volatile", u"while", u"true",
+    u"false", u"null"
+};
+
+static bool isValidSymbol(const StringPiece16& symbol) {
+    return sJavaIdentifiers.find(symbol) == sJavaIdentifiers.end();
+}
+
+/*
+ * Java symbols can not contain . or -, but those are valid in a resource name.
+ * Replace those with '_'.
+ */
+static std::u16string transform(const StringPiece16& symbol) {
+    std::u16string output = symbol.toString();
+    for (char16_t& c : output) {
+        if (c == u'.' || c == u'-') {
+            c = u'_';
+        }
+    }
+    return output;
+}
+
+bool JavaClassGenerator::generateType(std::ostream& out, const ResourceTableType& type,
+                                      size_t packageId) {
+    const StringPiece finalModifier = mOptions.useFinal ? " final" : "";
+
+    for (const auto& entry : type.entries) {
+        ResourceId id = { packageId, type.typeId, entry->entryId };
+        assert(id.isValid());
+
+        if (!isValidSymbol(entry->name)) {
+            mError = (std::stringstream()
+                    << "invalid symbol name '"
+                    << StringPiece16(entry->name)
+                    << "'").str();
+            return false;
+        }
+
+        out << "        "
+            << "public static" << finalModifier
+            << " int " << transform(entry->name) << " = " << id << ";" << std::endl;
+    }
+    return true;
+}
+
+struct GenArgs : ValueVisitorArgs {
+    GenArgs(std::ostream& o, const ResourceEntry& e) : out(o), entry(e) {
+    }
+
+    std::ostream& out;
+    const ResourceEntry& entry;
+};
+
+void JavaClassGenerator::visit(const Styleable& styleable, ValueVisitorArgs& a) {
+    const StringPiece finalModifier = mOptions.useFinal ? " final" : "";
+    std::ostream& out = static_cast<GenArgs&>(a).out;
+    const ResourceEntry& entry = static_cast<GenArgs&>(a).entry;
+
+    // This must be sorted by resource ID.
+    std::vector<std::pair<ResourceId, StringPiece16>> sortedAttributes;
+    sortedAttributes.reserve(styleable.entries.size());
+    for (const auto& attr : styleable.entries) {
+        assert(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.entry);
+    }
+    std::sort(sortedAttributes.begin(), sortedAttributes.end());
+
+    // First we emit the array containing the IDs of each attribute.
+    out << "        "
+        << "public static final int[] " << transform(entry.name) << " = {";
+
+    const size_t attrCount = sortedAttributes.size();
+    for (size_t i = 0; i < attrCount; i++) {
+        if (i % kAttribsPerLine == 0) {
+            out << std::endl << "            ";
+        }
+
+        out << sortedAttributes[i].first;
+        if (i != attrCount - 1) {
+            out << ", ";
+        }
+    }
+    out << std::endl << "        };" << std::endl;
+
+    // Now we emit the indices into the array.
+    for (size_t i = 0; i < attrCount; i++) {
+        out << "        "
+            << "public static" << finalModifier
+            << " int " << transform(entry.name) << "_" << transform(sortedAttributes[i].second)
+            << " = " << i << ";" << std::endl;
+    }
+}
+
+bool JavaClassGenerator::generate(std::ostream& out) {
+    const size_t packageId = mTable->getPackageId();
+
+    generateHeader(out, mTable->getPackage());
+
+    out << "public final class R {" << std::endl;
+
+    for (const auto& type : *mTable) {
+        out << "    public static final class " << type->type << " {" << std::endl;
+        bool result;
+        if (type->type == ResourceType::kStyleable) {
+            for (const auto& entry : type->entries) {
+                assert(!entry->values.empty());
+                if (!isValidSymbol(entry->name)) {
+                    mError = (std::stringstream()
+                            << "invalid symbol name '"
+                            << StringPiece16(entry->name)
+                            << "'").str();
+                    return false;
+                }
+                entry->values.front().value->accept(*this, GenArgs{ out, *entry });
+            }
+        } else {
+            result = generateType(out, *type, packageId);
+        }
+
+        if (!result) {
+            return false;
+        }
+        out << "    }" << std::endl;
+    }
+
+    out << "}" << std::endl;
+    return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/JavaClassGenerator.h b/tools/aapt2/JavaClassGenerator.h
new file mode 100644
index 0000000..5b8e500
--- /dev/null
+++ b/tools/aapt2/JavaClassGenerator.h
@@ -0,0 +1,72 @@
+/*
+ * 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_JAVA_CLASS_GENERATOR_H
+#define AAPT_JAVA_CLASS_GENERATOR_H
+
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+
+#include <ostream>
+#include <string>
+
+namespace aapt {
+
+/*
+ * Generates the R.java file for a resource table.
+ */
+class JavaClassGenerator : ConstValueVisitor {
+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(std::shared_ptr<const ResourceTable> table, Options options);
+
+    /*
+     * Writes the R.java file to `out`. Returns true on success.
+     */
+    bool generate(std::ostream& out);
+
+    /*
+     * ConstValueVisitor implementation.
+     */
+    void visit(const Styleable& styleable, ValueVisitorArgs& args);
+
+    const std::string& getError() const;
+
+private:
+    bool generateType(std::ostream& out, const ResourceTableType& type, size_t packageId);
+
+    std::shared_ptr<const ResourceTable> mTable;
+    Options mOptions;
+    std::string mError;
+};
+
+inline const std::string& JavaClassGenerator::getError() const {
+    return mError;
+}
+
+} // namespace aapt
+
+#endif // AAPT_JAVA_CLASS_GENERATOR_H
diff --git a/tools/aapt2/JavaClassGenerator_test.cpp b/tools/aapt2/JavaClassGenerator_test.cpp
new file mode 100644
index 0000000..32050e3
--- /dev/null
+++ b/tools/aapt2/JavaClassGenerator_test.cpp
@@ -0,0 +1,85 @@
+/*
+ * 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 "JavaClassGenerator.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "Util.h"
+
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+
+namespace aapt {
+
+struct JavaClassGeneratorTest : public ::testing::Test {
+    virtual void SetUp() override {
+        mTable = std::make_shared<ResourceTable>();
+        mTable->setPackage(u"android");
+        mTable->setPackageId(0x01);
+    }
+
+    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, {});
+
+    std::stringstream out;
+    EXPECT_FALSE(generator.generate(out));
+}
+
+TEST_F(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) {
+    ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"hey-man" },
+                            ResourceId{ 0x01, 0x02, 0x0000 }));
+
+    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, {});
+
+    std::stringstream out;
+    EXPECT_TRUE(generator.generate(out));
+    std::string output = out.str();
+
+    EXPECT_NE(std::string::npos,
+              output.find("public static final int hey_man = 0x01020000;"));
+
+    EXPECT_NE(std::string::npos,
+              output.find("public static final int[] hey_dude = {"));
+
+    EXPECT_NE(std::string::npos,
+              output.find("public static final int hey_dude_cool_attr = 0;"));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Linker.cpp b/tools/aapt2/Linker.cpp
new file mode 100644
index 0000000..a863197
--- /dev/null
+++ b/tools/aapt2/Linker.cpp
@@ -0,0 +1,282 @@
+/*
+ * 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 "ResourceParser.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "StringPiece.h"
+#include "Util.h"
+
+#include <androidfw/AssetManager.h>
+#include <array>
+#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(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver) :
+        mTable(table), mResolver(resolver), mError(false) {
+}
+
+bool Linker::linkAndValidate() {
+    std::bitset<256> usedTypeIds;
+    std::array<std::set<uint16_t>, 256> usedIds;
+    usedTypeIds.set(0);
+
+    // First build the graph of references.
+    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);
+            }
+
+            for (auto& valueConfig : entry->values) {
+                // Dispatch to the right method of this linker
+                // based on the value's type.
+                valueConfig.value->accept(*this, Args{
+                        ResourceNameRef{ mTable->getPackage(), type->type, entry->name },
+                        valueConfig.source
+                });
+            }
+        }
+    }
+
+    /*
+     * Assign resource IDs that are available.
+     */
+    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++;
+
+                // Update callers of this resource with the right ID.
+                auto callersIter = mGraph.find(ResourceNameRef{
+                        mTable->getPackage(),
+                        type->type,
+                        entry->name
+                });
+
+                if (callersIter != std::end(mGraph)) {
+                    for (Node& caller : callersIter->second) {
+                        caller.reference->id = ResourceId(mTable->getPackageId(),
+                                                          type->typeId,
+                                                          entry->entryId);
+                    }
+                }
+            }
+        }
+    }
+
+    return !mError;
+}
+
+const Linker::ResourceNameToSourceMap& Linker::getUnresolvedReferences() const {
+    return mUnresolvedSymbols;
+}
+
+void Linker::visit(Reference& reference, ValueVisitorArgs& a) {
+    Args& args = static_cast<Args&>(a);
+
+    Maybe<ResourceId> result = mResolver->findId(reference.name);
+    if (!result) {
+        addUnresolvedSymbol(reference.name, args.source);
+        return;
+    }
+
+    const ResourceId& id = result.value();
+    if (id.isValid()) {
+        reference.id = id;
+    } else {
+        // We need to update the ID when it is set, so add it
+        // to the graph.
+        mGraph[reference.name].push_back(Node{
+                args.referrer,
+                args.source.path,
+                args.source.line,
+                &reference
+        });
+    }
+
+    // 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) {
+            mTable->addResource(name, ConfigDescription{},
+                    source, util::make_unique<Id>());
+        };
+
+        convertedValue = ResourceParser::parseItemForAttribute(
+                *str.value, attr, mResolver->getDefaultPackage(),
+                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()) {
+        visit(style.parent, a);
+    }
+
+    for (Style::Entry& styleEntry : style.entries) {
+        Maybe<Resolver::Entry> result = mResolver->findAttribute(styleEntry.key.name);
+        if (!result || !result.value().attr) {
+            addUnresolvedSymbol(styleEntry.key.name, args.source);
+            continue;
+        }
+
+        const Resolver::Entry& entry = result.value();
+        if (entry.id.isValid()) {
+            styleEntry.key.id = entry.id;
+        } else {
+            // Create a dependency for the style on this attribute.
+            mGraph[styleEntry.key.name].push_back(Node{
+                    args.referrer,
+                    args.source.path,
+                    args.source.line,
+                    &styleEntry.key
+            });
+        }
+        processAttributeValue(args.referrer, args.source, *entry.attr, styleEntry.value);
+    }
+}
+
+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(Sentinel& sentinel, ValueVisitorArgs& a) {
+    Args& args = static_cast<Args&>(a);
+    addUnresolvedSymbol(args.referrer, args.source);
+}
+
+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);
+}
+
+::std::ostream& operator<<(::std::ostream& out, const Linker::Node& node) {
+    return out << node.name << "(" << node.source << ":" << node.line << ")";
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Linker.h b/tools/aapt2/Linker.h
new file mode 100644
index 0000000..9b911b7
--- /dev/null
+++ b/tools/aapt2/Linker.h
@@ -0,0 +1,128 @@
+/*
+ * 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:
+    /**
+     * Create a Linker for the given resource table with the sources available in
+     * Resolver. Resolver should contain the ResourceTable as a source too.
+     */
+    Linker(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver);
+
+    Linker(const Linker&) = delete;
+
+    /**
+     * 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;
+
+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(Sentinel& sentinel, 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);
+
+    /**
+     * Node of the resource table graph.
+     */
+    struct Node {
+        // We use ResourceNameRef and StringPiece, which are safe so long as the ResourceTable
+        // that defines the data isn't modified.
+        ResourceNameRef name;
+        StringPiece source;
+        size_t line;
+
+        // The reference object that points to name.
+        Reference* reference;
+
+        bool operator<(const Node& rhs) const;
+        bool operator==(const Node& rhs) const;
+        bool operator!=(const Node& rhs) const;
+    };
+    friend ::std::ostream& operator<<(::std::ostream&, const Node&);
+
+    std::shared_ptr<ResourceTable> mTable;
+    std::shared_ptr<Resolver> mResolver;
+    std::map<ResourceNameRef, std::vector<Node>> mGraph;
+    std::map<ResourceName, std::vector<SourceLine>> mUnresolvedSymbols;
+    bool mError;
+};
+
+} // namespace aapt
+
+#endif // AAPT_LINKER_H
diff --git a/tools/aapt2/Linker_test.cpp b/tools/aapt2/Linker_test.cpp
new file mode 100644
index 0000000..b1e201b
--- /dev/null
+++ b/tools/aapt2/Linker_test.cpp
@@ -0,0 +1,143 @@
+/*
+ * 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 "Resolver.h"
+#include "ResourceTable.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");
+        mLinker = std::make_shared<Linker>(mTable, std::make_shared<Resolver>(
+                mTable, std::make_shared<android::AssetManager>()));
+
+        // 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);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Locale.cpp b/tools/aapt2/Locale.cpp
new file mode 100644
index 0000000..eed0ea7
--- /dev/null
+++ b/tools/aapt2/Locale.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 "Locale.h"
+#include "Util.h"
+
+#include <algorithm>
+#include <ctype.h>
+#include <string>
+#include <vector>
+
+namespace aapt {
+
+using android::ResTable_config;
+
+void LocaleValue::setLanguage(const char* languageChars) {
+     size_t i = 0;
+     while ((*languageChars) != '\0') {
+          language[i++] = ::tolower(*languageChars);
+          languageChars++;
+     }
+}
+
+void LocaleValue::setRegion(const char* regionChars) {
+    size_t i = 0;
+    while ((*regionChars) != '\0') {
+         region[i++] = ::toupper(*regionChars);
+         regionChars++;
+    }
+}
+
+void LocaleValue::setScript(const char* scriptChars) {
+    size_t i = 0;
+    while ((*scriptChars) != '\0') {
+         if (i == 0) {
+             script[i++] = ::toupper(*scriptChars);
+         } else {
+             script[i++] = ::tolower(*scriptChars);
+         }
+         scriptChars++;
+    }
+}
+
+void LocaleValue::setVariant(const char* variantChars) {
+     size_t i = 0;
+     while ((*variantChars) != '\0') {
+          variant[i++] = *variantChars;
+          variantChars++;
+     }
+}
+
+static inline bool isAlpha(const std::string& str) {
+    return std::all_of(std::begin(str), std::end(str), ::isalpha);
+}
+
+static inline bool isNumber(const std::string& str) {
+    return std::all_of(std::begin(str), std::end(str), ::isdigit);
+}
+
+bool LocaleValue::initFromFilterString(const std::string& str) {
+     // A locale (as specified in the filter) is an underscore separated name such
+     // as "en_US", "en_Latn_US", or "en_US_POSIX".
+     std::vector<std::string> parts = util::splitAndLowercase(str, '_');
+
+     const int numTags = parts.size();
+     bool valid = false;
+     if (numTags >= 1) {
+         const std::string& lang = parts[0];
+         if (isAlpha(lang) && (lang.length() == 2 || lang.length() == 3)) {
+             setLanguage(lang.c_str());
+             valid = true;
+         }
+     }
+
+     if (!valid || numTags == 1) {
+         return valid;
+     }
+
+     // At this point, valid == true && numTags > 1.
+     const std::string& part2 = parts[1];
+     if ((part2.length() == 2 && isAlpha(part2)) ||
+         (part2.length() == 3 && isNumber(part2))) {
+         setRegion(part2.c_str());
+     } else if (part2.length() == 4 && isAlpha(part2)) {
+         setScript(part2.c_str());
+     } else if (part2.length() >= 5 && part2.length() <= 8) {
+         setVariant(part2.c_str());
+     } else {
+         valid = false;
+     }
+
+     if (!valid || numTags == 2) {
+         return valid;
+     }
+
+     // At this point, valid == true && numTags > 1.
+     const std::string& part3 = parts[2];
+     if (((part3.length() == 2 && isAlpha(part3)) ||
+         (part3.length() == 3 && isNumber(part3))) && script[0]) {
+         setRegion(part3.c_str());
+     } else if (part3.length() >= 5 && part3.length() <= 8) {
+         setVariant(part3.c_str());
+     } else {
+         valid = false;
+     }
+
+     if (!valid || numTags == 3) {
+         return valid;
+     }
+
+     const std::string& part4 = parts[3];
+     if (part4.length() >= 5 && part4.length() <= 8) {
+         setVariant(part4.c_str());
+     } else {
+         valid = false;
+     }
+
+     if (!valid || numTags > 4) {
+         return false;
+     }
+
+     return true;
+}
+
+ssize_t LocaleValue::initFromParts(std::vector<std::string>::iterator iter,
+        std::vector<std::string>::iterator end) {
+    const std::vector<std::string>::iterator startIter = iter;
+
+    std::string& part = *iter;
+    if (part[0] == 'b' && part[1] == '+') {
+        // This is a "modified" BCP-47 language tag. Same semantics as BCP-47 tags,
+        // except that the separator is "+" and not "-".
+        std::vector<std::string> subtags = util::splitAndLowercase(part, '+');
+        subtags.erase(subtags.begin());
+        if (subtags.size() == 1) {
+            setLanguage(subtags[0].c_str());
+        } else if (subtags.size() == 2) {
+            setLanguage(subtags[0].c_str());
+
+            // The second tag can either be a region, a variant or a script.
+            switch (subtags[1].size()) {
+                case 2:
+                case 3:
+                    setRegion(subtags[1].c_str());
+                    break;
+                case 4:
+                    setScript(subtags[1].c_str());
+                    break;
+                case 5:
+                case 6:
+                case 7:
+                case 8:
+                    setVariant(subtags[1].c_str());
+                    break;
+                default:
+                    return -1;
+            }
+        } else if (subtags.size() == 3) {
+            // The language is always the first subtag.
+            setLanguage(subtags[0].c_str());
+
+            // The second subtag can either be a script or a region code.
+            // If its size is 4, it's a script code, else it's a region code.
+            if (subtags[1].size() == 4) {
+                setScript(subtags[1].c_str());
+            } else if (subtags[1].size() == 2 || subtags[1].size() == 3) {
+                setRegion(subtags[1].c_str());
+            } else {
+                return -1;
+            }
+
+            // The third tag can either be a region code (if the second tag was
+            // a script), else a variant code.
+            if (subtags[2].size() > 4) {
+                setVariant(subtags[2].c_str());
+            } else {
+                setRegion(subtags[2].c_str());
+            }
+        } else if (subtags.size() == 4) {
+            setLanguage(subtags[0].c_str());
+            setScript(subtags[1].c_str());
+            setRegion(subtags[2].c_str());
+            setVariant(subtags[3].c_str());
+        } else {
+            return -1;
+        }
+
+        ++iter;
+
+    } else {
+        if ((part.length() == 2 || part.length() == 3)
+                && isAlpha(part) && part != "car") {
+            setLanguage(part.c_str());
+            ++iter;
+
+            if (iter != end) {
+                const std::string& regionPart = *iter;
+                if (regionPart.c_str()[0] == 'r' && regionPart.length() == 3) {
+                    setRegion(regionPart.c_str() + 1);
+                    ++iter;
+                }
+            }
+        }
+    }
+
+    return static_cast<ssize_t>(iter - startIter);
+}
+
+
+std::string LocaleValue::toDirName() const {
+    std::string dirName;
+    if (language[0]) {
+        dirName += language;
+    } else {
+        return dirName;
+    }
+
+    if (script[0]) {
+        dirName += "-s";
+        dirName += script;
+    }
+
+    if (region[0]) {
+        dirName += "-r";
+        dirName += region;
+    }
+
+    if (variant[0]) {
+        dirName += "-v";
+        dirName += variant;
+    }
+
+    return dirName;
+}
+
+void LocaleValue::initFromResTable(const ResTable_config& config) {
+    config.unpackLanguage(language);
+    config.unpackRegion(region);
+    if (config.localeScript[0]) {
+        memcpy(script, config.localeScript, sizeof(config.localeScript));
+    }
+
+    if (config.localeVariant[0]) {
+        memcpy(variant, config.localeVariant, sizeof(config.localeVariant));
+    }
+}
+
+void LocaleValue::writeTo(ResTable_config* out) const {
+    out->packLanguage(language);
+    out->packRegion(region);
+
+    if (script[0]) {
+        memcpy(out->localeScript, script, sizeof(out->localeScript));
+    }
+
+    if (variant[0]) {
+        memcpy(out->localeVariant, variant, sizeof(out->localeVariant));
+    }
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Locale.h b/tools/aapt2/Locale.h
new file mode 100644
index 0000000..ceec764
--- /dev/null
+++ b/tools/aapt2/Locale.h
@@ -0,0 +1,114 @@
+/*
+ * 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_LOCALE_VALUE_H
+#define AAPT_LOCALE_VALUE_H
+
+#include <androidfw/ResourceTypes.h>
+#include <string>
+#include <vector>
+
+namespace aapt {
+
+/**
+ * A convenience class to build and parse locales.
+ */
+struct LocaleValue {
+    char language[4];
+    char region[4];
+    char script[4];
+    char variant[8];
+
+    inline LocaleValue();
+
+    /**
+     * Initialize this LocaleValue from a config string.
+     */
+    bool initFromFilterString(const std::string& config);
+
+    /**
+     * Initialize this LocaleValue from parts of a vector.
+     */
+    ssize_t initFromParts(std::vector<std::string>::iterator iter,
+            std::vector<std::string>::iterator end);
+
+    /**
+     * Initialize this LocaleValue from a ResTable_config.
+     */
+    void initFromResTable(const android::ResTable_config& config);
+
+    /**
+     * Set the locale in a ResTable_config from this LocaleValue.
+     */
+    void writeTo(android::ResTable_config* out) const;
+
+    std::string toDirName() const;
+
+    inline int compare(const LocaleValue& other) const;
+
+    inline bool operator<(const LocaleValue& o) const;
+    inline bool operator<=(const LocaleValue& o) const;
+    inline bool operator==(const LocaleValue& o) const;
+    inline bool operator!=(const LocaleValue& o) const;
+    inline bool operator>=(const LocaleValue& o) const;
+    inline bool operator>(const LocaleValue& o) const;
+
+private:
+     void setLanguage(const char* language);
+     void setRegion(const char* language);
+     void setScript(const char* script);
+     void setVariant(const char* variant);
+};
+
+//
+// Implementation
+//
+
+LocaleValue::LocaleValue() {
+    memset(this, 0, sizeof(LocaleValue));
+}
+
+int LocaleValue::compare(const LocaleValue& other) const {
+    return memcmp(this, &other, sizeof(LocaleValue));
+}
+
+bool LocaleValue::operator<(const LocaleValue& o) const {
+    return compare(o) < 0;
+}
+
+bool LocaleValue::operator<=(const LocaleValue& o) const {
+    return compare(o) <= 0;
+}
+
+bool LocaleValue::operator==(const LocaleValue& o) const {
+    return compare(o) == 0;
+}
+
+bool LocaleValue::operator!=(const LocaleValue& o) const {
+    return compare(o) != 0;
+}
+
+bool LocaleValue::operator>=(const LocaleValue& o) const {
+    return compare(o) >= 0;
+}
+
+bool LocaleValue::operator>(const LocaleValue& o) const {
+    return compare(o) > 0;
+}
+
+} // namespace aapt
+
+#endif // AAPT_LOCALE_VALUE_H
diff --git a/tools/aapt2/Locale_test.cpp b/tools/aapt2/Locale_test.cpp
new file mode 100644
index 0000000..4e154d6
--- /dev/null
+++ b/tools/aapt2/Locale_test.cpp
@@ -0,0 +1,82 @@
+/*
+ * 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 "Locale.h"
+#include "Util.h"
+
+#include <gtest/gtest.h>
+#include <string>
+
+namespace aapt {
+
+static ::testing::AssertionResult TestLanguage(const char* input, const char* lang) {
+    std::vector<std::string> parts = util::splitAndLowercase(std::string(input), '-');
+    LocaleValue lv;
+    ssize_t count = lv.initFromParts(std::begin(parts), std::end(parts));
+    if (count < 0) {
+        return ::testing::AssertionFailure() << " failed to parse '" << input << "'.";
+    }
+
+    if (count != 1) {
+        return ::testing::AssertionFailure() << count
+            << " parts were consumed parsing '" << input << "' but expected 1.";
+    }
+
+    if (memcmp(lv.language, lang, std::min(strlen(lang), sizeof(lv.language))) != 0) {
+        return ::testing::AssertionFailure() << "expected " << lang << " but got "
+            << std::string(lv.language, sizeof(lv.language)) << ".";
+    }
+
+    return ::testing::AssertionSuccess();
+}
+
+static ::testing::AssertionResult TestLanguageRegion(const char* input, const char* lang,
+                                                     const char* region) {
+    std::vector<std::string> parts = util::splitAndLowercase(std::string(input), '-');
+    LocaleValue lv;
+    ssize_t count = lv.initFromParts(std::begin(parts), std::end(parts));
+    if (count < 0) {
+        return ::testing::AssertionFailure() << " failed to parse '" << input << "'.";
+    }
+
+    if (count != 2) {
+        return ::testing::AssertionFailure() << count
+            << " parts were consumed parsing '" << input << "' but expected 2.";
+    }
+
+    if (memcmp(lv.language, lang, std::min(strlen(lang), sizeof(lv.language))) != 0) {
+        return ::testing::AssertionFailure() << "expected " << input << " but got "
+            << std::string(lv.language, sizeof(lv.language)) << ".";
+    }
+
+    if (memcmp(lv.region, region, std::min(strlen(region), sizeof(lv.region))) != 0) {
+        return ::testing::AssertionFailure() << "expected " << region << " but got "
+            << std::string(lv.region, sizeof(lv.region)) << ".";
+    }
+
+    return ::testing::AssertionSuccess();
+}
+
+TEST(ConfigDescriptionTest, ParseLanguage) {
+    EXPECT_TRUE(TestLanguage("en", "en"));
+    EXPECT_TRUE(TestLanguage("fr", "fr"));
+    EXPECT_FALSE(TestLanguage("land", ""));
+    EXPECT_TRUE(TestLanguage("fr-land", "fr"));
+
+    EXPECT_TRUE(TestLanguageRegion("fr-rCA", "fr", "CA"));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Logger.cpp b/tools/aapt2/Logger.cpp
new file mode 100644
index 0000000..3847185
--- /dev/null
+++ b/tools/aapt2/Logger.cpp
@@ -0,0 +1,97 @@
+/*
+ * 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
new file mode 100644
index 0000000..1d437eb
--- /dev/null
+++ b/tools/aapt2/Logger.h
@@ -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.
+ */
+
+#ifndef AAPT_LOGGER_H
+#define AAPT_LOGGER_H
+
+#include "Source.h"
+
+#include <memory>
+#include <ostream>
+#include <string>
+#include <utils/String8.h>
+
+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;
+};
+
+inline ::std::ostream& operator<<(::std::ostream& out, const std::u16string& str) {
+    android::String8 utf8(str.data(), str.size());
+    return out.write(utf8.string(), utf8.size());
+}
+
+} // namespace aapt
+
+#endif // AAPT_LOGGER_H
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
new file mode 100644
index 0000000..f4e80c5
--- /dev/null
+++ b/tools/aapt2/Main.cpp
@@ -0,0 +1,1421 @@
+/*
+ * 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 "BigBuffer.h"
+#include "BinaryResourceParser.h"
+#include "Files.h"
+#include "JavaClassGenerator.h"
+#include "Linker.h"
+#include "ManifestParser.h"
+#include "ManifestValidator.h"
+#include "ResourceParser.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "SdkConstants.h"
+#include "SourceXmlPullParser.h"
+#include "StringPiece.h"
+#include "TableFlattener.h"
+#include "Util.h"
+#include "XmlFlattener.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>
+
+using namespace aapt;
+
+void printTable(const ResourceTable& table) {
+    std::cout << "ResourceTable package=" << table.getPackage();
+    if (table.getPackageId() != ResourceTable::kUnsetPackageId) {
+        std::cout << " id=" << std::hex << table.getPackageId() << std::dec;
+    }
+    std::cout << std::endl
+         << "---------------------------------------------------------" << std::endl;
+
+    for (const auto& type : table) {
+        std::cout << "Type " << type->type;
+        if (type->typeId != ResourceTableType::kUnsetTypeId) {
+            std::cout << " [" << type->typeId << "]";
+        }
+        std::cout << " (" << type->entries.size() << " entries)" << std::endl;
+        for (const auto& entry : type->entries) {
+            std::cout << "  " << entry->name;
+            if (entry->entryId != ResourceEntry::kUnsetEntryId) {
+                std::cout << " [" << entry->entryId << "]";
+            }
+            std::cout << " (" << entry->values.size() << " configurations)";
+            if (entry->publicStatus.isPublic) {
+                std::cout << " PUBLIC";
+            }
+            std::cout << std::endl;
+            for (const auto& value : entry->values) {
+                std::cout << "    " << value.config << " (" << value.source << ") : ";
+                value.value->print(std::cout);
+                std::cout << std::endl;
+            }
+        }
+    }
+}
+
+void printStringPool(const StringPool& pool) {
+    std::cout << "String pool of length " << pool.size() << std::endl
+         << "---------------------------------------------------------" << std::endl;
+
+    size_t i = 0;
+    for (const auto& entry : pool) {
+        std::cout << "[" << i << "]: "
+             << entry->value
+             << " (Priority " << entry->context.priority
+             << ", Config '" << entry->context.config << "')"
+             << std::endl;
+        i++;
+    }
+}
+
+std::unique_ptr<FileReference> makeFileReference(StringPool& pool, const StringPiece& filename,
+        ResourceType type, const ConfigDescription& config) {
+    std::stringstream path;
+    path << "res/" << type;
+    if (config != ConfigDescription{}) {
+        path << "-" << config;
+    }
+    path << "/" << filename;
+    return util::make_unique<FileReference>(pool.makeRef(util::utf8ToUtf16(path.str())));
+}
+
+/**
+ * Collect files from 'root', filtering out any files that do not
+ * match the FileFilter 'filter'.
+ */
+bool walkTree(const StringPiece& root, const FileFilter& filter,
+        std::vector<Source>& outEntries) {
+    bool error = false;
+
+    for (const std::string& dirName : listFiles(root)) {
+        std::string dir(root.toString());
+        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.emplace_back(Source{ file });
+        }
+    }
+    return !error;
+}
+
+bool loadBinaryResourceTable(std::shared_ptr<ResourceTable> table, const Source& source) {
+    std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
+    if (!ifs) {
+        Logger::error(source) << strerror(errno) << std::endl;
+        return false;
+    }
+
+    std::streampos fsize = ifs.tellg();
+    ifs.seekg(0, std::ios::end);
+    fsize = ifs.tellg() - fsize;
+    ifs.seekg(0, std::ios::beg);
+
+    assert(fsize >= 0);
+    size_t dataSize = static_cast<size_t>(fsize);
+    char* buf = new char[dataSize];
+    ifs.read(buf, dataSize);
+
+    BinaryResourceParser parser(table, source, buf, dataSize);
+    bool result = parser.parse();
+
+    delete [] buf;
+    return result;
+}
+
+bool loadResTable(android::ResTable* table, const Source& source) {
+    std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
+    if (!ifs) {
+        Logger::error(source) << strerror(errno) << std::endl;
+        return false;
+    }
+
+    std::streampos fsize = ifs.tellg();
+    ifs.seekg(0, std::ios::end);
+    fsize = ifs.tellg() - fsize;
+    ifs.seekg(0, std::ios::beg);
+
+    assert(fsize >= 0);
+    size_t dataSize = static_cast<size_t>(fsize);
+    char* buf = new char[dataSize];
+    ifs.read(buf, dataSize);
+
+    bool result = table->add(buf, dataSize, -1, true) == android::NO_ERROR;
+
+    delete [] buf;
+    return result;
+}
+
+void versionStylesForCompat(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.entry);
+                            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())
+                        };
+
+                        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();
+            }
+        }
+    }
+}
+
+bool collectXml(std::shared_ptr<ResourceTable> table, const Source& source,
+                const ResourceName& name,
+                const ConfigDescription& config) {
+    std::ifstream in(source.path, std::ifstream::binary);
+    if (!in) {
+        Logger::error(source) << strerror(errno) << std::endl;
+        return false;
+    }
+
+    std::set<size_t> sdkLevels;
+
+    SourceXmlPullParser pullParser(in);
+    while (XmlPullParser::isGoodEvent(pullParser.next())) {
+        if (pullParser.getEvent() != XmlPullParser::Event::kStartElement) {
+            continue;
+        }
+
+        const auto endIter = pullParser.endAttributes();
+        for (auto iter = pullParser.beginAttributes(); iter != endIter; ++iter) {
+            if (iter->namespaceUri == u"http://schemas.android.com/apk/res/android") {
+                size_t sdkLevel = findAttributeSdkLevel(iter->name);
+                if (sdkLevel > 1) {
+                    sdkLevels.insert(sdkLevel);
+                }
+            }
+
+            ResourceNameRef refName;
+            bool create = false;
+            bool privateRef = false;
+            if (ResourceParser::tryParseReference(iter->value, &refName, &create, &privateRef) &&
+                    create) {
+                table->addResource(refName, {}, source.line(pullParser.getLineNumber()),
+                                   util::make_unique<Id>());
+            }
+        }
+    }
+
+    std::unique_ptr<FileReference> fileResource = makeFileReference(
+            table->getValueStringPool(),
+            util::utf16ToUtf8(name.entry) + ".xml",
+            name.type,
+            config);
+    table->addResource(name, config, source.line(0), std::move(fileResource));
+
+    for (size_t level : sdkLevels) {
+        Logger::note(source)
+                << "creating v" << level << " versioned file."
+                << std::endl;
+        ConfigDescription newConfig = config;
+        newConfig.sdkVersion = level;
+
+        std::unique_ptr<FileReference> fileResource = makeFileReference(
+                table->getValueStringPool(),
+                util::utf16ToUtf8(name.entry) + ".xml",
+                name.type,
+                newConfig);
+        table->addResource(name, newConfig, source.line(0), std::move(fileResource));
+    }
+    return true;
+}
+
+struct CompileXml {
+    Source source;
+    ResourceName name;
+    ConfigDescription config;
+};
+
+bool compileXml(std::shared_ptr<Resolver> resolver, const CompileXml& item,
+                const Source& outputSource, std::queue<CompileXml>* queue) {
+    std::ifstream in(item.source.path, std::ifstream::binary);
+    if (!in) {
+        Logger::error(item.source) << strerror(errno) << std::endl;
+        return false;
+    }
+
+    BigBuffer outBuffer(1024);
+    std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
+    XmlFlattener flattener(resolver);
+
+    // We strip attributes that do not belong in this version of the resource.
+    // Non-version qualified resources have an implicit version 1 requirement.
+    XmlFlattener::Options options = { item.config.sdkVersion ? item.config.sdkVersion : 1 };
+    Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, xmlParser, &outBuffer, options);
+    if (!minStrippedSdk) {
+        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.
+        CompileXml newWork = item;
+        newWork.config.sdkVersion = minStrippedSdk.value();
+        queue->push(newWork);
+    }
+
+    std::ofstream out(outputSource.path, std::ofstream::binary);
+    if (!out) {
+        Logger::error(outputSource) << strerror(errno) << std::endl;
+        return false;
+    }
+
+    if (!util::writeAll(out, outBuffer)) {
+        Logger::error(outputSource) << strerror(errno) << std::endl;
+        return false;
+    }
+    return true;
+}
+
+struct AaptOptions {
+    enum class Phase {
+        LegacyFull,
+        Collect,
+        Link,
+        Compile,
+    };
+
+    // The phase to process.
+    Phase phase;
+
+    // Details about the app.
+    AppInfo appInfo;
+
+    // The location of the manifest file.
+    Source manifest;
+
+    // The files to process.
+    std::vector<Source> sources;
+
+    // The libraries these files may reference.
+    std::vector<Source> libraries;
+
+    // Output directory.
+    Source output;
+
+    // Whether to generate a Java Class.
+    Maybe<Source> generateJavaClass;
+
+    // Whether to output verbose details about
+    // compilation.
+    bool verbose = false;
+};
+
+bool compileAndroidManifest(std::shared_ptr<Resolver> resolver, const AaptOptions& options) {
+    Source outSource = options.output;
+    appendPath(&outSource.path, "AndroidManifest.xml");
+
+    if (options.verbose) {
+        Logger::note(outSource) << "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;
+    }
+
+    BigBuffer outBuffer(1024);
+    std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
+    XmlFlattener flattener(resolver);
+
+    Maybe<size_t> result = flattener.flatten(options.manifest, xmlParser, &outBuffer,
+                                             XmlFlattener::Options{});
+    if (!result) {
+        return false;
+    }
+
+    std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(new uint8_t[outBuffer.size()]);
+    uint8_t* p = data.get();
+    for (const auto& b : outBuffer) {
+        memcpy(p, b.buffer.get(), b.size);
+        p += b.size;
+    }
+
+    android::ResXMLTree tree;
+    if (tree.setTo(data.get(), outBuffer.size()) != android::NO_ERROR) {
+        return false;
+    }
+
+    ManifestValidator validator(resolver->getResTable());
+    if (!validator.validate(options.manifest, &tree)) {
+        return false;
+    }
+
+    std::ofstream out(outSource.path, std::ofstream::binary);
+    if (!out) {
+        Logger::error(outSource) << strerror(errno) << std::endl;
+        return false;
+    }
+
+    if (!util::writeAll(out, outBuffer)) {
+        Logger::error(outSource) << strerror(errno) << std::endl;
+        return false;
+    }
+    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);
+}
+
+/**
+ * Parses legacy options and walks the source directories collecting
+ * files to process.
+ */
+bool prepareLegacy(std::vector<StringPiece>::const_iterator argsIter,
+        const std::vector<StringPiece>::const_iterator argsEndIter,
+        AaptOptions &options) {
+    options.phase = AaptOptions::Phase::LegacyFull;
+
+    std::vector<StringPiece> sourceDirs;
+    while (argsIter != argsEndIter) {
+        if (*argsIter == "-S") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "-S missing argument." << std::endl;
+                return false;
+            }
+            sourceDirs.push_back(*argsIter);
+        } else if (*argsIter == "-I") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "-I missing argument." << std::endl;
+                return false;
+            }
+            options.libraries.push_back(Source{ argsIter->toString() });
+        } else if (*argsIter == "-M") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "-M missing argument." << std::endl;
+                return false;
+            }
+
+            if (!options.manifest.path.empty()) {
+                Logger::error() << "multiple -M flags are not allowed." << std::endl;
+                return false;
+            }
+            options.manifest.path = argsIter->toString();
+        } else if (*argsIter == "-o") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "-o missing argument." << std::endl;
+                return false;
+            }
+            options.output = Source{ argsIter->toString() };
+        } else if (*argsIter == "-J") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "-J missing argument." << std::endl;
+                return false;
+            }
+            options.generateJavaClass = make_value<Source>(Source{ argsIter->toString() });
+        } else if (*argsIter == "-v") {
+            options.verbose = true;
+        } else {
+            Logger::error() << "unrecognized option '" << *argsIter << "'." << std::endl;
+            return false;
+        }
+
+        ++argsIter;
+    }
+
+    if (options.manifest.path.empty()) {
+        Logger::error() << "must specify manifest file with -M." << std::endl;
+        return false;
+    }
+
+    // Load the App's package name, etc.
+    if (!loadAppInfo(options.manifest, &options.appInfo)) {
+        return false;
+    }
+
+    /**
+     * Set up the file filter to ignore certain files.
+     */
+    const char* customIgnore = getenv("ANDROID_AAPT_IGNORE");
+    FileFilter fileFilter;
+    if (customIgnore && customIgnore[0]) {
+        fileFilter.setPattern(customIgnore);
+    } else {
+        fileFilter.setPattern(
+                "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~");
+    }
+
+    /*
+     * Enumerate the files in each source directory.
+     */
+    for (const StringPiece& source : sourceDirs) {
+        if (!walkTree(source, fileFilter, options.sources)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool prepareCollect(std::vector<StringPiece>::const_iterator argsIter,
+        const std::vector<StringPiece>::const_iterator argsEndIter,
+        AaptOptions& options) {
+    options.phase = AaptOptions::Phase::Collect;
+
+    while (argsIter != argsEndIter) {
+        if (*argsIter == "--package") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "--package missing argument." << std::endl;
+                return false;
+            }
+            options.appInfo.package = util::utf8ToUtf16(*argsIter);
+        } else if (*argsIter == "-o") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "-o missing argument." << std::endl;
+                return false;
+            }
+            options.output = Source{ argsIter->toString() };
+        } else if (*argsIter == "-v") {
+            options.verbose = true;
+        } else if (argsIter->data()[0] != '-') {
+            options.sources.push_back(Source{ argsIter->toString() });
+        } else {
+            Logger::error()
+                    << "unknown option '"
+                    << *argsIter
+                    << "'."
+                    << std::endl;
+            return false;
+        }
+        ++argsIter;
+    }
+    return true;
+}
+
+bool prepareLink(std::vector<StringPiece>::const_iterator argsIter,
+        const std::vector<StringPiece>::const_iterator argsEndIter,
+        AaptOptions& options) {
+    options.phase = AaptOptions::Phase::Link;
+
+    while (argsIter != argsEndIter) {
+        if (*argsIter == "--package") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "--package missing argument." << std::endl;
+                return false;
+            }
+            options.appInfo.package = util::utf8ToUtf16(*argsIter);
+        } else if (*argsIter == "-o") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "-o missing argument." << std::endl;
+                return false;
+            }
+            options.output = Source{ argsIter->toString() };
+        } else if (*argsIter == "-I") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "-I missing argument." << std::endl;
+                return false;
+            }
+            options.libraries.push_back(Source{ argsIter->toString() });
+        } else if (*argsIter == "--java") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "--java missing argument." << std::endl;
+                return false;
+            }
+            options.generateJavaClass = make_value<Source>(Source{ argsIter->toString() });
+        } else if (*argsIter == "-v") {
+            options.verbose = true;
+        } else if (argsIter->data()[0] != '-') {
+            options.sources.push_back(Source{ argsIter->toString() });
+        } else {
+            Logger::error()
+                    << "unknown option '"
+                    << *argsIter
+                    << "'."
+                    << std::endl;
+            return false;
+        }
+        ++argsIter;
+    }
+    return true;
+}
+
+bool prepareCompile(std::vector<StringPiece>::const_iterator argsIter,
+        const std::vector<StringPiece>::const_iterator argsEndIter,
+        AaptOptions& options) {
+    options.phase = AaptOptions::Phase::Compile;
+
+    while (argsIter != argsEndIter) {
+        if (*argsIter == "--package") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "--package missing argument." << std::endl;
+                return false;
+            }
+            options.appInfo.package = util::utf8ToUtf16(*argsIter);
+        } else if (*argsIter == "-o") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "-o missing argument." << std::endl;
+                return false;
+            }
+            options.output = Source{ argsIter->toString() };
+        } else if (*argsIter == "-I") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "-I missing argument." << std::endl;
+                return false;
+            }
+            options.libraries.push_back(Source{ argsIter->toString() });
+        } else if (*argsIter == "-v") {
+            options.verbose = true;
+        } else if (argsIter->data()[0] != '-') {
+            options.sources.push_back(Source{ argsIter->toString() });
+        } else {
+            Logger::error()
+                    << "unknown option '"
+                    << *argsIter
+                    << "'."
+                    << std::endl;
+            return false;
+        }
+        ++argsIter;
+    }
+    return true;
+}
+
+struct CollectValuesItem {
+    Source source;
+    ConfigDescription config;
+};
+
+bool collectValues(std::shared_ptr<ResourceTable> table, const CollectValuesItem& item) {
+    std::ifstream in(item.source.path, std::ifstream::binary);
+    if (!in) {
+        Logger::error(item.source) << strerror(errno) << std::endl;
+        return false;
+    }
+
+    std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
+    ResourceParser parser(table, item.source, item.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
+ */
+Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
+    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
+    };
+}
+
+static bool doLegacy(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
+                     const AaptOptions& options) {
+    bool error = false;
+    std::queue<CompileXml> xmlCompileQueue;
+
+    //
+    // Read values XML files and XML/PNG files.
+    // Need to parse the resource type/config/filename.
+    //
+    for (const Source& source : options.sources) {
+        Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
+        if (!maybePathData) {
+            return false;
+        }
+
+        const ResourcePathData& pathData = maybePathData.value();
+        if (pathData.resourceDir == u"values") {
+            if (options.verbose) {
+                Logger::note(source) << "collecting values..." << std::endl;
+            }
+
+            error |= !collectValues(table, CollectValuesItem{ source, pathData.config });
+            continue;
+        }
+
+        const ResourceType* type = parseResourceType(pathData.resourceDir);
+        if (!type) {
+            Logger::error(source)
+                    << "invalid resource type '"
+                    << pathData.resourceDir
+                    << "'."
+                    << std::endl;
+            return false;
+        }
+
+        ResourceName resourceName = { table->getPackage(), *type, pathData.name };
+        if (pathData.extension == "xml") {
+            if (options.verbose) {
+                Logger::note(source) << "collecting XML..." << std::endl;
+            }
+
+            error |= !collectXml(table, source, resourceName, pathData.config);
+            xmlCompileQueue.push(CompileXml{
+                    source,
+                    resourceName,
+                    pathData.config
+            });
+        } else {
+            std::unique_ptr<FileReference> fileReference = makeFileReference(
+                    table->getValueStringPool(),
+                    util::utf16ToUtf8(pathData.name) + "." + pathData.extension,
+                    *type, pathData.config);
+
+            error |= !table->addResource(resourceName, pathData.config, source.line(0),
+                                         std::move(fileReference));
+        }
+    }
+
+    if (error) {
+        return false;
+    }
+
+    versionStylesForCompat(table);
+
+    //
+    // Verify all references and data types.
+    //
+    Linker linker(table, resolver);
+    if (!linker.linkAndValidate()) {
+        Logger::error()
+                << "linking failed."
+                << std::endl;
+        return false;
+    }
+
+    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;
+    }
+
+    //
+    // Compile the XML files.
+    //
+    while (!xmlCompileQueue.empty()) {
+        const CompileXml& item = xmlCompileQueue.front();
+
+        // Create the output path from the resource name.
+        std::stringstream outputPath;
+        outputPath << item.name.type;
+        if (item.config != ConfigDescription{}) {
+            outputPath << "-" << item.config.toString();
+        }
+
+        Source outSource = options.output;
+        appendPath(&outSource.path, "res");
+        appendPath(&outSource.path, outputPath.str());
+
+        if (!mkdirs(outSource.path)) {
+            Logger::error(outSource) << strerror(errno) << std::endl;
+            return false;
+        }
+
+        appendPath(&outSource.path, util::utf16ToUtf8(item.name.entry) + ".xml");
+
+        if (options.verbose) {
+            Logger::note(outSource) << "compiling XML file." << std::endl;
+        }
+
+        error |= !compileXml(resolver, item, outSource, &xmlCompileQueue);
+        xmlCompileQueue.pop();
+    }
+
+    if (error) {
+        return false;
+    }
+
+    //
+    // Compile the AndroidManifest.xml file.
+    //
+    if (!compileAndroidManifest(resolver, options)) {
+        return false;
+    }
+
+    //
+    // Generate the Java R class.
+    //
+    if (options.generateJavaClass) {
+        Source outPath = options.generateJavaClass.value();
+        if (options.verbose) {
+            Logger::note()
+                    << "writing symbols to "
+                    << outPath
+                    << "."
+                    << std::endl;
+        }
+
+        for (std::string& part : util::split(util::utf16ToUtf8(table->getPackage()), '.')) {
+            appendPath(&outPath.path, part);
+        }
+
+        if (!mkdirs(outPath.path)) {
+            Logger::error(outPath) << strerror(errno) << std::endl;
+            return false;
+        }
+
+        appendPath(&outPath.path, "R.java");
+
+        std::ofstream fout(outPath.path);
+        if (!fout) {
+            Logger::error(outPath) << strerror(errno) << std::endl;
+            return false;
+        }
+
+        JavaClassGenerator generator(table, JavaClassGenerator::Options{});
+        if (!generator.generate(fout)) {
+            Logger::error(outPath)
+                    << generator.getError()
+                    << "."
+                    << std::endl;
+            return false;
+        }
+    }
+
+    //
+    // Flatten resource table.
+    //
+    if (table->begin() != table->end()) {
+        BigBuffer buffer(1024);
+        TableFlattener::Options tableOptions;
+        tableOptions.useExtendedChunks = false;
+        TableFlattener flattener(tableOptions);
+        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;
+        }
+
+        std::string outTable(options.output.path);
+        appendPath(&outTable, "resources.arsc");
+
+        std::ofstream fout(outTable, std::ofstream::binary);
+        if (!fout) {
+            Logger::error(Source{outTable})
+                    << strerror(errno)
+                    << "."
+                    << std::endl;
+            return false;
+        }
+
+        if (!util::writeAll(fout, buffer)) {
+            Logger::error(Source{outTable})
+                    << strerror(errno)
+                    << "."
+                    << std::endl;
+            return false;
+        }
+        fout.flush();
+    }
+    return true;
+}
+
+static bool doCollect(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
+                      const AaptOptions& options) {
+    bool error = false;
+
+    //
+    // Read values XML files and XML/PNG files.
+    // Need to parse the resource type/config/filename.
+    //
+    for (const Source& source : options.sources) {
+        Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
+        if (!maybePathData) {
+            return false;
+        }
+
+        const ResourcePathData& pathData = maybePathData.value();
+        if (pathData.resourceDir == u"values") {
+            if (options.verbose) {
+                Logger::note(source) << "collecting values..." << std::endl;
+            }
+
+            error |= !collectValues(table, CollectValuesItem{ source, pathData.config });
+            continue;
+        }
+
+        const ResourceType* type = parseResourceType(pathData.resourceDir);
+        if (!type) {
+            Logger::error(source)
+                    << "invalid resource type '"
+                    << pathData.resourceDir
+                    << "'."
+                    << std::endl;
+            return false;
+        }
+
+        ResourceName resourceName = { table->getPackage(), *type, pathData.name };
+        if (pathData.extension == "xml") {
+            if (options.verbose) {
+                Logger::note(source) << "collecting XML..." << std::endl;
+            }
+
+            error |= !collectXml(table, source, resourceName, pathData.config);
+        } else {
+            std::unique_ptr<FileReference> fileReference = makeFileReference(
+                    table->getValueStringPool(),
+                    util::utf16ToUtf8(pathData.name) + "." + pathData.extension,
+                    *type,
+                    pathData.config);
+            error |= !table->addResource(resourceName, pathData.config, source.line(0),
+                                         std::move(fileReference));
+        }
+    }
+
+    if (error) {
+        return false;
+    }
+
+    Linker linker(table, resolver);
+    if (!linker.linkAndValidate()) {
+        return false;
+    }
+
+    //
+    // Flatten resource table->
+    //
+    if (table->begin() != table->end()) {
+        BigBuffer buffer(1024);
+        TableFlattener::Options tableOptions;
+        tableOptions.useExtendedChunks = true;
+        TableFlattener flattener(tableOptions);
+        if (!flattener.flatten(&buffer, *table)) {
+            Logger::error()
+                    << "failed to flatten resource table->"
+                    << std::endl;
+            return false;
+        }
+
+        std::ofstream fout(options.output.path, std::ofstream::binary);
+        if (!fout) {
+            Logger::error(options.output)
+                    << strerror(errno)
+                    << "."
+                    << std::endl;
+            return false;
+        }
+
+        if (!util::writeAll(fout, buffer)) {
+            Logger::error(options.output)
+                    << strerror(errno)
+                    << "."
+                    << std::endl;
+            return false;
+        }
+        fout.flush();
+    }
+    return true;
+}
+
+static bool doLink(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
+                   const AaptOptions& options) {
+    bool error = false;
+
+    for (const Source& source : options.sources) {
+        error |= !loadBinaryResourceTable(table, source);
+    }
+
+    if (error) {
+        return false;
+    }
+
+    versionStylesForCompat(table);
+
+    Linker linker(table, resolver);
+    if (!linker.linkAndValidate()) {
+        return false;
+    }
+
+    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;
+    }
+
+    //
+    // Generate the Java R class.
+    //
+    if (options.generateJavaClass) {
+        Source outPath = options.generateJavaClass.value();
+        if (options.verbose) {
+            Logger::note()
+                    << "writing symbols to "
+                    << outPath
+                    << "."
+                    << std::endl;
+        }
+
+        for (std::string& part : util::split(util::utf16ToUtf8(table->getPackage()), '.')) {
+            appendPath(&outPath.path, part);
+        }
+
+        if (!mkdirs(outPath.path)) {
+            Logger::error(outPath) << strerror(errno) << std::endl;
+            return false;
+        }
+
+        appendPath(&outPath.path, "R.java");
+
+        std::ofstream fout(outPath.path);
+        if (!fout) {
+            Logger::error(outPath) << strerror(errno) << std::endl;
+            return false;
+        }
+
+        JavaClassGenerator generator(table, JavaClassGenerator::Options{});
+        if (!generator.generate(fout)) {
+            Logger::error(outPath)
+                    << generator.getError()
+                    << "."
+                    << std::endl;
+            return false;
+        }
+    }
+
+    //
+    // Flatten resource table.
+    //
+    if (table->begin() != table->end()) {
+        BigBuffer buffer(1024);
+        TableFlattener::Options tableOptions;
+        tableOptions.useExtendedChunks = false;
+        TableFlattener flattener(tableOptions);
+        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;
+        }
+
+        std::ofstream fout(options.output.path, std::ofstream::binary);
+        if (!fout) {
+            Logger::error(options.output)
+                    << strerror(errno)
+                    << "."
+                    << std::endl;
+            return false;
+        }
+
+        if (!util::writeAll(fout, buffer)) {
+            Logger::error(options.output)
+                    << strerror(errno)
+                    << "."
+                    << std::endl;
+            return false;
+        }
+        fout.flush();
+    }
+    return true;
+}
+
+static bool doCompile(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
+                      const AaptOptions& options) {
+    std::queue<CompileXml> xmlCompileQueue;
+
+    for (const Source& source : options.sources) {
+        Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
+        if (!maybePathData) {
+            return false;
+        }
+
+        ResourcePathData& pathData = maybePathData.value();
+        const ResourceType* type = parseResourceType(pathData.resourceDir);
+        if (!type) {
+            Logger::error(source)
+                    << "invalid resource type '"
+                    << pathData.resourceDir
+                    << "'."
+                    << std::endl;
+            return false;
+        }
+
+        ResourceName resourceName = { table->getPackage(), *type, pathData.name };
+        if (pathData.extension == "xml") {
+            xmlCompileQueue.push(CompileXml{
+                    source,
+                    resourceName,
+                    pathData.config
+            });
+        } else {
+            // TODO(adamlesinski): Handle images here.
+        }
+    }
+
+    bool error = false;
+    while (!xmlCompileQueue.empty()) {
+        const CompileXml& item = xmlCompileQueue.front();
+
+        // Create the output path from the resource name.
+        std::stringstream outputPath;
+        outputPath << item.name.type;
+        if (item.config != ConfigDescription{}) {
+            outputPath << "-" << item.config.toString();
+        }
+
+        Source outSource = options.output;
+        appendPath(&outSource.path, "res");
+        appendPath(&outSource.path, outputPath.str());
+
+        if (!mkdirs(outSource.path)) {
+            Logger::error(outSource) << strerror(errno) << std::endl;
+            return false;
+        }
+
+        appendPath(&outSource.path, util::utf16ToUtf8(item.name.entry) + ".xml");
+
+        if (options.verbose) {
+            Logger::note(outSource) << "compiling XML file." << std::endl;
+        }
+
+        error |= !compileXml(resolver, item, outSource, &xmlCompileQueue);
+        xmlCompileQueue.pop();
+    }
+    return !error;
+}
+
+int main(int argc, char** argv) {
+    Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
+
+    std::vector<StringPiece> args;
+    args.reserve(argc - 1);
+    for (int i = 1; i < argc; i++) {
+        args.emplace_back(argv[i], strlen(argv[i]));
+    }
+
+    if (args.empty()) {
+        Logger::error() << "no command specified." << std::endl;
+        return 1;
+    }
+
+    AaptOptions options;
+
+    // Check the command we're running.
+    const StringPiece& command = args.front();
+    if (command == "package") {
+        if (!prepareLegacy(std::begin(args) + 1, std::end(args), options)) {
+            return 1;
+        }
+    } else if (command == "collect") {
+        if (!prepareCollect(std::begin(args) + 1, std::end(args), options)) {
+            return 1;
+        }
+    } else if (command == "link") {
+        if (!prepareLink(std::begin(args) + 1, std::end(args), options)) {
+            return 1;
+        }
+    } else if (command == "compile") {
+        if (!prepareCompile(std::begin(args) + 1, std::end(args), options)) {
+            return 1;
+        }
+    } else {
+        Logger::error() << "unknown command '" << command << "'." << std::endl;
+        return 1;
+    }
+
+    //
+    // Verify we have some common options set.
+    //
+
+    if (options.sources.empty()) {
+        Logger::error() << "no sources specified." << std::endl;
+        return false;
+    }
+
+    if (options.output.path.empty()) {
+        Logger::error() << "no output directory specified." << std::endl;
+        return false;
+    }
+
+    if (options.appInfo.package.empty()) {
+        Logger::error() << "no package name specified." << std::endl;
+        return false;
+    }
+
+
+    //
+    // Every phase needs a resource table and a resolver/linker.
+    //
+
+    std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
+    table->setPackage(options.appInfo.package);
+    if (options.appInfo.package == u"android") {
+        table->setPackageId(0x01);
+    } else {
+        table->setPackageId(0x7f);
+    }
+
+    //
+    // Load the included libraries.
+    //
+    std::shared_ptr<android::AssetManager> libraries = std::make_shared<android::AssetManager>();
+    for (const Source& source : options.libraries) {
+        if (util::stringEndsWith(source.path, ".arsc")) {
+            // We'll process these last so as to avoid a cookie issue.
+            continue;
+        }
+
+        int32_t cookie;
+        if (!libraries->addAssetPath(android::String8(source.path.data()), &cookie)) {
+            Logger::error(source) << "failed to load library." << std::endl;
+            return false;
+        }
+    }
+
+    for (const Source& source : options.libraries) {
+        if (!util::stringEndsWith(source.path, ".arsc")) {
+            // We've already processed this.
+            continue;
+        }
+
+        // Dirty hack but there is no other way to get a
+        // writeable ResTable.
+        if (!loadResTable(const_cast<android::ResTable*>(&libraries->getResources(false)),
+                          source)) {
+            return false;
+        }
+    }
+
+    // Make the resolver that will cache IDs for us.
+    std::shared_ptr<Resolver> resolver = std::make_shared<Resolver>(table, libraries);
+
+    //
+    // Dispatch to the real phase here.
+    //
+
+    bool result = true;
+    switch (options.phase) {
+        case AaptOptions::Phase::LegacyFull:
+            result = doLegacy(table, resolver, options);
+            break;
+
+        case AaptOptions::Phase::Collect:
+            result = doCollect(table, resolver, options);
+            break;
+
+        case AaptOptions::Phase::Link:
+            result = doLink(table, resolver, options);
+            break;
+
+        case AaptOptions::Phase::Compile:
+            result = doCompile(table, resolver, options);
+            break;
+    }
+
+    if (!result) {
+        Logger::error()
+                << "aapt exiting with failures."
+                << std::endl;
+        return 1;
+    }
+    return 0;
+}
diff --git a/tools/aapt2/ManifestParser.cpp b/tools/aapt2/ManifestParser.cpp
new file mode 100644
index 0000000..b8f0a43
--- /dev/null
+++ b/tools/aapt2/ManifestParser.cpp
@@ -0,0 +1,84 @@
+/*
+ * 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
new file mode 100644
index 0000000..f2e43d4
--- /dev/null
+++ b/tools/aapt2/ManifestParser.h
@@ -0,0 +1,45 @@
+/*
+ * 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
new file mode 100644
index 0000000..be3a6fb
--- /dev/null
+++ b/tools/aapt2/ManifestParser_test.cpp
@@ -0,0 +1,42 @@
+/*
+ * 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
new file mode 100644
index 0000000..596c758
--- /dev/null
+++ b/tools/aapt2/ManifestValidator.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 "Logger.h"
+#include "ManifestValidator.h"
+#include "Maybe.h"
+#include "Source.h"
+#include "Util.h"
+
+#include <androidfw/ResourceTypes.h>
+
+namespace aapt {
+
+ManifestValidator::ManifestValidator(const android::ResTable& table)
+: mTable(table) {
+}
+
+bool ManifestValidator::validate(const Source& source, android::ResXMLParser* parser) {
+    SourceLogger logger(source);
+
+    android::ResXMLParser::event_code_t code;
+    while ((code = parser->next()) != android::ResXMLParser::END_DOCUMENT &&
+            code != android::ResXMLParser::BAD_DOCUMENT) {
+        if (code != android::ResXMLParser::START_TAG) {
+            continue;
+        }
+
+        size_t len = 0;
+        const StringPiece16 namespaceUri(parser->getElementNamespace(&len), len);
+        if (!namespaceUri.empty()) {
+            continue;
+        }
+
+        const StringPiece16 name(parser->getElementName(&len), len);
+        if (name.empty()) {
+            logger.error(parser->getLineNumber())
+                    << "failed to get the element name."
+                    << std::endl;
+            return false;
+        }
+
+        if (name == u"manifest") {
+            if (!validateManifest(source, parser)) {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+Maybe<StringPiece16> ManifestValidator::getAttributeValue(android::ResXMLParser* parser,
+                                                          size_t idx) {
+    android::Res_value value;
+    if (parser->getAttributeValue(idx, &value) < 0) {
+        return StringPiece16();
+    }
+
+    const android::ResStringPool* pool = &parser->getStrings();
+    if (value.dataType == android::Res_value::TYPE_REFERENCE) {
+        ssize_t strIdx = mTable.resolveReference(&value, 0x10000000u);
+        if (strIdx < 0) {
+            return {};
+        }
+        pool = mTable.getTableStringBlock(strIdx);
+    }
+
+    if (value.dataType != android::Res_value::TYPE_STRING || !pool) {
+        return {};
+    }
+    return util::getString(*pool, value.data);
+}
+
+Maybe<StringPiece16> ManifestValidator::getAttributeInlineValue(android::ResXMLParser* parser,
+                                                                size_t idx) {
+    android::Res_value value;
+    if (parser->getAttributeValue(idx, &value) < 0) {
+        return StringPiece16();
+    }
+
+    if (value.dataType != android::Res_value::TYPE_STRING) {
+        return {};
+    }
+    return util::getString(parser->getStrings(), value.data);
+}
+
+bool ManifestValidator::validateInlineAttribute(android::ResXMLParser* parser, size_t idx,
+                                                SourceLogger& logger,
+                                                const StringPiece16& charSet) {
+    size_t len = 0;
+    StringPiece16 element(parser->getElementName(&len), len);
+    StringPiece16 attributeName(parser->getAttributeName(idx, &len), len);
+    Maybe<StringPiece16> result = getAttributeInlineValue(parser, idx);
+    if (!result) {
+        logger.error(parser->getLineNumber())
+                << "<"
+                << element
+                << "> must have a '"
+                << attributeName
+                << "' attribute with a string literal value."
+                << std::endl;
+        return false;
+    }
+    return validateAttributeImpl(element, attributeName, result.value(), charSet,
+                                 parser->getLineNumber(), logger);
+}
+
+bool ManifestValidator::validateAttribute(android::ResXMLParser* parser, size_t idx,
+                                          SourceLogger& logger, const StringPiece16& charSet) {
+    size_t len = 0;
+    StringPiece16 element(parser->getElementName(&len), len);
+    StringPiece16 attributeName(parser->getAttributeName(idx, &len), len);
+    Maybe<StringPiece16> result = getAttributeValue(parser, idx);
+    if (!result) {
+        logger.error(parser->getLineNumber())
+                << "<"
+                << element
+                << "> must have a '"
+                << attributeName
+                << "' attribute that points to a string."
+                << std::endl;
+        return false;
+    }
+    return validateAttributeImpl(element, attributeName, result.value(), charSet,
+                                 parser->getLineNumber(), logger);
+}
+
+bool ManifestValidator::validateAttributeImpl(const StringPiece16& element,
+                                              const StringPiece16& attributeName,
+                                              const StringPiece16& attributeValue,
+                                              const StringPiece16& charSet, size_t lineNumber,
+                                              SourceLogger& logger) {
+    StringPiece16::const_iterator badIter =
+            util::findNonAlphaNumericAndNotInSet(attributeValue, charSet);
+    if (badIter != attributeValue.end()) {
+        logger.error(lineNumber)
+                << "tag <"
+                << element
+                << "> attribute '"
+                << attributeName
+                << "' has invalid character '"
+                << *badIter
+                << "'."
+                << std::endl;
+        return false;
+    }
+
+    if (!attributeValue.empty()) {
+        StringPiece16 trimmed = util::trimWhitespace(attributeValue);
+        if (attributeValue.begin() != trimmed.begin()) {
+            logger.error(lineNumber)
+                    << "tag <"
+                    << element
+                    << "> attribute '"
+                    << attributeName
+                    << "' can not start with whitespace."
+                    << std::endl;
+            return false;
+        }
+
+        if (attributeValue.end() != trimmed.end()) {
+            logger.error(lineNumber)
+                    << "tag <"
+                    << element
+                    << "> attribute '"
+                    << attributeName
+                    << "' can not end with whitespace."
+                    << std::endl;
+            return false;
+        }
+    }
+    return true;
+}
+
+constexpr const char16_t* kPackageIdentSet = u"._";
+
+bool ManifestValidator::validateManifest(const Source& source, android::ResXMLParser* parser) {
+    bool error = false;
+    SourceLogger logger(source);
+
+    const size_t attrCount = parser->getAttributeCount();
+    for (size_t i = 0; i < attrCount; i++) {
+        size_t len = 0;
+        StringPiece16 attrNamespace(parser->getAttributeNamespace(i, &len), len);
+        StringPiece16 attrName(parser->getAttributeName(i, &len), len);
+        if (attrNamespace.empty() && attrName == u"package") {
+            error |= !validateInlineAttribute(parser, i, logger, kPackageIdentSet);
+        } else if (attrNamespace == u"android") {
+            if (attrName == u"sharedUserId") {
+                error |= !validateInlineAttribute(parser, i, logger, kPackageIdentSet);
+            }
+        }
+    }
+    return !error;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ManifestValidator.h b/tools/aapt2/ManifestValidator.h
new file mode 100644
index 0000000..3188784
--- /dev/null
+++ b/tools/aapt2/ManifestValidator.h
@@ -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.
+ */
+
+#ifndef AAPT_MANIFEST_VALIDATOR_H
+#define AAPT_MANIFEST_VALIDATOR_H
+
+#include "Logger.h"
+#include "Maybe.h"
+#include "Source.h"
+#include "StringPiece.h"
+
+#include <androidfw/ResourceTypes.h>
+
+namespace aapt {
+
+class ManifestValidator {
+public:
+    ManifestValidator(const android::ResTable& table);
+    ManifestValidator(const ManifestValidator&) = delete;
+
+    bool validate(const Source& source, android::ResXMLParser* parser);
+
+private:
+    bool validateManifest(const Source& source, android::ResXMLParser* parser);
+
+    Maybe<StringPiece16> getAttributeInlineValue(android::ResXMLParser* parser, size_t idx);
+    Maybe<StringPiece16> getAttributeValue(android::ResXMLParser* parser, size_t idx);
+
+    bool validateInlineAttribute(android::ResXMLParser* parser, size_t idx,
+                                 SourceLogger& logger, const StringPiece16& charSet);
+    bool validateAttribute(android::ResXMLParser* parser, size_t idx, SourceLogger& logger,
+                           const StringPiece16& charSet);
+    bool validateAttributeImpl(const StringPiece16& element, const StringPiece16& attributeName,
+                               const StringPiece16& attributeValue, const StringPiece16& charSet,
+                               size_t lineNumber, SourceLogger& logger);
+
+    const android::ResTable& mTable;
+};
+
+} // namespace aapt
+
+#endif // AAPT_MANIFEST_VALIDATOR_H
diff --git a/tools/aapt2/Maybe.h b/tools/aapt2/Maybe.h
new file mode 100644
index 0000000..f6a396d
--- /dev/null
+++ b/tools/aapt2/Maybe.h
@@ -0,0 +1,224 @@
+/*
+ * 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_MAYBE_H
+#define AAPT_MAYBE_H
+
+#include <cassert>
+#include <type_traits>
+#include <utility>
+
+namespace aapt {
+
+/**
+ * Either holds a valid value of type T, or holds Nothing.
+ * The value is stored inline in this structure, so no
+ * heap memory is used when creating a Maybe<T> object.
+ */
+template <typename T>
+class Maybe {
+public:
+    /**
+     * Construct Nothing.
+     */
+    inline Maybe();
+
+    inline ~Maybe();
+
+    template <typename U>
+    inline Maybe(const Maybe<U>& rhs);
+
+    template <typename U>
+    inline Maybe(Maybe<U>&& rhs);
+
+    template <typename U>
+    inline Maybe& operator=(const Maybe<U>& rhs);
+
+    template <typename U>
+    inline Maybe& operator=(Maybe<U>&& rhs);
+
+    /**
+     * Construct a Maybe holding a value.
+     */
+    inline Maybe(const T& value);
+
+    /**
+     * Construct a Maybe holding a value.
+     */
+    inline Maybe(T&& value);
+
+    /**
+     * True if this holds a value, false if
+     * it holds Nothing.
+     */
+    inline operator bool() const;
+
+    /**
+     * Gets the value if one exists, or else
+     * panics.
+     */
+    inline T& value();
+
+    /**
+     * Gets the value if one exists, or else
+     * panics.
+     */
+    inline const T& value() const;
+
+private:
+    template <typename U>
+    friend class Maybe;
+
+    void destroy();
+
+    bool mNothing;
+
+    typename std::aligned_storage<sizeof(T), alignof(T)>::type mStorage;
+};
+
+template <typename T>
+Maybe<T>::Maybe()
+: mNothing(true) {
+}
+
+template <typename T>
+Maybe<T>::~Maybe() {
+    if (!mNothing) {
+        destroy();
+    }
+}
+
+template <typename T>
+template <typename U>
+Maybe<T>::Maybe(const Maybe<U>& rhs)
+: mNothing(rhs.mNothing) {
+    if (!rhs.mNothing) {
+        new (&mStorage) T(reinterpret_cast<const U&>(rhs.mStorage));
+    }
+}
+
+template <typename T>
+template <typename U>
+Maybe<T>::Maybe(Maybe<U>&& rhs)
+: mNothing(rhs.mNothing) {
+    if (!rhs.mNothing) {
+        rhs.mNothing = true;
+
+        // Move the value from rhs.
+        new (&mStorage) T(std::move(reinterpret_cast<U&>(rhs.mStorage)));
+
+        // Since the value in rhs is now Nothing,
+        // run the destructor for the value.
+        rhs.destroy();
+    }
+}
+
+template <typename T>
+template <typename U>
+Maybe<T>& Maybe<T>::operator=(const Maybe<U>& rhs) {
+    if (mNothing && rhs.mNothing) {
+        // Both are nothing, nothing to do.
+        return *this;
+    } else if  (!mNothing && !rhs.mNothing) {
+        // We both are something, so assign rhs to us.
+        reinterpret_cast<T&>(mStorage) = reinterpret_cast<const U&>(rhs.mStorage);
+    } else if (mNothing) {
+        // We are nothing but rhs is something.
+        mNothing = rhs.mNothing;
+
+        // Copy the value from rhs.
+        new (&mStorage) T(reinterpret_cast<const U&>(rhs.mStorage));
+    } else {
+        // We are something but rhs is nothing, so destroy our value.
+        mNothing = rhs.mNothing;
+        destroy();
+    }
+    return *this;
+}
+
+template <typename T>
+template <typename U>
+Maybe<T>& Maybe<T>::operator=(Maybe<U>&& rhs) {
+    if (mNothing && rhs.mNothing) {
+        // Both are nothing, nothing to do.
+        return *this;
+    } else if  (!mNothing && !rhs.mNothing) {
+        // We both are something, so move assign rhs to us.
+        rhs.mNothing = true;
+        reinterpret_cast<T&>(mStorage) = std::move(reinterpret_cast<U&>(rhs.mStorage));
+        rhs.destroy();
+    } else if (mNothing) {
+        // We are nothing but rhs is something.
+        mNothing = rhs.mNothing;
+
+        // Move the value from rhs.
+        new (&mStorage) T(std::move(reinterpret_cast<U&>(rhs.mStorage)));
+        rhs.destroy();
+    } else {
+        // We are something but rhs is nothing, so destroy our value.
+        mNothing = rhs.mNothing;
+        destroy();
+    }
+    return *this;
+}
+
+template <typename T>
+Maybe<T>::Maybe(const T& value)
+: mNothing(false) {
+    new (&mStorage) T(value);
+}
+
+template <typename T>
+Maybe<T>::Maybe(T&& value)
+: mNothing(false) {
+    new (&mStorage) T(std::forward<T>(value));
+}
+
+template <typename T>
+Maybe<T>::operator bool() const {
+    return !mNothing;
+}
+
+template <typename T>
+T& Maybe<T>::value() {
+    assert(!mNothing && "Maybe<T>::value() called on Nothing");
+    return reinterpret_cast<T&>(mStorage);
+}
+
+template <typename T>
+const T& Maybe<T>::value() const {
+    assert(!mNothing && "Maybe<T>::value() called on Nothing");
+    return reinterpret_cast<const T&>(mStorage);
+}
+
+template <typename T>
+void Maybe<T>::destroy() {
+    reinterpret_cast<T&>(mStorage).~T();
+}
+
+template <typename T>
+inline Maybe<typename std::remove_reference<T>::type> make_value(T&& value) {
+    return Maybe<typename std::remove_reference<T>::type>(std::forward<T>(value));
+}
+
+template <typename T>
+inline Maybe<T> make_nothing() {
+    return Maybe<T>();
+}
+
+} // namespace aapt
+
+#endif // AAPT_MAYBE_H
diff --git a/tools/aapt2/Maybe_test.cpp b/tools/aapt2/Maybe_test.cpp
new file mode 100644
index 0000000..348d7dd
--- /dev/null
+++ b/tools/aapt2/Maybe_test.cpp
@@ -0,0 +1,69 @@
+/*
+ * 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 "Maybe.h"
+
+namespace aapt {
+
+struct Dummy {
+    Dummy() {
+        std::cerr << "Constructing Dummy " << (void *) this << std::endl;
+    }
+
+    Dummy(const Dummy& rhs) {
+        std::cerr << "Copying Dummy " << (void *) this << " from " << (const void*) &rhs << std::endl;
+    }
+
+    Dummy(Dummy&& rhs) {
+        std::cerr << "Moving Dummy " << (void *) this << " from " << (void*) &rhs << std::endl;
+    }
+
+    ~Dummy() {
+        std::cerr << "Destroying Dummy " << (void *) this << std::endl;
+    }
+};
+
+TEST(MaybeTest, MakeNothing) {
+    Maybe<int> val = make_nothing<int>();
+    EXPECT_FALSE(val);
+
+    Maybe<std::string> val2 = make_nothing<std::string>();
+    EXPECT_FALSE(val2);
+
+    val2 = make_nothing<std::string>();
+    EXPECT_FALSE(val2);
+}
+
+TEST(MaybeTest, MakeSomething) {
+    Maybe<int> val = make_value(23);
+    ASSERT_TRUE(val);
+    EXPECT_EQ(23, val.value());
+
+    Maybe<std::string> val2 = make_value(std::string("hey"));
+    ASSERT_TRUE(val2);
+    EXPECT_EQ(std::string("hey"), val2.value());
+}
+
+TEST(MaybeTest, Lifecycle) {
+    Maybe<Dummy> val = make_nothing<Dummy>();
+
+    Maybe<Dummy> val2 = make_value(Dummy());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ResChunkPullParser.cpp b/tools/aapt2/ResChunkPullParser.cpp
new file mode 100644
index 0000000..78ea60e
--- /dev/null
+++ b/tools/aapt2/ResChunkPullParser.cpp
@@ -0,0 +1,68 @@
+/*
+ * 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 "ResChunkPullParser.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <cstddef>
+
+namespace aapt {
+
+using android::ResChunk_header;
+
+ResChunkPullParser::Event ResChunkPullParser::next() {
+    if (!isGoodEvent(mEvent)) {
+        return mEvent;
+    }
+
+    if (mEvent == Event::StartDocument) {
+        mCurrentChunk = mData;
+    } else {
+        mCurrentChunk = reinterpret_cast<const ResChunk_header*>(
+                reinterpret_cast<const char*>(mCurrentChunk) + mCurrentChunk->size);
+    }
+
+    const std::ptrdiff_t diff = reinterpret_cast<const char*>(mCurrentChunk)
+            - reinterpret_cast<const char*>(mData);
+    assert(diff >= 0 && "diff is negative");
+    const size_t offset = static_cast<const size_t>(diff);
+
+    if (offset == mLen) {
+        mCurrentChunk = nullptr;
+        return (mEvent = Event::EndDocument);
+    } else if (offset + sizeof(ResChunk_header) > mLen) {
+        mLastError = "chunk is past the end of the document";
+        mCurrentChunk = nullptr;
+        return (mEvent = Event::BadDocument);
+    }
+
+    if (mCurrentChunk->headerSize < sizeof(ResChunk_header)) {
+        mLastError = "chunk has too small header";
+        mCurrentChunk = nullptr;
+        return (mEvent = Event::BadDocument);
+    } else if (mCurrentChunk->size < mCurrentChunk->headerSize) {
+        mLastError = "chunk's total size is smaller than header";
+        mCurrentChunk = nullptr;
+        return (mEvent = Event::BadDocument);
+    } else if (offset + mCurrentChunk->size > mLen) {
+        mLastError = "chunk's data extends past the end of the document";
+        mCurrentChunk = nullptr;
+        return (mEvent = Event::BadDocument);
+    }
+    return (mEvent = Event::Chunk);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ResChunkPullParser.h b/tools/aapt2/ResChunkPullParser.h
new file mode 100644
index 0000000..7366c89
--- /dev/null
+++ b/tools/aapt2/ResChunkPullParser.h
@@ -0,0 +1,106 @@
+/*
+ * 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_RES_CHUNK_PULL_PARSER_H
+#define AAPT_RES_CHUNK_PULL_PARSER_H
+
+#include <androidfw/ResourceTypes.h>
+#include <string>
+
+namespace aapt {
+
+/**
+ * A pull parser, modeled after XmlPullParser, that reads
+ * android::ResChunk_header structs from a block of data.
+ *
+ * An android::ResChunk_header specifies a type, headerSize,
+ * and size. The pull parser will verify that the chunk's size
+ * doesn't extend beyond the available data, and will iterate
+ * over each chunk in the given block of data.
+ *
+ * Processing nested chunks is done by creating a new ResChunkPullParser
+ * pointing to the data portion of a chunk.
+ */
+class ResChunkPullParser {
+public:
+    enum class Event {
+        StartDocument,
+        EndDocument,
+        BadDocument,
+
+        Chunk,
+    };
+
+    /**
+     * Returns false if the event is EndDocument or BadDocument.
+     */
+    static bool isGoodEvent(Event event);
+
+    /**
+     * Create a ResChunkPullParser to read android::ResChunk_headers
+     * from the memory pointed to by data, of len bytes.
+     */
+    ResChunkPullParser(const void* data, size_t len);
+
+    ResChunkPullParser(const ResChunkPullParser&) = delete;
+
+    Event getEvent() const;
+    const std::string& getLastError() const;
+    const android::ResChunk_header* getChunk() const;
+
+    /**
+     * Move to the next android::ResChunk_header.
+     */
+    Event next();
+
+private:
+    Event mEvent;
+    const android::ResChunk_header* mData;
+    size_t mLen;
+    const android::ResChunk_header* mCurrentChunk;
+    std::string mLastError;
+};
+
+//
+// Implementation
+//
+
+inline bool ResChunkPullParser::isGoodEvent(ResChunkPullParser::Event event) {
+    return event != Event::EndDocument && event != Event::BadDocument;
+}
+
+inline ResChunkPullParser::ResChunkPullParser(const void* data, size_t len) :
+        mEvent(Event::StartDocument),
+        mData(reinterpret_cast<const android::ResChunk_header*>(data)),
+        mLen(len),
+        mCurrentChunk(nullptr) {
+}
+
+inline ResChunkPullParser::Event ResChunkPullParser::getEvent() const {
+    return mEvent;
+}
+
+inline const std::string& ResChunkPullParser::getLastError() const {
+    return mLastError;
+}
+
+inline const android::ResChunk_header* ResChunkPullParser::getChunk() const {
+    return mCurrentChunk;
+}
+
+} // namespace aapt
+
+#endif // AAPT_RES_CHUNK_PULL_PARSER_H
diff --git a/tools/aapt2/Resolver.cpp b/tools/aapt2/Resolver.cpp
new file mode 100644
index 0000000..93b5e98
--- /dev/null
+++ b/tools/aapt2/Resolver.cpp
@@ -0,0 +1,151 @@
+/*
+ * 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 "Resolver.h"
+#include "Resource.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "Util.h"
+
+#include <androidfw/AssetManager.h>
+#include <androidfw/ResourceTypes.h>
+#include <memory>
+#include <vector>
+
+namespace aapt {
+
+Resolver::Resolver(std::shared_ptr<const ResourceTable> table,
+                   std::shared_ptr<const android::AssetManager> sources) :
+        mTable(table), mSources(sources) {
+}
+
+Maybe<ResourceId> Resolver::findId(const ResourceName& name) {
+    Maybe<Entry> result = findAttribute(name);
+    if (result) {
+        return result.value().id;
+    }
+    return {};
+}
+
+Maybe<Resolver::Entry> Resolver::findAttribute(const ResourceName& name) {
+    auto cacheIter = mCache.find(name);
+    if (cacheIter != std::end(mCache)) {
+        return Entry{ cacheIter->second.id, cacheIter->second.attr.get() };
+    }
+
+    const ResourceTableType* type;
+    const ResourceEntry* entry;
+    std::tie(type, entry) = mTable->findResource(name);
+    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;
+    }
+
+    const CacheEntry* cacheEntry = buildCacheEntry(name);
+    if (cacheEntry) {
+        return Entry{ cacheEntry->id, cacheEntry->attr.get() };
+    }
+    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 Resolver::CacheEntry* Resolver::buildCacheEntry(const ResourceName& name) {
+    const android::ResTable& table = mSources->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()) {
+        return nullptr;
+    }
+
+    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;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Resolver.h b/tools/aapt2/Resolver.h
new file mode 100644
index 0000000..90a8cd9
--- /dev/null
+++ b/tools/aapt2/Resolver.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_RESOLVER_H
+#define AAPT_RESOLVER_H
+
+#include "Maybe.h"
+#include "Resource.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+
+#include <androidfw/AssetManager.h>
+#include <androidfw/ResourceTypes.h>
+#include <memory>
+#include <vector>
+
+namespace aapt {
+
+/**
+ * Resolves symbolic references (package:type/entry) into resource IDs/objects.
+ * Encapsulates the search of library sources as well as the local ResourceTable.
+ */
+class Resolver {
+public:
+    /**
+     * Creates a resolver with a local ResourceTable and an AssetManager
+     * loaded with library packages.
+     */
+    Resolver(std::shared_ptr<const ResourceTable> table,
+             std::shared_ptr<const android::AssetManager> sources);
+
+    Resolver(const Resolver&) = delete; // Not copyable.
+
+    /**
+     * 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;
+    };
+
+    /**
+     * Return the package to use when none is specified. This
+     * is the package name of the app being built.
+     */
+    const std::u16string& getDefaultPackage() const;
+
+    /**
+     * Returns a ResourceID if the name is found. The ResourceID
+     * may not be valid if the resource was not assigned an ID.
+     */
+    Maybe<ResourceId> findId(const ResourceName& name);
+
+    /**
+     * Returns an Entry if the name is found. Entry::attr
+     * may be nullptr if the resource is not an attribute.
+     */
+    Maybe<Entry> findAttribute(const ResourceName& name);
+
+    const android::ResTable& getResTable() const;
+
+private:
+    struct CacheEntry {
+        ResourceId id;
+        std::unique_ptr<Attribute> attr;
+    };
+
+    const CacheEntry* buildCacheEntry(const ResourceName& name);
+
+    std::shared_ptr<const ResourceTable> mTable;
+    std::shared_ptr<const android::AssetManager> mSources;
+    std::map<ResourceName, CacheEntry> mCache;
+};
+
+inline const std::u16string& Resolver::getDefaultPackage() const {
+    return mTable->getPackage();
+}
+
+inline const android::ResTable& Resolver::getResTable() const {
+    return mSources->getResources(false);
+}
+
+} // namespace aapt
+
+#endif // AAPT_RESOLVER_H
diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp
new file mode 100644
index 0000000..287d8de
--- /dev/null
+++ b/tools/aapt2/Resource.cpp
@@ -0,0 +1,90 @@
+/*
+ * 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 "StringPiece.h"
+
+#include <map>
+#include <string>
+
+namespace aapt {
+
+StringPiece16 toString(ResourceType type) {
+    switch (type) {
+        case ResourceType::kAnim:          return u"anim";
+        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::kBool:          return u"bool";
+        case ResourceType::kColor:         return u"color";
+        case ResourceType::kDimen:         return u"dimen";
+        case ResourceType::kDrawable:      return u"drawable";
+        case ResourceType::kFraction:      return u"fraction";
+        case ResourceType::kId:            return u"id";
+        case ResourceType::kInteger:       return u"integer";
+        case ResourceType::kIntegerArray:  return u"integer-array";
+        case ResourceType::kInterpolator:  return u"interpolator";
+        case ResourceType::kLayout:        return u"layout";
+        case ResourceType::kMenu:          return u"menu";
+        case ResourceType::kMipmap:        return u"mipmap";
+        case ResourceType::kPlurals:       return u"plurals";
+        case ResourceType::kRaw:           return u"raw";
+        case ResourceType::kString:        return u"string";
+        case ResourceType::kStyle:         return u"style";
+        case ResourceType::kStyleable:     return u"styleable";
+        case ResourceType::kTransition:    return u"transition";
+        case ResourceType::kXml:           return u"xml";
+    }
+    return {};
+}
+
+static const std::map<StringPiece16, ResourceType> sResourceTypeMap {
+        { u"anim", ResourceType::kAnim },
+        { u"animator", ResourceType::kAnimator },
+        { u"array", ResourceType::kArray },
+        { u"attr", ResourceType::kAttr },
+        { u"^attr-private", ResourceType::kAttrPrivate },
+        { u"bool", ResourceType::kBool },
+        { u"color", ResourceType::kColor },
+        { u"dimen", ResourceType::kDimen },
+        { u"drawable", ResourceType::kDrawable },
+        { u"fraction", ResourceType::kFraction },
+        { u"id", ResourceType::kId },
+        { u"integer", ResourceType::kInteger },
+        { u"integer-array", ResourceType::kIntegerArray },
+        { u"interpolator", ResourceType::kInterpolator },
+        { u"layout", ResourceType::kLayout },
+        { u"menu", ResourceType::kMenu },
+        { u"mipmap", ResourceType::kMipmap },
+        { u"plurals", ResourceType::kPlurals },
+        { u"raw", ResourceType::kRaw },
+        { u"string", ResourceType::kString },
+        { u"style", ResourceType::kStyle },
+        { u"styleable", ResourceType::kStyleable },
+        { u"transition", ResourceType::kTransition },
+        { u"xml", ResourceType::kXml },
+};
+
+const ResourceType* parseResourceType(const StringPiece16& str) {
+    auto iter = sResourceTypeMap.find(str);
+    if (iter == std::end(sResourceTypeMap)) {
+        return nullptr;
+    }
+    return &iter->second;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
new file mode 100644
index 0000000..3fd678e
--- /dev/null
+++ b/tools/aapt2/Resource.h
@@ -0,0 +1,276 @@
+/*
+ * 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_H
+#define AAPT_RESOURCE_H
+
+#include "StringPiece.h"
+
+#include <iomanip>
+#include <string>
+#include <tuple>
+
+namespace aapt {
+
+/**
+ * The various types of resource types available. Corresponds
+ * to the 'type' in package:type/entry.
+ */
+enum class ResourceType {
+    kAnim,
+    kAnimator,
+    kArray,
+    kAttr,
+    kAttrPrivate,
+    kBool,
+    kColor,
+    kDimen,
+    kDrawable,
+    kFraction,
+    kId,
+    kInteger,
+    kIntegerArray,
+    kInterpolator,
+    kLayout,
+    kMenu,
+    kMipmap,
+    kPlurals,
+    kRaw,
+    kString,
+    kStyle,
+    kStyleable,
+    kTransition,
+    kXml,
+};
+
+StringPiece16 toString(ResourceType type);
+
+/**
+ * Returns a pointer to a valid ResourceType, or nullptr if
+ * the string was invalid.
+ */
+const ResourceType* parseResourceType(const StringPiece16& str);
+
+/**
+ * A resource's name. This can uniquely identify
+ * a resource in the ResourceTable.
+ */
+struct ResourceName {
+    std::u16string package;
+    ResourceType type;
+    std::u16string entry;
+
+    bool isValid() const;
+    bool operator<(const ResourceName& rhs) const;
+    bool operator==(const ResourceName& rhs) const;
+    bool operator!=(const ResourceName& rhs) const;
+};
+
+/**
+ * Same as ResourceName, but uses StringPieces instead.
+ * Use this if you need to avoid copying and know that
+ * the lifetime of this object is shorter than that
+ * of the original string.
+ */
+struct ResourceNameRef {
+    StringPiece16 package;
+    ResourceType type;
+    StringPiece16 entry;
+
+    ResourceNameRef() = default;
+    ResourceNameRef(const ResourceNameRef&) = default;
+    ResourceNameRef(ResourceNameRef&&) = default;
+    ResourceNameRef(const ResourceName& rhs);
+    ResourceNameRef(const StringPiece16& p, ResourceType t, const StringPiece16& e);
+    ResourceNameRef& operator=(const ResourceName& rhs);
+
+    ResourceName toResourceName() const;
+    bool isValid() const;
+
+    bool operator<(const ResourceNameRef& rhs) const;
+    bool operator==(const ResourceNameRef& rhs) const;
+    bool operator!=(const ResourceNameRef& rhs) const;
+};
+
+/**
+ * A binary identifier representing a resource. Internally it
+ * is a 32bit integer split as follows:
+ *
+ * 0xPPTTEEEE
+ *
+ * PP: 8 bit package identifier. 0x01 is reserved for system
+ *     and 0x7f is reserved for the running app.
+ * TT: 8 bit type identifier. 0x00 is invalid.
+ * EEEE: 16 bit entry identifier.
+ */
+struct ResourceId {
+    uint32_t id;
+
+    ResourceId();
+    ResourceId(const ResourceId& rhs);
+    ResourceId(uint32_t resId);
+    ResourceId(size_t p, size_t t, size_t e);
+
+    bool isValid() const;
+    uint8_t packageId() const;
+    uint8_t typeId() const;
+    uint16_t entryId() const;
+    bool operator<(const ResourceId& rhs) const;
+};
+
+//
+// ResourceId implementation.
+//
+
+inline ResourceId::ResourceId() : id(0) {
+}
+
+inline ResourceId::ResourceId(const ResourceId& rhs) : id(rhs.id) {
+}
+
+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 bool ResourceId::isValid() const {
+    return (id & 0xff000000u) != 0 && (id & 0x00ff0000u) != 0;
+}
+
+inline uint8_t ResourceId::packageId() const {
+    return static_cast<uint8_t>(id >> 24);
+}
+
+inline uint8_t ResourceId::typeId() const {
+    return static_cast<uint8_t>(id >> 16);
+}
+
+inline uint16_t ResourceId::entryId() const {
+    return static_cast<uint16_t>(id);
+}
+
+inline bool ResourceId::operator<(const ResourceId& rhs) const {
+    return id < rhs.id;
+}
+
+inline ::std::ostream& operator<<(::std::ostream& out,
+        const ResourceId& resId) {
+    std::ios_base::fmtflags oldFlags = out.flags();
+    char oldFill = out.fill();
+    out << "0x" << std::internal << std::setfill('0') << std::setw(8)
+        << std::hex << resId.id;
+    out.flags(oldFlags);
+    out.fill(oldFill);
+    return out;
+}
+
+//
+// ResourceType implementation.
+//
+
+inline ::std::ostream& operator<<(::std::ostream& out,
+        const ResourceType& val) {
+    return out << toString(val);
+}
+
+//
+// ResourceName implementation.
+//
+
+inline bool ResourceName::isValid() const {
+    return !package.empty() && !entry.empty();
+}
+
+inline bool ResourceName::operator<(const ResourceName& rhs) const {
+    return std::tie(package, type, entry)
+            < std::tie(rhs.package, rhs.type, rhs.entry);
+}
+
+inline bool ResourceName::operator==(const ResourceName& rhs) const {
+    return std::tie(package, type, entry)
+            == std::tie(rhs.package, rhs.type, rhs.entry);
+}
+
+inline bool ResourceName::operator!=(const ResourceName& rhs) const {
+    return std::tie(package, type, entry)
+            != std::tie(rhs.package, rhs.type, rhs.entry);
+}
+
+//
+// ResourceNameRef implementation.
+//
+
+inline ResourceNameRef::ResourceNameRef(const ResourceName& rhs) :
+        package(rhs.package), type(rhs.type), entry(rhs.entry) {
+}
+
+inline ResourceNameRef::ResourceNameRef(const StringPiece16& p, ResourceType t,
+                                        const StringPiece16& e) :
+        package(p), type(t), entry(e) {
+}
+
+inline ResourceNameRef& ResourceNameRef::operator=(const ResourceName& rhs) {
+    package = rhs.package;
+    type = rhs.type;
+    entry = rhs.entry;
+    return *this;
+}
+
+inline ResourceName ResourceNameRef::toResourceName() const {
+    return { package.toString(), type, entry.toString() };
+}
+
+inline bool ResourceNameRef::isValid() const {
+    return !package.empty() && !entry.empty();
+}
+
+inline bool ResourceNameRef::operator<(const ResourceNameRef& rhs) const {
+    return std::tie(package, type, entry)
+            < std::tie(rhs.package, rhs.type, rhs.entry);
+}
+
+inline bool ResourceNameRef::operator==(const ResourceNameRef& rhs) const {
+    return std::tie(package, type, entry)
+            == std::tie(rhs.package, rhs.type, rhs.entry);
+}
+
+inline bool ResourceNameRef::operator!=(const ResourceNameRef& rhs) const {
+    return std::tie(package, type, entry)
+            != std::tie(rhs.package, rhs.type, rhs.entry);
+}
+
+inline ::std::ostream& operator<<(::std::ostream& out,
+        const ResourceNameRef& name) {
+    if (!name.package.empty()) {
+        out << name.package << ":";
+    }
+    return out << name.type << "/" << name.entry;
+}
+
+} // namespace aapt
+
+#endif // AAPT_RESOURCE_H
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
new file mode 100644
index 0000000..d3720c4
--- /dev/null
+++ b/tools/aapt2/ResourceParser.cpp
@@ -0,0 +1,1317 @@
+/*
+ * 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 "ResourceParser.h"
+#include "ResourceValues.h"
+#include "ScopedXmlPullParser.h"
+#include "SourceXmlPullParser.h"
+#include "Util.h"
+#include "XliffXmlPullParser.h"
+
+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);
+}
+
+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;
+}
+
+std::unique_ptr<Reference> ResourceParser::tryParseReference(const StringPiece16& str,
+                                                             const StringPiece16& defaultPackage,
+                                                             bool* outCreate) {
+    ResourceNameRef ref;
+    bool privateRef = false;
+    if (tryParseReference(str, &ref, outCreate, &privateRef)) {
+        if (ref.package.empty()) {
+            ref.package = defaultPackage;
+        }
+        std::unique_ptr<Reference> value = util::make_unique<Reference>(ref);
+        value->privateReference = privateRef;
+        return value;
+    }
+
+    if (tryParseAttributeReference(str, &ref)) {
+        if (ref.package.empty()) {
+            ref.package = defaultPackage;
+        }
+        *outCreate = false;
+        return util::make_unique<Reference>(ref, Reference::Type::kAttribute);
+    }
+    return {};
+}
+
+std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseNullOrEmpty(const StringPiece16& str) {
+    StringPiece16 trimmedStr(util::trimWhitespace(str));
+    uint32_t data = 0;
+    if (trimmedStr == u"@null") {
+        data = android::Res_value::DATA_NULL_UNDEFINED;
+    } else if (trimmedStr == u"@empty") {
+        data = android::Res_value::DATA_NULL_EMPTY;
+    } else {
+        return {};
+    }
+
+    android::Res_value value = {};
+    value.dataType = android::Res_value::TYPE_NULL;
+    value.data = data;
+    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);
+        }
+    }
+    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 = 1;
+    } 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, const StringPiece16& defaultPackage,
+        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, defaultPackage, &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, const StringPiece16& defaultPackage,
+        std::function<void(const ResourceName&)> onCreateReference) {
+    const uint32_t typeMask = attr.typeMask;
+    std::unique_ptr<Item> value = parseItemForAttribute(str, typeMask, defaultPackage,
+                                                        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)) {
+}
+
+/**
+ * Build a string from XML that converts nested elements into Span objects.
+ */
+bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString,
+                                       StyleString* outStyleString) {
+    std::vector<Span> spanStack;
+
+    outRawString->clear();
+    outStyleString->spans.clear();
+    util::StringBuilder builder;
+    size_t depth = 1;
+    while (XmlPullParser::isGoodEvent(parser->next())) {
+        const XmlPullParser::Event event = parser->getEvent();
+        if (event == XmlPullParser::Event::kEndElement) {
+            depth--;
+            if (depth == 0) {
+                break;
+            }
+
+            spanStack.back().lastChar = builder.str().size();
+            outStyleString->spans.push_back(spanStack.back());
+            spanStack.pop_back();
+
+        } else if (event == XmlPullParser::Event::kText) {
+            // TODO(adamlesinski): Verify format strings.
+            outRawString->append(parser->getText());
+            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);
+                continue;
+            }
+            depth++;
+
+            // Build a span object out of the nested element.
+            std::u16string spanName = parser->getElementName();
+            const auto endAttrIter = parser->endAttributes();
+            for (auto attrIter = parser->beginAttributes(); attrIter != endAttrIter; ++attrIter) {
+                spanName += u";";
+                spanName += attrIter->name;
+                spanName += u"=";
+                spanName += attrIter->value;
+            }
+
+            if (builder.str().size() > std::numeric_limits<uint32_t>::max()) {
+                mLogger.error(parser->getLineNumber())
+                        << "style string '"
+                        << builder.str()
+                        << "' is too long."
+                        << std::endl;
+                return false;
+            }
+            spanStack.push_back(Span{ spanName, static_cast<uint32_t>(builder.str().size()) });
+
+        } else if (event == XmlPullParser::Event::kComment) {
+            // Skip
+        } else {
+            mLogger.warn(parser->getLineNumber())
+                    << "unknown event "
+                    << event
+                    << "."
+                    << std::endl;
+        }
+    }
+    assert(spanStack.empty() && "spans haven't been fully processed");
+
+    outStyleString->str = builder.str();
+    return true;
+}
+
+bool ResourceParser::parse() {
+    while (XmlPullParser::isGoodEvent(mParser->next())) {
+        if (mParser->getEvent() != XmlPullParser::Event::kStartElement) {
+            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;
+            return false;
+        }
+
+        if (!parseResources(&parser)) {
+            return false;
+        }
+    }
+
+    if (mParser->getEvent() == XmlPullParser::Event::kBadDocument) {
+        mLogger.error(mParser->getLineNumber())
+                << mParser->getLastError()
+                << std::endl;
+        return false;
+    }
+    return true;
+}
+
+bool ResourceParser::parseResources(XmlPullParser* parser) {
+    bool success = true;
+
+    std::u16string comment;
+    while (XmlPullParser::isGoodEvent(parser->next())) {
+        const XmlPullParser::Event event = parser->getEvent();
+        if (event == XmlPullParser::Event::kComment) {
+            comment = parser->getComment();
+            continue;
+        }
+
+        if (event == XmlPullParser::Event::kText) {
+            if (!util::trimWhitespace(parser->getText()).empty()) {
+                comment = u"";
+            }
+            continue;
+        }
+
+        if (event != XmlPullParser::Event::kStartElement) {
+            continue;
+        }
+
+        ScopedXmlPullParser childParser(parser);
+
+        if (!childParser.getElementNamespace().empty()) {
+            // Skip unknown namespace.
+            continue;
+        }
+
+        StringPiece16 name = childParser.getElementName();
+        if (name == u"skip" || name == u"eat-comment") {
+            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;
+            continue;
+        }
+
+        // Copy because our iterator will go out of scope when
+        // we parse more XML.
+        std::u16string attributeName = attrIter->value;
+
+        if (name == 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;
+                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 (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
+        mLogger.error(parser->getLineNumber())
+                << parser->getLastError()
+                << std::endl;
+        return false;
+    }
+    return success;
+}
+
+
+
+enum {
+    kAllowRawString = true,
+    kNoRawString = false
+};
+
+/**
+ * Reads the entire XML subtree and attempts to parse it as some Item,
+ * with typeMask denoting which items it can be. If allowRawValue is
+ * true, a RawString is returned if the XML couldn't be parsed as
+ * an Item. If allowRawValue is false, nullptr is returned in this
+ * case.
+ */
+std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, uint32_t typeMask,
+                                               bool allowRawValue) {
+    const size_t beginXmlLine = parser->getLineNumber();
+
+    std::u16string rawValue;
+    StyleString styleString;
+    if (!flattenXmlSubtree(parser, &rawValue, &styleString)) {
+        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 }));
+    }
+
+    auto onCreateReference = [&](const ResourceName& name) {
+        mTable->addResource(name, {}, mSource.line(beginXmlLine), util::make_unique<Id>());
+    };
+
+    // Process the raw value.
+    std::unique_ptr<Item> processedItem = parseItemForAttribute(rawValue, typeMask,
+                                                                mTable->getPackage(),
+                                                                onCreateReference);
+    if (processedItem) {
+        return processedItem;
+    }
+
+    // Try making a regular string.
+    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 }));
+    }
+
+    // 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 }));
+    }
+    return {};
+}
+
+bool ResourceParser::parseString(XmlPullParser* parser, const ResourceNameRef& resourceName) {
+    const SourceLine source = mSource.line(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).
+
+    // 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.
+            return true;
+        }
+    }
+
+    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;
+        return false;
+    }
+
+    return mTable->addResource(resourceName, mConfig, source, std::move(processedItem));
+}
+
+bool ResourceParser::parseColor(XmlPullParser* parser, const ResourceNameRef& resourceName) {
+    const SourceLine source = mSource.line(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;
+        return false;
+    }
+    return mTable->addResource(resourceName, mConfig, source, std::move(item));
+}
+
+bool ResourceParser::parsePrimitive(XmlPullParser* parser, const ResourceNameRef& resourceName) {
+    const SourceLine source = mSource.line(parser->getLineNumber());
+
+    uint32_t typeMask = 0;
+    switch (resourceName.type) {
+        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::kBool:
+            typeMask |= android::ResTable_map::TYPE_BOOLEAN;
+            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;
+        return false;
+    }
+
+    return mTable->addResource(resourceName, mConfig, source, std::move(item));
+}
+
+bool ResourceParser::parsePublic(XmlPullParser* parser, const StringPiece16& name) {
+    const SourceLine source = mSource.line(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;
+        return false;
+    }
+
+    const ResourceType* parsedType = parseResourceType(typeAttrIter->value);
+    if (!parsedType) {
+        mLogger.error(source.line)
+                << "invalid resource type '"
+                << typeAttrIter->value
+                << "' in <public>."
+                << std::endl;
+        return false;
+    }
+
+    ResourceNameRef resourceName { {}, *parsedType, name };
+    ResourceId resourceId;
+
+    const auto idAttrIter = parser->findAttribute(u"", u"id");
+    if (idAttrIter != endAttrIter && !idAttrIter->value.empty()) {
+        android::Res_value val;
+        bool result = android::ResTable::stringToInt(idAttrIter->value.data(),
+                                                     idAttrIter->value.size(), &val);
+        resourceId.id = val.data;
+        if (!result || !resourceId.isValid()) {
+            mLogger.error(source.line)
+                    << "invalid resource ID '"
+                    << idAttrIter->value
+                    << "' in <public>."
+                    << std::endl;
+            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>());
+    }
+
+    return mTable->markPublic(resourceName, resourceId, source);
+}
+
+static uint32_t parseFormatType(const StringPiece16& piece) {
+    if (piece == u"reference")      return android::ResTable_map::TYPE_REFERENCE;
+    else if (piece == u"string")    return android::ResTable_map::TYPE_STRING;
+    else if (piece == u"integer")   return android::ResTable_map::TYPE_INTEGER;
+    else if (piece == u"boolean")   return android::ResTable_map::TYPE_BOOLEAN;
+    else if (piece == u"color")     return android::ResTable_map::TYPE_COLOR;
+    else if (piece == u"float")     return android::ResTable_map::TYPE_FLOAT;
+    else if (piece == u"dimension") return android::ResTable_map::TYPE_DIMENSION;
+    else if (piece == u"fraction")  return android::ResTable_map::TYPE_FRACTION;
+    else if (piece == u"enum")      return android::ResTable_map::TYPE_ENUM;
+    else if (piece == u"flags")     return android::ResTable_map::TYPE_FLAGS;
+    return 0;
+}
+
+static uint32_t parseFormatAttribute(const StringPiece16& str) {
+    uint32_t mask = 0;
+    for (StringPiece16 part : util::tokenize(str, u'|')) {
+        StringPiece16 trimmedPart = util::trimWhitespace(part);
+        uint32_t type = parseFormatType(trimmedPart);
+        if (type == 0) {
+            return 0;
+        }
+        mask |= type;
+    }
+    return mask;
+}
+
+bool ResourceParser::parseAttr(XmlPullParser* parser, const ResourceNameRef& resourceName) {
+    const SourceLine source = mSource.line(parser->getLineNumber());
+    std::unique_ptr<Attribute> attr = parseAttrImpl(parser, resourceName, false);
+    if (!attr) {
+        return false;
+    }
+    return mTable->addResource(resourceName, mConfig, source, std::move(attr));
+}
+
+std::unique_ptr<Attribute> ResourceParser::parseAttrImpl(XmlPullParser* parser,
+                                                         const ResourceNameRef& resourceName,
+                                                         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);
+        if (typeMask == 0) {
+            mLogger.error(parser->getLineNumber())
+                    << "invalid attribute format '"
+                    << formatAttrIter->value
+                    << "'."
+                    << std::endl;
+            return {};
+        }
+    }
+
+    std::vector<Attribute::Symbol> items;
+
+    bool error = false;
+    while (XmlPullParser::isGoodEvent(parser->next())) {
+        if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+            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;
+            }
+            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;
+            } else {
+                items.push_back(std::move(item));
+            }
+        } else {
+            error = true;
+        }
+    }
+
+    if (error) {
+        return {};
+    }
+
+    std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak);
+    attr->symbols.swap(items);
+    attr->typeMask = typeMask ? typeMask : android::ResTable_map::TYPE_ANY;
+    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;
+    }
+
+    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;
+    }
+
+    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;
+    }
+
+    outSymbol->symbol.name = ResourceName {
+            mTable->getPackage(), ResourceType::kId, nameAttrIter->value };
+    outSymbol->value = val.data;
+    return true;
+}
+
+static bool parseXmlAttributeName(StringPiece16 str, ResourceNameRef* outRef) {
+    str = util::trimWhitespace(str);
+    const char16_t* const start = str.data();
+    const char16_t* const end = start + str.size();
+    const char16_t* p = start;
+
+    StringPiece16 package;
+    StringPiece16 name;
+    while (p != end) {
+        if (*p == u':') {
+            package = StringPiece16(start, p - start);
+            name = StringPiece16(p + 1, end - (p + 1));
+            break;
+        }
+        p++;
+    }
+
+    outRef->package = package;
+    outRef->type = ResourceType::kAttr;
+    if (name.size() == 0) {
+        outRef->entry = str;
+    } else {
+        outRef->entry = name;
+    }
+    return true;
+}
+
+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;
+        return false;
+    }
+
+    ResourceNameRef keyRef;
+    if (!parseXmlAttributeName(nameAttrIter->value, &keyRef)) {
+        mLogger.error(parser->getLineNumber())
+                << "invalid attribute name '"
+                << nameAttrIter->value
+                << "'."
+                << std::endl;
+        return false;
+    }
+
+    if (keyRef.package.empty()) {
+        keyRef.package = mTable->getPackage();
+    }
+
+    // Create a copy instead of a reference because we
+    // are about to invalidate keyRef when advancing the parser.
+    ResourceName key = keyRef.toResourceName();
+
+    std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString);
+    if (!value) {
+        return false;
+    }
+
+    style.entries.push_back(Style::Entry{ Reference(key), std::move(value) });
+    return true;
+}
+
+bool ResourceParser::parseStyle(XmlPullParser* parser, const ResourceNameRef& resourceName) {
+    const SourceLine source = mSource.line(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) {
+        ResourceNameRef ref;
+        bool create = false;
+        bool privateRef = false;
+        if (tryParseReference(parentAttrIter->value, &ref, &create, &privateRef)) {
+            if (create) {
+                mLogger.error(source.line)
+                        << "parent of style can not be an ID."
+                        << std::endl;
+                return false;
+            }
+            style->parent.name = ref.toResourceName();
+            style->parent.privateReference = privateRef;
+        } else if (tryParseAttributeReference(parentAttrIter->value, &ref)) {
+            style->parent.name = ref.toResourceName();
+        } else {
+            // TODO(adamlesinski): Try parsing without the '@' or '?'.
+            // Also, make sure to check the entry name for weird symbols.
+            style->parent.name = ResourceName {
+                {}, ResourceType::kStyle, parentAttrIter->value
+            };
+        }
+
+        if (style->parent.name.package.empty()) {
+            style->parent.name.package = mTable->getPackage();
+        }
+    }
+
+    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())) {
+        if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+            continue;
+        }
+
+        ScopedXmlPullParser childParser(parser);
+
+        if (childParser.getElementName() != u"item") {
+            mLogger.error(childParser.getLineNumber())
+                    << "unexpected tag <"
+                    << childParser.getElementName()
+                    << "> in <array> resource."
+                    << std::endl;
+            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(array));
+}
+
+bool ResourceParser::parsePlural(XmlPullParser* parser, const ResourceNameRef& resourceName) {
+    const SourceLine source = mSource.line(parser->getLineNumber());
+    std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+
+    bool success = true;
+    while (XmlPullParser::isGoodEvent(parser->next())) {
+        if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+            continue;
+        }
+
+        ScopedXmlPullParser childParser(parser);
+
+        if (!childParser.getElementNamespace().empty() ||
+                childParser.getElementName() != u"item") {
+            success = false;
+            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;
+        }
+
+        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 {
+            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;
+        }
+    }
+
+    if (!success) {
+        return false;
+    }
+
+    return mTable->addResource(resourceName, mConfig, source, std::move(plural));
+}
+
+bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser,
+                                           const ResourceNameRef& resourceName) {
+    const SourceLine source = mSource.line(parser->getLineNumber());
+    std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
+
+    bool success = true;
+    while (XmlPullParser::isGoodEvent(parser->next())) {
+        if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+            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");
+            if (attrIter == endAttrIter || attrIter->value.empty()) {
+                mLogger.error(childParser.getLineNumber())
+                        << "<attr> tag must have a 'name' attribute."
+                        << std::endl;
+                success = false;
+                continue;
+            }
+
+            // Copy because our iterator will be invalidated.
+            std::u16string attrName = attrIter->value;
+
+            ResourceNameRef attrResourceName = {
+                    mTable->getPackage(),
+                    ResourceType::kAttr,
+                    attrName
+            };
+
+            std::unique_ptr<Attribute> attr = parseAttrImpl(&childParser, attrResourceName, true);
+            if (!attr) {
+                success = false;
+                continue;
+            }
+
+            styleable->entries.emplace_back(attrResourceName);
+
+            success &= mTable->addResource(attrResourceName, mConfig,
+                                           mSource.line(childParser.getLineNumber()),
+                                           std::move(attr));
+
+        } else if (elementName != u"eat-comment" && elementName != u"skip") {
+            mLogger.error(childParser.getLineNumber())
+                    << "<"
+                    << elementName
+                    << "> is not allowed inside <declare-styleable>."
+                    << std::endl;
+            success = false;
+        }
+    }
+
+    if (!success) {
+        return false;
+    }
+
+    return mTable->addResource(resourceName, mConfig, source, std::move(styleable));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
new file mode 100644
index 0000000..96bba4f
--- /dev/null
+++ b/tools/aapt2/ResourceParser.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_RESOURCE_PARSER_H
+#define AAPT_RESOURCE_PARSER_H
+
+#include "ConfigDescription.h"
+#include "Logger.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "StringPiece.h"
+#include "StringPool.h"
+#include "XmlPullParser.h"
+
+#include <istream>
+#include <memory>
+
+namespace aapt {
+
+/*
+ * Parses an XML file for resources and adds them to a ResourceTable.
+ */
+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 a Reference object if the string was parsed as a resource or attribute reference,
+     * ( @[+][package:]type/name | ?[package:]type/name )
+     * assigning defaultPackage if the package was not present in the string, and setting
+     * outCreate to true if the '+' was present in the string.
+     */
+    static std::unique_ptr<Reference> tryParseReference(const StringPiece16& str,
+                                                        const StringPiece16& defaultPackage,
+                                                        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 defaultPackage is used when the string is a reference with no defined package.
+     * 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, const StringPiece16& defaultPackage,
+            std::function<void(const ResourceName&)> onCreateReference = {});
+
+    static std::unique_ptr<Item> parseItemForAttribute(
+            const StringPiece16& value, uint32_t typeMask, const StringPiece16& defaultPackage,
+            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(const ResourceParser&) = delete; // No copy.
+
+    bool parse();
+
+private:
+    /*
+     * Parses the XML subtree as a StyleString (flattened XML representation for strings
+     * with formatting). If successful, `outStyleString`
+     * contains the escaped and whitespace trimmed text, while `outRawString`
+     * contains the unescaped text. Returns true on success.
+     */
+    bool flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString,\
+                           StyleString* outStyleString);
+
+    /*
+     * Parses the XML subtree and converts it to an Item. The type of Item that can be
+     * parsed is denoted by the `typeMask`. If `allowRawValue` is true and the subtree
+     * can not be parsed as a regular Item, then a RawString is returned. Otherwise
+     * this returns nullptr.
+     */
+    std::unique_ptr<Item> parseXml(XmlPullParser* parser, uint32_t typeMask, bool allowRawValue);
+
+    bool parseResources(XmlPullParser* parser);
+    bool parseString(XmlPullParser* parser, const ResourceNameRef& resourceName);
+    bool parseColor(XmlPullParser* parser, const ResourceNameRef& resourceName);
+    bool parsePrimitive(XmlPullParser* parser, const ResourceNameRef& resourceName);
+    bool parsePublic(XmlPullParser* parser, const StringPiece16& name);
+    bool parseAttr(XmlPullParser* parser, const ResourceNameRef& resourceName);
+    std::unique_ptr<Attribute> parseAttrImpl(XmlPullParser* parser,
+                                             const ResourceNameRef& resourceName,
+                                             bool weak);
+    bool parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag,
+                             Attribute::Symbol* outSymbol);
+    bool parseStyle(XmlPullParser* parser, const ResourceNameRef& resourceName);
+    bool parseUntypedItem(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;
+    Source mSource;
+    ConfigDescription mConfig;
+    SourceLogger mLogger;
+    std::shared_ptr<XmlPullParser> mParser;
+};
+
+} // namespace aapt
+
+#endif // AAPT_RESOURCE_PARSER_H
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
new file mode 100644
index 0000000..5afbaf4
--- /dev/null
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -0,0 +1,399 @@
+/*
+ * 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 "ResourceParser.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "SourceXmlPullParser.h"
+
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+
+namespace aapt {
+
+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));
+}
+
+struct ResourceParserTest : public ::testing::Test {
+    virtual void SetUp() override {
+        mTable = std::make_shared<ResourceTable>();
+        mTable->setPackage(u"android");
+    }
+
+    ::testing::AssertionResult testParse(std::istream& in) {
+        std::stringstream input(kXmlPreamble);
+        input << "<resources>" << std::endl
+              << in.rdbuf() << std::endl
+              << "</resources>" << std::endl;
+        ResourceParser parser(mTable, Source{ "test" }, {},
+                              std::make_shared<SourceXmlPullParser>(input));
+        if (parser.parse()) {
+            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::stringstream input("<string name=\"foo\">   \"  hey there \" </string>");
+    ASSERT_TRUE(testParse(input));
+
+    const String* str = findResource<String>(ResourceName{
+            u"android", ResourceType::kString, u"foo"});
+    ASSERT_NE(nullptr, str);
+    EXPECT_EQ(std::u16string(u"  hey there "), *str->value);
+}
+
+TEST_F(ResourceParserTest, ParseEscapedString) {
+    std::stringstream input("<string name=\"foo\">\\?123</string>");
+    ASSERT_TRUE(testParse(input));
+
+    const String* str = findResource<String>(ResourceName{
+            u"android", ResourceType::kString, u"foo" });
+    ASSERT_NE(nullptr, str);
+    EXPECT_EQ(std::u16string(u"?123"), *str->value);
+}
+
+TEST_F(ResourceParserTest, ParseAttr) {
+    std::stringstream input;
+    input << "<attr name=\"foo\" format=\"string\"/>" << std::endl
+          << "<attr name=\"bar\"/>" << std::endl;
+    ASSERT_TRUE(testParse(input));
+
+    const Attribute* attr = findResource<Attribute>(ResourceName{
+            u"android", ResourceType::kAttr, u"foo"});
+    EXPECT_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);
+    EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask);
+}
+
+TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) {
+    std::stringstream input;
+    input << "<declare-styleable name=\"Styleable\">" << std::endl
+          << "  <attr name=\"foo\" />" << std::endl
+          << "</declare-styleable>" << std::endl
+          << "<attr name=\"foo\" format=\"string\"/>" << std::endl;
+    ASSERT_TRUE(testParse(input));
+
+    const Attribute* attr = findResource<Attribute>(ResourceName{
+            u"android", ResourceType::kAttr, u"foo"});
+    ASSERT_NE(nullptr, attr);
+    EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
+}
+
+TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) {
+    std::stringstream input;
+    input << "<declare-styleable name=\"Theme\">" << std::endl
+          << "  <attr name=\"foo\" />" << std::endl
+          << "</declare-styleable>" << std::endl
+          << "<declare-styleable name=\"Window\">" << std::endl
+          << "  <attr name=\"foo\" format=\"boolean\"/>" << std::endl
+          << "</declare-styleable>" << std::endl;
+
+    ASSERT_TRUE(testParse(input));
+
+    const Attribute* attr = findResource<Attribute>(ResourceName{
+            u"android", ResourceType::kAttr, u"foo"});
+    ASSERT_NE(nullptr, attr);
+    EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->typeMask);
+}
+
+TEST_F(ResourceParserTest, ParseEnumAttr) {
+    std::stringstream input;
+    input << "<attr name=\"foo\">" << std::endl
+          << "  <enum name=\"bar\" value=\"0\"/>" << std::endl
+          << "  <enum name=\"bat\" value=\"1\"/>" << std::endl
+          << "  <enum name=\"baz\" value=\"2\"/>" << std::endl
+          << "</attr>" << std::endl;
+    ASSERT_TRUE(testParse(input));
+
+    const Attribute* enumAttr = findResource<Attribute>(ResourceName{
+            u"android", ResourceType::kAttr, u"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");
+    EXPECT_EQ(enumAttr->symbols[0].value, 0u);
+
+    EXPECT_EQ(enumAttr->symbols[1].symbol.name.entry, u"bat");
+    EXPECT_EQ(enumAttr->symbols[1].value, 1u);
+
+    EXPECT_EQ(enumAttr->symbols[2].symbol.name.entry, u"baz");
+    EXPECT_EQ(enumAttr->symbols[2].value, 2u);
+}
+
+TEST_F(ResourceParserTest, ParseFlagAttr) {
+    std::stringstream input;
+    input << "<attr name=\"foo\">" << std::endl
+          << "  <flag name=\"bar\" value=\"0\"/>" << std::endl
+          << "  <flag name=\"bat\" value=\"1\"/>" << std::endl
+          << "  <flag name=\"baz\" value=\"2\"/>" << std::endl
+          << "</attr>" << std::endl;
+    ASSERT_TRUE(testParse(input));
+
+    const Attribute* flagAttr = findResource<Attribute>(ResourceName{
+            u"android", ResourceType::kAttr, u"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");
+    EXPECT_EQ(flagAttr->symbols[0].value, 0u);
+
+    EXPECT_EQ(flagAttr->symbols[1].symbol.name.entry, u"bat");
+    EXPECT_EQ(flagAttr->symbols[1].value, 1u);
+
+    EXPECT_EQ(flagAttr->symbols[2].symbol.name.entry, u"baz");
+    EXPECT_EQ(flagAttr->symbols[2].value, 2u);
+
+    std::unique_ptr<BinaryPrimitive> flagValue =
+            ResourceParser::tryParseFlagSymbol(*flagAttr, u"baz|bat");
+    ASSERT_NE(flagValue, nullptr);
+    EXPECT_EQ(flagValue->value.data, 1u | 2u);
+}
+
+TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) {
+    std::stringstream input;
+    input << "<attr name=\"foo\">" << std::endl
+          << "  <enum name=\"bar\" value=\"0\"/>" << std::endl
+          << "  <enum name=\"bat\" value=\"1\"/>" << std::endl
+          << "  <enum name=\"bat\" value=\"2\"/>" << std::endl
+          << "</attr>" << std::endl;
+    ASSERT_FALSE(testParse(input));
+}
+
+TEST_F(ResourceParserTest, ParseStyle) {
+    std::stringstream input;
+    input << "<style name=\"foo\" parent=\"fu\">" << std::endl
+          << "  <item name=\"bar\">#ffffffff</item>" << std::endl
+          << "  <item name=\"bat\">@string/hey</item>" << std::endl
+          << "  <item name=\"baz\"><b>hey</b></item>" << std::endl
+          << "</style>" << std::endl;
+    ASSERT_TRUE(testParse(input));
+
+    const Style* style = findResource<Style>(ResourceName{
+            u"android", ResourceType::kStyle, u"foo"});
+    ASSERT_NE(style, nullptr);
+    EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kStyle, u"fu"), style->parent.name);
+    ASSERT_EQ(style->entries.size(), 3u);
+
+    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" }));
+}
+
+TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) {
+    std::stringstream input;
+    input << "<string name=\"foo\">@+id/bar</string>" << std::endl;
+    ASSERT_TRUE(testParse(input));
+
+    const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"bar"});
+    ASSERT_NE(id, nullptr);
+}
+
+TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) {
+    std::stringstream input;
+    input << "<declare-styleable name=\"foo\">" << std::endl
+          << "  <attr name=\"bar\" />" << std::endl
+          << "  <attr name=\"bat\" format=\"string|reference\"/>" << std::endl
+          << "</declare-styleable>" << std::endl;
+    ASSERT_TRUE(testParse(input));
+
+    const Attribute* attr = findResource<Attribute>(ResourceName{
+            u"android", ResourceType::kAttr, u"bar"});
+    ASSERT_NE(attr, nullptr);
+    EXPECT_TRUE(attr->isWeak());
+
+    attr = findResource<Attribute>(ResourceName{ u"android", ResourceType::kAttr, u"bat"});
+    ASSERT_NE(attr, nullptr);
+    EXPECT_TRUE(attr->isWeak());
+
+    const Styleable* styleable = findResource<Styleable>(ResourceName{
+            u"android", ResourceType::kStyleable, u"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);
+}
+
+TEST_F(ResourceParserTest, ParseArray) {
+    std::stringstream input;
+    input << "<array name=\"foo\">" << std::endl
+          << "  <item>@string/ref</item>" << std::endl
+          << "  <item>hey</item>" << std::endl
+          << "  <item>23</item>" << std::endl
+          << "</array>" << std::endl;
+    ASSERT_TRUE(testParse(input));
+
+    const Array* array = findResource<Array>(ResourceName{
+            u"android", ResourceType::kArray, u"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()));
+}
+
+TEST_F(ResourceParserTest, ParsePlural) {
+    std::stringstream input;
+    input << "<plurals name=\"foo\">" << std::endl
+          << "  <item quantity=\"other\">apples</item>" << std::endl
+          << "  <item quantity=\"one\">apple</item>" << std::endl
+          << "</plurals>" << std::endl
+          << std::endl;
+    ASSERT_TRUE(testParse(input));
+}
+
+TEST_F(ResourceParserTest, ParseCommentsWithResource) {
+    std::stringstream input;
+    input << "<!-- This is a comment -->" << std::endl
+          << "<string name=\"foo\">Hi</string>" << std::endl;
+    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);
+    ASSERT_NE(entry, nullptr);
+    ASSERT_FALSE(entry->values.empty());
+    EXPECT_EQ(entry->values.front().comment, u"This is a comment");
+}
+
+/*
+ * Declaring an ID as public should not require a separate definition
+ * (as an ID has no value).
+ */
+TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) {
+    std::stringstream input("<public type=\"id\" name=\"foo\"/>");
+    ASSERT_TRUE(testParse(input));
+
+    const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"foo" });
+    ASSERT_NE(nullptr, id);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
new file mode 100644
index 0000000..0b3dd78
--- /dev/null
+++ b/tools/aapt2/ResourceTable.cpp
@@ -0,0 +1,334 @@
+/*
+ * 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 "Logger.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "Util.h"
+
+#include <algorithm>
+#include <androidfw/ResourceTypes.h>
+#include <memory>
+#include <string>
+#include <tuple>
+
+namespace aapt {
+
+static bool compareConfigs(const ResourceConfigValue& lhs, const ConfigDescription& rhs) {
+    return lhs.config < rhs;
+}
+
+static bool lessThanType(const std::unique_ptr<ResourceTableType>& lhs, ResourceType rhs) {
+    return lhs->type < rhs;
+}
+
+static bool lessThanEntry(const std::unique_ptr<ResourceEntry>& lhs, const StringPiece16& rhs) {
+    return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
+}
+
+ResourceTable::ResourceTable() : mPackageId(kUnsetPackageId) {
+}
+
+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;
+        }
+    }
+    return *mTypes.emplace(iter, new ResourceTableType{ type });
+}
+
+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;
+        }
+    }
+    return *type->entries.emplace(iter, new ResourceEntry{ name });
+}
+
+struct IsAttributeVisitor : ConstValueVisitor {
+    bool isAttribute = false;
+
+    void visit(const Attribute&, ValueVisitorArgs&) override {
+        isAttribute = true;
+    }
+
+    operator bool() {
+        return isAttribute;
+    }
+};
+
+/**
+ * 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, {});
+
+    if (!incomingIsAttr) {
+        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()) {
+            // Override the weak resource with the new strong resource.
+            return 1;
+        }
+        // The existing and incoming values are strong, this is an error
+        // if the values are not both attributes.
+        return 0;
+    }
+
+    if (!existingIsAttr) {
+        if (existing.isWeak()) {
+            // The existing value is not an attribute and it is weak,
+            // so take the incoming attribute value.
+            return 1;
+        }
+        // The existing value is not an attribute and it is strong,
+        // so the incoming attribute value is an error.
+        return 0;
+    }
+
+    //
+    // 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) {
+        // The two attributes are both DECLs, but they are plain attributes
+        // with the same formats.
+        // Keep the strongest one.
+        return existingAttr.isWeak() ? 1 : -1;
+    }
+
+    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) {
+        // The incoming attribute may be a USE instead of a DECL.
+        // Keep the existing attribute.
+        return -1;
+    }
+    return 0;
+}
+
+static constexpr const char16_t* kValidNameChars = u"._-";
+
+bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId resId,
+        const ConfigDescription& config, const SourceLine& source,
+        std::unique_ptr<Value> value) {
+    if (!name.package.empty() && name.package != mPackage) {
+        Logger::error(source)
+                << "resource '"
+                << name
+                << "' has incompatible package. Must be '"
+                << mPackage
+                << "'."
+                << std::endl;
+        return false;
+    }
+
+    auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, kValidNameChars);
+    if (badCharIter != name.entry.end()) {
+        Logger::error(source)
+                << "resource '"
+                << name
+                << "' has invalid entry name '"
+                << name.entry
+                << "'. Invalid character '"
+                << *badCharIter
+                << "'."
+                << std::endl;
+        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;
+        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;
+        return false;
+    }
+
+    const auto endIter = std::end(entry->values);
+    auto iter = std::lower_bound(std::begin(entry->values), 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);
+        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;
+            return false;
+        }
+    }
+
+    if (resId.isValid()) {
+        type->typeId = resId.typeId();
+        entry->entryId = resId.entryId();
+    }
+    return true;
+}
+
+bool ResourceTable::addResource(const ResourceNameRef& name, const ConfigDescription& config,
+                                const SourceLine& source, std::unique_ptr<Value> value) {
+    return addResource(name, ResourceId{}, config, source, std::move(value));
+}
+
+bool ResourceTable::markPublic(const ResourceNameRef& name, const ResourceId resId,
+                               const SourceLine& source) {
+    if (!name.package.empty() && name.package != mPackage) {
+        Logger::error(source)
+                << "resource '"
+                << name
+                << "' has incompatible package. Must be '"
+                << mPackage
+                << "'."
+            << std::endl;
+        return false;
+    }
+
+    auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, kValidNameChars);
+    if (badCharIter != name.entry.end()) {
+        Logger::error(source)
+                << "resource '"
+                << name
+                << "' has invalid entry name '"
+                << name.entry
+                << "'. Invalid character '"
+                << *badCharIter
+                << "'."
+                << std::endl;
+        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;
+        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;
+        return false;
+    }
+
+    type->publicStatus.isPublic = true;
+    entry->publicStatus.isPublic = true;
+
+    if (resId.isValid()) {
+        type->typeId = resId.typeId();
+        entry->entryId = resId.entryId();
+    }
+
+    if (entry->values.empty()) {
+        entry->values.push_back(ResourceConfigValue{ {}, source, {},
+                                    util::make_unique<Sentinel>() });
+    }
+    return true;
+}
+
+std::tuple<const ResourceTableType*, const ResourceEntry*>
+ResourceTable::findResource(const ResourceNameRef& name) const {
+    if (name.package != mPackage) {
+        return {nullptr, nullptr};
+    }
+
+    auto iter = std::lower_bound(mTypes.begin(), mTypes.end(), name.type, lessThanType);
+    if (iter == mTypes.end() || (*iter)->type != name.type) {
+        return {nullptr, nullptr};
+    }
+
+    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) {
+        return {nullptr, nullptr};
+    }
+    return {iter->get(), iter2->get()};
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
new file mode 100644
index 0000000..57b5213
--- /dev/null
+++ b/tools/aapt2/ResourceTable.h
@@ -0,0 +1,254 @@
+/*
+ * 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_H
+#define AAPT_RESOURCE_TABLE_H
+
+#include "ConfigDescription.h"
+#include "Resource.h"
+#include "ResourceValues.h"
+#include "Source.h"
+#include "StringPool.h"
+
+#include <memory>
+#include <string>
+#include <tuple>
+#include <vector>
+
+namespace aapt {
+
+/**
+ * The Public status of a resource.
+ */
+struct Public {
+    bool isPublic = false;
+    std::u16string comment;
+};
+
+/**
+ * The resource value for a specific configuration.
+ */
+struct ResourceConfigValue {
+    ConfigDescription config;
+    SourceLine source;
+    std::u16string comment;
+    std::unique_ptr<Value> value;
+};
+
+/**
+ * Represents a resource entry, which may have
+ * 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
+     * when doing lookups.
+     */
+    const std::u16string name;
+
+    /**
+     * The entry ID for this resource.
+     */
+    size_t entryId;
+
+    /**
+     * Whether this resource is public (and must maintain the same
+     * entry ID across builds).
+     */
+    Public publicStatus;
+
+    /**
+     * The resource's values for each configuration.
+     */
+    std::vector<ResourceConfigValue> values;
+
+    inline ResourceEntry(const StringPiece16& _name);
+    inline ResourceEntry(const ResourceEntry* rhs);
+};
+
+/**
+ * Represents a resource type, which holds entries defined
+ * for this type.
+ */
+struct ResourceTableType {
+    enum {
+        kUnsetTypeId = 0xffffffffu
+    };
+
+    /**
+     * The logical type of resource (string, drawable, layout, etc.).
+     */
+    const ResourceType type;
+
+    /**
+     * The type ID for this resource.
+     */
+    size_t typeId;
+
+    /**
+     * Whether this type is public (and must maintain the same
+     * type ID across builds).
+     */
+    Public publicStatus;
+
+    /**
+     * List of resources for this type.
+     */
+    std::vector<std::unique_ptr<ResourceEntry>> entries;
+
+    ResourceTableType(const ResourceType _type);
+    ResourceTableType(const ResourceTableType* rhs);
+};
+
+/**
+ * The container and index for all resources defined for an app. This gets
+ * flattened into a binary resource table (resources.arsc).
+ */
+class ResourceTable {
+public:
+    using iterator = std::vector<std::unique_ptr<ResourceTableType>>::iterator;
+    using const_iterator = std::vector<std::unique_ptr<ResourceTableType>>::const_iterator;
+
+    enum {
+        kUnsetPackageId = 0xffffffff
+    };
+
+    ResourceTable();
+
+    size_t getPackageId() const;
+    void setPackageId(size_t packageId);
+
+    const std::u16string& getPackage() const;
+    void setPackage(const StringPiece16& package);
+
+    bool addResource(const ResourceNameRef& name, const ConfigDescription& config,
+                     const SourceLine& source, std::unique_ptr<Value> value);
+
+    bool addResource(const ResourceNameRef& name, const ResourceId resId,
+                     const ConfigDescription& config, const SourceLine& source,
+                     std::unique_ptr<Value> value);
+
+    bool markPublic(const ResourceNameRef& name, const ResourceId resId, const SourceLine& source);
+
+    /**
+     * Returns the string pool used by this ResourceTable.
+     * Values that reference strings should use this pool to create
+     * their strings.
+     */
+    StringPool& getValueStringPool();
+    const StringPool& getValueStringPool() const;
+
+    std::tuple<const ResourceTableType*, const ResourceEntry*>
+    findResource(const ResourceNameRef& name) const;
+
+    iterator begin();
+    iterator end();
+    const_iterator begin() const;
+    const_iterator end() const;
+
+private:
+    std::unique_ptr<ResourceTableType>& findOrCreateType(ResourceType type);
+    std::unique_ptr<ResourceEntry>& findOrCreateEntry(std::unique_ptr<ResourceTableType>& type,
+                                                      const StringPiece16& name);
+
+    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;
+};
+
+//
+// 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/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
new file mode 100644
index 0000000..785ea15
--- /dev/null
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -0,0 +1,228 @@
+/*
+ * 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 "Util.h"
+
+#include <algorithm>
+#include <gtest/gtest.h>
+#include <ostream>
+#include <string>
+
+namespace aapt {
+
+struct TestValue : public Value {
+    std::u16string value;
+
+    TestValue(StringPiece16 str) : value(str.toString()) {
+    }
+
+    TestValue* clone() 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 {}
+};
+
+struct TestWeakValue : public Value {
+    bool isWeak() const override {
+        return true;
+    }
+
+    TestWeakValue* clone() 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) {
+    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")));
+
+    EXPECT_FALSE(table.addResource(
+            ResourceNameRef{ u"android", ResourceType::kId, u"hey:there" },
+            {}, SourceLine{ "test.xml", 21 },
+            util::make_unique<TestValue>(u"rawValue")));
+}
+
+TEST(ResourceTableTest, AddOneResource) {
+    const std::u16string kAndroidPackage = u"android";
+
+    ResourceTable table;
+    table.setPackage(kAndroidPackage);
+
+    const ResourceName name = { kAndroidPackage, ResourceType::kAttr, u"id" };
+
+    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{};
+                      }));
+}
+
+TEST(ResourceTableTest, AddMultipleResources) {
+    const std::u16string kAndroidPackage = u"android";
+    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")));
+
+    EXPECT_TRUE(table.addResource(
+            ResourceName{ kAndroidPackage, ResourceType::kAttr, u"id" },
+            config, SourceLine{ "test/path/file.xml", 12 },
+            util::make_unique<TestValue>(u"rawValue")));
+
+    EXPECT_TRUE(table.addResource(
+            ResourceName{ kAndroidPackage, ResourceType::kString, u"ok" },
+            config, SourceLine{ "test/path/file.xml", 14 },
+            util::make_unique<TestValue>(u"Ok")));
+
+    EXPECT_TRUE(table.addResource(
+            ResourceName{ kAndroidPackage, ResourceType::kString, u"ok" },
+            languageConfig, SourceLine{ "test/path/file.xml", 20 },
+            util::make_unique<TestValue>(u"Tak")));
+
+    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);
+}
+
+TEST(ResourceTableTest, OverrideWeakResourceValue) {
+    const std::u16string kAndroid = u"android";
+
+    ResourceTable table;
+    table.setPackage(kAndroid);
+    table.setPackageId(0x01);
+
+    ASSERT_TRUE(table.addResource(
+            ResourceName{ kAndroid, ResourceType::kAttr, u"foo" },
+            {}, {}, util::make_unique<TestWeakValue>()));
+
+    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());
+
+    ASSERT_TRUE(table.addResource(ResourceName{ kAndroid, ResourceType::kAttr, u"foo" }, {}, {},
+                                  util::make_unique<TestValue>(u"bar")));
+
+    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());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ResourceTypeExtensions.h b/tools/aapt2/ResourceTypeExtensions.h
new file mode 100644
index 0000000..60e225e
--- /dev/null
+++ b/tools/aapt2/ResourceTypeExtensions.h
@@ -0,0 +1,120 @@
+/*
+ * 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_TYPE_EXTENSIONS_H
+#define AAPT_RESOURCE_TYPE_EXTENSIONS_H
+
+#include <androidfw/ResourceTypes.h>
+
+namespace aapt {
+
+/**
+ * New android::ResChunk_header types defined
+ * for AAPT to use.
+ *
+ * TODO(adamlesinski): Consider reserving these
+ * enums in androidfw/ResourceTypes.h to avoid
+ * future collisions.
+ */
+enum {
+    /**
+     * A chunk that holds the string pool
+     * for source entries (path/to/source:line).
+     */
+    RES_TABLE_SOURCE_POOL_TYPE = 0x000e,
+
+    /**
+     * A chunk holding names of externally
+     * defined symbols and offsets to where
+     * they are referenced in the table.
+     */
+    RES_TABLE_SYMBOL_TABLE_TYPE = 0x000f,
+};
+
+/**
+ * New resource types that are meant to only be used
+ * by AAPT and will not end up on the device.
+ */
+struct ExtendedTypes {
+    enum {
+        /**
+         * A sentinel value used when a resource is defined as
+         * public but it has no defined value yet. If we don't
+         * flatten it with some value, we will lose its name.
+         */
+        TYPE_SENTINEL = 0xff,
+
+        /**
+         * A raw string value that hasn't had its escape sequences
+         * processed nor whitespace removed.
+         */
+        TYPE_RAW_STRING = 0xfe
+    };
+};
+
+/**
+ * A chunk with type RES_TABLE_SYMBOL_TABLE_TYPE.
+ * Following the header are count number of SymbolTable_entry
+ * structures, followed by an android::ResStringPool_header.
+ */
+struct SymbolTable_header {
+    android::ResChunk_header header;
+
+    /**
+     * Number of SymbolTable_entry structures following
+     * this header.
+     */
+    uint32_t count;
+};
+
+struct SymbolTable_entry {
+    /**
+     * Offset from the beginning of the resource table
+     * where the symbol entry is referenced.
+     */
+    uint32_t offset;
+
+    /**
+     * The index into the string pool where the name of this
+     * symbol exists.
+     */
+    uint32_t stringIndex;
+};
+
+/**
+ * A structure representing the source of a resourc entry.
+ * Appears after an android::ResTable_entry or android::ResTable_map_entry.
+ *
+ * TODO(adamlesinski): This causes some issues when runtime code checks
+ * the size of an android::ResTable_entry. It assumes it is an
+ * android::ResTable_map_entry if the size is bigger than an android::ResTable_entry
+ * which may not be true if this structure is present.
+ */
+struct ResTable_entry_source {
+    /**
+     * Index into the source string pool.
+     */
+    uint32_t pathIndex;
+
+    /**
+     * Line number this resource was defined on.
+     */
+    uint32_t line;
+};
+
+} // namespace aapt
+
+#endif // AAPT_RESOURCE_TYPE_EXTENSIONS_H
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
new file mode 100644
index 0000000..60ef1a8
--- /dev/null
+++ b/tools/aapt2/ResourceValues.cpp
@@ -0,0 +1,447 @@
+/*
+ * 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 "ResourceTypeExtensions.h"
+#include "ResourceValues.h"
+#include "Util.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <limits>
+
+namespace aapt {
+
+bool Value::isItem() const {
+    return false;
+}
+
+bool Value::isWeak() const {
+    return false;
+}
+
+bool Item::isItem() const {
+    return true;
+}
+
+RawString::RawString(const StringPool::Ref& ref) : value(ref) {
+}
+
+RawString* RawString::clone() const {
+    return new RawString(value);
+}
+
+bool RawString::flatten(android::Res_value& outValue) const {
+    outValue.dataType = ExtendedTypes::TYPE_RAW_STRING;
+    outValue.data = static_cast<uint32_t>(value.getIndex());
+    return true;
+}
+
+void RawString::print(std::ostream& out) const {
+    out << "(raw string) " << *value;
+}
+
+Reference::Reference() : referenceType(Reference::Type::kResource) {
+}
+
+Reference::Reference(const ResourceNameRef& n, Type t) :
+        name(n.toResourceName()), referenceType(t) {
+}
+
+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)
+        ? android::Res_value::TYPE_REFERENCE
+        : android::Res_value::TYPE_ATTRIBUTE;
+    outValue.data = id.id;
+    return true;
+}
+
+Reference* Reference::clone() const {
+    Reference* ref = new Reference();
+    ref->referenceType = referenceType;
+    ref->name = name;
+    ref->id = id;
+    return ref;
+}
+
+void Reference::print(std::ostream& out) const {
+    out << "(reference) ";
+    if (referenceType == Reference::Type::kResource) {
+        out << "@";
+    } else {
+        out << "?";
+    }
+
+    if (name.isValid()) {
+        out << name;
+    }
+
+    if (id.isValid() || Res_INTERNALID(id.id)) {
+        out << " " << id;
+    }
+}
+
+bool Id::isWeak() const {
+    return true;
+}
+
+bool Id::flatten(android::Res_value& out) const {
+    out.dataType = android::Res_value::TYPE_NULL;
+    out.data = android::Res_value::DATA_NULL_UNDEFINED;
+    return true;
+}
+
+Id* Id::clone() const {
+    return new 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.
+    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());
+    return true;
+}
+
+String* String::clone() const {
+    return new 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 {
+    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());
+    return true;
+}
+
+StyledString* StyledString::clone() const {
+    return new StyledString(value);
+}
+
+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 {
+    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());
+    return true;
+}
+
+FileReference* FileReference::clone() const {
+    return new FileReference(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;
+    return true;
+}
+
+BinaryPrimitive* BinaryPrimitive::clone() const {
+    return new BinaryPrimitive(value);
+}
+
+void BinaryPrimitive::print(std::ostream& out) const {
+    switch (value.dataType) {
+        case android::Res_value::TYPE_NULL:
+            out << "(null)";
+            break;
+        case android::Res_value::TYPE_INT_DEC:
+            out << "(integer) " << value.data;
+            break;
+        case android::Res_value::TYPE_INT_HEX:
+            out << "(integer) " << std::hex << value.data << std::dec;
+            break;
+        case android::Res_value::TYPE_INT_BOOLEAN:
+            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;
+            break;
+        default:
+            out << "(unknown 0x" << std::hex << (int) value.dataType << ") 0x"
+                << std::hex << value.data << std::dec;
+            break;
+    }
+}
+
+bool Sentinel::isWeak() const {
+    return true;
+}
+
+bool Sentinel::flatten(android::Res_value& outValue) const {
+    outValue.dataType = ExtendedTypes::TYPE_SENTINEL;
+    outValue.data = 0;
+    return true;
+}
+
+Sentinel* Sentinel::clone() const {
+    return new Sentinel();
+}
+
+void Sentinel::print(std::ostream& out) const {
+    out << "(sentinel)";
+    return;
+}
+
+Attribute::Attribute(bool w, uint32_t t) : weak(w), typeMask(t) {
+}
+
+bool Attribute::isWeak() const {
+    return weak;
+}
+
+Attribute* Attribute::clone() const {
+    Attribute* attr = new Attribute(weak);
+    attr->typeMask = typeMask;
+    std::copy(symbols.begin(), symbols.end(), std::back_inserter(attr->symbols));
+    return attr;
+}
+
+void Attribute::print(std::ostream& out) const {
+    out << "(attr)";
+    if (typeMask == android::ResTable_map::TYPE_ANY) {
+        out << " any";
+        return;
+    }
+
+    bool set = false;
+    if ((typeMask & android::ResTable_map::TYPE_REFERENCE) != 0) {
+        if (!set) {
+            out << " ";
+            set = true;
+        } else {
+            out << "|";
+        }
+        out << "reference";
+    }
+
+    if ((typeMask & android::ResTable_map::TYPE_STRING) != 0) {
+        if (!set) {
+            out << " ";
+            set = true;
+        } else {
+            out << "|";
+        }
+        out << "string";
+    }
+
+    if ((typeMask & android::ResTable_map::TYPE_INTEGER) != 0) {
+        if (!set) {
+            out << " ";
+            set = true;
+        } else {
+            out << "|";
+        }
+        out << "integer";
+    }
+
+    if ((typeMask & android::ResTable_map::TYPE_BOOLEAN) != 0) {
+        if (!set) {
+            out << " ";
+            set = true;
+        } else {
+            out << "|";
+        }
+        out << "boolean";
+    }
+
+    if ((typeMask & android::ResTable_map::TYPE_COLOR) != 0) {
+        if (!set) {
+            out << " ";
+            set = true;
+        } else {
+            out << "|";
+        }
+        out << "color";
+    }
+
+    if ((typeMask & android::ResTable_map::TYPE_FLOAT) != 0) {
+        if (!set) {
+            out << " ";
+            set = true;
+        } else {
+            out << "|";
+        }
+        out << "float";
+    }
+
+    if ((typeMask & android::ResTable_map::TYPE_DIMENSION) != 0) {
+        if (!set) {
+            out << " ";
+            set = true;
+        } else {
+            out << "|";
+        }
+        out << "dimension";
+    }
+
+    if ((typeMask & android::ResTable_map::TYPE_FRACTION) != 0) {
+        if (!set) {
+            out << " ";
+            set = true;
+        } else {
+            out << "|";
+        }
+        out << "fraction";
+    }
+
+    if ((typeMask & android::ResTable_map::TYPE_ENUM) != 0) {
+        if (!set) {
+            out << " ";
+            set = true;
+        } else {
+            out << "|";
+        }
+        out << "enum";
+    }
+
+    if ((typeMask & android::ResTable_map::TYPE_FLAGS) != 0) {
+        if (!set) {
+            out << " ";
+            set = true;
+        } else {
+            out << "|";
+        }
+        out << "flags";
+    }
+
+    out << " ["
+        << util::joiner(symbols.begin(), symbols.end(), ", ")
+        << "]";
+
+    if (weak) {
+        out << " [weak]";
+    }
+}
+
+static ::std::ostream& operator<<(::std::ostream& out, const Attribute::Symbol& s) {
+    return out << s.symbol.name.entry << "=" << s.value;
+}
+
+Style* Style::clone() const {
+    Style* style = new Style();
+    style->parent = parent;
+    for (auto& entry : entries) {
+        style->entries.push_back(Entry{
+                entry.key,
+                std::unique_ptr<Item>(entry.value->clone())
+        });
+    }
+    return style;
+}
+
+void Style::print(std::ostream& out) const {
+    out << "(style) ";
+    if (!parent.name.entry.empty()) {
+        out << parent.name;
+    }
+    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);
+    return out;
+}
+
+Array* Array::clone() const {
+    Array* array = new Array();
+    for (auto& item : items) {
+        array->items.emplace_back(std::unique_ptr<Item>(item->clone()));
+    }
+    return array;
+}
+
+void Array::print(std::ostream& out) const {
+    out << "(array) ["
+        << util::joiner(items.begin(), items.end(), ", ")
+        << "]";
+}
+
+Plural* Plural::clone() const {
+    Plural* p = new Plural();
+    const size_t count = values.size();
+    for (size_t i = 0; i < count; i++) {
+        if (values[i]) {
+            p->values[i] = std::unique_ptr<Item>(values[i]->clone());
+        }
+    }
+    return p;
+}
+
+void Plural::print(std::ostream& out) const {
+    out << "(plural)";
+}
+
+static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Item>& item) {
+    return out << *item;
+}
+
+Styleable* Styleable::clone() const {
+    Styleable* styleable = new Styleable();
+    std::copy(entries.begin(), entries.end(), std::back_inserter(styleable->entries));
+    return styleable;
+}
+
+void Styleable::print(std::ostream& out) const {
+    out << "(styleable) " << " ["
+        << util::joiner(entries.begin(), entries.end(), ", ")
+        << "]";
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
new file mode 100644
index 0000000..f25bcf0
--- /dev/null
+++ b/tools/aapt2/ResourceValues.h
@@ -0,0 +1,456 @@
+/*
+ * 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_VALUES_H
+#define AAPT_RESOURCE_VALUES_H
+
+#include "Resource.h"
+#include "StringPool.h"
+
+#include <array>
+#include <androidfw/ResourceTypes.h>
+#include <ostream>
+#include <vector>
+
+namespace aapt {
+
+struct ValueVisitor;
+struct ConstValueVisitor;
+struct ValueVisitorArgs;
+
+/**
+ * A resource value. This is an all-encompassing representation
+ * of Item and Map and their subclasses. The way to do
+ * type specific operations is to check the Value's type() and
+ * cast it to the appropriate subclass. This isn't super clean,
+ * but it is the simplest strategy.
+ */
+struct Value {
+    /**
+     * Whether or not this is an Item.
+     */
+    virtual bool isItem() const;
+
+    /**
+     * Whether this value is weak and can be overriden without
+     * warning or error. Default for base class is false.
+     */
+    virtual bool isWeak() const;
+
+    /**
+     * 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;
+
+    /**
+     * Clone the value.
+     */
+    virtual Value* clone() const = 0;
+
+    /**
+     * Human readable printout of this value.
+     */
+    virtual void print(std::ostream& out) const = 0;
+};
+
+/**
+ * Inherit from this to get visitor accepting implementations for free.
+ */
+template <typename Derived>
+struct BaseValue : public Value {
+    virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) override;
+    virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const override;
+};
+
+/**
+ * A resource item with a single value. This maps to android::ResTable_entry.
+ */
+struct Item : public Value {
+    /**
+     * An Item is, of course, an Item.
+     */
+    virtual bool isItem() const override;
+
+    /**
+     * Clone the Item.
+     */
+    virtual Item* clone() const override = 0;
+
+    /**
+     * Fills in an android::Res_value structure with this Item's binary representation.
+     * Returns false if an error ocurred.
+     */
+    virtual bool flatten(android::Res_value& outValue) const = 0;
+};
+
+/**
+ * Inherit from this to get visitor accepting implementations for free.
+ */
+template <typename Derived>
+struct BaseItem : public Item {
+    virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) override;
+    virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const override;
+};
+
+/**
+ * A reference to another resource. This maps to android::Res_value::TYPE_REFERENCE.
+ *
+ * A reference can be symbolic (with the name set to a valid resource name) or be
+ * numeric (the id is set to a valid resource ID).
+ */
+struct Reference : public BaseItem<Reference> {
+    enum class Type {
+        kResource,
+        kAttribute,
+    };
+
+    ResourceName name;
+    ResourceId id;
+    Reference::Type referenceType;
+    bool privateReference = false;
+
+    Reference();
+    Reference(const ResourceNameRef& n, Type type = Type::kResource);
+    Reference(const ResourceId& i, Type type = Type::kResource);
+
+    bool flatten(android::Res_value& outValue) const override;
+    Reference* clone() const override;
+    void print(std::ostream& out) const override;
+};
+
+/**
+ * An ID resource. Has no real value, just a place holder.
+ */
+struct Id : public BaseItem<Id> {
+    bool isWeak() const override;
+    bool flatten(android::Res_value& out) const override;
+    Id* clone() const override;
+    void print(std::ostream& out) const override;
+};
+
+/**
+ * A raw, unprocessed string. This may contain quotations,
+ * escape sequences, and whitespace. This shall *NOT*
+ * end up in the final resource table.
+ */
+struct RawString : public BaseItem<RawString> {
+    StringPool::Ref value;
+
+    RawString(const StringPool::Ref& ref);
+
+    bool flatten(android::Res_value& outValue) const override;
+    RawString* clone() const override;
+    void print(std::ostream& out) const override;
+};
+
+struct String : public BaseItem<String> {
+    StringPool::Ref value;
+
+    String(const StringPool::Ref& ref);
+
+    bool flatten(android::Res_value& outValue) const override;
+    String* clone() const override;
+    void print(std::ostream& out) const override;
+};
+
+struct StyledString : public BaseItem<StyledString> {
+    StringPool::StyleRef value;
+
+    StyledString(const StringPool::StyleRef& ref);
+
+    bool flatten(android::Res_value& outValue) const override;
+    StyledString* clone() const override;
+    void print(std::ostream& out) const override;
+};
+
+struct FileReference : public BaseItem<FileReference> {
+    StringPool::Ref path;
+
+    FileReference() = default;
+    FileReference(const StringPool::Ref& path);
+
+    bool flatten(android::Res_value& outValue) const override;
+    FileReference* clone() const override;
+    void print(std::ostream& out) const override;
+};
+
+/**
+ * Represents any other android::Res_value.
+ */
+struct BinaryPrimitive : public BaseItem<BinaryPrimitive> {
+    android::Res_value value;
+
+    BinaryPrimitive() = default;
+    BinaryPrimitive(const android::Res_value& val);
+
+    bool flatten(android::Res_value& outValue) const override;
+    BinaryPrimitive* clone() const override;
+    void print(::std::ostream& out) const override;
+};
+
+/**
+ * Sentinel value that should be ignored in the final output.
+ * Mainly used as a placeholder for public entries with no
+ * values defined yet.
+ */
+struct Sentinel : public BaseItem<Sentinel> {
+    bool isWeak() const override;
+    bool flatten(android::Res_value& outValue) const override;
+    Sentinel* clone() const override;
+    void print(::std::ostream& out) const override;
+};
+
+struct Attribute : public BaseValue<Attribute> {
+    struct Symbol {
+        Reference symbol;
+        uint32_t value;
+    };
+
+    bool weak;
+    uint32_t typeMask;
+    uint32_t minInt;
+    uint32_t maxInt;
+    std::vector<Symbol> symbols;
+
+    Attribute(bool w, uint32_t t = 0u);
+
+    bool isWeak() const override;
+    virtual Attribute* clone() const override;
+    virtual void print(std::ostream& out) const override;
+};
+
+struct Style : public BaseValue<Style> {
+    struct Entry {
+        Reference key;
+        std::unique_ptr<Item> value;
+    };
+
+    Reference parent;
+    std::vector<Entry> entries;
+
+    Style* clone() const override;
+    void print(std::ostream& out) const override;
+};
+
+struct Array : public BaseValue<Array> {
+    std::vector<std::unique_ptr<Item>> items;
+
+    Array* clone() const override;
+    void print(std::ostream& out) const override;
+};
+
+struct Plural : public BaseValue<Plural> {
+    enum {
+        Zero = 0,
+        One,
+        Two,
+        Few,
+        Many,
+        Other,
+        Count
+    };
+
+    std::array<std::unique_ptr<Item>, Count> values;
+
+    Plural* clone() const override;
+    void print(std::ostream& out) const override;
+};
+
+struct Styleable : public BaseValue<Styleable> {
+    std::vector<Reference> entries;
+
+    Styleable* clone() 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);
+    return out;
+}
+
+/**
+ * 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);
+    }
+
+    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(Sentinel& sentinel, ValueVisitorArgs& args) {
+        visitItem(sentinel, 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 Sentinel& sentinel, ValueVisitorArgs& args) {
+        visitItem(sentinel, 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);
+}
+
+} // namespace aapt
+
+#endif // AAPT_RESOURCE_VALUES_H
diff --git a/tools/aapt2/Resource_test.cpp b/tools/aapt2/Resource_test.cpp
new file mode 100644
index 0000000..d957999
--- /dev/null
+++ b/tools/aapt2/Resource_test.cpp
@@ -0,0 +1,120 @@
+/*
+ * 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 "Resource.h"
+
+namespace aapt {
+
+TEST(ResourceTypeTest, ParseResourceTypes) {
+    const ResourceType* type = parseResourceType(u"anim");
+    ASSERT_NE(type, nullptr);
+    EXPECT_EQ(*type, ResourceType::kAnim);
+
+    type = parseResourceType(u"animator");
+    ASSERT_NE(type, nullptr);
+    EXPECT_EQ(*type, ResourceType::kAnimator);
+
+    type = parseResourceType(u"array");
+    ASSERT_NE(type, nullptr);
+    EXPECT_EQ(*type, ResourceType::kArray);
+
+    type = parseResourceType(u"attr");
+    ASSERT_NE(type, nullptr);
+    EXPECT_EQ(*type, ResourceType::kAttr);
+
+    type = parseResourceType(u"^attr-private");
+    ASSERT_NE(type, nullptr);
+    EXPECT_EQ(*type, ResourceType::kAttrPrivate);
+
+    type = parseResourceType(u"bool");
+    ASSERT_NE(type, nullptr);
+    EXPECT_EQ(*type, ResourceType::kBool);
+
+    type = parseResourceType(u"color");
+    ASSERT_NE(type, nullptr);
+    EXPECT_EQ(*type, ResourceType::kColor);
+
+    type = parseResourceType(u"dimen");
+    ASSERT_NE(type, nullptr);
+    EXPECT_EQ(*type, ResourceType::kDimen);
+
+    type = parseResourceType(u"drawable");
+    ASSERT_NE(type, nullptr);
+    EXPECT_EQ(*type, ResourceType::kDrawable);
+
+    type = parseResourceType(u"fraction");
+    ASSERT_NE(type, nullptr);
+    EXPECT_EQ(*type, ResourceType::kFraction);
+
+    type = parseResourceType(u"id");
+    ASSERT_NE(type, nullptr);
+    EXPECT_EQ(*type, ResourceType::kId);
+
+    type = parseResourceType(u"integer");
+    ASSERT_NE(type, nullptr);
+    EXPECT_EQ(*type, ResourceType::kInteger);
+
+    type = parseResourceType(u"integer-array");
+    ASSERT_NE(type, nullptr);
+    EXPECT_EQ(*type, ResourceType::kIntegerArray);
+
+    type = parseResourceType(u"interpolator");
+    ASSERT_NE(type, nullptr);
+    EXPECT_EQ(*type, ResourceType::kInterpolator);
+
+    type = parseResourceType(u"layout");
+    ASSERT_NE(type, nullptr);
+    EXPECT_EQ(*type, ResourceType::kLayout);
+
+    type = parseResourceType(u"menu");
+    ASSERT_NE(type, nullptr);
+    EXPECT_EQ(*type, ResourceType::kMenu);
+
+    type = parseResourceType(u"mipmap");
+    ASSERT_NE(type, nullptr);
+    EXPECT_EQ(*type, ResourceType::kMipmap);
+
+    type = parseResourceType(u"plurals");
+    ASSERT_NE(type, nullptr);
+    EXPECT_EQ(*type, ResourceType::kPlurals);
+
+    type = parseResourceType(u"raw");
+    ASSERT_NE(type, nullptr);
+    EXPECT_EQ(*type, ResourceType::kRaw);
+
+    type = parseResourceType(u"string");
+    ASSERT_NE(type, nullptr);
+    EXPECT_EQ(*type, ResourceType::kString);
+
+    type = parseResourceType(u"style");
+    ASSERT_NE(type, nullptr);
+    EXPECT_EQ(*type, ResourceType::kStyle);
+
+    type = parseResourceType(u"transition");
+    ASSERT_NE(type, nullptr);
+    EXPECT_EQ(*type, ResourceType::kTransition);
+
+    type = parseResourceType(u"xml");
+    ASSERT_NE(type, nullptr);
+    EXPECT_EQ(*type, ResourceType::kXml);
+
+    type = parseResourceType(u"blahaha");
+    EXPECT_EQ(type, nullptr);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ScopedXmlPullParser.cpp b/tools/aapt2/ScopedXmlPullParser.cpp
new file mode 100644
index 0000000..d9ae72c
--- /dev/null
+++ b/tools/aapt2/ScopedXmlPullParser.cpp
@@ -0,0 +1,99 @@
+/*
+ * 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();
+}
+
+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
new file mode 100644
index 0000000..e660499
--- /dev/null
+++ b/tools/aapt2/ScopedXmlPullParser.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_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;
+    const std::string& getLastError() const;
+    Event next();
+
+    const std::u16string& getComment() const;
+    size_t getLineNumber() const;
+    size_t getDepth() const;
+
+    const std::u16string& getText() const;
+
+    const std::u16string& getNamespacePrefix() const;
+    const std::u16string& getNamespaceUri() const;
+
+    const std::u16string& getElementNamespace() const;
+    const std::u16string& getElementName() const;
+
+    const_iterator beginAttributes() const;
+    const_iterator endAttributes() const;
+    size_t getAttributeCount() const;
+
+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
new file mode 100644
index 0000000..342f305
--- /dev/null
+++ b/tools/aapt2/ScopedXmlPullParser_test.cpp
@@ -0,0 +1,106 @@
+/*
+ * 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
new file mode 100644
index 0000000..3f156a6
--- /dev/null
+++ b/tools/aapt2/SdkConstants.cpp
@@ -0,0 +1,693 @@
+/*
+ * 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 <string>
+#include <unordered_map>
+
+namespace aapt {
+
+static const std::unordered_map<std::u16string, size_t> sAttrMap = {
+    { u"marqueeRepeatLimit", 2 },
+    { u"windowNoDisplay", 3 },
+    { u"backgroundDimEnabled", 3 },
+    { u"inputType", 3 },
+    { u"isDefault", 3 },
+    { u"windowDisablePreview", 3 },
+    { u"privateImeOptions", 3 },
+    { u"editorExtras", 3 },
+    { u"settingsActivity", 3 },
+    { u"fastScrollEnabled", 3 },
+    { u"reqTouchScreen", 3 },
+    { u"reqKeyboardType", 3 },
+    { u"reqHardKeyboard", 3 },
+    { u"reqNavigation", 3 },
+    { u"windowSoftInputMode", 3 },
+    { u"imeFullscreenBackground", 3 },
+    { u"noHistory", 3 },
+    { u"headerDividersEnabled", 3 },
+    { u"footerDividersEnabled", 3 },
+    { u"candidatesTextStyleSpans", 3 },
+    { u"smoothScrollbar", 3 },
+    { u"reqFiveWayNav", 3 },
+    { u"keyBackground", 3 },
+    { u"keyTextSize", 3 },
+    { u"labelTextSize", 3 },
+    { u"keyTextColor", 3 },
+    { u"keyPreviewLayout", 3 },
+    { u"keyPreviewOffset", 3 },
+    { u"keyPreviewHeight", 3 },
+    { u"verticalCorrection", 3 },
+    { u"popupLayout", 3 },
+    { u"state_long_pressable", 3 },
+    { u"keyWidth", 3 },
+    { u"keyHeight", 3 },
+    { u"horizontalGap", 3 },
+    { u"verticalGap", 3 },
+    { u"rowEdgeFlags", 3 },
+    { u"codes", 3 },
+    { u"popupKeyboard", 3 },
+    { u"popupCharacters", 3 },
+    { u"keyEdgeFlags", 3 },
+    { u"isModifier", 3 },
+    { u"isSticky", 3 },
+    { u"isRepeatable", 3 },
+    { u"iconPreview", 3 },
+    { u"keyOutputText", 3 },
+    { u"keyLabel", 3 },
+    { u"keyIcon", 3 },
+    { u"keyboardMode", 3 },
+    { u"isScrollContainer", 3 },
+    { u"fillEnabled", 3 },
+    { u"updatePeriodMillis", 3 },
+    { u"initialLayout", 3 },
+    { u"voiceSearchMode", 3 },
+    { u"voiceLanguageModel", 3 },
+    { u"voicePromptText", 3 },
+    { u"voiceLanguage", 3 },
+    { u"voiceMaxResults", 3 },
+    { u"bottomOffset", 3 },
+    { u"topOffset", 3 },
+    { u"allowSingleTap", 3 },
+    { u"handle", 3 },
+    { u"content", 3 },
+    { u"animateOnClick", 3 },
+    { u"configure", 3 },
+    { u"hapticFeedbackEnabled", 3 },
+    { u"innerRadius", 3 },
+    { u"thickness", 3 },
+    { u"sharedUserLabel", 3 },
+    { u"dropDownWidth", 3 },
+    { u"dropDownAnchor", 3 },
+    { u"imeOptions", 3 },
+    { u"imeActionLabel", 3 },
+    { u"imeActionId", 3 },
+    { u"imeExtractEnterAnimation", 3 },
+    { u"imeExtractExitAnimation", 3 },
+    { u"tension", 4 },
+    { u"extraTension", 4 },
+    { u"anyDensity", 4 },
+    { u"searchSuggestThreshold", 4 },
+    { u"includeInGlobalSearch", 4 },
+    { u"onClick", 4 },
+    { u"targetSdkVersion", 4 },
+    { u"maxSdkVersion", 4 },
+    { u"testOnly", 4 },
+    { u"contentDescription", 4 },
+    { u"gestureStrokeWidth", 4 },
+    { u"gestureColor", 4 },
+    { u"uncertainGestureColor", 4 },
+    { u"fadeOffset", 4 },
+    { u"fadeDuration", 4 },
+    { u"gestureStrokeType", 4 },
+    { u"gestureStrokeLengthThreshold", 4 },
+    { u"gestureStrokeSquarenessThreshold", 4 },
+    { u"gestureStrokeAngleThreshold", 4 },
+    { u"eventsInterceptionEnabled", 4 },
+    { u"fadeEnabled", 4 },
+    { u"backupAgent", 4 },
+    { u"allowBackup", 4 },
+    { u"glEsVersion", 4 },
+    { u"queryAfterZeroResults", 4 },
+    { u"dropDownHeight", 4 },
+    { u"smallScreens", 4 },
+    { u"normalScreens", 4 },
+    { u"largeScreens", 4 },
+    { u"progressBarStyleInverse", 4 },
+    { u"progressBarStyleSmallInverse", 4 },
+    { u"progressBarStyleLargeInverse", 4 },
+    { u"searchSettingsDescription", 4 },
+    { u"textColorPrimaryInverseDisableOnly", 4 },
+    { u"autoUrlDetect", 4 },
+    { u"resizeable", 4 },
+    { u"required", 5 },
+    { u"accountType", 5 },
+    { u"contentAuthority", 5 },
+    { u"userVisible", 5 },
+    { u"windowShowWallpaper", 5 },
+    { u"wallpaperOpenEnterAnimation", 5 },
+    { u"wallpaperOpenExitAnimation", 5 },
+    { u"wallpaperCloseEnterAnimation", 5 },
+    { u"wallpaperCloseExitAnimation", 5 },
+    { u"wallpaperIntraOpenEnterAnimation", 5 },
+    { u"wallpaperIntraOpenExitAnimation", 5 },
+    { u"wallpaperIntraCloseEnterAnimation", 5 },
+    { u"wallpaperIntraCloseExitAnimation", 5 },
+    { u"supportsUploading", 5 },
+    { u"killAfterRestore", 5 },
+    { u"restoreNeedsApplication", 5 },
+    { u"smallIcon", 5 },
+    { u"accountPreferences", 5 },
+    { u"textAppearanceSearchResultSubtitle", 5 },
+    { u"textAppearanceSearchResultTitle", 5 },
+    { u"summaryColumn", 5 },
+    { u"detailColumn", 5 },
+    { u"detailSocialSummary", 5 },
+    { u"thumbnail", 5 },
+    { u"detachWallpaper", 5 },
+    { u"finishOnCloseSystemDialogs", 5 },
+    { u"scrollbarFadeDuration", 5 },
+    { u"scrollbarDefaultDelayBeforeFade", 5 },
+    { u"fadeScrollbars", 5 },
+    { u"colorBackgroundCacheHint", 5 },
+    { u"dropDownHorizontalOffset", 5 },
+    { u"dropDownVerticalOffset", 5 },
+    { u"quickContactBadgeStyleWindowSmall", 6 },
+    { u"quickContactBadgeStyleWindowMedium", 6 },
+    { u"quickContactBadgeStyleWindowLarge", 6 },
+    { u"quickContactBadgeStyleSmallWindowSmall", 6 },
+    { u"quickContactBadgeStyleSmallWindowMedium", 6 },
+    { u"quickContactBadgeStyleSmallWindowLarge", 6 },
+    { u"author", 7 },
+    { u"autoStart", 7 },
+    { u"expandableListViewWhiteStyle", 8 },
+    { u"installLocation", 8 },
+    { u"vmSafeMode", 8 },
+    { u"webTextViewStyle", 8 },
+    { u"restoreAnyVersion", 8 },
+    { u"tabStripLeft", 8 },
+    { u"tabStripRight", 8 },
+    { u"tabStripEnabled", 8 },
+    { u"logo", 9 },
+    { u"xlargeScreens", 9 },
+    { u"immersive", 9 },
+    { u"overScrollMode", 9 },
+    { u"overScrollHeader", 9 },
+    { u"overScrollFooter", 9 },
+    { u"filterTouchesWhenObscured", 9 },
+    { u"textSelectHandleLeft", 9 },
+    { u"textSelectHandleRight", 9 },
+    { u"textSelectHandle", 9 },
+    { u"textSelectHandleWindowStyle", 9 },
+    { u"popupAnimationStyle", 9 },
+    { u"screenSize", 9 },
+    { u"screenDensity", 9 },
+    { u"allContactsName", 11 },
+    { u"windowActionBar", 11 },
+    { u"actionBarStyle", 11 },
+    { u"navigationMode", 11 },
+    { u"displayOptions", 11 },
+    { u"subtitle", 11 },
+    { u"customNavigationLayout", 11 },
+    { u"hardwareAccelerated", 11 },
+    { u"measureWithLargestChild", 11 },
+    { u"animateFirstView", 11 },
+    { u"dropDownSpinnerStyle", 11 },
+    { u"actionDropDownStyle", 11 },
+    { u"actionButtonStyle", 11 },
+    { u"showAsAction", 11 },
+    { u"previewImage", 11 },
+    { u"actionModeBackground", 11 },
+    { u"actionModeCloseDrawable", 11 },
+    { u"windowActionModeOverlay", 11 },
+    { u"valueFrom", 11 },
+    { u"valueTo", 11 },
+    { u"valueType", 11 },
+    { u"propertyName", 11 },
+    { u"ordering", 11 },
+    { u"fragment", 11 },
+    { u"windowActionBarOverlay", 11 },
+    { u"fragmentOpenEnterAnimation", 11 },
+    { u"fragmentOpenExitAnimation", 11 },
+    { u"fragmentCloseEnterAnimation", 11 },
+    { u"fragmentCloseExitAnimation", 11 },
+    { u"fragmentFadeEnterAnimation", 11 },
+    { u"fragmentFadeExitAnimation", 11 },
+    { u"actionBarSize", 11 },
+    { u"imeSubtypeLocale", 11 },
+    { u"imeSubtypeMode", 11 },
+    { u"imeSubtypeExtraValue", 11 },
+    { u"splitMotionEvents", 11 },
+    { u"listChoiceBackgroundIndicator", 11 },
+    { u"spinnerMode", 11 },
+    { u"animateLayoutChanges", 11 },
+    { u"actionBarTabStyle", 11 },
+    { u"actionBarTabBarStyle", 11 },
+    { u"actionBarTabTextStyle", 11 },
+    { u"actionOverflowButtonStyle", 11 },
+    { u"actionModeCloseButtonStyle", 11 },
+    { u"titleTextStyle", 11 },
+    { u"subtitleTextStyle", 11 },
+    { u"iconifiedByDefault", 11 },
+    { u"actionLayout", 11 },
+    { u"actionViewClass", 11 },
+    { u"activatedBackgroundIndicator", 11 },
+    { u"state_activated", 11 },
+    { u"listPopupWindowStyle", 11 },
+    { u"popupMenuStyle", 11 },
+    { u"textAppearanceLargePopupMenu", 11 },
+    { u"textAppearanceSmallPopupMenu", 11 },
+    { u"breadCrumbTitle", 11 },
+    { u"breadCrumbShortTitle", 11 },
+    { u"listDividerAlertDialog", 11 },
+    { u"textColorAlertDialogListItem", 11 },
+    { u"loopViews", 11 },
+    { u"dialogTheme", 11 },
+    { u"alertDialogTheme", 11 },
+    { u"dividerVertical", 11 },
+    { u"homeAsUpIndicator", 11 },
+    { u"enterFadeDuration", 11 },
+    { u"exitFadeDuration", 11 },
+    { u"selectableItemBackground", 11 },
+    { u"autoAdvanceViewId", 11 },
+    { u"useIntrinsicSizeAsMinimum", 11 },
+    { u"actionModeCutDrawable", 11 },
+    { u"actionModeCopyDrawable", 11 },
+    { u"actionModePasteDrawable", 11 },
+    { u"textEditPasteWindowLayout", 11 },
+    { u"textEditNoPasteWindowLayout", 11 },
+    { u"textIsSelectable", 11 },
+    { u"windowEnableSplitTouch", 11 },
+    { u"indeterminateProgressStyle", 11 },
+    { u"progressBarPadding", 11 },
+    { u"animationResolution", 11 },
+    { u"state_accelerated", 11 },
+    { u"baseline", 11 },
+    { u"homeLayout", 11 },
+    { u"opacity", 11 },
+    { u"alpha", 11 },
+    { u"transformPivotX", 11 },
+    { u"transformPivotY", 11 },
+    { u"translationX", 11 },
+    { u"translationY", 11 },
+    { u"scaleX", 11 },
+    { u"scaleY", 11 },
+    { u"rotation", 11 },
+    { u"rotationX", 11 },
+    { u"rotationY", 11 },
+    { u"showDividers", 11 },
+    { u"dividerPadding", 11 },
+    { u"borderlessButtonStyle", 11 },
+    { u"dividerHorizontal", 11 },
+    { u"itemPadding", 11 },
+    { u"buttonBarStyle", 11 },
+    { u"buttonBarButtonStyle", 11 },
+    { u"segmentedButtonStyle", 11 },
+    { u"staticWallpaperPreview", 11 },
+    { u"allowParallelSyncs", 11 },
+    { u"isAlwaysSyncable", 11 },
+    { u"verticalScrollbarPosition", 11 },
+    { u"fastScrollAlwaysVisible", 11 },
+    { u"fastScrollThumbDrawable", 11 },
+    { u"fastScrollPreviewBackgroundLeft", 11 },
+    { u"fastScrollPreviewBackgroundRight", 11 },
+    { u"fastScrollTrackDrawable", 11 },
+    { u"fastScrollOverlayPosition", 11 },
+    { u"customTokens", 11 },
+    { u"nextFocusForward", 11 },
+    { u"firstDayOfWeek", 11 },
+    { u"showWeekNumber", 11 },
+    { u"minDate", 11 },
+    { u"maxDate", 11 },
+    { u"shownWeekCount", 11 },
+    { u"selectedWeekBackgroundColor", 11 },
+    { u"focusedMonthDateColor", 11 },
+    { u"unfocusedMonthDateColor", 11 },
+    { u"weekNumberColor", 11 },
+    { u"weekSeparatorLineColor", 11 },
+    { u"selectedDateVerticalBar", 11 },
+    { u"weekDayTextAppearance", 11 },
+    { u"dateTextAppearance", 11 },
+    { u"solidColor", 11 },
+    { u"spinnersShown", 11 },
+    { u"calendarViewShown", 11 },
+    { u"state_multiline", 11 },
+    { u"detailsElementBackground", 11 },
+    { u"textColorHighlightInverse", 11 },
+    { u"textColorLinkInverse", 11 },
+    { u"editTextColor", 11 },
+    { u"editTextBackground", 11 },
+    { u"horizontalScrollViewStyle", 11 },
+    { u"layerType", 11 },
+    { u"alertDialogIcon", 11 },
+    { u"windowMinWidthMajor", 11 },
+    { u"windowMinWidthMinor", 11 },
+    { u"queryHint", 11 },
+    { u"fastScrollTextColor", 11 },
+    { u"largeHeap", 11 },
+    { u"windowCloseOnTouchOutside", 11 },
+    { u"datePickerStyle", 11 },
+    { u"calendarViewStyle", 11 },
+    { u"textEditSidePasteWindowLayout", 11 },
+    { u"textEditSideNoPasteWindowLayout", 11 },
+    { u"actionMenuTextAppearance", 11 },
+    { u"actionMenuTextColor", 11 },
+    { u"textCursorDrawable", 12 },
+    { u"resizeMode", 12 },
+    { u"requiresSmallestWidthDp", 12 },
+    { u"compatibleWidthLimitDp", 12 },
+    { u"largestWidthLimitDp", 12 },
+    { u"state_hovered", 13 },
+    { u"state_drag_can_accept", 13 },
+    { u"state_drag_hovered", 13 },
+    { u"stopWithTask", 13 },
+    { u"switchTextOn", 13 },
+    { u"switchTextOff", 13 },
+    { u"switchPreferenceStyle", 13 },
+    { u"switchTextAppearance", 13 },
+    { u"track", 13 },
+    { u"switchMinWidth", 13 },
+    { u"switchPadding", 13 },
+    { u"thumbTextPadding", 13 },
+    { u"textSuggestionsWindowStyle", 13 },
+    { u"textEditSuggestionItemLayout", 13 },
+    { u"rowCount", 13 },
+    { u"rowOrderPreserved", 13 },
+    { u"columnCount", 13 },
+    { u"columnOrderPreserved", 13 },
+    { u"useDefaultMargins", 13 },
+    { u"alignmentMode", 13 },
+    { u"layout_row", 13 },
+    { u"layout_rowSpan", 13 },
+    { u"layout_columnSpan", 13 },
+    { u"actionModeSelectAllDrawable", 13 },
+    { u"isAuxiliary", 13 },
+    { u"accessibilityEventTypes", 13 },
+    { u"packageNames", 13 },
+    { u"accessibilityFeedbackType", 13 },
+    { u"notificationTimeout", 13 },
+    { u"accessibilityFlags", 13 },
+    { u"canRetrieveWindowContent", 13 },
+    { u"listPreferredItemHeightLarge", 13 },
+    { u"listPreferredItemHeightSmall", 13 },
+    { u"actionBarSplitStyle", 13 },
+    { u"actionProviderClass", 13 },
+    { u"backgroundStacked", 13 },
+    { u"backgroundSplit", 13 },
+    { u"textAllCaps", 13 },
+    { u"colorPressedHighlight", 13 },
+    { u"colorLongPressedHighlight", 13 },
+    { u"colorFocusedHighlight", 13 },
+    { u"colorActivatedHighlight", 13 },
+    { u"colorMultiSelectHighlight", 13 },
+    { u"drawableStart", 13 },
+    { u"drawableEnd", 13 },
+    { u"actionModeStyle", 13 },
+    { u"minResizeWidth", 13 },
+    { u"minResizeHeight", 13 },
+    { u"actionBarWidgetTheme", 13 },
+    { u"uiOptions", 13 },
+    { u"subtypeLocale", 13 },
+    { u"subtypeExtraValue", 13 },
+    { u"actionBarDivider", 13 },
+    { u"actionBarItemBackground", 13 },
+    { u"actionModeSplitBackground", 13 },
+    { u"textAppearanceListItem", 13 },
+    { u"textAppearanceListItemSmall", 13 },
+    { u"targetDescriptions", 13 },
+    { u"directionDescriptions", 13 },
+    { u"overridesImplicitlyEnabledSubtype", 13 },
+    { u"listPreferredItemPaddingLeft", 13 },
+    { u"listPreferredItemPaddingRight", 13 },
+    { u"requiresFadingEdge", 13 },
+    { u"publicKey", 13 },
+    { u"parentActivityName", 16 },
+    { u"isolatedProcess", 16 },
+    { u"importantForAccessibility", 16 },
+    { u"keyboardLayout", 16 },
+    { u"fontFamily", 16 },
+    { u"mediaRouteButtonStyle", 16 },
+    { u"mediaRouteTypes", 16 },
+    { u"supportsRtl", 17 },
+    { u"textDirection", 17 },
+    { u"textAlignment", 17 },
+    { u"layoutDirection", 17 },
+    { u"paddingStart", 17 },
+    { u"paddingEnd", 17 },
+    { u"layout_marginStart", 17 },
+    { u"layout_marginEnd", 17 },
+    { u"layout_toStartOf", 17 },
+    { u"layout_toEndOf", 17 },
+    { u"layout_alignStart", 17 },
+    { u"layout_alignEnd", 17 },
+    { u"layout_alignParentStart", 17 },
+    { u"layout_alignParentEnd", 17 },
+    { u"listPreferredItemPaddingStart", 17 },
+    { u"listPreferredItemPaddingEnd", 17 },
+    { u"singleUser", 17 },
+    { u"presentationTheme", 17 },
+    { u"subtypeId", 17 },
+    { u"initialKeyguardLayout", 17 },
+    { u"widgetCategory", 17 },
+    { u"permissionGroupFlags", 17 },
+    { u"labelFor", 17 },
+    { u"permissionFlags", 17 },
+    { u"checkedTextViewStyle", 17 },
+    { u"showOnLockScreen", 17 },
+    { u"format12Hour", 17 },
+    { u"format24Hour", 17 },
+    { u"timeZone", 17 },
+    { u"mipMap", 18 },
+    { u"mirrorForRtl", 18 },
+    { u"windowOverscan", 18 },
+    { u"requiredForAllUsers", 18 },
+    { u"indicatorStart", 18 },
+    { u"indicatorEnd", 18 },
+    { u"childIndicatorStart", 18 },
+    { u"childIndicatorEnd", 18 },
+    { u"restrictedAccountType", 18 },
+    { u"requiredAccountType", 18 },
+    { u"canRequestTouchExplorationMode", 18 },
+    { u"canRequestEnhancedWebAccessibility", 18 },
+    { u"canRequestFilterKeyEvents", 18 },
+    { u"layoutMode", 18 },
+    { u"keySet", 19 },
+    { u"targetId", 19 },
+    { u"fromScene", 19 },
+    { u"toScene", 19 },
+    { u"transition", 19 },
+    { u"transitionOrdering", 19 },
+    { u"fadingMode", 19 },
+    { u"startDelay", 19 },
+    { u"ssp", 19 },
+    { u"sspPrefix", 19 },
+    { u"sspPattern", 19 },
+    { u"addPrintersActivity", 19 },
+    { u"vendor", 19 },
+    { u"category", 19 },
+    { u"isAsciiCapable", 19 },
+    { u"autoMirrored", 19 },
+    { u"supportsSwitchingToNextInputMethod", 19 },
+    { u"requireDeviceUnlock", 19 },
+    { u"apduServiceBanner", 19 },
+    { u"accessibilityLiveRegion", 19 },
+    { u"windowTranslucentStatus", 19 },
+    { u"windowTranslucentNavigation", 19 },
+    { u"advancedPrintOptionsActivity", 19 },
+    { u"banner", 20 },
+    { u"windowSwipeToDismiss", 20 },
+    { u"isGame", 20 },
+    { u"allowEmbedded", 20 },
+    { u"setupActivity", 20 },
+    { u"fastScrollStyle", 21 },
+    { u"windowContentTransitions", 21 },
+    { u"windowContentTransitionManager", 21 },
+    { u"translationZ", 21 },
+    { u"tintMode", 21 },
+    { u"controlX1", 21 },
+    { u"controlY1", 21 },
+    { u"controlX2", 21 },
+    { u"controlY2", 21 },
+    { u"transitionName", 21 },
+    { u"transitionGroup", 21 },
+    { u"viewportWidth", 21 },
+    { u"viewportHeight", 21 },
+    { u"fillColor", 21 },
+    { u"pathData", 21 },
+    { u"strokeColor", 21 },
+    { u"strokeWidth", 21 },
+    { u"trimPathStart", 21 },
+    { u"trimPathEnd", 21 },
+    { u"trimPathOffset", 21 },
+    { u"strokeLineCap", 21 },
+    { u"strokeLineJoin", 21 },
+    { u"strokeMiterLimit", 21 },
+    { u"colorControlNormal", 21 },
+    { u"colorControlActivated", 21 },
+    { u"colorButtonNormal", 21 },
+    { u"colorControlHighlight", 21 },
+    { u"persistableMode", 21 },
+    { u"titleTextAppearance", 21 },
+    { u"subtitleTextAppearance", 21 },
+    { u"slideEdge", 21 },
+    { u"actionBarTheme", 21 },
+    { u"textAppearanceListItemSecondary", 21 },
+    { u"colorPrimary", 21 },
+    { u"colorPrimaryDark", 21 },
+    { u"colorAccent", 21 },
+    { u"nestedScrollingEnabled", 21 },
+    { u"windowEnterTransition", 21 },
+    { u"windowExitTransition", 21 },
+    { u"windowSharedElementEnterTransition", 21 },
+    { u"windowSharedElementExitTransition", 21 },
+    { u"windowAllowReturnTransitionOverlap", 21 },
+    { u"windowAllowEnterTransitionOverlap", 21 },
+    { u"sessionService", 21 },
+    { u"stackViewStyle", 21 },
+    { u"switchStyle", 21 },
+    { u"elevation", 21 },
+    { u"excludeId", 21 },
+    { u"excludeClass", 21 },
+    { u"hideOnContentScroll", 21 },
+    { u"actionOverflowMenuStyle", 21 },
+    { u"documentLaunchMode", 21 },
+    { u"maxRecents", 21 },
+    { u"autoRemoveFromRecents", 21 },
+    { u"stateListAnimator", 21 },
+    { u"toId", 21 },
+    { u"fromId", 21 },
+    { u"reversible", 21 },
+    { u"splitTrack", 21 },
+    { u"targetName", 21 },
+    { u"excludeName", 21 },
+    { u"matchOrder", 21 },
+    { u"windowDrawsSystemBarBackgrounds", 21 },
+    { u"statusBarColor", 21 },
+    { u"navigationBarColor", 21 },
+    { u"contentInsetStart", 21 },
+    { u"contentInsetEnd", 21 },
+    { u"contentInsetLeft", 21 },
+    { u"contentInsetRight", 21 },
+    { u"paddingMode", 21 },
+    { u"layout_rowWeight", 21 },
+    { u"layout_columnWeight", 21 },
+    { u"translateX", 21 },
+    { u"translateY", 21 },
+    { u"selectableItemBackgroundBorderless", 21 },
+    { u"elegantTextHeight", 21 },
+    { u"searchKeyphraseId", 21 },
+    { u"searchKeyphrase", 21 },
+    { u"searchKeyphraseSupportedLocales", 21 },
+    { u"windowTransitionBackgroundFadeDuration", 21 },
+    { u"overlapAnchor", 21 },
+    { u"progressTint", 21 },
+    { u"progressTintMode", 21 },
+    { u"progressBackgroundTint", 21 },
+    { u"progressBackgroundTintMode", 21 },
+    { u"secondaryProgressTint", 21 },
+    { u"secondaryProgressTintMode", 21 },
+    { u"indeterminateTint", 21 },
+    { u"indeterminateTintMode", 21 },
+    { u"backgroundTint", 21 },
+    { u"backgroundTintMode", 21 },
+    { u"foregroundTint", 21 },
+    { u"foregroundTintMode", 21 },
+    { u"buttonTint", 21 },
+    { u"buttonTintMode", 21 },
+    { u"thumbTint", 21 },
+    { u"thumbTintMode", 21 },
+    { u"fullBackupOnly", 21 },
+    { u"propertyXName", 21 },
+    { u"propertyYName", 21 },
+    { u"relinquishTaskIdentity", 21 },
+    { u"tileModeX", 21 },
+    { u"tileModeY", 21 },
+    { u"actionModeShareDrawable", 21 },
+    { u"actionModeFindDrawable", 21 },
+    { u"actionModeWebSearchDrawable", 21 },
+    { u"transitionVisibilityMode", 21 },
+    { u"minimumHorizontalAngle", 21 },
+    { u"minimumVerticalAngle", 21 },
+    { u"maximumAngle", 21 },
+    { u"searchViewStyle", 21 },
+    { u"closeIcon", 21 },
+    { u"goIcon", 21 },
+    { u"searchIcon", 21 },
+    { u"voiceIcon", 21 },
+    { u"commitIcon", 21 },
+    { u"suggestionRowLayout", 21 },
+    { u"queryBackground", 21 },
+    { u"submitBackground", 21 },
+    { u"buttonBarPositiveButtonStyle", 21 },
+    { u"buttonBarNeutralButtonStyle", 21 },
+    { u"buttonBarNegativeButtonStyle", 21 },
+    { u"popupElevation", 21 },
+    { u"actionBarPopupTheme", 21 },
+    { u"multiArch", 21 },
+    { u"touchscreenBlocksFocus", 21 },
+    { u"windowElevation", 21 },
+    { u"launchTaskBehindTargetAnimation", 21 },
+    { u"launchTaskBehindSourceAnimation", 21 },
+    { u"restrictionType", 21 },
+    { u"dayOfWeekBackground", 21 },
+    { u"dayOfWeekTextAppearance", 21 },
+    { u"headerMonthTextAppearance", 21 },
+    { u"headerDayOfMonthTextAppearance", 21 },
+    { u"headerYearTextAppearance", 21 },
+    { u"yearListItemTextAppearance", 21 },
+    { u"yearListSelectorColor", 21 },
+    { u"calendarTextColor", 21 },
+    { u"recognitionService", 21 },
+    { u"timePickerStyle", 21 },
+    { u"timePickerDialogTheme", 21 },
+    { u"headerTimeTextAppearance", 21 },
+    { u"headerAmPmTextAppearance", 21 },
+    { u"numbersTextColor", 21 },
+    { u"numbersBackgroundColor", 21 },
+    { u"numbersSelectorColor", 21 },
+    { u"amPmTextColor", 21 },
+    { u"amPmBackgroundColor", 21 },
+    { u"searchKeyphraseRecognitionFlags", 21 },
+    { u"checkMarkTint", 21 },
+    { u"checkMarkTintMode", 21 },
+    { u"popupTheme", 21 },
+    { u"toolbarStyle", 21 },
+    { u"windowClipToOutline", 21 },
+    { u"datePickerDialogTheme", 21 },
+    { u"showText", 21 },
+    { u"windowReturnTransition", 21 },
+    { u"windowReenterTransition", 21 },
+    { u"windowSharedElementReturnTransition", 21 },
+    { u"windowSharedElementReenterTransition", 21 },
+    { u"resumeWhilePausing", 21 },
+    { u"datePickerMode", 21 },
+    { u"timePickerMode", 21 },
+    { u"inset", 21 },
+    { u"letterSpacing", 21 },
+    { u"fontFeatureSettings", 21 },
+    { u"outlineProvider", 21 },
+    { u"contentAgeHint", 21 },
+    { u"country", 21 },
+    { u"windowSharedElementsUseOverlay", 21 },
+    { u"reparent", 21 },
+    { u"reparentWithOverlay", 21 },
+    { u"ambientShadowAlpha", 21 },
+    { u"spotShadowAlpha", 21 },
+    { u"navigationIcon", 21 },
+    { u"navigationContentDescription", 21 },
+    { u"fragmentExitTransition", 21 },
+    { u"fragmentEnterTransition", 21 },
+    { u"fragmentSharedElementEnterTransition", 21 },
+    { u"fragmentReturnTransition", 21 },
+    { u"fragmentSharedElementReturnTransition", 21 },
+    { u"fragmentReenterTransition", 21 },
+    { u"fragmentAllowEnterTransitionOverlap", 21 },
+    { u"fragmentAllowReturnTransitionOverlap", 21 },
+    { u"patternPathData", 21 },
+    { u"strokeAlpha", 21 },
+    { u"fillAlpha", 21 },
+    { u"windowActivityTransitions", 21 },
+    { u"colorEdgeEffect", 21 }
+};
+
+size_t findAttributeSdkLevel(const std::u16string& name) {
+    auto iter = sAttrMap.find(name);
+    if (iter != sAttrMap.end()) {
+        return iter->second;
+    }
+    return 0;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h
new file mode 100644
index 0000000..469c355
--- /dev/null
+++ b/tools/aapt2/SdkConstants.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_SDK_CONSTANTS_H
+#define AAPT_SDK_CONSTANTS_H
+
+#include "Resource.h"
+
+#include <string>
+
+namespace aapt {
+
+enum {
+    SDK_CUPCAKE = 3,
+    SDK_DONUT = 4,
+    SDK_ECLAIR = 5,
+    SDK_ECLAIR_0_1 = 6,
+    SDK_ECLAIR_MR1 = 7,
+    SDK_FROYO = 8,
+    SDK_GINGERBREAD = 9,
+    SDK_GINGERBREAD_MR1 = 10,
+    SDK_HONEYCOMB = 11,
+    SDK_HONEYCOMB_MR1 = 12,
+    SDK_HONEYCOMB_MR2 = 13,
+    SDK_ICE_CREAM_SANDWICH = 14,
+    SDK_ICE_CREAM_SANDWICH_MR1 = 15,
+    SDK_JELLY_BEAN = 16,
+    SDK_JELLY_BEAN_MR1 = 17,
+    SDK_JELLY_BEAN_MR2 = 18,
+    SDK_KITKAT = 19,
+    SDK_KITKAT_WATCH = 20,
+    SDK_LOLLIPOP = 21,
+    SDK_LOLLIPOP_MR1 = 22,
+};
+
+size_t findAttributeSdkLevel(const std::u16string& name);
+
+} // namespace aapt
+
+#endif // AAPT_SDK_CONSTANTS_H
diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h
new file mode 100644
index 0000000..10c75aa
--- /dev/null
+++ b/tools/aapt2/Source.h
@@ -0,0 +1,85 @@
+/*
+ * 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_H
+#define AAPT_SOURCE_H
+
+#include <ostream>
+#include <string>
+
+namespace aapt {
+
+struct SourceLineColumn;
+struct SourceLine;
+
+/**
+ * Represents a file on disk. Used for logging and
+ * showing errors.
+ */
+struct Source {
+    std::string path;
+
+    inline SourceLine line(size_t line) const;
+};
+
+/**
+ * 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 SourceLineColumn column(size_t column) const;
+};
+
+/**
+ * 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;
+};
+
+//
+// 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;
+}
+
+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;
+}
+
+} // namespace aapt
+
+#endif // AAPT_SOURCE_H
diff --git a/tools/aapt2/SourceXmlPullParser.cpp b/tools/aapt2/SourceXmlPullParser.cpp
new file mode 100644
index 0000000..cb6a3c0
--- /dev/null
+++ b/tools/aapt2/SourceXmlPullParser.cpp
@@ -0,0 +1,248 @@
+/*
+ * 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 <iostream>
+#include <string>
+
+#include "SourceXmlPullParser.h"
+#include "Util.h"
+
+namespace aapt {
+
+constexpr char kXmlNamespaceSep = 1;
+
+SourceXmlPullParser::SourceXmlPullParser(std::istream& in) : mIn(in), mEmpty(), mDepth(0) {
+    mParser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep);
+    XML_SetUserData(mParser, this);
+    XML_SetElementHandler(mParser, startElementHandler, endElementHandler);
+    XML_SetNamespaceDeclHandler(mParser, startNamespaceHandler, endNamespaceHandler);
+    XML_SetCharacterDataHandler(mParser, characterDataHandler);
+    XML_SetCommentHandler(mParser, commentDataHandler);
+    mEventQueue.push(EventData{ Event::kStartDocument, 0, mDepth++ });
+}
+
+SourceXmlPullParser::~SourceXmlPullParser() {
+    XML_ParserFree(mParser);
+}
+
+SourceXmlPullParser::Event SourceXmlPullParser::next() {
+    const Event currentEvent = getEvent();
+    if (currentEvent == Event::kBadDocument || currentEvent == Event::kEndDocument) {
+        return currentEvent;
+    }
+
+    mEventQueue.pop();
+    while (mEventQueue.empty()) {
+        mIn.read(mBuffer, sizeof(mBuffer) / sizeof(*mBuffer));
+
+        const bool done = mIn.eof();
+        if (mIn.bad() && !done) {
+            mLastError = strerror(errno);
+            mEventQueue.push(EventData{ Event::kBadDocument });
+            continue;
+        }
+
+        if (XML_Parse(mParser, mBuffer, mIn.gcount(), done) == XML_STATUS_ERROR) {
+            mLastError = XML_ErrorString(XML_GetErrorCode(mParser));
+            mEventQueue.push(EventData{ Event::kBadDocument });
+            continue;
+        }
+
+        if (done) {
+            mEventQueue.push(EventData{ Event::kEndDocument, 0, 0 });
+        }
+    }
+
+    return getEvent();
+}
+
+SourceXmlPullParser::Event SourceXmlPullParser::getEvent() const {
+    return mEventQueue.front().event;
+}
+
+const std::string& SourceXmlPullParser::getLastError() const {
+    return mLastError;
+}
+
+const std::u16string& SourceXmlPullParser::getComment() const {
+    return mEventQueue.front().comment;
+}
+
+size_t SourceXmlPullParser::getLineNumber() const {
+    return mEventQueue.front().lineNumber;
+}
+
+size_t SourceXmlPullParser::getDepth() const {
+    return mEventQueue.front().depth;
+}
+
+const std::u16string& SourceXmlPullParser::getText() const {
+    if (getEvent() != Event::kText) {
+        return mEmpty;
+    }
+    return mEventQueue.front().data1;
+}
+
+const std::u16string& SourceXmlPullParser::getNamespacePrefix() const {
+    const Event currentEvent = getEvent();
+    if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) {
+        return mEmpty;
+    }
+    return mEventQueue.front().data1;
+}
+
+const std::u16string& SourceXmlPullParser::getNamespaceUri() const {
+    const Event currentEvent = getEvent();
+    if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) {
+        return mEmpty;
+    }
+    return mEventQueue.front().data2;
+}
+
+const std::u16string& SourceXmlPullParser::getElementNamespace() const {
+    const Event currentEvent = getEvent();
+    if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) {
+        return mEmpty;
+    }
+    return mEventQueue.front().data1;
+}
+
+const std::u16string& SourceXmlPullParser::getElementName() const {
+    const Event currentEvent = getEvent();
+    if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) {
+        return mEmpty;
+    }
+    return mEventQueue.front().data2;
+}
+
+XmlPullParser::const_iterator SourceXmlPullParser::beginAttributes() const {
+    return mEventQueue.front().attributes.begin();
+}
+
+XmlPullParser::const_iterator SourceXmlPullParser::endAttributes() const {
+    return mEventQueue.front().attributes.end();
+}
+
+size_t SourceXmlPullParser::getAttributeCount() const {
+    if (getEvent() != Event::kStartElement) {
+        return 0;
+    }
+    return mEventQueue.front().attributes.size();
+}
+
+/**
+ * Extracts the namespace and name of an expanded element or attribute name.
+ */
+static void splitName(const char* name, std::u16string& outNs, std::u16string& outName) {
+    const char* p = name;
+    while (*p != 0 && *p != kXmlNamespaceSep) {
+        p++;
+    }
+
+    if (*p == 0) {
+        outNs = std::u16string();
+        outName = util::utf8ToUtf16(name);
+    } else {
+        outNs = util::utf8ToUtf16(StringPiece(name, (p - name)));
+        outName = util::utf8ToUtf16(p + 1);
+    }
+}
+
+void XMLCALL SourceXmlPullParser::startNamespaceHandler(void* userData, const char* prefix,
+        const char* uri) {
+    SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+    std::u16string namespaceUri = uri != nullptr ? util::utf8ToUtf16(uri) : std::u16string();
+    parser->mNamespaceUris.push(namespaceUri);
+    parser->mEventQueue.push(EventData{
+            Event::kStartNamespace,
+            XML_GetCurrentLineNumber(parser->mParser),
+            parser->mDepth++,
+            prefix != nullptr ? util::utf8ToUtf16(prefix) : std::u16string(),
+            namespaceUri
+    });
+}
+
+void XMLCALL SourceXmlPullParser::startElementHandler(void* userData, const char* name,
+        const char** attrs) {
+    SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+
+    EventData data = {
+            Event::kStartElement, XML_GetCurrentLineNumber(parser->mParser), parser->mDepth++
+    };
+    splitName(name, data.data1, data.data2);
+
+    while (*attrs) {
+        Attribute attribute;
+        splitName(*attrs++, attribute.namespaceUri, attribute.name);
+        attribute.value = util::utf8ToUtf16(*attrs++);
+
+        // Insert in sorted order.
+        auto iter = std::lower_bound(data.attributes.begin(), data.attributes.end(), attribute);
+        data.attributes.insert(iter, std::move(attribute));
+    }
+
+    // Move the structure into the queue (no copy).
+    parser->mEventQueue.push(std::move(data));
+}
+
+void XMLCALL SourceXmlPullParser::characterDataHandler(void* userData, const char* s, int len) {
+    SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+
+    parser->mEventQueue.push(EventData{
+            Event::kText,
+            XML_GetCurrentLineNumber(parser->mParser),
+            parser->mDepth,
+            util::utf8ToUtf16(StringPiece(s, len))
+    });
+}
+
+void XMLCALL SourceXmlPullParser::endElementHandler(void* userData, const char* name) {
+    SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+
+    EventData data = {
+            Event::kEndElement, XML_GetCurrentLineNumber(parser->mParser), --(parser->mDepth)
+    };
+    splitName(name, data.data1, data.data2);
+
+    // Move the data into the queue (no copy).
+    parser->mEventQueue.push(std::move(data));
+}
+
+void XMLCALL SourceXmlPullParser::endNamespaceHandler(void* userData, const char* prefix) {
+    SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+
+    parser->mEventQueue.push(EventData{
+            Event::kEndNamespace,
+            XML_GetCurrentLineNumber(parser->mParser),
+            --(parser->mDepth),
+            prefix != nullptr ? util::utf8ToUtf16(prefix) : std::u16string(),
+            parser->mNamespaceUris.top()
+    });
+    parser->mNamespaceUris.pop();
+}
+
+void XMLCALL SourceXmlPullParser::commentDataHandler(void* userData, const char* comment) {
+    SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+
+    parser->mEventQueue.push(EventData{
+            Event::kComment,
+            XML_GetCurrentLineNumber(parser->mParser),
+            parser->mDepth,
+            util::utf8ToUtf16(comment)
+    });
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/SourceXmlPullParser.h b/tools/aapt2/SourceXmlPullParser.h
new file mode 100644
index 0000000..ba904ba
--- /dev/null
+++ b/tools/aapt2/SourceXmlPullParser.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_SOURCE_XML_PULL_PARSER_H
+#define AAPT_SOURCE_XML_PULL_PARSER_H
+
+#include <istream>
+#include <libexpat/expat.h>
+#include <queue>
+#include <stack>
+#include <string>
+#include <vector>
+
+#include "XmlPullParser.h"
+
+namespace aapt {
+
+class SourceXmlPullParser : public XmlPullParser {
+public:
+    SourceXmlPullParser(std::istream& in);
+    SourceXmlPullParser(const SourceXmlPullParser& rhs) = delete;
+    ~SourceXmlPullParser();
+
+    Event getEvent() const;
+    const std::string& getLastError() const;
+    Event next();
+
+    const std::u16string& getComment() const;
+    size_t getLineNumber() const;
+    size_t getDepth() const;
+
+    const std::u16string& getText() const;
+
+    const std::u16string& getNamespacePrefix() const;
+    const std::u16string& getNamespaceUri() const;
+
+    const std::u16string& getElementNamespace() const;
+    const std::u16string& getElementName() const;
+
+    const_iterator beginAttributes() const;
+    const_iterator endAttributes() const;
+    size_t getAttributeCount() 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;
+};
+
+} // namespace aapt
+
+#endif // AAPT_SOURCE_XML_PULL_PARSER_H
diff --git a/tools/aapt2/StringPiece.h b/tools/aapt2/StringPiece.h
new file mode 100644
index 0000000..e2a1597
--- /dev/null
+++ b/tools/aapt2/StringPiece.h
@@ -0,0 +1,232 @@
+/*
+ * 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_STRING_PIECE_H
+#define AAPT_STRING_PIECE_H
+
+#include <ostream>
+#include <string>
+#include <utils/String8.h>
+#include <utils/Unicode.h>
+
+namespace aapt {
+
+/**
+ * Read only wrapper around basic C strings.
+ * Prevents excessive copying.
+ *
+ * WARNING: When creating from std::basic_string<>, moving the original
+ * std::basic_string<> will invalidate the data held in a BasicStringPiece<>.
+ * BasicStringPiece<> should only be used transitively.
+ */
+template <typename TChar>
+class BasicStringPiece {
+public:
+    using const_iterator = const TChar*;
+
+    BasicStringPiece();
+    BasicStringPiece(const BasicStringPiece<TChar>& str);
+    BasicStringPiece(const std::basic_string<TChar>& str);
+    BasicStringPiece(const TChar* str);
+    BasicStringPiece(const TChar* str, size_t len);
+
+    BasicStringPiece<TChar>& operator=(const BasicStringPiece<TChar>& rhs);
+    BasicStringPiece<TChar>& assign(const TChar* str, size_t len);
+
+    BasicStringPiece<TChar> substr(size_t start, size_t len) const;
+    BasicStringPiece<TChar> substr(BasicStringPiece<TChar>::const_iterator begin,
+                                   BasicStringPiece<TChar>::const_iterator end) const;
+
+    const TChar* data() const;
+    size_t length() const;
+    size_t size() const;
+    bool empty() const;
+    std::basic_string<TChar> toString() const;
+
+    int compare(const BasicStringPiece<TChar>& rhs) const;
+    bool operator<(const BasicStringPiece<TChar>& rhs) const;
+    bool operator>(const BasicStringPiece<TChar>& rhs) const;
+    bool operator==(const BasicStringPiece<TChar>& rhs) const;
+    bool operator!=(const BasicStringPiece<TChar>& rhs) const;
+
+    const_iterator begin() const;
+    const_iterator end() const;
+
+private:
+    const TChar* mData;
+    size_t mLength;
+};
+
+using StringPiece = BasicStringPiece<char>;
+using StringPiece16 = BasicStringPiece<char16_t>;
+
+//
+// BasicStringPiece implementation.
+//
+
+template <typename TChar>
+inline BasicStringPiece<TChar>::BasicStringPiece() : mData(nullptr) , mLength(0) {
+}
+
+template <typename TChar>
+inline BasicStringPiece<TChar>::BasicStringPiece(const BasicStringPiece<TChar>& str) :
+        mData(str.mData), mLength(str.mLength) {
+}
+
+template <typename TChar>
+inline BasicStringPiece<TChar>::BasicStringPiece(const std::basic_string<TChar>& str) :
+        mData(str.data()), mLength(str.length()) {
+}
+
+template <>
+inline BasicStringPiece<char>::BasicStringPiece(const char* str) :
+        mData(str), mLength(str != nullptr ? strlen(str) : 0) {
+}
+
+template <>
+inline BasicStringPiece<char16_t>::BasicStringPiece(const char16_t* str) :
+        mData(str), mLength(str != nullptr ? strlen16(str) : 0) {
+}
+
+template <typename TChar>
+inline BasicStringPiece<TChar>::BasicStringPiece(const TChar* str, size_t len) :
+        mData(str), mLength(len) {
+}
+
+template <typename TChar>
+inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::operator=(
+        const BasicStringPiece<TChar>& rhs) {
+    mData = rhs.mData;
+    mLength = rhs.mLength;
+    return *this;
+}
+
+template <typename TChar>
+inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::assign(const TChar* str, size_t len) {
+    mData = str;
+    mLength = len;
+    return *this;
+}
+
+
+template <typename TChar>
+inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(size_t start, size_t len) const {
+    if (start + len > mLength) {
+        return BasicStringPiece<TChar>();
+    }
+    return BasicStringPiece<TChar>(mData + start, len);
+}
+
+template <typename TChar>
+inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(
+        BasicStringPiece<TChar>::const_iterator begin,
+        BasicStringPiece<TChar>::const_iterator end) const {
+    return BasicStringPiece<TChar>(begin, end - begin);
+}
+
+template <typename TChar>
+inline const TChar* BasicStringPiece<TChar>::data() const {
+    return mData;
+}
+
+template <typename TChar>
+inline size_t BasicStringPiece<TChar>::length() const {
+    return mLength;
+}
+
+template <typename TChar>
+inline size_t BasicStringPiece<TChar>::size() const {
+    return mLength;
+}
+
+template <typename TChar>
+inline bool BasicStringPiece<TChar>::empty() const {
+    return mLength == 0;
+}
+
+template <typename TChar>
+inline std::basic_string<TChar> BasicStringPiece<TChar>::toString() const {
+    return std::basic_string<TChar>(mData, mLength);
+}
+
+template <>
+inline int BasicStringPiece<char>::compare(const BasicStringPiece<char>& rhs) const {
+    const char nullStr = '\0';
+    const char* b1 = mData != nullptr ? mData : &nullStr;
+    const char* e1 = b1 + mLength;
+    const char* b2 = rhs.mData != nullptr ? rhs.mData : &nullStr;
+    const char* e2 = b2 + rhs.mLength;
+
+    while (b1 < e1 && b2 < e2) {
+        const int d = static_cast<int>(*b1++) - static_cast<int>(*b2++);
+        if (d) {
+            return d;
+        }
+    }
+    return static_cast<int>(mLength - rhs.mLength);
+}
+
+inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char16_t>& str) {
+    android::String8 utf8(str.data(), str.size());
+    return out.write(utf8.string(), utf8.size());
+}
+
+
+template <>
+inline int BasicStringPiece<char16_t>::compare(const BasicStringPiece<char16_t>& rhs) const {
+    const char16_t nullStr = u'\0';
+    const char16_t* b1 = mData != nullptr ? mData : &nullStr;
+    const char16_t* b2 = rhs.mData != nullptr ? rhs.mData : &nullStr;
+    return strzcmp16(b1, mLength, b2, rhs.mLength);
+}
+
+template <typename TChar>
+inline bool BasicStringPiece<TChar>::operator<(const BasicStringPiece<TChar>& rhs) const {
+    return compare(rhs) < 0;
+}
+
+template <typename TChar>
+inline bool BasicStringPiece<TChar>::operator>(const BasicStringPiece<TChar>& rhs) const {
+    return compare(rhs) > 0;
+}
+
+template <typename TChar>
+inline bool BasicStringPiece<TChar>::operator==(const BasicStringPiece<TChar>& rhs) const {
+    return compare(rhs) == 0;
+}
+
+template <typename TChar>
+inline bool BasicStringPiece<TChar>::operator!=(const BasicStringPiece<TChar>& rhs) const {
+    return compare(rhs) != 0;
+}
+
+template <typename TChar>
+inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::begin() const {
+    return mData;
+}
+
+template <typename TChar>
+inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::end() const {
+    return mData + mLength;
+}
+
+inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char>& str) {
+    return out.write(str.data(), str.size());
+}
+
+} // namespace aapt
+
+#endif // AAPT_STRING_PIECE_H
diff --git a/tools/aapt2/StringPiece_test.cpp b/tools/aapt2/StringPiece_test.cpp
new file mode 100644
index 0000000..43f7a37
--- /dev/null
+++ b/tools/aapt2/StringPiece_test.cpp
@@ -0,0 +1,62 @@
+/*
+ * 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 <algorithm>
+#include <gtest/gtest.h>
+#include <string>
+#include <vector>
+
+#include "StringPiece.h"
+
+namespace aapt {
+
+TEST(StringPieceTest, CompareNonNullTerminatedPiece) {
+    StringPiece a("hello world", 5);
+    StringPiece b("hello moon", 5);
+    EXPECT_EQ(a, b);
+
+    StringPiece16 a16(u"hello world", 5);
+    StringPiece16 b16(u"hello moon", 5);
+    EXPECT_EQ(a16, b16);
+}
+
+TEST(StringPieceTest, PiecesHaveCorrectSortOrder) {
+    std::u16string testing(u"testing");
+    std::u16string banana(u"banana");
+    std::u16string car(u"car");
+
+    EXPECT_TRUE(StringPiece16(testing) > banana);
+    EXPECT_TRUE(StringPiece16(testing) > car);
+    EXPECT_TRUE(StringPiece16(banana) < testing);
+    EXPECT_TRUE(StringPiece16(banana) < car);
+    EXPECT_TRUE(StringPiece16(car) < testing);
+    EXPECT_TRUE(StringPiece16(car) > banana);
+}
+
+TEST(StringPieceTest, PiecesHaveCorrectSortOrderUtf8) {
+    std::string testing("testing");
+    std::string banana("banana");
+    std::string car("car");
+
+    EXPECT_TRUE(StringPiece(testing) > banana);
+    EXPECT_TRUE(StringPiece(testing) > car);
+    EXPECT_TRUE(StringPiece(banana) < testing);
+    EXPECT_TRUE(StringPiece(banana) < car);
+    EXPECT_TRUE(StringPiece(car) < testing);
+    EXPECT_TRUE(StringPiece(car) > banana);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp
new file mode 100644
index 0000000..b159a00
--- /dev/null
+++ b/tools/aapt2/StringPool.cpp
@@ -0,0 +1,348 @@
+/*
+ * 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 "StringPiece.h"
+#include "StringPool.h"
+#include "Util.h"
+
+#include <algorithm>
+#include <androidfw/ResourceTypes.h>
+#include <memory>
+#include <string>
+
+namespace aapt {
+
+StringPool::Ref::Ref() : mEntry(nullptr) {
+}
+
+StringPool::Ref::Ref(const StringPool::Ref& rhs) : mEntry(rhs.mEntry) {
+    if (mEntry != nullptr) {
+        mEntry->ref++;
+    }
+}
+
+StringPool::Ref::Ref(StringPool::Entry* entry) : mEntry(entry) {
+    if (mEntry != nullptr) {
+        mEntry->ref++;
+    }
+}
+
+StringPool::Ref::~Ref() {
+    if (mEntry != nullptr) {
+        mEntry->ref--;
+    }
+}
+
+StringPool::Ref& StringPool::Ref::operator=(const StringPool::Ref& rhs) {
+    if (rhs.mEntry != nullptr) {
+        rhs.mEntry->ref++;
+    }
+
+    if (mEntry != nullptr) {
+        mEntry->ref--;
+    }
+    mEntry = rhs.mEntry;
+    return *this;
+}
+
+const std::u16string* StringPool::Ref::operator->() const {
+    return &mEntry->value;
+}
+
+const std::u16string& StringPool::Ref::operator*() const {
+    return mEntry->value;
+}
+
+size_t StringPool::Ref::getIndex() const {
+    return mEntry->index;
+}
+
+const StringPool::Context& StringPool::Ref::getContext() const {
+    return mEntry->context;
+}
+
+StringPool::StyleRef::StyleRef() : mEntry(nullptr) {
+}
+
+StringPool::StyleRef::StyleRef(const StringPool::StyleRef& rhs) : mEntry(rhs.mEntry) {
+    if (mEntry != nullptr) {
+        mEntry->ref++;
+    }
+}
+
+StringPool::StyleRef::StyleRef(StringPool::StyleEntry* entry) : mEntry(entry) {
+    if (mEntry != nullptr) {
+        mEntry->ref++;
+    }
+}
+
+StringPool::StyleRef::~StyleRef() {
+    if (mEntry != nullptr) {
+        mEntry->ref--;
+    }
+}
+
+StringPool::StyleRef& StringPool::StyleRef::operator=(const StringPool::StyleRef& rhs) {
+    if (rhs.mEntry != nullptr) {
+        rhs.mEntry->ref++;
+    }
+
+    if (mEntry != nullptr) {
+        mEntry->ref--;
+    }
+    mEntry = rhs.mEntry;
+    return *this;
+}
+
+const StringPool::StyleEntry* StringPool::StyleRef::operator->() const {
+    return mEntry;
+}
+
+const StringPool::StyleEntry& StringPool::StyleRef::operator*() const {
+    return *mEntry;
+}
+
+size_t StringPool::StyleRef::getIndex() const {
+    return mEntry->str.getIndex();
+}
+
+const StringPool::Context& StringPool::StyleRef::getContext() const {
+    return mEntry->str.getContext();
+}
+
+StringPool::Ref StringPool::makeRef(const StringPiece16& str) {
+    return makeRefImpl(str, Context{}, true);
+}
+
+StringPool::Ref StringPool::makeRef(const StringPiece16& str, const Context& context) {
+    return makeRefImpl(str, context, true);
+}
+
+StringPool::Ref StringPool::makeRefImpl(const StringPiece16& str, const Context& context,
+        bool unique) {
+    if (unique) {
+        auto iter = mIndexedStrings.find(str);
+        if (iter != std::end(mIndexedStrings)) {
+            return Ref(iter->second);
+        }
+    }
+
+    Entry* entry = new Entry();
+    entry->value = str.toString();
+    entry->context = context;
+    entry->index = mStrings.size();
+    entry->ref = 0;
+    mStrings.emplace_back(entry);
+    mIndexedStrings.insert(std::make_pair(StringPiece16(entry->value), entry));
+    return Ref(entry);
+}
+
+StringPool::StyleRef StringPool::makeRef(const StyleString& str) {
+    return makeRef(str, Context{});
+}
+
+StringPool::StyleRef StringPool::makeRef(const StyleString& str, const Context& context) {
+    Entry* entry = new Entry();
+    entry->value = str.str;
+    entry->context = context;
+    entry->index = mStrings.size();
+    entry->ref = 0;
+    mStrings.emplace_back(entry);
+    mIndexedStrings.insert(std::make_pair(StringPiece16(entry->value), entry));
+
+    StyleEntry* styleEntry = new StyleEntry();
+    styleEntry->str = Ref(entry);
+    for (const aapt::Span& span : str.spans) {
+        styleEntry->spans.emplace_back(Span{makeRef(span.name),
+                span.firstChar, span.lastChar});
+    }
+    styleEntry->ref = 0;
+    mStyles.emplace_back(styleEntry);
+    return StyleRef(styleEntry);
+}
+
+void StringPool::merge(StringPool&& pool) {
+    mIndexedStrings.insert(pool.mIndexedStrings.begin(), pool.mIndexedStrings.end());
+    pool.mIndexedStrings.clear();
+    std::move(pool.mStrings.begin(), pool.mStrings.end(), std::back_inserter(mStrings));
+    pool.mStrings.clear();
+    std::move(pool.mStyles.begin(), pool.mStyles.end(), std::back_inserter(mStyles));
+    pool.mStyles.clear();
+
+    // Assign the indices.
+    const size_t len = mStrings.size();
+    for (size_t index = 0; index < len; index++) {
+        mStrings[index]->index = index;
+    }
+}
+
+void StringPool::hintWillAdd(size_t stringCount, size_t styleCount) {
+    mStrings.reserve(mStrings.size() + stringCount);
+    mStyles.reserve(mStyles.size() + styleCount);
+}
+
+void StringPool::prune() {
+    const auto iterEnd = std::end(mIndexedStrings);
+    auto indexIter = std::begin(mIndexedStrings);
+    while (indexIter != iterEnd) {
+        if (indexIter->second->ref <= 0) {
+            mIndexedStrings.erase(indexIter++);
+        } else {
+            ++indexIter;
+        }
+    }
+
+    auto endIter2 = std::remove_if(std::begin(mStrings), std::end(mStrings),
+            [](const std::unique_ptr<Entry>& entry) -> bool {
+                return entry->ref <= 0;
+            }
+    );
+
+    auto endIter3 = std::remove_if(std::begin(mStyles), std::end(mStyles),
+            [](const std::unique_ptr<StyleEntry>& entry) -> bool {
+                return entry->ref <= 0;
+            }
+    );
+
+    // Remove the entries at the end or else we'll be accessing
+    // a deleted string from the StyleEntry.
+    mStrings.erase(endIter2, std::end(mStrings));
+    mStyles.erase(endIter3, std::end(mStyles));
+}
+
+void StringPool::sort(const std::function<bool(const Entry&, const Entry&)>& cmp) {
+    std::sort(std::begin(mStrings), std::end(mStrings),
+            [&cmp](const std::unique_ptr<Entry>& a, const std::unique_ptr<Entry>& b) -> bool {
+                return cmp(*a, *b);
+            }
+    );
+
+    // Assign the indices.
+    const size_t len = mStrings.size();
+    for (size_t index = 0; index < len; index++) {
+        mStrings[index]->index = index;
+    }
+
+    // Reorder the styles.
+    std::sort(std::begin(mStyles), std::end(mStyles),
+            [](const std::unique_ptr<StyleEntry>& lhs,
+               const std::unique_ptr<StyleEntry>& rhs) -> bool {
+                return lhs->str.getIndex() < rhs->str.getIndex();
+            }
+    );
+}
+
+static uint8_t* encodeLength(uint8_t* data, size_t length) {
+    if (length > 0x7fu) {
+        *data++ = 0x80u | (0x000000ffu & (length >> 8));
+    }
+    *data++ = 0x000000ffu & length;
+    return data;
+}
+
+static size_t encodedLengthByteCount(size_t length) {
+    return length > 0x7fu ? 2 : 1;
+}
+
+bool StringPool::flattenUtf8(BigBuffer* out, const StringPool& pool) {
+    const size_t startIndex = out->size();
+    android::ResStringPool_header* header = out->nextBlock<android::ResStringPool_header>();
+    header->header.type = android::RES_STRING_POOL_TYPE;
+    header->header.headerSize = sizeof(*header);
+    header->stringCount = pool.size();
+    header->flags |= android::ResStringPool_header::UTF8_FLAG;
+
+    uint32_t* indices = out->nextBlock<uint32_t>(pool.size());
+
+    uint32_t* styleIndices = nullptr;
+    if (!pool.mStyles.empty()) {
+        header->styleCount = pool.mStyles.back()->str.getIndex() + 1;
+        styleIndices = out->nextBlock<uint32_t>(header->styleCount);
+    }
+
+    const size_t beforeStringsIndex = out->size();
+    header->stringsStart = beforeStringsIndex - startIndex;
+
+    for (const auto& entry : pool) {
+        *indices = out->size() - beforeStringsIndex;
+        indices++;
+
+        std::string encoded = util::utf16ToUtf8(entry->value);
+
+        const size_t stringByteLength = sizeof(char) * encoded.length();
+        const size_t totalSize = encodedLengthByteCount(entry->value.size())
+                + encodedLengthByteCount(encoded.length())
+                + stringByteLength
+                + sizeof(char);
+
+        uint8_t* data = out->nextBlock<uint8_t>(totalSize);
+
+        // First encode the actual UTF16 string length.
+        data = encodeLength(data, entry->value.size());
+
+        // Now encode the size of the converted UTF8 string.
+        data = encodeLength(data, encoded.length());
+
+        memcpy(data, encoded.data(), stringByteLength);
+        data += stringByteLength;
+        *data = 0;
+    }
+
+    out->align4();
+
+    if (!pool.mStyles.empty()) {
+        const size_t beforeStylesIndex = out->size();
+        header->stylesStart = beforeStylesIndex - startIndex;
+
+        size_t currentIndex = 0;
+        for (const auto& entry : pool.mStyles) {
+            while (entry->str.getIndex() > currentIndex) {
+                styleIndices[currentIndex++] = out->size() - beforeStylesIndex;
+
+                uint32_t* spanOffset = out->nextBlock<uint32_t>();
+                *spanOffset = android::ResStringPool_span::END;
+            }
+            styleIndices[currentIndex++] = out->size() - beforeStylesIndex;
+
+            android::ResStringPool_span* span =
+                    out->nextBlock<android::ResStringPool_span>(entry->spans.size());
+            for (const auto& s : entry->spans) {
+                span->name.index = s.name.getIndex();
+                span->firstChar = s.firstChar;
+                span->lastChar = s.lastChar;
+                span++;
+            }
+
+            uint32_t* spanEnd = out->nextBlock<uint32_t>();
+            *spanEnd = android::ResStringPool_span::END;
+        }
+
+        // The error checking code in the platform looks for an entire
+        // ResStringPool_span structure worth of 0xFFFFFFFF at the end
+        // of the style block, so fill in the remaining 2 32bit words
+        // with 0xFFFFFFFF.
+        const size_t paddingLength = sizeof(android::ResStringPool_span)
+                - sizeof(android::ResStringPool_span::name);
+        uint8_t* padding = out->nextBlock<uint8_t>(paddingLength);
+        memset(padding, 0xff, paddingLength);
+        out->align4();
+    }
+    header->header.size = out->size() - startIndex;
+    return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h
new file mode 100644
index 0000000..2aa5b65
--- /dev/null
+++ b/tools/aapt2/StringPool.h
@@ -0,0 +1,215 @@
+/*
+ * 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_STRING_POOL_H
+#define AAPT_STRING_POOL_H
+
+#include "BigBuffer.h"
+#include "ConfigDescription.h"
+#include "StringPiece.h"
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace aapt {
+
+struct Span {
+    std::u16string name;
+    uint32_t firstChar;
+    uint32_t lastChar;
+};
+
+struct StyleString {
+    std::u16string str;
+    std::vector<Span> spans;
+};
+
+class StringPool {
+public:
+    struct Context {
+        uint32_t priority;
+        ConfigDescription config;
+    };
+
+    class Entry;
+
+    class Ref {
+    public:
+        Ref();
+        Ref(const Ref&);
+        ~Ref();
+
+        Ref& operator=(const Ref& rhs);
+        const std::u16string* operator->() const;
+        const std::u16string& operator*() const;
+
+        size_t getIndex() const;
+        const Context& getContext() const;
+
+    private:
+        friend class StringPool;
+
+        Ref(Entry* entry);
+
+        Entry* mEntry;
+    };
+
+    class StyleEntry;
+
+    class StyleRef {
+    public:
+        StyleRef();
+        StyleRef(const StyleRef&);
+        ~StyleRef();
+
+        StyleRef& operator=(const StyleRef& rhs);
+        const StyleEntry* operator->() const;
+        const StyleEntry& operator*() const;
+
+        size_t getIndex() const;
+        const Context& getContext() const;
+
+    private:
+        friend class StringPool;
+
+        StyleRef(StyleEntry* entry);
+
+        StyleEntry* mEntry;
+    };
+
+    class Entry {
+    public:
+        std::u16string value;
+        Context context;
+        size_t index;
+
+    private:
+        friend class StringPool;
+        friend class Ref;
+
+        int ref;
+    };
+
+    struct Span {
+        Ref name;
+        uint32_t firstChar;
+        uint32_t lastChar;
+    };
+
+    class StyleEntry {
+    public:
+        Ref str;
+        std::vector<Span> spans;
+
+    private:
+        friend class StringPool;
+        friend class StyleRef;
+
+        int ref;
+    };
+
+    using const_iterator = std::vector<std::unique_ptr<Entry>>::const_iterator;
+
+    static bool flattenUtf8(BigBuffer* out, const StringPool& pool);
+    static bool flatten(BigBuffer* out, const StringPool& pool);
+
+    StringPool() = default;
+    StringPool(const StringPool&) = delete;
+
+    /**
+     * Adds a string to the pool, unless it already exists. Returns
+     * a reference to the string in the pool.
+     */
+    Ref makeRef(const StringPiece16& str);
+
+    /**
+     * Adds a string to the pool, unless it already exists, with a context
+     * object that can be used when sorting the string pool. Returns
+     * a reference to the string in the pool.
+     */
+    Ref makeRef(const StringPiece16& str, const Context& context);
+
+    /**
+     * Adds a style to the string pool and returns a reference to it.
+     */
+    StyleRef makeRef(const StyleString& str);
+
+    /**
+     * Adds a style to the string pool with a context object that
+     * can be used when sorting the string pool. Returns a reference
+     * to the style in the string pool.
+     */
+    StyleRef makeRef(const StyleString& str, const Context& context);
+
+    /**
+     * Moves pool into this one without coalescing strings. When this
+     * function returns, pool will be empty.
+     */
+    void merge(StringPool&& pool);
+
+    /**
+     * Retuns the number of strings in the table.
+     */
+    inline size_t size() const;
+
+    /**
+     * Reserves space for strings and styles as an optimization.
+     */
+    void hintWillAdd(size_t stringCount, size_t styleCount);
+
+    /**
+     * Sorts the strings according to some comparison function.
+     */
+    void sort(const std::function<bool(const Entry&, const Entry&)>& cmp);
+
+    /**
+     * Removes any strings that have no references.
+     */
+    void prune();
+
+private:
+    friend const_iterator begin(const StringPool& pool);
+    friend const_iterator end(const StringPool& pool);
+
+    Ref makeRefImpl(const StringPiece16& str, const Context& context, bool unique);
+
+    std::vector<std::unique_ptr<Entry>> mStrings;
+    std::vector<std::unique_ptr<StyleEntry>> mStyles;
+    std::multimap<StringPiece16, Entry*> mIndexedStrings;
+};
+
+//
+// Inline implementation
+//
+
+inline size_t StringPool::size() const {
+    return mStrings.size();
+}
+
+inline StringPool::const_iterator begin(const StringPool& pool) {
+    return pool.mStrings.begin();
+}
+
+inline StringPool::const_iterator end(const StringPool& pool) {
+    return pool.mStrings.end();
+}
+
+} // namespace aapt
+
+#endif // AAPT_STRING_POOL_H
diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp
new file mode 100644
index 0000000..5ee1a2d
--- /dev/null
+++ b/tools/aapt2/StringPool_test.cpp
@@ -0,0 +1,218 @@
+/*
+ * 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 "StringPool.h"
+#include "Util.h"
+
+#include <gtest/gtest.h>
+#include <string>
+
+namespace aapt {
+
+TEST(StringPoolTest, InsertOneString) {
+    StringPool pool;
+
+    StringPool::Ref ref = pool.makeRef(u"wut");
+    EXPECT_EQ(*ref, u"wut");
+}
+
+TEST(StringPoolTest, InsertTwoUniqueStrings) {
+    StringPool pool;
+
+    StringPool::Ref ref = pool.makeRef(u"wut");
+    StringPool::Ref ref2 = pool.makeRef(u"hey");
+
+    EXPECT_EQ(*ref, u"wut");
+    EXPECT_EQ(*ref2, u"hey");
+}
+
+TEST(StringPoolTest, DoNotInsertNewDuplicateString) {
+    StringPool pool;
+
+    StringPool::Ref ref = pool.makeRef(u"wut");
+    StringPool::Ref ref2 = pool.makeRef(u"wut");
+
+    EXPECT_EQ(*ref, u"wut");
+    EXPECT_EQ(*ref2, u"wut");
+    EXPECT_EQ(1u, pool.size());
+}
+
+TEST(StringPoolTest, MaintainInsertionOrderIndex) {
+    StringPool pool;
+
+    StringPool::Ref ref = pool.makeRef(u"z");
+    StringPool::Ref ref2 = pool.makeRef(u"a");
+    StringPool::Ref ref3 = pool.makeRef(u"m");
+
+    EXPECT_EQ(0u, ref.getIndex());
+    EXPECT_EQ(1u, ref2.getIndex());
+    EXPECT_EQ(2u, ref3.getIndex());
+}
+
+TEST(StringPoolTest, PruneStringsWithNoReferences) {
+    StringPool pool;
+
+    {
+        StringPool::Ref ref = pool.makeRef(u"wut");
+        EXPECT_EQ(*ref, u"wut");
+        EXPECT_EQ(1u, pool.size());
+    }
+
+    EXPECT_EQ(1u, pool.size());
+    pool.prune();
+    EXPECT_EQ(0u, pool.size());
+}
+
+TEST(StringPoolTest, SortAndMaintainIndexesInReferences) {
+    StringPool pool;
+
+    StringPool::Ref ref = pool.makeRef(u"z");
+    StringPool::StyleRef ref2 = pool.makeRef(StyleString{ {u"a"} });
+    StringPool::Ref ref3 = pool.makeRef(u"m");
+
+    EXPECT_EQ(*ref, u"z");
+    EXPECT_EQ(0u, ref.getIndex());
+
+    EXPECT_EQ(*(ref2->str), u"a");
+    EXPECT_EQ(1u, ref2.getIndex());
+
+    EXPECT_EQ(*ref3, u"m");
+    EXPECT_EQ(2u, ref3.getIndex());
+
+    pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
+        return a.value < b.value;
+    });
+
+
+    EXPECT_EQ(*ref, u"z");
+    EXPECT_EQ(2u, ref.getIndex());
+
+    EXPECT_EQ(*(ref2->str), u"a");
+    EXPECT_EQ(0u, ref2.getIndex());
+
+    EXPECT_EQ(*ref3, u"m");
+    EXPECT_EQ(1u, ref3.getIndex());
+}
+
+TEST(StringPoolTest, SortAndStillDedupe) {
+    StringPool pool;
+
+    StringPool::Ref ref = pool.makeRef(u"z");
+    StringPool::Ref ref2 = pool.makeRef(u"a");
+    StringPool::Ref ref3 = pool.makeRef(u"m");
+
+    pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
+        return a.value < b.value;
+    });
+
+    StringPool::Ref ref4 = pool.makeRef(u"z");
+    StringPool::Ref ref5 = pool.makeRef(u"a");
+    StringPool::Ref ref6 = pool.makeRef(u"m");
+
+    EXPECT_EQ(ref4.getIndex(), ref.getIndex());
+    EXPECT_EQ(ref5.getIndex(), ref2.getIndex());
+    EXPECT_EQ(ref6.getIndex(), ref3.getIndex());
+}
+
+TEST(StringPoolTest, AddStyles) {
+    StringPool pool;
+
+    StyleString str {
+        { u"android" },
+        {
+            Span{ { u"b" }, 2, 6 }
+        }
+    };
+
+    StringPool::StyleRef ref = pool.makeRef(str);
+
+    EXPECT_EQ(0u, ref.getIndex());
+    EXPECT_EQ(std::u16string(u"android"), *(ref->str));
+    ASSERT_EQ(1u, ref->spans.size());
+
+    const StringPool::Span& span = ref->spans.front();
+    EXPECT_EQ(*(span.name), u"b");
+    EXPECT_EQ(2u, span.firstChar);
+    EXPECT_EQ(6u, span.lastChar);
+}
+
+TEST(StringPoolTest, DoNotDedupeStyleWithSameStringAsNonStyle) {
+    StringPool pool;
+
+    StringPool::Ref ref = pool.makeRef(u"android");
+
+    StyleString str { { u"android" } };
+    StringPool::StyleRef styleRef = pool.makeRef(str);
+
+    EXPECT_NE(ref.getIndex(), styleRef.getIndex());
+}
+
+constexpr const char16_t* sLongString = u"バッテリーを長持ちさせるため、バッテリーセーバーは端末のパフォーマンスを抑え、バイブレーション、位置情報サービス、大半のバックグラウンドデータを制限します。メール、SMSや、同期を使 用するその他のアプリは、起動しても更新されないことがあります。バッテリーセーバーは端末の充電中は自動的にOFFになります。";
+
+TEST(StringPoolTest, FlattenUtf8) {
+    StringPool pool;
+
+    StringPool::Ref ref1 = pool.makeRef(u"hello");
+    StringPool::Ref ref2 = pool.makeRef(u"goodbye");
+    StringPool::Ref ref3 = pool.makeRef(sLongString);
+    StringPool::StyleRef ref4 = pool.makeRef(StyleString{
+            { u"style" },
+            { Span{ { u"b" }, 0, 1 }, Span{ { u"i" }, 2, 3 } }
+    });
+
+    EXPECT_EQ(0u, ref1.getIndex());
+    EXPECT_EQ(1u, ref2.getIndex());
+    EXPECT_EQ(2u, ref3.getIndex());
+    EXPECT_EQ(3u, ref4.getIndex());
+
+    BigBuffer buffer(1024);
+    StringPool::flattenUtf8(&buffer, pool);
+
+    uint8_t* data = new uint8_t[buffer.size()];
+    uint8_t* p = data;
+    for (const auto& b : buffer) {
+        memcpy(p, b.buffer.get(), b.size);
+        p += b.size;
+    }
+
+    {
+        android::ResStringPool test;
+        ASSERT_EQ(android::NO_ERROR, test.setTo(data, buffer.size()));
+
+        EXPECT_EQ(util::getString(test, 0), u"hello");
+        EXPECT_EQ(util::getString(test, 1), u"goodbye");
+        EXPECT_EQ(util::getString(test, 2), sLongString);
+        EXPECT_EQ(util::getString(test, 3), u"style");
+
+        const android::ResStringPool_span* span = test.styleAt(3);
+        ASSERT_NE(nullptr, span);
+        EXPECT_EQ(util::getString(test, span->name.index), u"b");
+        EXPECT_EQ(0u, span->firstChar);
+        EXPECT_EQ(1u, span->lastChar);
+        span++;
+
+        ASSERT_NE(android::ResStringPool_span::END, span->name.index);
+        EXPECT_EQ(util::getString(test, span->name.index), u"i");
+        EXPECT_EQ(2u, span->firstChar);
+        EXPECT_EQ(3u, span->lastChar);
+        span++;
+
+        EXPECT_EQ(android::ResStringPool_span::END, span->name.index);
+    }
+    delete[] data;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/TableFlattener.cpp b/tools/aapt2/TableFlattener.cpp
new file mode 100644
index 0000000..c306185
--- /dev/null
+++ b/tools/aapt2/TableFlattener.cpp
@@ -0,0 +1,511 @@
+/*
+ * 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 <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,
+                 std::vector<std::pair<ResourceNameRef, uint32_t>>& 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()) {
+            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);
+    }
+
+    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) {
+            flattenEntry({}, *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;
+    std::vector<std::pair<ResourceNameRef, uint32_t>>& mSymbols;
+    android::ResTable_map_entry* mMap;
+};
+
+TableFlattener::TableFlattener(Options options)
+: mOptions(options) {
+}
+
+bool TableFlattener::flattenValue(BigBuffer* out, const FlatEntry& flatEntry,
+        std::vector<std::pair<ResourceNameRef, uint32_t>>& symbolEntries) {
+    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);
+        }
+
+        android::Res_value* outValue = out->nextBlock<android::Res_value>();
+
+        const Item& item = static_cast<const Item&>(flatEntry.value);
+        if (!item.flatten(*outValue)) {
+            return false;
+        }
+
+        if (outValue->data == 0x0) {
+            visitFunc<Reference>(item, [&](const Reference& reference) {
+                symbolEntries.push_back({
+                        ResourceNameRef(reference.name),
+                        out->size() - sizeof(outValue->data)
+                });
+            });
+        }
+        outValue->size = sizeof(*outValue);
+        return true;
+    }
+
+    MapFlattener flattener(out, flatEntry, symbolEntries);
+    flatEntry.value.accept(flattener, {});
+    return true;
+}
+
+bool TableFlattener::flatten(BigBuffer* out, const ResourceTable& table) {
+    const size_t beginning = out->size();
+
+    if (table.getPackage().size() == 0) {
+        Logger::error()
+                << "ResourceTable has no package name."
+                << std::endl;
+        return false;
+    }
+
+    if (table.getPackageId() == ResourceTable::kUnsetPackageId) {
+        Logger::error()
+                << "ResourceTable has no package ID set."
+                << std::endl;
+        return false;
+    }
+
+    std::vector<std::pair<ResourceNameRef, uint32_t>> 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();
+
+        // 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);
+                }
+            }
+        }
+
+        // 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;
+            }
+
+            for (const auto& configValue : entry->values) {
+                data[configValue.config].push_back(FlatEntry{
+                        *entry,
+                        *configValue.value,
+                        static_cast<uint32_t>(keyIndex),
+                        static_cast<uint32_t>(sourcePool.makeRef(util::utf8ToUtf16(
+                                        configValue.source.path)).getIndex()),
+                        static_cast<uint32_t>(configValue.source.line)
+                });
+            }
+        }
+
+        // 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::flattenUtf8(out, typePool);
+    package->keyStrings = out->size() - beforePackageIndex;
+    StringPool::flattenUtf8(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
new file mode 100644
index 0000000..0ae798c
--- /dev/null
+++ b/tools/aapt2/TableFlattener.h
@@ -0,0 +1,60 @@
+/*
+ * 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 {
+
+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,
+                      std::vector<std::pair<ResourceNameRef, uint32_t>>& symbolEntries);
+
+    Options mOptions;
+};
+
+} // namespace aapt
+
+#endif // AAPT_TABLE_FLATTENER_H
diff --git a/tools/aapt2/Util.cpp b/tools/aapt2/Util.cpp
new file mode 100644
index 0000000..8a4c88f
--- /dev/null
+++ b/tools/aapt2/Util.cpp
@@ -0,0 +1,290 @@
+/*
+ * 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 "Maybe.h"
+#include "StringPiece.h"
+#include "Util.h"
+
+#include <algorithm>
+#include <ostream>
+#include <string>
+#include <utils/Unicode.h>
+#include <vector>
+
+namespace aapt {
+namespace util {
+
+static std::vector<std::string> splitAndTransform(const StringPiece& str, char sep,
+        const std::function<char(char)>& f) {
+    std::vector<std::string> parts;
+    const StringPiece::const_iterator end = std::end(str);
+    StringPiece::const_iterator start = std::begin(str);
+    StringPiece::const_iterator current;
+    do {
+        current = std::find(start, end, sep);
+        parts.emplace_back(str.substr(start, current).toString());
+        if (f) {
+            std::string& part = parts.back();
+            std::transform(part.begin(), part.end(), part.begin(), f);
+        }
+        start = current + 1;
+    } while (current != end);
+    return parts;
+}
+
+std::vector<std::string> split(const StringPiece& str, char sep) {
+    return splitAndTransform(str, sep, nullptr);
+}
+
+std::vector<std::string> splitAndLowercase(const StringPiece& str, char sep) {
+    return splitAndTransform(str, sep, ::tolower);
+}
+
+bool stringEndsWith(const StringPiece& str, const StringPiece& suffix) {
+    if (str.size() < suffix.size()) {
+        return false;
+    }
+    return str.substr(str.size() - suffix.size(), suffix.size()) == suffix;
+}
+
+StringPiece16 trimWhitespace(const StringPiece16& str) {
+    if (str.size() == 0 || str.data() == nullptr) {
+        return str;
+    }
+
+    const char16_t* start = str.data();
+    const char16_t* end = str.data() + str.length();
+
+    while (start != end && util::isspace16(*start)) {
+        start++;
+    }
+
+    while (end != start && util::isspace16(*(end - 1))) {
+        end--;
+    }
+
+    return StringPiece16(start, end - start);
+}
+
+StringPiece16::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece16& str,
+        const StringPiece16& allowedChars) {
+    const auto endIter = str.end();
+    for (auto iter = str.begin(); iter != endIter; ++iter) {
+        char16_t c = *iter;
+        if ((c >= u'a' && c <= u'z') ||
+                (c >= u'A' && c <= u'Z') ||
+                (c >= u'0' && c <= u'9')) {
+            continue;
+        }
+
+        bool match = false;
+        for (char16_t i : allowedChars) {
+            if (c == i) {
+                match = true;
+                break;
+            }
+        }
+
+        if (!match) {
+            return iter;
+        }
+    }
+    return endIter;
+}
+
+static Maybe<char16_t> parseUnicodeCodepoint(const char16_t** start, const char16_t* end) {
+    char16_t code = 0;
+    for (size_t i = 0; i < 4 && *start != end; i++, (*start)++) {
+        char16_t c = **start;
+        int a;
+        if (c >= '0' && c <= '9') {
+            a = c - '0';
+        } else if (c >= 'a' && c <= 'f') {
+            a = c - 'a' + 10;
+        } else if (c >= 'A' && c <= 'F') {
+            a = c - 'A' + 10;
+        } else {
+            return make_nothing<char16_t>();
+        }
+        code = (code << 4) | a;
+    }
+    return make_value(code);
+}
+
+StringBuilder& StringBuilder::append(const StringPiece16& str) {
+    if (!mError.empty()) {
+        return *this;
+    }
+
+    const char16_t* const end = str.end();
+    const char16_t* start = str.begin();
+    const char16_t* current = start;
+    while (current != end) {
+        if (*current == u'"') {
+            if (!mQuote && mTrailingSpace) {
+                // We found an opening quote, and we have
+                // trailing space, so we should append that
+                // space now.
+                if (mTrailingSpace) {
+                    // We had trailing whitespace, so
+                    // replace with a single space.
+                    if (!mStr.empty()) {
+                        mStr += u' ';
+                    }
+                    mTrailingSpace = false;
+                }
+            }
+            mQuote = !mQuote;
+            mStr.append(start, current - start);
+            start = current + 1;
+        } else if (*current == u'\'' && !mQuote) {
+            // This should be escaped.
+            mError = "unescaped apostrophe";
+            return *this;
+        } else if (*current == u'\\') {
+            // This is an escape sequence, convert to the real value.
+            if (!mQuote && mTrailingSpace) {
+                // We had trailing whitespace, so
+                // replace with a single space.
+                if (!mStr.empty()) {
+                    mStr += u' ';
+                }
+                mTrailingSpace = false;
+            }
+            mStr.append(start, current - start);
+            start = current + 1;
+
+            current++;
+            if (current != end) {
+                switch (*current) {
+                    case u't':
+                        mStr += u'\t';
+                        break;
+                    case u'n':
+                        mStr += u'\n';
+                        break;
+                    case u'#':
+                        mStr += u'#';
+                        break;
+                    case u'@':
+                        mStr += u'@';
+                        break;
+                    case u'?':
+                        mStr += u'?';
+                        break;
+                    case u'"':
+                        mStr += u'"';
+                        break;
+                    case u'\'':
+                        mStr += u'\'';
+                        break;
+                    case u'\\':
+                        mStr += u'\\';
+                        break;
+                    case u'u': {
+                        current++;
+                        Maybe<char16_t> c = parseUnicodeCodepoint(&current, end);
+                        if (!c) {
+                            mError = "invalid unicode escape sequence";
+                            return *this;
+                        }
+                        mStr += c.value();
+                        current -= 1;
+                        break;
+                    }
+
+                    default:
+                        // Ignore.
+                        break;
+                }
+                start = current + 1;
+            }
+        } else if (!mQuote) {
+            // This is not quoted text, so look for whitespace.
+            if (isspace16(*current)) {
+                // We found whitespace, see if we have seen some
+                // before.
+                if (!mTrailingSpace) {
+                    // We didn't see a previous adjacent space,
+                    // so mark that we did.
+                    mTrailingSpace = true;
+                    mStr.append(start, current - start);
+                }
+
+                // Keep skipping whitespace.
+                start = current + 1;
+            } else if (mTrailingSpace) {
+                // We saw trailing space before, so replace all
+                // that trailing space with one space.
+                if (!mStr.empty()) {
+                    mStr += u' ';
+                }
+                mTrailingSpace = false;
+            }
+        }
+        current++;
+    }
+    mStr.append(start, end - start);
+    return *this;
+}
+
+std::u16string utf8ToUtf16(const StringPiece& utf8) {
+    ssize_t utf16Length = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(utf8.data()),
+            utf8.length());
+    if (utf16Length <= 0) {
+        return {};
+    }
+
+    std::u16string utf16;
+    utf16.resize(utf16Length);
+    utf8_to_utf16(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length(), &*utf16.begin());
+    return utf16;
+}
+
+std::string utf16ToUtf8(const StringPiece16& utf16) {
+    ssize_t utf8Length = utf16_to_utf8_length(utf16.data(), utf16.length());
+    if (utf8Length <= 0) {
+        return {};
+    }
+
+    std::string utf8;
+    utf8.resize(utf8Length);
+    utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin());
+    return utf8;
+}
+
+bool writeAll(std::ostream& out, const BigBuffer& buffer) {
+    for (const auto& b : buffer) {
+        if (!out.write(reinterpret_cast<const char*>(b.buffer.get()), b.size)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+std::unique_ptr<uint8_t[]> copy(const BigBuffer& buffer) {
+    std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(new uint8_t[buffer.size()]);
+    uint8_t* p = data.get();
+    for (const auto& block : buffer) {
+        memcpy(p, block.buffer.get(), block.size);
+        p += block.size;
+    }
+    return data;
+}
+
+} // namespace util
+} // namespace aapt
diff --git a/tools/aapt2/Util.h b/tools/aapt2/Util.h
new file mode 100644
index 0000000..4c5249be
--- /dev/null
+++ b/tools/aapt2/Util.h
@@ -0,0 +1,276 @@
+/*
+ * 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_UTIL_H
+#define AAPT_UTIL_H
+
+#include "BigBuffer.h"
+#include "StringPiece.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <functional>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <vector>
+
+namespace aapt {
+namespace util {
+
+std::vector<std::string> split(const StringPiece& str, char sep);
+std::vector<std::string> splitAndLowercase(const StringPiece& str, char sep);
+
+/**
+ * Returns true if the string ends with suffix.
+ */
+bool stringEndsWith(const StringPiece& str, const StringPiece& suffix);
+
+/**
+ * Creates a new StringPiece16 that points to a substring
+ * of the original string without leading or trailing whitespace.
+ */
+StringPiece16 trimWhitespace(const StringPiece16& str);
+
+/**
+ * UTF-16 isspace(). It basically checks for lower range characters that are
+ * whitespace.
+ */
+inline bool isspace16(char16_t c) {
+    return c < 0x0080 && isspace(c);
+}
+
+/**
+ * Returns an iterator to the first character that is not alpha-numeric and that
+ * is not in the allowedChars set.
+ */
+StringPiece16::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece16& str,
+        const StringPiece16& allowedChars);
+
+/**
+ * Makes a std::unique_ptr<> with the template parameter inferred by the compiler.
+ * This will be present in C++14 and can be removed then.
+ */
+template <typename T, class... Args>
+std::unique_ptr<T> make_unique(Args&&... args) {
+    return std::unique_ptr<T>(new T{std::forward<Args>(args)...});
+}
+
+/**
+ * Writes a set of items to the std::ostream, joining the times with the provided
+ * separator.
+ */
+template <typename Iterator>
+::std::function<::std::ostream&(::std::ostream&)> joiner(Iterator begin, Iterator end,
+        const char* sep) {
+    return [begin, end, sep](::std::ostream& out) -> ::std::ostream& {
+        for (auto iter = begin; iter != end; ++iter) {
+            if (iter != begin) {
+                out << sep;
+            }
+            out << *iter;
+        }
+        return out;
+    };
+}
+
+inline ::std::function<::std::ostream&(::std::ostream&)> formatSize(size_t size) {
+    return [size](::std::ostream& out) -> ::std::ostream& {
+        constexpr size_t K = 1024;
+        constexpr size_t M = K * K;
+        constexpr size_t G = M * M;
+        if (size < K) {
+            out << size << "B";
+        } else if (size < M) {
+            out << (double(size) / K) << " KiB";
+        } else if (size < G) {
+            out << (double(size) / M) << " MiB";
+        } else {
+            out << (double(size) / G) << " GiB";
+        }
+        return out;
+    };
+}
+
+/**
+ * Helper method to extract a string from a StringPool.
+ */
+inline StringPiece16 getString(const android::ResStringPool& pool, size_t idx) {
+    size_t len;
+    const char16_t* str = pool.stringAt(idx, &len);
+    if (str != nullptr) {
+        return StringPiece16(str, len);
+    }
+    return StringPiece16();
+}
+
+class StringBuilder {
+public:
+    StringBuilder& append(const StringPiece16& str);
+    const std::u16string& str() const;
+    const std::string& error() const;
+    operator bool() const;
+
+private:
+    std::u16string mStr;
+    bool mQuote = false;
+    bool mTrailingSpace = false;
+    std::string mError;
+};
+
+inline const std::u16string& StringBuilder::str() const {
+    return mStr;
+}
+
+inline const std::string& StringBuilder::error() const {
+    return mError;
+}
+
+inline StringBuilder::operator bool() const {
+    return mError.empty();
+}
+
+/**
+ * Converts a UTF8 string to a UTF16 string.
+ */
+std::u16string utf8ToUtf16(const StringPiece& utf8);
+std::string utf16ToUtf8(const StringPiece16& utf8);
+
+/**
+ * Writes the entire BigBuffer to the output stream.
+ */
+bool writeAll(std::ostream& out, const BigBuffer& buffer);
+
+/*
+ * Copies the entire BigBuffer into a single buffer.
+ */
+std::unique_ptr<uint8_t[]> copy(const BigBuffer& buffer);
+
+/**
+ * A Tokenizer implemented as an iterable collection. It does not allocate
+ * any memory on the heap nor use standard containers.
+ */
+template <typename Char>
+class Tokenizer {
+public:
+    class iterator {
+    public:
+        iterator(const iterator&) = default;
+        iterator& operator=(const iterator&) = default;
+
+        iterator& operator++();
+        BasicStringPiece<Char> operator*();
+        bool operator==(const iterator& rhs) const;
+        bool operator!=(const iterator& rhs) const;
+
+    private:
+        friend class Tokenizer<Char>;
+
+        iterator(BasicStringPiece<Char> s, Char sep, BasicStringPiece<Char> tok);
+
+        BasicStringPiece<Char> str;
+        Char separator;
+        BasicStringPiece<Char> token;
+    };
+
+    Tokenizer(BasicStringPiece<Char> str, Char sep);
+    iterator begin();
+    iterator end();
+
+private:
+    const iterator mBegin;
+    const iterator mEnd;
+};
+
+template <typename Char>
+inline Tokenizer<Char> tokenize(BasicStringPiece<Char> str, Char sep) {
+    return Tokenizer<Char>(str, sep);
+}
+
+template <typename Char>
+typename Tokenizer<Char>::iterator& Tokenizer<Char>::iterator::operator++() {
+    const Char* start = token.end();
+    const Char* end = str.end();
+    if (start == end) {
+        token.assign(token.end(), 0);
+        return *this;
+    }
+
+    start += 1;
+    const Char* current = start;
+    while (current != end) {
+        if (*current == separator) {
+            token.assign(start, current - start);
+            return *this;
+        }
+        ++current;
+    }
+    token.assign(start, end - start);
+    return *this;
+}
+
+template <typename Char>
+inline BasicStringPiece<Char> Tokenizer<Char>::iterator::operator*() {
+    return token;
+}
+
+template <typename Char>
+inline bool Tokenizer<Char>::iterator::operator==(const iterator& rhs) const {
+    // We check equality here a bit differently.
+    // We need to know that the addresses are the same.
+    return token.begin() == rhs.token.begin() && token.end() == rhs.token.end();
+}
+
+template <typename Char>
+inline bool Tokenizer<Char>::iterator::operator!=(const iterator& rhs) const {
+    return !(*this == rhs);
+}
+
+template <typename Char>
+inline Tokenizer<Char>::iterator::iterator(BasicStringPiece<Char> s, Char sep,
+                                           BasicStringPiece<Char> tok) :
+        str(s), separator(sep), token(tok) {
+}
+
+template <typename Char>
+inline typename Tokenizer<Char>::iterator Tokenizer<Char>::begin() {
+    return mBegin;
+}
+
+template <typename Char>
+inline typename Tokenizer<Char>::iterator Tokenizer<Char>::end() {
+    return mEnd;
+}
+
+template <typename Char>
+inline Tokenizer<Char>::Tokenizer(BasicStringPiece<Char> str, Char sep) :
+        mBegin(++iterator(str, sep, BasicStringPiece<Char>(str.begin() - 1, 0))),
+        mEnd(str, sep, BasicStringPiece<Char>(str.end(), 0)) {
+}
+
+} // namespace util
+
+/**
+ * Stream operator for functions. Calls the function with the stream as an argument.
+ * In the aapt namespace for lookup.
+ */
+inline ::std::ostream& operator<<(::std::ostream& out,
+                                  ::std::function<::std::ostream&(::std::ostream&)> f) {
+    return f(out);
+}
+
+} // namespace aapt
+
+#endif // AAPT_UTIL_H
diff --git a/tools/aapt2/Util_test.cpp b/tools/aapt2/Util_test.cpp
new file mode 100644
index 0000000..7dbe7e0
--- /dev/null
+++ b/tools/aapt2/Util_test.cpp
@@ -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.
+ */
+
+#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("hello.xml", ".xml"));
+}
+
+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);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/XliffXmlPullParser.cpp b/tools/aapt2/XliffXmlPullParser.cpp
new file mode 100644
index 0000000..f0950a3
--- /dev/null
+++ b/tools/aapt2/XliffXmlPullParser.cpp
@@ -0,0 +1,108 @@
+/*
+ * 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();
+}
+
+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
new file mode 100644
index 0000000..d362521
--- /dev/null
+++ b/tools/aapt2/XliffXmlPullParser.h
@@ -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.
+ */
+
+#ifndef AAPT_XLIFF_XML_PULL_PARSER_H
+#define AAPT_XLIFF_XML_PULL_PARSER_H
+
+#include "XmlPullParser.h"
+
+#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;
+    const std::string& getLastError() const;
+    Event next();
+
+    const std::u16string& getComment() const;
+    size_t getLineNumber() const;
+    size_t getDepth() const;
+
+    const std::u16string& getText() const;
+
+    const std::u16string& getNamespacePrefix() const;
+    const std::u16string& getNamespaceUri() const;
+
+    const std::u16string& getElementNamespace() const;
+    const std::u16string& getElementName() const;
+
+    const_iterator beginAttributes() const;
+    const_iterator endAttributes() const;
+    size_t getAttributeCount() const;
+
+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
new file mode 100644
index 0000000..f9030724
--- /dev/null
+++ b/tools/aapt2/XliffXmlPullParser_test.cpp
@@ -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.
+ */
+
+#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/XmlFlattener.cpp b/tools/aapt2/XmlFlattener.cpp
new file mode 100644
index 0000000..b6ca6d5
--- /dev/null
+++ b/tools/aapt2/XmlFlattener.cpp
@@ -0,0 +1,437 @@
+/*
+ * 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 {
+
+struct AttributeValueFlattener : ValueVisitor {
+    struct Args : ValueVisitorArgs {
+        Args(std::shared_ptr<Resolver> r, SourceLogger& s, android::Res_value& oV,
+                std::shared_ptr<XmlPullParser> p, bool& e, StringPool::Ref& rV,
+                std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>& sR) :
+                resolver(r), logger(s), outValue(oV), parser(p), error(e), rawValue(rV),
+                stringRefs(sR) {
+        }
+
+        std::shared_ptr<Resolver> resolver;
+        SourceLogger& logger;
+        android::Res_value& outValue;
+        std::shared_ptr<XmlPullParser> parser;
+        bool& error;
+        StringPool::Ref& rawValue;
+        std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>& stringRefs;
+    };
+
+    void visit(Reference& reference, ValueVisitorArgs& a) override {
+        Args& args = static_cast<Args&>(a);
+
+        Maybe<ResourceId> result = args.resolver->findId(reference.name);
+        if (!result || !result.value().isValid()) {
+            args.logger.error(args.parser->getLineNumber())
+                    << "unresolved reference '"
+                    << reference.name
+                    << "'."
+                    << std::endl;
+            args.error = true;
+        } else {
+            reference.id = result.value();
+            reference.flatten(args.outValue);
+        }
+    }
+
+    void visit(String& string, ValueVisitorArgs& a) override {
+        Args& args = static_cast<Args&>(a);
+
+        args.outValue.dataType = android::Res_value::TYPE_STRING;
+        args.stringRefs.emplace_back(args.rawValue,
+                reinterpret_cast<android::ResStringPool_ref*>(&args.outValue.data));
+    }
+
+    void visitItem(Item& item, ValueVisitorArgs& a) override {
+        Args& args = static_cast<Args&>(a);
+        item.flatten(args.outValue);
+    }
+};
+
+struct XmlAttribute {
+    uint32_t resourceId;
+    const XmlPullParser::Attribute* xmlAttr;
+    const Attribute* attr;
+    StringPool::Ref nameRef;
+};
+
+static bool lessAttributeId(const XmlAttribute& a, uint32_t id) {
+    return a.resourceId < id;
+}
+
+XmlFlattener::XmlFlattener(const std::shared_ptr<Resolver>& resolver) : mResolver(resolver) {
+}
+
+/**
+ * Reads events from the parser and writes to a BigBuffer. 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.
+ */
+Maybe<size_t> XmlFlattener::flatten(const Source& source,
+                                    const std::shared_ptr<XmlPullParser>& parser,
+                                    BigBuffer* outBuffer, Options options) {
+    SourceLogger logger(source);
+    StringPool pool;
+    bool error = false;
+
+    size_t smallestStrippedAttributeSdk = std::numeric_limits<size_t>::max();
+
+    // 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;
+
+    // Attribute resource IDs are stored in the same order
+    // as the attribute names appear in the StringPool.
+    // Since the StringPool contains more than just attribute
+    // names, to maintain a tight packing of resource IDs,
+    // we must ensure that attribute names appear first
+    // in our StringPool. For this, we assign a low priority
+    // (0xffffffff) to non-attribute strings. Attribute
+    // names will be stored along with a priority equal
+    // to their resource ID so that they are ordered.
+    StringPool::Context lowPriority { 0xffffffffu };
+
+    // Once we sort the StringPool, we can assign the updated indices
+    // to the correct data locations.
+    std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>> 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);
+    while (XmlPullParser::isGoodEvent(parser->next())) {
+        XmlPullParser::Event event = parser->getEvent();
+        switch (event) {
+            case XmlPullParser::Event::kStartNamespace:
+            case XmlPullParser::Event::kEndNamespace: {
+                const size_t startIndex = out.size();
+                android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
+                if (event == XmlPullParser::Event::kStartNamespace) {
+                    node->header.type = android::RES_XML_START_NAMESPACE_TYPE;
+                } else {
+                    node->header.type = android::RES_XML_END_NAMESPACE_TYPE;
+                }
+
+                node->header.headerSize = sizeof(*node);
+                node->lineNumber = parser->getLineNumber();
+                node->comment.index = -1;
+
+                android::ResXMLTree_namespaceExt* ns =
+                        out.nextBlock<android::ResXMLTree_namespaceExt>();
+                stringRefs.emplace_back(
+                        pool.makeRef(parser->getNamespacePrefix(), lowPriority), &ns->prefix);
+                stringRefs.emplace_back(
+                        pool.makeRef(parser->getNamespaceUri(), lowPriority), &ns->uri);
+
+                out.align4();
+                node->header.size = out.size() - startIndex;
+                break;
+            }
+
+            case XmlPullParser::Event::kStartElement: {
+                const size_t startIndex = out.size();
+                android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
+                node->header.type = android::RES_XML_START_ELEMENT_TYPE;
+                node->header.headerSize = sizeof(*node);
+                node->lineNumber = parser->getLineNumber();
+                node->comment.index = -1;
+
+                android::ResXMLTree_attrExt* elem = out.nextBlock<android::ResXMLTree_attrExt>();
+                stringRefs.emplace_back(
+                        pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns);
+                stringRefs.emplace_back(
+                        pool.makeRef(parser->getElementName(), lowPriority), &elem->name);
+                elem->attributeStart = sizeof(*elem);
+                elem->attributeSize = sizeof(android::ResXMLTree_attribute);
+
+                // The resource system expects attributes to be sorted by resource ID.
+                std::vector<XmlAttribute> sortedAttributes;
+                uint32_t nextAttributeId = 0;
+                const auto endAttrIter = parser->endAttributes();
+                for (auto attrIter = parser->beginAttributes();
+                     attrIter != endAttrIter;
+                     ++attrIter) {
+                    uint32_t id;
+                    StringPool::Ref nameRef;
+                    const Attribute* attr = nullptr;
+                    if (attrIter->namespaceUri.empty()) {
+                        // 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.
+                        id = 0x80000000u | nextAttributeId++;
+                        nameRef = pool.makeRef(attrIter->name, StringPool::Context{ id });
+                    } else {
+                        StringPiece16 package;
+                        if (attrIter->namespaceUri == u"http://schemas.android.com/apk/res-auto") {
+                            package = mResolver->getDefaultPackage();
+                        } else {
+                            // TODO(adamlesinski): Extract package from namespace.
+                            // The package name appears like so:
+                            // http://schemas.android.com/apk/res/<package name>
+                            package = u"android";
+                        }
+
+                        // Find the Attribute object via our Resolver.
+                        ResourceName attrName = {
+                                package.toString(), ResourceType::kAttr, attrIter->name };
+                        Maybe<Resolver::Entry> result = mResolver->findAttribute(attrName);
+                        if (!result || !result.value().id.isValid()) {
+                            logger.error(parser->getLineNumber())
+                                    << "unresolved attribute '"
+                                    << attrName
+                                    << "'."
+                                    << std::endl;
+                            error = true;
+                            continue;
+                        }
+
+                        if (!result.value().attr) {
+                            logger.error(parser->getLineNumber())
+                                    << "not a valid attribute '"
+                                    << attrName
+                                    << "'."
+                                    << std::endl;
+                            error = true;
+                            continue;
+                        }
+
+                        if (options.maxSdkAttribute && package == u"android") {
+                            size_t sdkVersion = findAttributeSdkLevel(attrIter->name);
+                            if (sdkVersion > options.maxSdkAttribute.value()) {
+                                // We will silently omit this attribute
+                                smallestStrippedAttributeSdk =
+                                        std::min(smallestStrippedAttributeSdk, sdkVersion);
+                                continue;
+                            }
+                        }
+
+                        id = result.value().id.id;
+                        attr = result.value().attr;
+
+                        // Put the attribute name into a package specific pool, since we don't
+                        // want to collapse names from different packages.
+                        nameRef = packagePools[package.toString()].makeRef(
+                                attrIter->name, StringPool::Context{ id });
+                    }
+
+                    // Insert the attribute into the sorted vector.
+                    auto iter = std::lower_bound(sortedAttributes.begin(), sortedAttributes.end(),
+                                                 id, lessAttributeId);
+                    sortedAttributes.insert(iter, XmlAttribute{ id, &*attrIter, attr, nameRef });
+                }
+
+                if (error) {
+                    break;
+                }
+
+                // Now that we have filtered out some attributes, get the final count.
+                elem->attributeCount = sortedAttributes.size();
+
+                // Flatten the sorted attributes.
+                for (auto entry : sortedAttributes) {
+                    android::ResXMLTree_attribute* attr =
+                            out.nextBlock<android::ResXMLTree_attribute>();
+                    stringRefs.emplace_back(
+                            pool.makeRef(entry.xmlAttr->namespaceUri, lowPriority), &attr->ns);
+                    StringPool::Ref rawValueRef = pool.makeRef(entry.xmlAttr->value, lowPriority);
+                    stringRefs.emplace_back(rawValueRef, &attr->rawValue);
+                    stringRefs.emplace_back(entry.nameRef, &attr->name);
+
+                    if (entry.attr) {
+                        std::unique_ptr<Item> value = ResourceParser::parseItemForAttribute(
+                                entry.xmlAttr->value, *entry.attr, mResolver->getDefaultPackage());
+                        if (value) {
+                            AttributeValueFlattener flattener;
+                            value->accept(flattener, AttributeValueFlattener::Args{
+                                    mResolver,
+                                    logger,
+                                    attr->typedValue,
+                                    parser,
+                                    error,
+                                    rawValueRef,
+                                    stringRefs
+                            });
+                        } else if (!(entry.attr->typeMask & android::ResTable_map::TYPE_STRING)) {
+                            logger.error(parser->getLineNumber())
+                                    << "'"
+                                    << *rawValueRef
+                                    << "' is not compatible with attribute "
+                                    << *entry.attr
+                                    << "."
+                                    << std::endl;
+                            error = true;
+                        } else {
+                            attr->typedValue.dataType = android::Res_value::TYPE_STRING;
+                            stringRefs.emplace_back(rawValueRef,
+                                    reinterpret_cast<android::ResStringPool_ref*>(
+                                            &attr->typedValue.data));
+                        }
+                    } else {
+                        attr->typedValue.dataType = android::Res_value::TYPE_STRING;
+                        stringRefs.emplace_back(rawValueRef,
+                                reinterpret_cast<android::ResStringPool_ref*>(
+                                        &attr->typedValue.data));
+                    }
+                    attr->typedValue.size = sizeof(attr->typedValue);
+                }
+
+                out.align4();
+                node->header.size = out.size() - startIndex;
+                break;
+            }
+
+            case XmlPullParser::Event::kEndElement: {
+                const size_t startIndex = out.size();
+                android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
+                node->header.type = android::RES_XML_END_ELEMENT_TYPE;
+                node->header.headerSize = sizeof(*node);
+                node->lineNumber = parser->getLineNumber();
+                node->comment.index = -1;
+
+                android::ResXMLTree_endElementExt* elem =
+                        out.nextBlock<android::ResXMLTree_endElementExt>();
+                stringRefs.emplace_back(
+                        pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns);
+                stringRefs.emplace_back(
+                        pool.makeRef(parser->getElementName(), lowPriority), &elem->name);
+
+                out.align4();
+                node->header.size = out.size() - startIndex;
+                break;
+            }
+
+            case XmlPullParser::Event::kText: {
+                StringPiece16 text = util::trimWhitespace(parser->getText());
+                if (text.empty()) {
+                    break;
+                }
+
+                const size_t startIndex = out.size();
+                android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
+                node->header.type = android::RES_XML_CDATA_TYPE;
+                node->header.headerSize = sizeof(*node);
+                node->lineNumber = parser->getLineNumber();
+                node->comment.index = -1;
+
+                android::ResXMLTree_cdataExt* elem = out.nextBlock<android::ResXMLTree_cdataExt>();
+                stringRefs.emplace_back(pool.makeRef(text, lowPriority), &elem->data);
+
+                out.align4();
+                node->header.size = out.size() - startIndex;
+                break;
+            }
+
+            default:
+                break;
+        }
+
+    }
+    out.align4();
+
+    if (error) {
+        return {};
+    }
+
+    if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
+        logger.error(parser->getLineNumber())
+                << parser->getLastError()
+                << std::endl;
+        return {};
+    }
+
+    // Merge the package pools into the main pool.
+    for (auto& packagePoolEntry : packagePools) {
+        pool.merge(std::move(packagePoolEntry.second));
+    }
+
+    // Sort 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);
+
+    // 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.isValid()) {
+            // When we see the first non-resource ID,
+            // we're done.
+            break;
+        }
+
+        uint32_t* flatId = outBuffer->nextBlock<uint32_t>();
+        *flatId = id.id;
+    }
+    resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex;
+
+    // Flatten the StringPool.
+    StringPool::flattenUtf8(outBuffer, pool);
+
+    // Move the temporary BigBuffer into outBuffer->
+    outBuffer->appendBuffer(std::move(out));
+
+    header->header.size = outBuffer->size() - beforeXmlTreeIndex;
+
+    if (smallestStrippedAttributeSdk == std::numeric_limits<size_t>::max()) {
+        // Nothing was stripped
+        return 0u;
+    }
+    return smallestStrippedAttributeSdk;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/XmlFlattener.h b/tools/aapt2/XmlFlattener.h
new file mode 100644
index 0000000..abf64ab
--- /dev/null
+++ b/tools/aapt2/XmlFlattener.h
@@ -0,0 +1,68 @@
+/*
+ * 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 "XmlPullParser.h"
+
+namespace aapt {
+
+/**
+ * Flattens an XML file into a binary representation parseable by
+ * the Android resource system. References to resources are checked
+ * and string values are transformed to typed data where possible.
+ */
+class XmlFlattener {
+public:
+    struct Options {
+        /**
+         * If set, tells the XmlFlattener to strip out
+         * attributes that have been introduced after
+         * max SDK.
+         */
+        Maybe<size_t> maxSdkAttribute;
+    };
+
+    /**
+     * Creates a flattener with a Resolver to resolve references
+     * and attributes.
+     */
+    XmlFlattener(const std::shared_ptr<Resolver>& resolver);
+
+    XmlFlattener(const XmlFlattener&) = delete; // Not copyable.
+
+    /**
+     * Flatten an XML file, reading from the XML parser and writing to the
+     * BigBuffer. The source object is mainly for logging errors. If the
+     * function succeeds, returns the smallest SDK version of an attribute that
+     * was stripped out. If no attributes were stripped out, the return value
+     * is 0.
+     */
+    Maybe<size_t> flatten(const Source& source, const std::shared_ptr<XmlPullParser>& parser,
+                          BigBuffer* outBuffer, Options options);
+
+private:
+    std::shared_ptr<Resolver> mResolver;
+};
+
+} // namespace aapt
+
+#endif // AAPT_XML_FLATTENER_H
diff --git a/tools/aapt2/XmlFlattener_test.cpp b/tools/aapt2/XmlFlattener_test.cpp
new file mode 100644
index 0000000..79030be
--- /dev/null
+++ b/tools/aapt2/XmlFlattener_test.cpp
@@ -0,0 +1,85 @@
+/*
+ * 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 "Resolver.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "SourceXmlPullParser.h"
+#include "Util.h"
+#include "XmlFlattener.h"
+
+#include <androidfw/AssetManager.h>
+#include <androidfw/ResourceTypes.h>
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+
+namespace aapt {
+
+constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
+
+class XmlFlattenerTest : public ::testing::Test {
+public:
+    virtual void SetUp() override {
+        std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
+        table->setPackage(u"android");
+        table->setPackageId(0x01);
+
+        table->addResource(ResourceName{ {}, ResourceType::kAttr, u"id" },
+                           ResourceId{ 0x01010000 }, {}, {},
+                           util::make_unique<Attribute>(false, android::ResTable_map::TYPE_ANY));
+
+        table->addResource(ResourceName{ {}, ResourceType::kId, u"test" },
+                           ResourceId{ 0x01020000 }, {}, {}, util::make_unique<Id>());
+
+        mFlattener = std::make_shared<XmlFlattener>(
+                std::make_shared<Resolver>(table, std::make_shared<android::AssetManager>()));
+    }
+
+    ::testing::AssertionResult testFlatten(std::istream& in, android::ResXMLTree* outTree) {
+        std::stringstream input(kXmlPreamble);
+        input << in.rdbuf() << std::endl;
+        std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(input);
+        BigBuffer outBuffer(1024);
+        if (!mFlattener->flatten(Source{ "test" }, xmlParser, &outBuffer, {})) {
+            return ::testing::AssertionFailure();
+        }
+
+        std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
+        if (outTree->setTo(data.get(), outBuffer.size(), true) != android::NO_ERROR) {
+            return ::testing::AssertionFailure();
+        }
+        return ::testing::AssertionSuccess();
+    }
+
+    std::shared_ptr<XmlFlattener> mFlattener;
+};
+
+TEST_F(XmlFlattenerTest, ParseSimpleView) {
+    std::stringstream input;
+    input << "<View xmlns:android=\"http://schemas.android.com/apk/res/android\"" << std::endl
+          << "      android:id=\"@id/test\">" << std::endl
+          << "</View>" << std::endl;
+
+    android::ResXMLTree tree;
+    ASSERT_TRUE(testFlatten(input, &tree));
+
+    while (tree.next() != android::ResXMLTree::END_DOCUMENT) {
+        ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
+    }
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/XmlPullParser.h b/tools/aapt2/XmlPullParser.h
new file mode 100644
index 0000000..c667df2
--- /dev/null
+++ b/tools/aapt2/XmlPullParser.h
@@ -0,0 +1,234 @@
+/*
+ * 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_PULL_PARSER_H
+#define AAPT_XML_PULL_PARSER_H
+
+#include <algorithm>
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include "StringPiece.h"
+
+namespace aapt {
+
+class XmlPullParser {
+public:
+    enum class Event {
+        kBadDocument,
+        kStartDocument,
+        kEndDocument,
+
+        kStartNamespace,
+        kEndNamespace,
+        kStartElement,
+        kEndElement,
+        kText,
+        kComment,
+    };
+
+    static void skipCurrentElement(XmlPullParser* parser);
+    static bool isGoodEvent(Event event);
+
+    virtual ~XmlPullParser() {}
+
+    /**
+     * Returns the current event that is being processed.
+     */
+    virtual Event getEvent() const = 0;
+
+    virtual const std::string& getLastError() const = 0;
+
+    /**
+     * Note, unlike XmlPullParser, the first call to next() will return
+     * StartElement of the first element.
+     */
+    virtual Event next() = 0;
+
+    //
+    // 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;
+
+    /**
+     * Returns the character data for a Text event.
+     */
+    virtual const std::u16string& getText() const = 0;
+
+    /**
+     * Namespace prefix is available for StartNamespace and EndNamespace.
+     */
+    virtual const std::u16string& getNamespacePrefix() const = 0;
+
+    /**
+     * Namespace URI is available for StartNamespace.
+     */
+    virtual const std::u16string& getNamespaceUri() const = 0;
+
+    //
+    // These are available for StartElement and EndElement.
+    //
+
+    virtual const std::u16string& getElementNamespace() const = 0;
+    virtual const std::u16string& getElementName() const = 0;
+
+    //
+    // Remaining methods are for retrieving information about attributes
+    // associated with a StartElement.
+    //
+    // Attributes must be in sorted order (according to the less than operator
+    // of struct Attribute).
+    //
+
+    struct Attribute {
+        std::u16string namespaceUri;
+        std::u16string name;
+        std::u16string value;
+
+        int compare(const Attribute& rhs) const;
+        bool operator<(const Attribute& rhs) const;
+        bool operator==(const Attribute& rhs) const;
+        bool operator!=(const Attribute& rhs) const;
+    };
+
+    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 findAttribute(StringPiece16 namespaceUri, StringPiece16 name) const;
+};
+
+/*
+ * Automatically reads up to the end tag of the element it was initialized with
+ * when being destroyed.
+ */
+class AutoFinishElement {
+public:
+    AutoFinishElement(const std::shared_ptr<XmlPullParser>& parser);
+    ~AutoFinishElement();
+
+private:
+    std::shared_ptr<XmlPullParser> mParser;
+    int mDepth;
+};
+
+//
+// Implementation
+//
+
+inline ::std::ostream& operator<<(::std::ostream& out, XmlPullParser::Event event) {
+    switch (event) {
+        case XmlPullParser::Event::kBadDocument: return out << "BadDocument";
+        case XmlPullParser::Event::kStartDocument: return out << "StartDocument";
+        case XmlPullParser::Event::kEndDocument: return out << "EndDocument";
+        case XmlPullParser::Event::kStartNamespace: return out << "StartNamespace";
+        case XmlPullParser::Event::kEndNamespace: return out << "EndNamespace";
+        case XmlPullParser::Event::kStartElement: return out << "StartElement";
+        case XmlPullParser::Event::kEndElement: return out << "EndElement";
+        case XmlPullParser::Event::kText: return out << "Text";
+        case XmlPullParser::Event::kComment: return out << "Comment";
+    }
+    return out;
+}
+
+inline void XmlPullParser::skipCurrentElement(XmlPullParser* parser) {
+    int depth = 1;
+    while (depth > 0) {
+        switch (parser->next()) {
+            case Event::kEndDocument:
+            case Event::kBadDocument:
+                return;
+            case Event::kStartElement:
+                depth++;
+                break;
+            case Event::kEndElement:
+                depth--;
+                break;
+            default:
+                break;
+        }
+    }
+}
+
+inline bool XmlPullParser::isGoodEvent(XmlPullParser::Event event) {
+    return event != Event::kBadDocument && event != Event::kEndDocument;
+}
+
+inline int XmlPullParser::Attribute::compare(const Attribute& rhs) const {
+    int cmp = namespaceUri.compare(rhs.namespaceUri);
+    if (cmp != 0) return cmp;
+    return name.compare(rhs.name);
+}
+
+inline bool XmlPullParser::Attribute::operator<(const Attribute& rhs) const {
+    return compare(rhs) < 0;
+}
+
+inline bool XmlPullParser::Attribute::operator==(const Attribute& rhs) const {
+    return compare(rhs) == 0;
+}
+
+inline bool XmlPullParser::Attribute::operator!=(const Attribute& rhs) const {
+    return compare(rhs) != 0;
+}
+
+inline XmlPullParser::const_iterator XmlPullParser::findAttribute(StringPiece16 namespaceUri,
+                                                                  StringPiece16 name) const {
+    const auto endIter = endAttributes();
+    const auto iter = std::lower_bound(beginAttributes(), endIter,
+            std::pair<StringPiece16, StringPiece16>(namespaceUri, name),
+            [](const Attribute& attr, const std::pair<StringPiece16, StringPiece16>& rhs) -> bool {
+                int cmp = attr.namespaceUri.compare(0, attr.namespaceUri.size(),
+                        rhs.first.data(), rhs.first.size());
+                if (cmp < 0) return true;
+                if (cmp > 0) return false;
+                cmp = attr.name.compare(0, attr.name.size(), rhs.second.data(), rhs.second.size());
+                if (cmp < 0) return true;
+                return false;
+            }
+    );
+
+    if (iter != endIter && namespaceUri == iter->namespaceUri && name == iter->name) {
+        return iter;
+    }
+    return endIter;
+}
+
+inline AutoFinishElement::AutoFinishElement(const std::shared_ptr<XmlPullParser>& parser) :
+        mParser(parser), mDepth(parser->getDepth()) {
+}
+
+inline AutoFinishElement::~AutoFinishElement() {
+    int depth;
+    XmlPullParser::Event event;
+    while ((depth = mParser->getDepth()) >= mDepth &&
+            XmlPullParser::isGoodEvent(event = mParser->getEvent())) {
+        if (depth == mDepth && (event == XmlPullParser::Event::kEndElement ||
+                event == XmlPullParser::Event::kEndNamespace)) {
+            return;
+        }
+        mParser->next();
+    }
+}
+
+} // namespace aapt
+
+#endif // AAPT_XML_PULL_PARSER_H
diff --git a/tools/aapt2/data/AndroidManifest.xml b/tools/aapt2/data/AndroidManifest.xml
new file mode 100644
index 0000000..c017a0d
--- /dev/null
+++ b/tools/aapt2/data/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.app">
+    <application>
+    </application>
+</manifest>
diff --git a/tools/aapt2/data/res/drawable/image.xml b/tools/aapt2/data/res/drawable/image.xml
new file mode 100644
index 0000000..9b38739
--- /dev/null
+++ b/tools/aapt2/data/res/drawable/image.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector />
diff --git a/tools/aapt2/data/res/layout/main.xml b/tools/aapt2/data/res/layout/main.xml
new file mode 100644
index 0000000..e0b55c0
--- /dev/null
+++ b/tools/aapt2/data/res/layout/main.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/view"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+    <View xmlns:app="http://schemas.android.com/apk/res-auto"
+        android:id="@+id/me"
+        android:layout_width="1dp"
+        android:layout_height="match_parent"
+        app:layout_width="false"
+        app:flags="complex|weak"
+        android:colorAccent="#ffffff"/>
+</LinearLayout>
diff --git a/tools/aapt2/data/res/values-v4/styles.xml b/tools/aapt2/data/res/values-v4/styles.xml
new file mode 100644
index 0000000..979a82a
--- /dev/null
+++ b/tools/aapt2/data/res/values-v4/styles.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <style name="App" parent="android:Theme.Material">
+        <item name="android:colorAccent">@color/accent</item>
+        <item name="android:text">Hey</item>
+    </style>
+</resources>
diff --git a/tools/aapt2/data/res/values/colors.xml b/tools/aapt2/data/res/values/colors.xml
new file mode 100644
index 0000000..89db5fb
--- /dev/null
+++ b/tools/aapt2/data/res/values/colors.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="primary">#f44336</color>
+    <color name="primary_dark">#b71c1c</color>
+    <color name="accent">#fdd835</color>
+</resources>
diff --git a/tools/aapt2/data/res/values/styles.xml b/tools/aapt2/data/res/values/styles.xml
new file mode 100644
index 0000000..71ce388
--- /dev/null
+++ b/tools/aapt2/data/res/values/styles.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <style name="App" parent="android:Theme.Material">
+        <item name="android:background">@color/primary</item>
+        <item name="android:colorPrimary">@color/primary</item>
+        <item name="android:colorPrimaryDark">@color/primary_dark</item>
+        <item name="android:colorAccent">@color/accent</item>
+    </style>
+    <attr name="custom" format="reference" />
+    <style name="Pop">
+        <item name="custom">@drawable/image</item>
+    </style>
+    <string name="yo">@string/wow</string>
+
+    <declare-styleable name="View">
+        <attr name="custom" />
+        <attr name="decor">
+            <enum name="no-border" value="0"/>
+            <enum name="border" value="1"/>
+            <enum name="shadow" value="2"/>
+        </attr>
+    </declare-styleable>
+
+</resources>
diff --git a/tools/aapt2/data/res/values/test.xml b/tools/aapt2/data/res/values/test.xml
new file mode 100644
index 0000000..d3ead34
--- /dev/null
+++ b/tools/aapt2/data/res/values/test.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <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" />
+    <attr name="layout_width" format="boolean" />
+    <attr name="flags">
+        <flag name="complex" value="1" />
+        <flag name="pub" value="2" />
+        <flag name="weak" value="4" />
+    </attr>
+</resources>
diff --git a/tools/aapt2/data/resources.arsc b/tools/aapt2/data/resources.arsc
new file mode 100644
index 0000000..6a416df
--- /dev/null
+++ b/tools/aapt2/data/resources.arsc
Binary files differ
diff --git a/tools/aapt2/data/resources_base.arsc b/tools/aapt2/data/resources_base.arsc
new file mode 100644
index 0000000..f9d0610
--- /dev/null
+++ b/tools/aapt2/data/resources_base.arsc
Binary files differ
diff --git a/tools/aapt2/data/resources_hdpi.arsc b/tools/aapt2/data/resources_hdpi.arsc
new file mode 100644
index 0000000..97232a3
--- /dev/null
+++ b/tools/aapt2/data/resources_hdpi.arsc
Binary files differ
diff --git a/tools/aapt2/process.dot b/tools/aapt2/process.dot
new file mode 100644
index 0000000..a92405d
--- /dev/null
+++ b/tools/aapt2/process.dot
@@ -0,0 +1,92 @@
+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"];
+
+    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_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;
+
+    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;
+
+    out_values_fr_table -> compile_values_fr;
+
+    compile_values_fr [shape=box,label="Compile",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;
+}
diff --git a/tools/aapt2/public_attr_map.py b/tools/aapt2/public_attr_map.py
new file mode 100644
index 0000000..92136a8
--- /dev/null
+++ b/tools/aapt2/public_attr_map.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+
+import sys
+import xml.etree.ElementTree as ET
+
+def findSdkLevelForAttribute(id):
+    intId = int(id, 16)
+    packageId = 0x000000ff & (intId >> 24)
+    typeId = 0x000000ff & (intId >> 16)
+    entryId = 0x0000ffff & intId
+
+    if packageId != 0x01 or typeId != 0x01:
+        return 0
+
+    levels = [(1, 0x021c), (2, 0x021d), (3, 0x0269), (4, 0x028d),
+              (5, 0x02ad), (6, 0x02b3), (7, 0x02b5), (8, 0x02bd),
+              (9, 0x02cb), (11, 0x0361), (12, 0x0366), (13, 0x03a6),
+              (16, 0x03ae), (17, 0x03cc), (18, 0x03da), (19, 0x03f1),
+              (20, 0x03f6), (21, 0x04ce)]
+    for level, attrEntryId in levels:
+        if entryId <= attrEntryId:
+            return level
+    return 22
+
+
+tree = None
+with open(sys.argv[1], 'rt') as f:
+    tree = ET.parse(f)
+
+attrs = []
+for node in tree.iter('public'):
+    if node.get('type') == 'attr':
+        sdkLevel = findSdkLevelForAttribute(node.get('id', '0'))
+        if sdkLevel > 1 and sdkLevel < 22:
+            attrs.append("{{ u\"{}\", {} }}".format(node.get('name'), sdkLevel))
+
+print "#include <string>"
+print "#include <unordered_map>"
+print
+print "namespace aapt {"
+print
+print "static std::unordered_map<std::u16string, size_t> sAttrMap = {"
+print ",\n    ".join(attrs)
+print "};"
+print
+print "size_t findAttributeSdkLevel(const std::u16string& name) {"
+print "    auto iter = sAttrMap.find(name);"
+print "    if (iter != sAttrMap.end()) {"
+print "        return iter->second;"
+print "    }"
+print "    return 0;"
+print "}"
+print
+print "} // namespace aapt"
+print
diff --git a/tools/aapt2/todo.txt b/tools/aapt2/todo.txt
new file mode 100644
index 0000000..acc8bfb
--- /dev/null
+++ b/tools/aapt2/todo.txt
@@ -0,0 +1,29 @@
+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.