AAPT2: Separate out the various steps

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

Also added a ton of tests!

Change-Id: Ic8f0a70c8222370352e63533b329c40457c0903e
diff --git a/tools/aapt2/link/AutoVersioner.cpp b/tools/aapt2/link/AutoVersioner.cpp
new file mode 100644
index 0000000..0ccafc2
--- /dev/null
+++ b/tools/aapt2/link/AutoVersioner.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ConfigDescription.h"
+#include "ResourceTable.h"
+#include "SdkConstants.h"
+#include "ValueVisitor.h"
+
+#include "link/Linkers.h"
+
+#include <algorithm>
+#include <cassert>
+
+namespace aapt {
+
+static bool cmpConfigValue(const ResourceConfigValue& lhs, const ConfigDescription& config) {
+    return lhs.config < config;
+}
+
+bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config,
+                                     const int sdkVersionToGenerate) {
+    assert(sdkVersionToGenerate > config.sdkVersion);
+    const auto endIter = entry->values.end();
+    auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmpConfigValue);
+
+    // The source config came from this list, so it should be here.
+    assert(iter != entry->values.end());
+    ++iter;
+
+    // The next configuration either only varies in sdkVersion, or it is completely different
+    // and therefore incompatible. If it is incompatible, we must generate the versioned resource.
+
+    // NOTE: The ordering of configurations takes sdkVersion as higher precedence than other
+    // qualifiers, so we need to iterate through the entire list to be sure there
+    // are no higher sdk level versions of this resource.
+    ConfigDescription tempConfig(config);
+    for (; iter != endIter; ++iter) {
+        tempConfig.sdkVersion = iter->config.sdkVersion;
+        if (tempConfig == iter->config) {
+            // The two configs are the same, check the sdk version.
+            return sdkVersionToGenerate < iter->config.sdkVersion;
+        }
+    }
+
+    // No match was found, so we should generate the versioned resource.
+    return true;
+}
+
+bool AutoVersioner::consume(IAaptContext* context, ResourceTable* table) {
+    for (auto& package : table->packages) {
+        for (auto& type : package->types) {
+            if (type->type != ResourceType::kStyle) {
+                continue;
+            }
+
+            for (auto& entry : type->entries) {
+                for (size_t i = 0; i < entry->values.size(); i++) {
+                    ResourceConfigValue& configValue = entry->values[i];
+                    if (configValue.config.sdkVersion >= SDK_LOLLIPOP_MR1) {
+                        // If this configuration is only used on L-MR1 then we don't need
+                        // to do anything since we use private attributes since that version.
+                        continue;
+                    }
+
+                    if (Style* style = valueCast<Style>(configValue.value.get())) {
+                        Maybe<size_t> minSdkStripped;
+                        std::vector<Style::Entry> stripped;
+
+                        auto iter = style->entries.begin();
+                        while (iter != style->entries.end()) {
+                            assert(iter->key.id && "IDs must be assigned and linked");
+
+                            // Find the SDK level that is higher than the configuration allows.
+                            const size_t sdkLevel = findAttributeSdkLevel(iter->key.id.value());
+                            if (sdkLevel > std::max<size_t>(configValue.config.sdkVersion, 1)) {
+                                // Record that we are about to strip this.
+                                stripped.emplace_back(std::move(*iter));
+
+                                // We use the smallest SDK level to generate the new style.
+                                if (minSdkStripped) {
+                                    minSdkStripped = std::min(minSdkStripped.value(), sdkLevel);
+                                } else {
+                                    minSdkStripped = sdkLevel;
+                                }
+
+                                // Erase this from this style.
+                                iter = style->entries.erase(iter);
+                                continue;
+                            }
+                            ++iter;
+                        }
+
+                        if (minSdkStripped && !stripped.empty()) {
+                            // We found attributes from a higher SDK level. Check that
+                            // there is no other defined resource for the version we want to
+                            // generate.
+                            if (shouldGenerateVersionedResource(entry.get(), configValue.config,
+                                                                minSdkStripped.value())) {
+                                // Let's create a new Style for this versioned resource.
+                                ConfigDescription newConfig(configValue.config);
+                                newConfig.sdkVersion = minSdkStripped.value();
+
+                                ResourceConfigValue newValue = {
+                                        newConfig,
+                                        configValue.source,
+                                        configValue.comment,
+                                        std::unique_ptr<Value>(configValue.value->clone(
+                                                &table->stringPool))
+                                };
+
+                                Style* newStyle = static_cast<Style*>(newValue.value.get());
+
+                                // Move the previously stripped attributes into this style.
+                                newStyle->entries.insert(newStyle->entries.end(),
+                                                         std::make_move_iterator(stripped.begin()),
+                                                         std::make_move_iterator(stripped.end()));
+
+                                // Insert the new Resource into the correct place.
+                                auto iter = std::lower_bound(entry->values.begin(),
+                                                             entry->values.end(), newConfig,
+                                                             cmpConfigValue);
+                                entry->values.insert(iter, std::move(newValue));
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/AutoVersioner_test.cpp b/tools/aapt2/link/AutoVersioner_test.cpp
new file mode 100644
index 0000000..29bcc93
--- /dev/null
+++ b/tools/aapt2/link/AutoVersioner_test.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ConfigDescription.h"
+
+#include "link/Linkers.h"
+
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(AutoVersionerTest, GenerateVersionedResources) {
+    const ConfigDescription defaultConfig = {};
+    const ConfigDescription landConfig = test::parseConfigOrDie("land");
+    const ConfigDescription sw600dpLandConfig = test::parseConfigOrDie("sw600dp-land");
+
+    ResourceEntry entry(u"foo");
+    entry.values.push_back(ResourceConfigValue{ defaultConfig });
+    entry.values.push_back(ResourceConfigValue{ landConfig });
+    entry.values.push_back(ResourceConfigValue{ sw600dpLandConfig });
+
+    EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17));
+    EXPECT_TRUE(shouldGenerateVersionedResource(&entry, landConfig, 17));
+}
+
+TEST(AutoVersionerTest, GenerateVersionedResourceWhenHigherVersionExists) {
+    const ConfigDescription defaultConfig = {};
+    const ConfigDescription sw600dpV13Config = test::parseConfigOrDie("sw600dp-v13");
+    const ConfigDescription v21Config = test::parseConfigOrDie("v21");
+
+    ResourceEntry entry(u"foo");
+    entry.values.push_back(ResourceConfigValue{ defaultConfig });
+    entry.values.push_back(ResourceConfigValue{ sw600dpV13Config });
+    entry.values.push_back(ResourceConfigValue{ v21Config });
+
+    EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17));
+    EXPECT_FALSE(shouldGenerateVersionedResource(&entry, defaultConfig, 22));
+}
+
+TEST(AutoVersionerTest, VersionStylesForTable) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"app", 0x7f)
+            .addValue(u"@app:style/Foo", ResourceId(0x7f020000), test::parseConfigOrDie("v4"),
+                      test::StyleBuilder()
+                            .addItem(u"@android:attr/onClick", ResourceId(0x0101026f),
+                                     util::make_unique<Id>())
+                            .addItem(u"@android:attr/paddingStart", ResourceId(0x010103b3),
+                                     util::make_unique<Id>())
+                            .addItem(u"@android:attr/requiresSmallestWidthDp",
+                                     ResourceId(0x01010364), util::make_unique<Id>())
+                            .addItem(u"@android:attr/colorAccent", ResourceId(0x01010435),
+                                     util::make_unique<Id>())
+                            .build())
+            .addValue(u"@app:style/Foo", ResourceId(0x7f020000), test::parseConfigOrDie("v21"),
+                      test::StyleBuilder()
+                            .addItem(u"@android:attr/paddingEnd", ResourceId(0x010103b4),
+                                     util::make_unique<Id>())
+                            .build())
+            .build();
+
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+            .setCompilationPackage(u"app")
+            .setPackageId(0x7f)
+            .build();
+
+    AutoVersioner versioner;
+    ASSERT_TRUE(versioner.consume(context.get(), table.get()));
+
+    Style* style = test::getValueForConfig<Style>(table.get(), u"@app:style/Foo",
+                                                  test::parseConfigOrDie("v4"));
+    ASSERT_NE(style, nullptr);
+    ASSERT_EQ(style->entries.size(), 1u);
+    AAPT_ASSERT_TRUE(style->entries.front().key.name);
+    EXPECT_EQ(style->entries.front().key.name.value(),
+              test::parseNameOrDie(u"@android:attr/onClick"));
+
+    style = test::getValueForConfig<Style>(table.get(), u"@app:style/Foo",
+                                           test::parseConfigOrDie("v13"));
+    ASSERT_NE(style, nullptr);
+    ASSERT_EQ(style->entries.size(), 2u);
+    AAPT_ASSERT_TRUE(style->entries[0].key.name);
+    EXPECT_EQ(style->entries[0].key.name.value(),
+              test::parseNameOrDie(u"@android:attr/onClick"));
+    AAPT_ASSERT_TRUE(style->entries[1].key.name);
+    EXPECT_EQ(style->entries[1].key.name.value(),
+                  test::parseNameOrDie(u"@android:attr/requiresSmallestWidthDp"));
+
+    style = test::getValueForConfig<Style>(table.get(), u"@app:style/Foo",
+                                           test::parseConfigOrDie("v17"));
+    ASSERT_NE(style, nullptr);
+    ASSERT_EQ(style->entries.size(), 3u);
+    AAPT_ASSERT_TRUE(style->entries[0].key.name);
+    EXPECT_EQ(style->entries[0].key.name.value(),
+                  test::parseNameOrDie(u"@android:attr/onClick"));
+    AAPT_ASSERT_TRUE(style->entries[1].key.name);
+    EXPECT_EQ(style->entries[1].key.name.value(),
+                  test::parseNameOrDie(u"@android:attr/requiresSmallestWidthDp"));
+    AAPT_ASSERT_TRUE(style->entries[2].key.name);
+    EXPECT_EQ(style->entries[2].key.name.value(),
+                  test::parseNameOrDie(u"@android:attr/paddingStart"));
+
+    style = test::getValueForConfig<Style>(table.get(), u"@app:style/Foo",
+                                           test::parseConfigOrDie("v21"));
+    ASSERT_NE(style, nullptr);
+    ASSERT_EQ(style->entries.size(), 1u);
+    AAPT_ASSERT_TRUE(style->entries.front().key.name);
+    EXPECT_EQ(style->entries.front().key.name.value(),
+              test::parseNameOrDie(u"@android:attr/paddingEnd"));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
new file mode 100644
index 0000000..2b4c4d2
--- /dev/null
+++ b/tools/aapt2/link/Link.cpp
@@ -0,0 +1,702 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "AppInfo.h"
+#include "Debug.h"
+#include "Flags.h"
+#include "JavaClassGenerator.h"
+#include "NameMangler.h"
+#include "ProguardRules.h"
+#include "XmlDom.h"
+
+#include "compile/IdAssigner.h"
+#include "flatten/Archive.h"
+#include "flatten/TableFlattener.h"
+#include "flatten/XmlFlattener.h"
+#include "link/Linkers.h"
+#include "link/TableMerger.h"
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+#include "unflatten/BinaryResourceParser.h"
+#include "unflatten/FileExportHeaderReader.h"
+#include "util/Files.h"
+#include "util/StringPiece.h"
+
+#include <fstream>
+#include <sys/stat.h>
+#include <utils/FileMap.h>
+#include <vector>
+
+namespace aapt {
+
+struct LinkOptions {
+    std::string outputPath;
+    std::string manifestPath;
+    std::vector<std::string> includePaths;
+    Maybe<std::string> generateJavaClassPath;
+    Maybe<std::string> generateProguardRulesPath;
+    bool noAutoVersion = false;
+    bool staticLib = false;
+    bool verbose = false;
+    bool outputToDirectory = false;
+};
+
+struct LinkContext : public IAaptContext {
+    StdErrDiagnostics mDiagnostics;
+    std::unique_ptr<NameMangler> mNameMangler;
+    std::u16string mCompilationPackage;
+    uint8_t mPackageId;
+    std::unique_ptr<ISymbolTable> mSymbols;
+
+    IDiagnostics* getDiagnostics() override {
+        return &mDiagnostics;
+    }
+
+    NameMangler* getNameMangler() override {
+        return mNameMangler.get();
+    }
+
+    StringPiece16 getCompilationPackage() override {
+        return mCompilationPackage;
+    }
+
+    uint8_t getPackageId() override {
+        return mPackageId;
+    }
+
+    ISymbolTable* getExternalSymbols() override {
+        return mSymbols.get();
+    }
+};
+
+struct LinkCommand {
+    LinkOptions mOptions;
+    LinkContext mContext;
+
+    std::string buildResourceFileName(const ResourceFile& resFile) {
+        std::stringstream out;
+        out << "res/" << resFile.name.type;
+        if (resFile.config != ConfigDescription{}) {
+            out << "-" << resFile.config;
+        }
+        out << "/";
+
+        if (mContext.getNameMangler()->shouldMangle(resFile.name.package)) {
+            out << NameMangler::mangleEntry(resFile.name.package, resFile.name.entry);
+        } else {
+            out << resFile.name.entry;
+        }
+        out << file::getExtension(resFile.source.path);
+        return out.str();
+    }
+
+    /**
+     * Creates a SymbolTable that loads symbols from the various APKs and caches the
+     * results for faster lookup.
+     */
+    std::unique_ptr<ISymbolTable> createSymbolTableFromIncludePaths() {
+        AssetManagerSymbolTableBuilder builder;
+        for (const std::string& path : mOptions.includePaths) {
+            if (mOptions.verbose) {
+                mContext.getDiagnostics()->note(
+                        DiagMessage(Source{ path }) << "loading include path");
+            }
+
+            std::unique_ptr<android::AssetManager> assetManager =
+                    util::make_unique<android::AssetManager>();
+            int32_t cookie = 0;
+            if (!assetManager->addAssetPath(android::String8(path.data(), path.size()), &cookie)) {
+                mContext.getDiagnostics()->error(
+                        DiagMessage(Source{ path }) << "failed to load include path");
+                return {};
+            }
+            builder.add(std::move(assetManager));
+        }
+        return builder.build();
+    }
+
+    /**
+     * Loads the resource table (not inside an apk) at the given path.
+     */
+    std::unique_ptr<ResourceTable> loadTable(const std::string& input) {
+        std::string errorStr;
+        Maybe<android::FileMap> map = file::mmapPath(input, &errorStr);
+        if (!map) {
+            mContext.getDiagnostics()->error(DiagMessage(Source{ input }) << errorStr);
+            return {};
+        }
+
+        std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
+        BinaryResourceParser parser(&mContext, table.get(), Source{ input },
+                                    map.value().getDataPtr(), map.value().getDataLength());
+        if (!parser.parse()) {
+            return {};
+        }
+        return table;
+    }
+
+    /**
+     * Inflates an XML file from the source path.
+     */
+    std::unique_ptr<XmlResource> loadXml(const std::string& path) {
+        std::ifstream fin(path, std::ifstream::binary);
+        if (!fin) {
+            mContext.getDiagnostics()->error(DiagMessage(Source{ path }) << strerror(errno));
+            return {};
+        }
+
+        return xml::inflate(&fin, mContext.getDiagnostics(), Source{ path });
+    }
+
+    /**
+     * Inflates a binary XML file from the source path.
+     */
+    std::unique_ptr<XmlResource> loadBinaryXmlSkipFileExport(const std::string& path) {
+        // Read header for symbol info and export info.
+        std::string errorStr;
+        Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
+        if (!maybeF) {
+            mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+            return {};
+        }
+
+        ssize_t offset = getWrappedDataOffset(maybeF.value().getDataPtr(),
+                                              maybeF.value().getDataLength(), &errorStr);
+        if (offset < 0) {
+            mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+            return {};
+        }
+
+        std::unique_ptr<XmlResource> xmlRes = xml::inflate(
+                (const uint8_t*) maybeF.value().getDataPtr() + (size_t) offset,
+                maybeF.value().getDataLength() - offset,
+                mContext.getDiagnostics(), Source(path));
+        if (!xmlRes) {
+            return {};
+        }
+        return xmlRes;
+    }
+
+    Maybe<ResourceFile> loadFileExportHeader(const std::string& path) {
+        // Read header for symbol info and export info.
+        std::string errorStr;
+        Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
+        if (!maybeF) {
+            mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+            return {};
+        }
+
+        ResourceFile resFile;
+        ssize_t offset = unwrapFileExportHeader(maybeF.value().getDataPtr(),
+                                                maybeF.value().getDataLength(),
+                                                &resFile, &errorStr);
+        if (offset < 0) {
+            mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+            return {};
+        }
+        return std::move(resFile);
+    }
+
+    bool copyFileToArchive(const std::string& path, const std::string& outPath, uint32_t flags,
+                           IArchiveWriter* writer) {
+        std::string errorStr;
+        Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
+        if (!maybeF) {
+            mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+            return false;
+        }
+
+        ssize_t offset = getWrappedDataOffset(maybeF.value().getDataPtr(),
+                                              maybeF.value().getDataLength(),
+                                              &errorStr);
+        if (offset < 0) {
+            mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+            return false;
+        }
+
+        ArchiveEntry* entry = writer->writeEntry(outPath, flags, &maybeF.value(),
+                                                 offset, maybeF.value().getDataLength() - offset);
+        if (!entry) {
+            mContext.getDiagnostics()->error(
+                    DiagMessage(mOptions.outputPath) << "failed to write file " << outPath);
+            return false;
+        }
+        return true;
+    }
+
+    Maybe<AppInfo> extractAppInfoFromManifest(XmlResource* xmlRes) {
+        xml::Node* node = xmlRes->root.get();
+
+        // Find the first xml::Element.
+        while (node && !xml::nodeCast<xml::Element>(node)) {
+            node = !node->children.empty() ? node->children.front().get() : nullptr;
+        }
+
+        // Make sure the first element is <manifest> with package attribute.
+        if (xml::Element* manifestEl = xml::nodeCast<xml::Element>(node)) {
+            if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") {
+                if (xml::Attribute* packageAttr = manifestEl->findAttribute({}, u"package")) {
+                    return AppInfo{ packageAttr->value };
+                }
+            }
+        }
+        return {};
+    }
+
+    bool verifyNoExternalPackages(ResourceTable* table) {
+        bool error = false;
+        for (const auto& package : table->packages) {
+            if (mContext.getCompilationPackage() != package->name ||
+                    !package->id || package->id.value() != mContext.getPackageId()) {
+                // We have a package that is not related to the one we're building!
+                for (const auto& type : package->types) {
+                    for (const auto& entry : type->entries) {
+                        for (const auto& configValue : entry->values) {
+                            mContext.getDiagnostics()->error(DiagMessage(configValue.source)
+                                                             << "defined resource '"
+                                                             << ResourceNameRef(package->name,
+                                                                                type->type,
+                                                                                entry->name)
+                                                             << "' for external package '"
+                                                             << package->name << "'");
+                            error = true;
+                        }
+                    }
+                }
+            }
+        }
+        return !error;
+    }
+
+    std::unique_ptr<IArchiveWriter> makeArchiveWriter() {
+        if (mOptions.outputToDirectory) {
+            return createDirectoryArchiveWriter(mOptions.outputPath);
+        } else {
+            return createZipFileArchiveWriter(mOptions.outputPath);
+        }
+    }
+
+    bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
+        BigBuffer buffer(1024);
+        TableFlattenerOptions options = {};
+        options.useExtendedChunks = mOptions.staticLib;
+        TableFlattener flattener(&buffer, options);
+        if (!flattener.consume(&mContext, table)) {
+            return false;
+        }
+
+        ArchiveEntry* entry = writer->writeEntry("resources.arsc", ArchiveEntry::kAlign, buffer);
+        if (!entry) {
+            mContext.getDiagnostics()->error(
+                    DiagMessage() << "failed to write resources.arsc to archive");
+            return false;
+        }
+        return true;
+    }
+
+    bool flattenXml(XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
+                    IArchiveWriter* writer) {
+        BigBuffer buffer(1024);
+        XmlFlattenerOptions options = {};
+        options.keepRawValues = mOptions.staticLib;
+        options.maxSdkLevel = maxSdkLevel;
+        XmlFlattener flattener(&buffer, options);
+        if (!flattener.consume(&mContext, xmlRes)) {
+            return false;
+        }
+
+        ArchiveEntry* entry = writer->writeEntry(path, ArchiveEntry::kCompress, buffer);
+        if (!entry) {
+            mContext.getDiagnostics()->error(
+                    DiagMessage() << "failed to write " << path << " to archive");
+            return false;
+        }
+        return true;
+    }
+
+    bool writeJavaFile(ResourceTable* table, const StringPiece16& package) {
+        if (!mOptions.generateJavaClassPath) {
+            return true;
+        }
+
+        std::string outPath = mOptions.generateJavaClassPath.value();
+        file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(package)));
+        file::mkdirs(outPath);
+        file::appendPath(&outPath, "R.java");
+
+        std::ofstream fout(outPath, std::ofstream::binary);
+        if (!fout) {
+            mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
+            return false;
+        }
+
+        JavaClassGeneratorOptions javaOptions;
+        if (mOptions.staticLib) {
+            javaOptions.useFinal = false;
+        }
+
+        JavaClassGenerator generator(table, javaOptions);
+        if (!generator.generate(mContext.getCompilationPackage(), &fout)) {
+            mContext.getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
+            return false;
+        }
+        return true;
+    }
+
+    bool writeProguardFile(const proguard::KeepSet& keepSet) {
+        if (!mOptions.generateProguardRulesPath) {
+            return true;
+        }
+
+        std::ofstream fout(mOptions.generateProguardRulesPath.value(), std::ofstream::binary);
+        if (!fout) {
+            mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
+            return false;
+        }
+
+        proguard::writeKeepSet(&fout, keepSet);
+        if (!fout) {
+            mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
+            return false;
+        }
+        return true;
+    }
+
+    int run(const std::vector<std::string>& inputFiles) {
+        // Load the AndroidManifest.xml
+        std::unique_ptr<XmlResource> manifestXml = loadXml(mOptions.manifestPath);
+        if (!manifestXml) {
+            return 1;
+        }
+
+        if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get())) {
+            mContext.mCompilationPackage = maybeAppInfo.value().package;
+        } else {
+            mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
+                                             << "no package specified in <manifest> tag");
+            return 1;
+        }
+
+        if (!util::isJavaPackageName(mContext.mCompilationPackage)) {
+            mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
+                                             << "invalid package name '"
+                                             << mContext.mCompilationPackage
+                                             << "'");
+            return 1;
+        }
+
+        mContext.mNameMangler = util::make_unique<NameMangler>(
+                NameManglerPolicy{ mContext.mCompilationPackage });
+        mContext.mPackageId = 0x7f;
+        mContext.mSymbols = createSymbolTableFromIncludePaths();
+        if (!mContext.mSymbols) {
+            return 1;
+        }
+
+        if (mOptions.verbose) {
+            mContext.getDiagnostics()->note(
+                    DiagMessage() << "linking package '" << mContext.mCompilationPackage << "' "
+                                  << "with package ID " << std::hex << (int) mContext.mPackageId);
+        }
+
+        ResourceTable mergedTable;
+        TableMerger tableMerger(&mContext, &mergedTable);
+
+        struct FilesToProcess {
+            Source source;
+            ResourceFile file;
+        };
+
+        bool error = false;
+        std::queue<FilesToProcess> filesToProcess;
+        for (const std::string& input : inputFiles) {
+            if (util::stringEndsWith<char>(input, ".apk")) {
+                // TODO(adamlesinski): Load resources from a static library APK
+                //                     Merge the table into TableMerger.
+
+            } else if (util::stringEndsWith<char>(input, ".arsc.flat")) {
+                if (mOptions.verbose) {
+                    mContext.getDiagnostics()->note(DiagMessage() << "linking " << input);
+                }
+
+                std::unique_ptr<ResourceTable> table = loadTable(input);
+                if (!table) {
+                    return 1;
+                }
+
+                if (!tableMerger.merge(Source(input), table.get())) {
+                    return 1;
+                }
+
+            } else {
+                // Extract the exported IDs here so we can build the resource table.
+                if (Maybe<ResourceFile> maybeF = loadFileExportHeader(input)) {
+                    ResourceFile& f = maybeF.value();
+
+                    if (f.name.package.empty()) {
+                        f.name.package = mContext.getCompilationPackage().toString();
+                    }
+
+                    Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(f.name);
+
+                    // Add this file to the table.
+                    if (!mergedTable.addFileReference(mangledName ? mangledName.value() : f.name,
+                                                      f.config, f.source,
+                                                      util::utf8ToUtf16(buildResourceFileName(f)),
+                                                      mContext.getDiagnostics())) {
+                        error = true;
+                    }
+
+                    // Add the exports of this file to the table.
+                    for (SourcedResourceName& exportedSymbol : f.exportedSymbols) {
+                        if (exportedSymbol.name.package.empty()) {
+                            exportedSymbol.name.package = mContext.getCompilationPackage()
+                                    .toString();
+                        }
+
+                        Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(
+                                exportedSymbol.name);
+                        if (!mergedTable.addResource(
+                                mangledName ? mangledName.value() : exportedSymbol.name,
+                                {}, {}, f.source.withLine(exportedSymbol.line),
+                                util::make_unique<Id>(), mContext.getDiagnostics())) {
+                            error = true;
+                        }
+                    }
+
+                    filesToProcess.push(FilesToProcess{ Source(input), std::move(f) });
+                } else {
+                    return 1;
+                }
+            }
+        }
+
+        if (error) {
+            mContext.getDiagnostics()->error(DiagMessage() << "failed parsing input");
+            return 1;
+        }
+
+        if (!verifyNoExternalPackages(&mergedTable)) {
+            return 1;
+        }
+
+        if (!mOptions.staticLib) {
+            PrivateAttributeMover mover;
+            if (!mover.consume(&mContext, &mergedTable)) {
+                mContext.getDiagnostics()->error(
+                        DiagMessage() << "failed moving private attributes");
+                return 1;
+            }
+        }
+
+        {
+            IdAssigner idAssigner;
+            if (!idAssigner.consume(&mContext, &mergedTable)) {
+                mContext.getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
+                return 1;
+            }
+        }
+
+        mContext.mNameMangler = util::make_unique<NameMangler>(
+                NameManglerPolicy{ mContext.mCompilationPackage, tableMerger.getMergedPackages() });
+        mContext.mSymbols = JoinedSymbolTableBuilder()
+                .addSymbolTable(util::make_unique<SymbolTableWrapper>(&mergedTable))
+                .addSymbolTable(std::move(mContext.mSymbols))
+                .build();
+
+        {
+            ReferenceLinker linker;
+            if (!linker.consume(&mContext, &mergedTable)) {
+                mContext.getDiagnostics()->error(DiagMessage() << "failed linking references");
+                return 1;
+            }
+        }
+
+        proguard::KeepSet proguardKeepSet;
+
+        std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter();
+        if (!archiveWriter) {
+            mContext.getDiagnostics()->error(DiagMessage() << "failed to create archive");
+            return 1;
+        }
+
+        {
+            XmlReferenceLinker manifestLinker;
+            if (manifestLinker.consume(&mContext, manifestXml.get())) {
+
+                if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
+                                                               manifestXml.get(),
+                                                               &proguardKeepSet)) {
+                    error = true;
+                }
+
+                if (!flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
+                                archiveWriter.get())) {
+                    error = true;
+                }
+            } else {
+                error = true;
+            }
+        }
+
+        for (; !filesToProcess.empty(); filesToProcess.pop()) {
+            FilesToProcess& f = filesToProcess.front();
+            if (f.file.name.type != ResourceType::kRaw &&
+                    util::stringEndsWith<char>(f.source.path, ".xml.flat")) {
+                if (mOptions.verbose) {
+                    mContext.getDiagnostics()->note(DiagMessage() << "linking " << f.source.path);
+                }
+
+                std::unique_ptr<XmlResource> xmlRes = loadBinaryXmlSkipFileExport(f.source.path);
+                if (!xmlRes) {
+                    return 1;
+                }
+
+                xmlRes->file = std::move(f.file);
+
+                XmlReferenceLinker xmlLinker;
+                if (xmlLinker.consume(&mContext, xmlRes.get())) {
+                    if (!proguard::collectProguardRules(xmlRes->file.source, xmlRes.get(),
+                                                        &proguardKeepSet)) {
+                        error = true;
+                    }
+
+                    Maybe<size_t> maxSdkLevel;
+                    if (!mOptions.noAutoVersion) {
+                        maxSdkLevel = std::max<size_t>(xmlRes->file.config.sdkVersion, 1u);
+                    }
+
+                    if (!flattenXml(xmlRes.get(), buildResourceFileName(xmlRes->file), maxSdkLevel,
+                                    archiveWriter.get())) {
+                        error = true;
+                    }
+
+                    if (!mOptions.noAutoVersion) {
+                        Maybe<ResourceTable::SearchResult> result = mergedTable.findResource(
+                                xmlRes->file.name);
+                        for (int sdkLevel : xmlLinker.getSdkLevels()) {
+                            if (sdkLevel > xmlRes->file.config.sdkVersion &&
+                                    shouldGenerateVersionedResource(result.value().entry,
+                                                                    xmlRes->file.config,
+                                                                    sdkLevel)) {
+                                xmlRes->file.config.sdkVersion = sdkLevel;
+                                if (!mergedTable.addFileReference(xmlRes->file.name,
+                                                                  xmlRes->file.config,
+                                                                  xmlRes->file.source,
+                                                                  util::utf8ToUtf16(
+                                                                     buildResourceFileName(xmlRes->file)),
+                                                             mContext.getDiagnostics())) {
+                                    error = true;
+                                    continue;
+                                }
+
+                                if (!flattenXml(xmlRes.get(), buildResourceFileName(xmlRes->file),
+                                                sdkLevel, archiveWriter.get())) {
+                                    error = true;
+                                }
+                            }
+                        }
+                    }
+
+                } else {
+                    error = true;
+                }
+            } else {
+                if (mOptions.verbose) {
+                    mContext.getDiagnostics()->note(DiagMessage() << "copying " << f.source.path);
+                }
+
+                if (!copyFileToArchive(f.source.path, buildResourceFileName(f.file), 0,
+                                       archiveWriter.get())) {
+                    error = true;
+                }
+            }
+        }
+
+        if (error) {
+            mContext.getDiagnostics()->error(DiagMessage() << "failed linking file resources");
+            return 1;
+        }
+
+        if (!mOptions.noAutoVersion) {
+            AutoVersioner versioner;
+            if (!versioner.consume(&mContext, &mergedTable)) {
+                mContext.getDiagnostics()->error(DiagMessage() << "failed versioning styles");
+                return 1;
+            }
+        }
+
+        if (!flattenTable(&mergedTable, archiveWriter.get())) {
+            mContext.getDiagnostics()->error(DiagMessage() << "failed to write resources.arsc");
+            return 1;
+        }
+
+        if (mOptions.generateJavaClassPath) {
+            if (!writeJavaFile(&mergedTable, mContext.getCompilationPackage())) {
+                return 1;
+            }
+        }
+
+        if (mOptions.generateProguardRulesPath) {
+            if (!writeProguardFile(proguardKeepSet)) {
+                return 1;
+            }
+        }
+
+        if (mOptions.verbose) {
+            Debug::printTable(&mergedTable);
+            for (; !tableMerger.getFileMergeQueue()->empty();
+                    tableMerger.getFileMergeQueue()->pop()) {
+                const FileToMerge& f = tableMerger.getFileMergeQueue()->front();
+                mContext.getDiagnostics()->note(
+                        DiagMessage() << f.srcPath << " -> " << f.dstPath << " from (0x"
+                                      << std::hex << (uintptr_t) f.srcTable << std::dec);
+            }
+        }
+
+        return 0;
+    }
+};
+
+int link(const std::vector<StringPiece>& args) {
+    LinkOptions options;
+    Flags flags = Flags()
+            .requiredFlag("-o", "Output path", &options.outputPath)
+            .requiredFlag("--manifest", "Path to the Android manifest to build",
+                          &options.manifestPath)
+            .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths)
+            .optionalFlag("--java", "Directory in which to generate R.java",
+                          &options.generateJavaClassPath)
+            .optionalFlag("--proguard", "Output file for generated Proguard rules",
+                          &options.generateProguardRulesPath)
+            .optionalSwitch("--no-auto-version",
+                            "Disables automatic style and layout SDK versioning",
+                            &options.noAutoVersion)
+            .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
+                            "by -o",
+                            &options.outputToDirectory)
+            .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib)
+            .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
+
+    if (!flags.parse("aapt2 link", args, &std::cerr)) {
+        return 1;
+    }
+
+    LinkCommand cmd = { options };
+    return cmd.run(flags.getArgs());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h
new file mode 100644
index 0000000..2cc8d9f
--- /dev/null
+++ b/tools/aapt2/link/Linkers.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_LINKER_LINKERS_H
+#define AAPT_LINKER_LINKERS_H
+
+#include "process/IResourceTableConsumer.h"
+
+#include <set>
+
+namespace aapt {
+
+class ResourceTable;
+struct ResourceEntry;
+struct ConfigDescription;
+
+/**
+ * Determines whether a versioned resource should be created. If a versioned resource already
+ * exists, it takes precedence.
+ */
+bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config,
+                                     const int sdkVersionToGenerate);
+
+struct AutoVersioner : public IResourceTableConsumer {
+    bool consume(IAaptContext* context, ResourceTable* table) override;
+};
+
+struct PrivateAttributeMover : public IResourceTableConsumer {
+    bool consume(IAaptContext* context, ResourceTable* table) override;
+};
+
+struct XmlAutoVersioner : public IXmlResourceConsumer {
+    bool consume(IAaptContext* context, XmlResource* resource) override;
+};
+
+struct ReferenceLinker : public IResourceTableConsumer {
+    bool consume(IAaptContext* context, ResourceTable* table) override;
+};
+
+class XmlReferenceLinker : IXmlResourceConsumer {
+private:
+    std::set<int> mSdkLevelsFound;
+
+public:
+    bool consume(IAaptContext* context, XmlResource* resource) override;
+
+    const std::set<int>& getSdkLevels() const {
+        return mSdkLevelsFound;
+    }
+};
+
+} // namespace aapt
+
+#endif /* AAPT_LINKER_LINKERS_H */
diff --git a/tools/aapt2/link/PrivateAttributeMover.cpp b/tools/aapt2/link/PrivateAttributeMover.cpp
new file mode 100644
index 0000000..db20bcb
--- /dev/null
+++ b/tools/aapt2/link/PrivateAttributeMover.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourceTable.h"
+
+#include "link/Linkers.h"
+
+#include <algorithm>
+#include <iterator>
+
+namespace aapt {
+
+template <typename InputContainer, typename OutputIterator, typename Predicate>
+OutputIterator moveIf(InputContainer& inputContainer, OutputIterator result,
+                      Predicate pred) {
+    const auto last = inputContainer.end();
+    auto newEnd = std::find_if(inputContainer.begin(), inputContainer.end(), pred);
+    if (newEnd == last) {
+        return result;
+    }
+
+    *result = std::move(*newEnd);
+
+    auto first = newEnd;
+    ++first;
+
+    for (; first != last; ++first) {
+        if (bool(pred(*first))) {
+            // We want to move this guy
+            *result = std::move(*first);
+            ++result;
+        } else {
+            // We want to keep this guy, but we will need to move it up the list to replace
+            // missing items.
+            *newEnd = std::move(*first);
+            ++newEnd;
+        }
+    }
+
+    inputContainer.erase(newEnd, last);
+    return result;
+}
+
+bool PrivateAttributeMover::consume(IAaptContext* context, ResourceTable* table) {
+    for (auto& package : table->packages) {
+        ResourceTableType* type = package->findType(ResourceType::kAttr);
+        if (!type) {
+            continue;
+        }
+
+        if (!type->publicStatus.isPublic) {
+            // No public attributes, so we can safely leave these private attributes where they are.
+            return true;
+        }
+
+        ResourceTableType* privAttrType = package->findOrCreateType(ResourceType::kAttrPrivate);
+        assert(privAttrType->entries.empty());
+
+        moveIf(type->entries, std::back_inserter(privAttrType->entries),
+               [](const std::unique_ptr<ResourceEntry>& entry) -> bool {
+                   return !entry->publicStatus.isPublic;
+               });
+        break;
+    }
+    return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/PrivateAttributeMover_test.cpp b/tools/aapt2/link/PrivateAttributeMover_test.cpp
new file mode 100644
index 0000000..8173c30
--- /dev/null
+++ b/tools/aapt2/link/PrivateAttributeMover_test.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "link/Linkers.h"
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(PrivateAttributeMoverTest, MovePrivateAttributes) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .addSimple(u"@android:attr/publicA")
+            .addSimple(u"@android:attr/privateA")
+            .addSimple(u"@android:attr/publicB")
+            .addSimple(u"@android:attr/privateB")
+            .build();
+    ASSERT_TRUE(table->markPublic(test::parseNameOrDie(u"@android:attr/publicA"),
+                                  ResourceId(0x01010000), {}, context->getDiagnostics()));
+    ASSERT_TRUE(table->markPublic(test::parseNameOrDie(u"@android:attr/publicB"),
+                                      ResourceId(0x01010002), {}, context->getDiagnostics()));
+
+    PrivateAttributeMover mover;
+    ASSERT_TRUE(mover.consume(context.get(), table.get()));
+
+    ResourceTablePackage* package = table->findPackage(u"android");
+    ASSERT_NE(package, nullptr);
+
+    ResourceTableType* type = package->findType(ResourceType::kAttr);
+    ASSERT_NE(type, nullptr);
+    ASSERT_EQ(type->entries.size(), 2u);
+    EXPECT_NE(type->findEntry(u"publicA"), nullptr);
+    EXPECT_NE(type->findEntry(u"publicB"), nullptr);
+
+    type = package->findType(ResourceType::kAttrPrivate);
+    ASSERT_NE(type, nullptr);
+    ASSERT_EQ(type->entries.size(), 2u);
+    EXPECT_NE(type->findEntry(u"privateA"), nullptr);
+    EXPECT_NE(type->findEntry(u"privateB"), nullptr);
+}
+
+TEST(PrivateAttributeMoverTest, LeavePrivateAttributesWhenNoPublicAttributesDefined) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .addSimple(u"@android:attr/privateA")
+            .addSimple(u"@android:attr/privateB")
+            .build();
+
+    PrivateAttributeMover mover;
+    ASSERT_TRUE(mover.consume(context.get(), table.get()));
+
+    ResourceTablePackage* package = table->findPackage(u"android");
+    ASSERT_NE(package, nullptr);
+
+    ResourceTableType* type = package->findType(ResourceType::kAttr);
+    ASSERT_NE(type, nullptr);
+    ASSERT_EQ(type->entries.size(), 2u);
+
+    type = package->findType(ResourceType::kAttrPrivate);
+    ASSERT_EQ(type, nullptr);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
new file mode 100644
index 0000000..c0356e5
--- /dev/null
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Diagnostics.h"
+#include "ResourceTable.h"
+#include "ResourceUtils.h"
+#include "ResourceValues.h"
+#include "util/Util.h"
+#include "ValueVisitor.h"
+
+#include "link/Linkers.h"
+#include "link/ReferenceLinkerVisitor.h"
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <cassert>
+
+namespace aapt {
+
+namespace {
+
+/**
+ * The ReferenceLinkerVisitor will follow all references and make sure they point
+ * to resources that actually exist, either in the local resource table, or as external
+ * symbols. Once the target resource has been found, the ID of the resource will be assigned
+ * to the reference object.
+ *
+ * NOTE: All of the entries in the ResourceTable must be assigned IDs.
+ */
+class StyleAndReferenceLinkerVisitor : public ValueVisitor {
+private:
+    ReferenceLinkerVisitor mReferenceVisitor;
+    IAaptContext* mContext;
+    ISymbolTable* mSymbols;
+    IPackageDeclStack* mPackageDecls;
+    StringPool* mStringPool;
+    bool mError = false;
+
+    const ISymbolTable::Symbol* findAttributeSymbol(Reference* reference) {
+        assert(reference);
+        assert(reference->name || reference->id);
+
+        if (reference->name) {
+            // Transform the package name if it is an alias.
+            Maybe<ResourceName> realName = mPackageDecls->transformPackage(
+                    reference->name.value(), mContext->getCompilationPackage());
+
+            // Mangle the reference name if it should be mangled.
+            Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
+                    realName ? realName.value() : reference->name.value());
+
+            const ISymbolTable::Symbol* s = nullptr;
+            if (mangledName) {
+                s = mSymbols->findByName(mangledName.value());
+            } else if (realName) {
+                s = mSymbols->findByName(realName.value());
+            } else {
+                s = mSymbols->findByName(reference->name.value());
+            }
+
+            if (s && s->attribute) {
+                return s;
+            }
+        }
+
+        if (reference->id) {
+            if (const ISymbolTable::Symbol* s = mSymbols->findById(reference->id.value())) {
+                if (s->attribute) {
+                    return s;
+                }
+            }
+        }
+        return nullptr;
+    }
+
+    /**
+     * Transform a RawString value into a more specific, appropriate value, based on the
+     * Attribute. If a non RawString value is passed in, this is an identity transform.
+     */
+    std::unique_ptr<Item> parseValueWithAttribute(std::unique_ptr<Item> value,
+                                                  const Attribute* attr) {
+        if (RawString* rawString = valueCast<RawString>(value.get())) {
+            std::unique_ptr<Item> transformed = ResourceUtils::parseItemForAttribute(
+                    *rawString->value, attr);
+
+            // If we could not parse as any specific type, try a basic STRING.
+            if (!transformed && (attr->typeMask & android::ResTable_map::TYPE_STRING)) {
+                util::StringBuilder stringBuilder;
+                stringBuilder.append(*rawString->value);
+                if (stringBuilder) {
+                    transformed = util::make_unique<String>(
+                            mStringPool->makeRef(stringBuilder.str()));
+                }
+            }
+
+            if (transformed) {
+                return transformed;
+            }
+        };
+        return value;
+    }
+
+    void buildAttributeMismatchMessage(DiagMessage* msg, const Attribute* attr,
+                                       const Item* value) {
+        *msg << "expected";
+        if (attr->typeMask & android::ResTable_map::TYPE_BOOLEAN) {
+            *msg << " boolean";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_COLOR) {
+            *msg << " color";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_DIMENSION) {
+            *msg << " dimension";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_ENUM) {
+            *msg << " enum";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_FLAGS) {
+            *msg << " flags";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_FLOAT) {
+            *msg << " float";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_FRACTION) {
+            *msg << " fraction";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_INTEGER) {
+            *msg << " integer";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_REFERENCE) {
+            *msg << " reference";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_STRING) {
+            *msg << " string";
+        }
+
+        *msg << " but got " << *value;
+    }
+
+public:
+    using ValueVisitor::visit;
+
+    StyleAndReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols,
+                                   StringPool* stringPool, IPackageDeclStack* decl) :
+            mReferenceVisitor(context, symbols, decl), mContext(context), mSymbols(symbols),
+            mPackageDecls(decl), mStringPool(stringPool) {
+    }
+
+    void visit(Reference* reference) override {
+        mReferenceVisitor.visit(reference);
+    }
+
+    /**
+     * We visit the Style specially because during this phase, values of attributes are
+     * all RawString values. Now that we are expected to resolve all symbols, we can
+     * lookup the attributes to find out which types are allowed for the attributes' values.
+     */
+    void visit(Style* style) override {
+        if (style->parent) {
+            visit(&style->parent.value());
+        }
+
+        for (Style::Entry& entry : style->entries) {
+            if (const ISymbolTable::Symbol* s = findAttributeSymbol(&entry.key)) {
+                // Assign our style key the correct ID.
+                entry.key.id = s->id;
+
+                // Try to convert the value to a more specific, typed value based on the
+                // attribute it is set to.
+                entry.value = parseValueWithAttribute(std::move(entry.value), s->attribute.get());
+
+                // Link/resolve the final value (mostly if it's a reference).
+                entry.value->accept(this);
+
+                // Now verify that the type of this item is compatible with the attribute it
+                // is defined for.
+                android::Res_value val = {};
+                entry.value->flatten(&val);
+
+                // Always allow references.
+                const uint32_t typeMask = s->attribute->typeMask |
+                        android::ResTable_map::TYPE_REFERENCE;
+
+                if (!(typeMask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) {
+                    // The actual type of this item is incompatible with the attribute.
+                    DiagMessage msg;
+                    buildAttributeMismatchMessage(&msg, s->attribute.get(), entry.value.get());
+                    mContext->getDiagnostics()->error(msg);
+                    mError = true;
+                }
+            } else {
+                DiagMessage msg;
+                msg << "style attribute '";
+                if (entry.key.name) {
+                    msg << entry.key.name.value().package << ":" << entry.key.name.value().entry;
+                } else {
+                    msg << entry.key.id.value();
+                }
+                msg << "' not found";
+                mContext->getDiagnostics()->error(msg);
+                mError = true;
+            }
+        }
+    }
+
+    inline bool hasError() {
+        return mError || mReferenceVisitor.hasError();
+    }
+};
+
+struct EmptyDeclStack : public IPackageDeclStack {
+    Maybe<ResourceName> transformPackage(const ResourceName& name,
+                                         const StringPiece16& localPackage) const override {
+        if (name.package.empty()) {
+            return ResourceName{ localPackage.toString(), name.type, name.entry };
+        }
+        return {};
+    }
+};
+
+} // namespace
+
+bool ReferenceLinker::consume(IAaptContext* context, ResourceTable* table) {
+    EmptyDeclStack declStack;
+    bool error = false;
+    for (auto& package : table->packages) {
+        for (auto& type : package->types) {
+            for (auto& entry : type->entries) {
+                // A public entry with no values will not be encoded properly.
+                if (entry->publicStatus.isPublic && entry->values.empty()) {
+                    context->getDiagnostics()->error(DiagMessage(entry->publicStatus.source)
+                                                     << "No value for public resource");
+                    error = true;
+                }
+
+                for (auto& configValue : entry->values) {
+                    StyleAndReferenceLinkerVisitor visitor(context,
+                                                           context->getExternalSymbols(),
+                                                           &table->stringPool, &declStack);
+                    configValue.value->accept(&visitor);
+                    if (visitor.hasError()) {
+                        error = true;
+                    }
+                }
+            }
+        }
+    }
+    return !error;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/ReferenceLinkerVisitor.h b/tools/aapt2/link/ReferenceLinkerVisitor.h
new file mode 100644
index 0000000..c70531b
--- /dev/null
+++ b/tools/aapt2/link/ReferenceLinkerVisitor.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_LINKER_REFERENCELINKERVISITOR_H
+#define AAPT_LINKER_REFERENCELINKERVISITOR_H
+
+#include "Resource.h"
+#include "ResourceValues.h"
+#include "ValueVisitor.h"
+
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+
+#include <cassert>
+
+namespace aapt {
+
+/**
+ * The ReferenceLinkerVisitor will follow all references and make sure they point
+ * to resources that actually exist in the given ISymbolTable.
+ * Once the target resource has been found, the ID of the resource will be assigned
+ * to the reference object.
+ */
+class ReferenceLinkerVisitor : public ValueVisitor {
+    using ValueVisitor::visit;
+private:
+    IAaptContext* mContext;
+    ISymbolTable* mSymbols;
+    IPackageDeclStack* mPackageDecls;
+    bool mError = false;
+
+public:
+    ReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols, IPackageDeclStack* decls) :
+            mContext(context), mSymbols(symbols), mPackageDecls(decls) {
+    }
+
+    /**
+     * Lookup a reference and ensure it exists, either in our local table, or as an external
+     * symbol. Once found, assign the ID of the target resource to this reference object.
+     */
+    void visit(Reference* reference) override {
+        assert(reference);
+        assert(reference->name || reference->id);
+
+        // We prefer to lookup by name if the name is set. Otherwise it could be
+        // an out-of-date ID.
+        if (reference->name) {
+            // Transform the package name if it is an alias.
+            Maybe<ResourceName> realName = mPackageDecls->transformPackage(
+                    reference->name.value(), mContext->getCompilationPackage());
+
+            // Mangle the reference name if it should be mangled.
+            Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
+                    realName ? realName.value() : reference->name.value());
+
+            const ISymbolTable::Symbol* s = nullptr;
+            if (mangledName) {
+                s = mSymbols->findByName(mangledName.value());
+            } else if (realName) {
+                s = mSymbols->findByName(realName.value());
+            } else {
+                s = mSymbols->findByName(reference->name.value());
+            }
+
+            if (s) {
+                reference->id = s->id;
+                return;
+            }
+
+            DiagMessage errorMsg;
+            errorMsg << "reference to " << reference->name.value();
+            if (realName) {
+                errorMsg << " (aka " << realName.value() << ")";
+            }
+            errorMsg << " was not found";
+            mContext->getDiagnostics()->error(errorMsg);
+            mError = true;
+            return;
+        }
+
+        if (!mSymbols->findById(reference->id.value())) {
+            mContext->getDiagnostics()->error(DiagMessage()
+                                              << "reference to " << reference->id.value()
+                                              << " was not found");
+            mError = true;
+        }
+    }
+
+    inline bool hasError() {
+        return mError;
+    }
+};
+
+} // namespace aapt
+
+#endif /* AAPT_LINKER_REFERENCELINKERVISITOR_H */
diff --git a/tools/aapt2/link/ReferenceLinker_test.cpp b/tools/aapt2/link/ReferenceLinker_test.cpp
new file mode 100644
index 0000000..5e7641a
--- /dev/null
+++ b/tools/aapt2/link/ReferenceLinker_test.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "link/Linkers.h"
+#include "process/SymbolTable.h"
+
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(ReferenceLinkerTest, LinkSimpleReferences) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.test", 0x7f)
+            .addReference(u"@com.app.test:string/foo", ResourceId(0x7f020000),
+                          u"@com.app.test:string/bar")
+
+            // Test use of local reference (w/o package name).
+            .addReference(u"@com.app.test:string/bar", ResourceId(0x7f020001), u"@string/baz")
+
+            .addReference(u"@com.app.test:string/baz", ResourceId(0x7f020002),
+                          u"@android:string/ok")
+            .build();
+
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+            .setCompilationPackage(u"com.app.test")
+            .setPackageId(0x7f)
+            .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" })
+            .setSymbolTable(JoinedSymbolTableBuilder()
+                            .addSymbolTable(util::make_unique<SymbolTableWrapper>(table.get()))
+                            .addSymbolTable(test::StaticSymbolTableBuilder()
+                                    .addSymbol(u"@android:string/ok", ResourceId(0x01040034))
+                                    .build())
+                            .build())
+            .build();
+
+    ReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(context.get(), table.get()));
+
+    Reference* ref = test::getValue<Reference>(table.get(), u"@com.app.test:string/foo");
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->id);
+    EXPECT_EQ(ref->id.value(), ResourceId(0x7f020001));
+
+    ref = test::getValue<Reference>(table.get(), u"@com.app.test:string/bar");
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->id);
+    EXPECT_EQ(ref->id.value(), ResourceId(0x7f020002));
+
+    ref = test::getValue<Reference>(table.get(), u"@com.app.test:string/baz");
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->id);
+    EXPECT_EQ(ref->id.value(), ResourceId(0x01040034));
+}
+
+TEST(ReferenceLinkerTest, LinkStyleAttributes) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.test", 0x7f)
+            .addValue(u"@com.app.test:style/Theme", test::StyleBuilder()
+                    .setParent(u"@android:style/Theme.Material")
+                    .addItem(u"@android:attr/foo", ResourceUtils::tryParseColor(u"#ff00ff"))
+                    .addItem(u"@android:attr/bar", {} /* placeholder */)
+                    .build())
+            .build();
+
+    {
+        // We need to fill in the value for the attribute android:attr/bar after we build the
+        // table, because we need access to the string pool.
+        Style* style = test::getValue<Style>(table.get(), u"@com.app.test:style/Theme");
+        ASSERT_NE(style, nullptr);
+        style->entries.back().value = util::make_unique<RawString>(
+                table->stringPool.makeRef(u"one|two"));
+    }
+
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+            .setCompilationPackage(u"com.app.test")
+            .setPackageId(0x7f)
+            .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" })
+            .setSymbolTable(test::StaticSymbolTableBuilder()
+                    .addSymbol(u"@android:style/Theme.Material", ResourceId(0x01060000))
+                    .addSymbol(u"@android:attr/foo", ResourceId(0x01010001),
+                               test::AttributeBuilder()
+                                    .setTypeMask(android::ResTable_map::TYPE_COLOR)
+                                    .build())
+                    .addSymbol(u"@android:attr/bar", ResourceId(0x01010002),
+                               test::AttributeBuilder()
+                                    .setTypeMask(android::ResTable_map::TYPE_FLAGS)
+                                    .addItem(u"one", 0x01)
+                                    .addItem(u"two", 0x02)
+                                    .build())
+                    .build())
+            .build();
+
+    ReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(context.get(), table.get()));
+
+    Style* style = test::getValue<Style>(table.get(), u"@com.app.test:style/Theme");
+    ASSERT_NE(style, nullptr);
+    AAPT_ASSERT_TRUE(style->parent);
+    AAPT_ASSERT_TRUE(style->parent.value().id);
+    EXPECT_EQ(style->parent.value().id.value(), ResourceId(0x01060000));
+
+    ASSERT_EQ(2u, style->entries.size());
+
+    AAPT_ASSERT_TRUE(style->entries[0].key.id);
+    EXPECT_EQ(style->entries[0].key.id.value(), ResourceId(0x01010001));
+    ASSERT_NE(valueCast<BinaryPrimitive>(style->entries[0].value.get()), nullptr);
+
+    AAPT_ASSERT_TRUE(style->entries[1].key.id);
+    EXPECT_EQ(style->entries[1].key.id.value(), ResourceId(0x01010002));
+    ASSERT_NE(valueCast<BinaryPrimitive>(style->entries[1].value.get()), nullptr);
+}
+
+TEST(ReferenceLinkerTest, LinkMangledReferencesAndAttributes) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+            .setCompilationPackage(u"com.app.test")
+            .setPackageId(0x7f)
+            .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test", { u"com.android.support" } })
+            .setSymbolTable(test::StaticSymbolTableBuilder()
+                    .addSymbol(u"@com.app.test:attr/com.android.support$foo",
+                               ResourceId(0x7f010000), test::AttributeBuilder()
+                                        .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
+                    .build())
+            .build();
+
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.test", 0x7f)
+            .addValue(u"@com.app.test:style/Theme", ResourceId(0x7f020000),
+                      test::StyleBuilder().addItem(u"@com.android.support:attr/foo",
+                                                   ResourceUtils::tryParseColor(u"#ff0000"))
+                                          .build())
+            .build();
+
+    ReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(context.get(), table.get()));
+
+    Style* style = test::getValue<Style>(table.get(), u"@com.app.test:style/Theme");
+    ASSERT_NE(style, nullptr);
+    ASSERT_EQ(1u, style->entries.size());
+    AAPT_ASSERT_TRUE(style->entries.front().key.id);
+    EXPECT_EQ(style->entries.front().key.id.value(), ResourceId(0x7f010000));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
new file mode 100644
index 0000000..d5fd1fc
--- /dev/null
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "ValueVisitor.h"
+
+#include "link/TableMerger.h"
+#include "util/Util.h"
+
+#include <cassert>
+
+namespace aapt {
+
+TableMerger::TableMerger(IAaptContext* context, ResourceTable* outTable) :
+        mContext(context), mMasterTable(outTable) {
+    // Create the desired package that all tables will be merged into.
+    mMasterPackage = mMasterTable->createPackage(
+            mContext->getCompilationPackage(), mContext->getPackageId());
+    assert(mMasterPackage && "package name or ID already taken");
+}
+
+bool TableMerger::merge(const Source& src, ResourceTable* table) {
+    const uint8_t desiredPackageId = mContext->getPackageId();
+
+    bool error = false;
+    for (auto& package : table->packages) {
+        // Warn of packages with an unrelated ID.
+        if (package->id && package->id.value() != desiredPackageId) {
+            mContext->getDiagnostics()->warn(DiagMessage(src)
+                                             << "ignoring package " << package->name);
+            continue;
+        }
+
+        bool manglePackage = false;
+        if (!package->name.empty() && mContext->getCompilationPackage() != package->name) {
+            manglePackage = true;
+            mMergedPackages.insert(package->name);
+        }
+
+        // Merge here. Once the entries are merged and mangled, any references to
+        // them are still valid. This is because un-mangled references are
+        // mangled, then looked up at resolution time.
+        // Also, when linking, we convert references with no package name to use
+        // the compilation package name.
+        if (!doMerge(src, table, package.get(), manglePackage)) {
+            error = true;
+        }
+    }
+    return !error;
+}
+
+bool TableMerger::doMerge(const Source& src, ResourceTable* srcTable,
+                          ResourceTablePackage* srcPackage, const bool manglePackage) {
+    bool error = false;
+
+    for (auto& srcType : srcPackage->types) {
+        ResourceTableType* dstType = mMasterPackage->findOrCreateType(srcType->type);
+        if (srcType->publicStatus.isPublic) {
+            if (dstType->publicStatus.isPublic && dstType->id && srcType->id
+                    && dstType->id.value() == srcType->id.value()) {
+                // Both types are public and have different IDs.
+                mContext->getDiagnostics()->error(DiagMessage(src)
+                                                  << "can not merge type '"
+                                                  << srcType->type
+                                                  << "': conflicting public IDs");
+                error = true;
+                continue;
+            }
+
+            dstType->publicStatus = std::move(srcType->publicStatus);
+            dstType->id = srcType->id;
+        }
+
+        for (auto& srcEntry : srcType->entries) {
+            ResourceEntry* dstEntry;
+            if (manglePackage) {
+                dstEntry = dstType->findOrCreateEntry(NameMangler::mangleEntry(
+                        srcPackage->name, srcEntry->name));
+            } else {
+                dstEntry = dstType->findOrCreateEntry(srcEntry->name);
+            }
+
+            if (srcEntry->publicStatus.isPublic) {
+                if (dstEntry->publicStatus.isPublic && dstEntry->id && srcEntry->id
+                        && dstEntry->id.value() != srcEntry->id.value()) {
+                    // Both entries are public and have different IDs.
+                    mContext->getDiagnostics()->error(DiagMessage(src)
+                                                      << "can not merge entry '"
+                                                      << srcEntry->name
+                                                      << "': conflicting public IDs");
+                    error = true;
+                    continue;
+                }
+
+                dstEntry->publicStatus = std::move(srcEntry->publicStatus);
+                dstEntry->id = srcEntry->id;
+            }
+
+            for (ResourceConfigValue& srcValue : srcEntry->values) {
+                auto cmp = [](const ResourceConfigValue& a,
+                              const ConfigDescription& b) -> bool {
+                    return a.config < b;
+                };
+
+                auto iter = std::lower_bound(dstEntry->values.begin(), dstEntry->values.end(),
+                                             srcValue.config, cmp);
+
+                if (iter != dstEntry->values.end() && iter->config == srcValue.config) {
+                    const int collisionResult = ResourceTable::resolveValueCollision(
+                            iter->value.get(), srcValue.value.get());
+                    if (collisionResult == 0) {
+                        // Error!
+                        ResourceNameRef resourceName =
+                                { srcPackage->name, srcType->type, srcEntry->name };
+                        mContext->getDiagnostics()->error(DiagMessage(srcValue.source)
+                                                          << "resource '" << resourceName
+                                                          << "' has a conflicting value for "
+                                                          << "configuration ("
+                                                          << srcValue.config << ")");
+                        mContext->getDiagnostics()->note(DiagMessage(iter->source)
+                                                         << "originally defined here");
+                        error = true;
+                        continue;
+                    } else if (collisionResult < 0) {
+                        // Keep our existing value.
+                        continue;
+                    }
+
+                } else {
+                    // Insert a new value.
+                    iter = dstEntry->values.insert(iter,
+                                                   ResourceConfigValue{ srcValue.config });
+                }
+
+                iter->source = std::move(srcValue.source);
+                iter->comment = std::move(srcValue.comment);
+                if (manglePackage) {
+                    iter->value = cloneAndMangle(srcTable, srcPackage->name,
+                                                 srcValue.value.get());
+                } else {
+                    iter->value = clone(srcValue.value.get());
+                }
+            }
+        }
+    }
+    return !error;
+}
+
+std::unique_ptr<Value> TableMerger::cloneAndMangle(ResourceTable* table,
+                                                   const std::u16string& package,
+                                                   Value* value) {
+    if (FileReference* f = valueCast<FileReference>(value)) {
+        // Mangle the path.
+        StringPiece16 prefix, entry, suffix;
+        if (util::extractResFilePathParts(*f->path, &prefix, &entry, &suffix)) {
+            std::u16string mangledEntry = NameMangler::mangleEntry(package, entry.toString());
+            std::u16string newPath = prefix.toString() + mangledEntry + suffix.toString();
+            mFilesToMerge.push(FileToMerge{ table, *f->path, newPath });
+            return util::make_unique<FileReference>(mMasterTable->stringPool.makeRef(newPath));
+        }
+    }
+    return clone(value);
+}
+
+std::unique_ptr<Value> TableMerger::clone(Value* value) {
+    return std::unique_ptr<Value>(value->clone(&mMasterTable->stringPool));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
new file mode 100644
index 0000000..157c16e
--- /dev/null
+++ b/tools/aapt2/link/TableMerger.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_TABLEMERGER_H
+#define AAPT_TABLEMERGER_H
+
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "util/Util.h"
+
+#include "process/IResourceTableConsumer.h"
+
+#include <queue>
+#include <set>
+
+namespace aapt {
+
+struct FileToMerge {
+    ResourceTable* srcTable;
+    std::u16string srcPath;
+    std::u16string dstPath;
+};
+
+/**
+ * TableMerger takes resource tables and merges all packages within the tables that have the same
+ * package ID.
+ *
+ * If a package has a different name, all the entries in that table have their names mangled
+ * to include the package name. This way there are no collisions. In order to do this correctly,
+ * the TableMerger needs to also mangle any FileReference paths. Once these are mangled,
+ * the original source path of the file, along with the new destination path is recorded in the
+ * queue returned from getFileMergeQueue().
+ *
+ * Once the merging is complete, a separate process can go collect the files from the various
+ * source APKs and either copy or process their XML and put them in the correct location in
+ * the final APK.
+ */
+class TableMerger {
+public:
+    TableMerger(IAaptContext* context, ResourceTable* outTable);
+
+    inline std::queue<FileToMerge>* getFileMergeQueue() {
+        return &mFilesToMerge;
+    }
+
+    inline const std::set<std::u16string>& getMergedPackages() const {
+        return mMergedPackages;
+    }
+
+    bool merge(const Source& src, ResourceTable* table);
+
+private:
+    IAaptContext* mContext;
+    ResourceTable* mMasterTable;
+    ResourceTablePackage* mMasterPackage;
+
+    std::set<std::u16string> mMergedPackages;
+    std::queue<FileToMerge> mFilesToMerge;
+
+    bool doMerge(const Source& src, ResourceTable* srcTable, ResourceTablePackage* srcPackage,
+                 const bool manglePackage);
+
+    std::unique_ptr<Value> cloneAndMangle(ResourceTable* table, const std::u16string& package,
+                                          Value* value);
+    std::unique_ptr<Value> clone(Value* value);
+};
+
+} // namespace aapt
+
+#endif /* AAPT_TABLEMERGER_H */
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
new file mode 100644
index 0000000..fa7ce86
--- /dev/null
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "link/TableMerger.h"
+
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+struct TableMergerTest : public ::testing::Test {
+    std::unique_ptr<IAaptContext> mContext;
+
+    void SetUp() override {
+        mContext = test::ContextBuilder()
+                // We are compiling this package.
+                .setCompilationPackage(u"com.app.a")
+
+                // Merge all packages that have this package ID.
+                .setPackageId(0x7f)
+
+                // Mangle all packages that do not have this package name.
+                .setNameManglerPolicy(NameManglerPolicy{ u"com.app.a", { u"com.app.b" } })
+
+                .build();
+    }
+};
+
+TEST_F(TableMergerTest, SimpleMerge) {
+    std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.a", 0x7f)
+            .addReference(u"@com.app.a:id/foo", u"@com.app.a:id/bar")
+            .addReference(u"@com.app.a:id/bar", u"@com.app.b:id/foo")
+            .addValue(u"@com.app.a:styleable/view", test::StyleableBuilder()
+                    .addItem(u"@com.app.b:id/foo")
+                    .build())
+            .build();
+
+    std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.b", 0x7f)
+            .addSimple(u"@com.app.b:id/foo")
+            .build();
+
+    ResourceTable finalTable;
+    TableMerger merger(mContext.get(), &finalTable);
+
+    ASSERT_TRUE(merger.merge({}, tableA.get()));
+    ASSERT_TRUE(merger.merge({}, tableB.get()));
+
+    EXPECT_TRUE(merger.getMergedPackages().count(u"com.app.b") != 0);
+
+    // Entries from com.app.a should not be mangled.
+    AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:id/foo")));
+    AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:id/bar")));
+    AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:styleable/view")));
+
+    // The unmangled name should not be present.
+    AAPT_EXPECT_FALSE(finalTable.findResource(test::parseNameOrDie(u"@com.app.b:id/foo")));
+
+    // Look for the mangled name.
+    AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:id/com.app.b$foo")));
+}
+
+TEST_F(TableMergerTest, MergeFileReferences) {
+    std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.a", 0x7f)
+            .addFileReference(u"@com.app.a:xml/file", u"res/xml/file.xml")
+            .build();
+    std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.b", 0x7f)
+            .addFileReference(u"@com.app.b:xml/file", u"res/xml/file.xml")
+            .build();
+
+    ResourceTable finalTable;
+    TableMerger merger(mContext.get(), &finalTable);
+
+    ASSERT_TRUE(merger.merge({}, tableA.get()));
+    ASSERT_TRUE(merger.merge({}, tableB.get()));
+
+    FileReference* f = test::getValue<FileReference>(&finalTable, u"@com.app.a:xml/file");
+    ASSERT_NE(f, nullptr);
+    EXPECT_EQ(std::u16string(u"res/xml/file.xml"), *f->path);
+
+    f = test::getValue<FileReference>(&finalTable, u"@com.app.a:xml/com.app.b$file");
+    ASSERT_NE(f, nullptr);
+    EXPECT_EQ(std::u16string(u"res/xml/com.app.b$file.xml"), *f->path);
+
+    std::queue<FileToMerge>* filesToMerge = merger.getFileMergeQueue();
+    ASSERT_FALSE(filesToMerge->empty());
+
+    FileToMerge& fileToMerge = filesToMerge->front();
+    EXPECT_EQ(fileToMerge.srcTable, tableB.get());
+    EXPECT_EQ(fileToMerge.srcPath, u"res/xml/file.xml");
+    EXPECT_EQ(fileToMerge.dstPath, u"res/xml/com.app.b$file.xml");
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp
new file mode 100644
index 0000000..147b9bf
--- /dev/null
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Diagnostics.h"
+#include "ResourceUtils.h"
+#include "SdkConstants.h"
+#include "XmlDom.h"
+
+#include "link/Linkers.h"
+#include "link/ReferenceLinkerVisitor.h"
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+#include "util/Util.h"
+
+namespace aapt {
+
+namespace {
+
+class XmlReferenceLinkerVisitor : public xml::PackageAwareVisitor {
+private:
+    IAaptContext* mContext;
+    ISymbolTable* mSymbols;
+    std::set<int>* mSdkLevelsFound;
+    ReferenceLinkerVisitor mReferenceLinkerVisitor;
+    bool mError = false;
+
+public:
+    using xml::PackageAwareVisitor::visit;
+
+    XmlReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols,
+                              std::set<int>* sdkLevelsFound) :
+            mContext(context), mSymbols(symbols), mSdkLevelsFound(sdkLevelsFound),
+            mReferenceLinkerVisitor(context, symbols, this) {
+    }
+
+    void visit(xml::Element* el) override {
+        for (xml::Attribute& attr : el->attributes) {
+            Maybe<std::u16string> maybePackage =
+                    util::extractPackageFromNamespace(attr.namespaceUri);
+            if (maybePackage) {
+                // There is a valid package name for this attribute. We will look this up.
+                StringPiece16 package = maybePackage.value();
+                if (package.empty()) {
+                    // Empty package means the 'current' or 'local' package.
+                    package = mContext->getCompilationPackage();
+                }
+
+                attr.compiledAttribute = compileAttribute(
+                        ResourceName{ package.toString(), ResourceType::kAttr, attr.name });
+
+                // Convert the string value into a compiled Value if this is a valid attribute.
+                if (attr.compiledAttribute) {
+                    // Record all SDK levels from which the attributes were defined.
+                    const int sdkLevel = findAttributeSdkLevel(attr.compiledAttribute.value().id);
+                    if (sdkLevel > 1) {
+                        mSdkLevelsFound->insert(sdkLevel);
+                    }
+
+                    const Attribute* attribute = &attr.compiledAttribute.value().attribute;
+                    attr.compiledValue = ResourceUtils::parseItemForAttribute(attr.value,
+                                                                              attribute);
+                    if (!attr.compiledValue &&
+                            !(attribute->typeMask & android::ResTable_map::TYPE_STRING)) {
+                        // We won't be able to encode this as a string.
+                        mContext->getDiagnostics()->error(
+                                DiagMessage() << "'" << attr.value << "' "
+                                              << "is incompatible with attribute "
+                                              << package << ":" << attr.name << " " << *attribute);
+                        mError = true;
+                    }
+                } else {
+                    mContext->getDiagnostics()->error(
+                            DiagMessage() << "attribute '" << package << ":" << attr.name
+                                          << "' was not found");
+                    mError = true;
+
+                }
+            } else {
+                // We still encode references.
+                attr.compiledValue = ResourceUtils::tryParseReference(attr.value);
+            }
+
+            if (attr.compiledValue) {
+                // With a compiledValue, we must resolve the reference and assign it an ID.
+                attr.compiledValue->accept(&mReferenceLinkerVisitor);
+            }
+        }
+
+        // Call the super implementation.
+        xml::PackageAwareVisitor::visit(el);
+    }
+
+    Maybe<xml::AaptAttribute> compileAttribute(const ResourceName& name) {
+        Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(name);
+        if (const ISymbolTable::Symbol* symbol = mSymbols->findByName(
+                mangledName ? mangledName.value() : name)) {
+            if (symbol->attribute) {
+                return xml::AaptAttribute{ symbol->id, *symbol->attribute };
+            }
+        }
+        return {};
+    }
+
+    inline bool hasError() {
+        return mError || mReferenceLinkerVisitor.hasError();
+    }
+};
+
+} // namespace
+
+bool XmlReferenceLinker::consume(IAaptContext* context, XmlResource* resource) {
+    mSdkLevelsFound.clear();
+    XmlReferenceLinkerVisitor visitor(context, context->getExternalSymbols(), &mSdkLevelsFound);
+    if (resource->root) {
+        resource->root->accept(&visitor);
+        return !visitor.hasError();
+    }
+    return false;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp
new file mode 100644
index 0000000..7f91ec3
--- /dev/null
+++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "link/Linkers.h"
+
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+class XmlReferenceLinkerTest : public ::testing::Test {
+public:
+    void SetUp() override {
+        mContext = test::ContextBuilder()
+                .setCompilationPackage(u"com.app.test")
+                .setNameManglerPolicy(
+                        NameManglerPolicy{ u"com.app.test", { u"com.android.support" } })
+                .setSymbolTable(test::StaticSymbolTableBuilder()
+                        .addSymbol(u"@android:attr/layout_width", ResourceId(0x01010000),
+                                   test::AttributeBuilder()
+                                        .setTypeMask(android::ResTable_map::TYPE_ENUM |
+                                                     android::ResTable_map::TYPE_DIMENSION)
+                                        .addItem(u"match_parent", 0xffffffff)
+                                        .build())
+                        .addSymbol(u"@android:attr/background", ResourceId(0x01010001),
+                                   test::AttributeBuilder()
+                                        .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
+                        .addSymbol(u"@android:attr/attr", ResourceId(0x01010002),
+                                   test::AttributeBuilder().build())
+                        .addSymbol(u"@android:attr/text", ResourceId(0x01010003),
+                                   test::AttributeBuilder()
+                                        .setTypeMask(android::ResTable_map::TYPE_STRING)
+                                        .build())
+
+                         // Add one real symbol that was introduces in v21
+                        .addSymbol(u"@android:attr/colorAccent", ResourceId(0x01010435),
+                                   test::AttributeBuilder().build())
+
+                        .addSymbol(u"@android:id/id", ResourceId(0x01030000))
+                        .addSymbol(u"@com.app.test:id/id", ResourceId(0x7f030000))
+                        .addSymbol(u"@com.app.test:color/green", ResourceId(0x7f020000))
+                        .addSymbol(u"@com.app.test:color/red", ResourceId(0x7f020001))
+                        .addSymbol(u"@com.app.test:attr/colorAccent", ResourceId(0x7f010000),
+                                   test::AttributeBuilder()
+                                       .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
+                        .addSymbol(u"@com.app.test:attr/com.android.support$colorAccent",
+                                   ResourceId(0x7f010001), test::AttributeBuilder()
+                                       .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
+                        .addSymbol(u"@com.app.test:attr/attr", ResourceId(0x7f010002),
+                                   test::AttributeBuilder().build())
+                        .build())
+                .build();
+    }
+
+protected:
+    std::unique_ptr<IAaptContext> mContext;
+};
+
+static xml::Element* getRootElement(XmlResource* doc) {
+    xml::Node* node = doc->root.get();
+    while (xml::nodeCast<xml::Namespace>(node)) {
+        if (node->children.empty()) {
+            return nullptr;
+        }
+        node = node->children.front().get();
+    }
+
+    if (xml::Element* el = xml::nodeCast<xml::Element>(node)) {
+        return el;
+    }
+    return nullptr;
+}
+
+TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+        <View xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:background="@color/green"
+              android:text="hello"
+              class="hello" />)EOF");
+
+    XmlReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+
+    xml::Element* viewEl = getRootElement(doc.get());
+    ASSERT_NE(viewEl, nullptr);
+
+    xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android",
+                                                    u"layout_width");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+    EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x01010000));
+    ASSERT_NE(xmlAttr->compiledValue, nullptr);
+    ASSERT_NE(valueCast<BinaryPrimitive>(xmlAttr->compiledValue.get()), nullptr);
+
+    xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android", u"background");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+    EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x01010001));
+    ASSERT_NE(xmlAttr->compiledValue, nullptr);
+    Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get());
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->name);
+    EXPECT_EQ(ref->name.value(), test::parseNameOrDie(u"@color/green")); // Make sure the name
+                                                                         // didn't change.
+    AAPT_ASSERT_TRUE(ref->id);
+    EXPECT_EQ(ref->id.value(), ResourceId(0x7f020000));
+
+    xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android", u"text");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+    ASSERT_FALSE(xmlAttr->compiledValue);   // Strings don't get compiled for memory sake.
+
+    xmlAttr = viewEl->findAttribute(u"", u"class");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_FALSE(xmlAttr->compiledAttribute);
+    ASSERT_EQ(xmlAttr->compiledValue, nullptr);
+}
+
+TEST_F(XmlReferenceLinkerTest, SdkLevelsAreRecorded) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+        <View xmlns:android="http://schemas.android.com/apk/res/android"
+              android:colorAccent="#ffffff" />)EOF");
+
+    XmlReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+    EXPECT_TRUE(linker.getSdkLevels().count(21) == 1);
+}
+
+TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+            <View xmlns:support="http://schemas.android.com/apk/res/com.android.support"
+                  support:colorAccent="#ff0000" />)EOF");
+
+    XmlReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+
+    xml::Element* viewEl = getRootElement(doc.get());
+    ASSERT_NE(viewEl, nullptr);
+
+    xml::Attribute* xmlAttr = viewEl->findAttribute(
+            u"http://schemas.android.com/apk/res/com.android.support", u"colorAccent");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+    EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x7f010001));
+    ASSERT_NE(valueCast<BinaryPrimitive>(xmlAttr->compiledValue.get()), nullptr);
+}
+
+TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+            <View xmlns:app="http://schemas.android.com/apk/res-auto"
+                  app:colorAccent="@app:color/red" />)EOF");
+
+    XmlReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+
+    xml::Element* viewEl = getRootElement(doc.get());
+    ASSERT_NE(viewEl, nullptr);
+
+    xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res-auto",
+                                                    u"colorAccent");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+    EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x7f010000));
+    Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get());
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->name);
+    AAPT_ASSERT_TRUE(ref->id);
+    EXPECT_EQ(ref->id.value(), ResourceId(0x7f020001));
+}
+
+TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+            <View xmlns:app="http://schemas.android.com/apk/res/android"
+                  app:attr="@app:id/id">
+              <View xmlns:app="http://schemas.android.com/apk/res/com.app.test"
+                    app:attr="@app:id/id"/>
+            </View>)EOF");
+
+    XmlReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+
+    xml::Element* viewEl = getRootElement(doc.get());
+    ASSERT_NE(viewEl, nullptr);
+
+    // All attributes and references in this element should be referring to "android" (0x01).
+    xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android",
+                                                    u"attr");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+    EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x01010002));
+    Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get());
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->id);
+    EXPECT_EQ(ref->id.value(), ResourceId(0x01030000));
+
+    ASSERT_FALSE(viewEl->getChildElements().empty());
+    viewEl = viewEl->getChildElements().front();
+    ASSERT_NE(viewEl, nullptr);
+
+    // All attributes and references in this element should be referring to "com.app.test" (0x7f).
+    xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/com.app.test", u"attr");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+    EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x7f010002));
+    ref = valueCast<Reference>(xmlAttr->compiledValue.get());
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->id);
+    EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000));
+}
+
+TEST_F(XmlReferenceLinkerTest, LinkViewWithLocalPackageAndAliasOfTheSameName) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+            <View xmlns:android="http://schemas.android.com/apk/res/com.app.test"
+                  android:attr="@id/id"/>)EOF");
+
+    XmlReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+
+    xml::Element* viewEl = getRootElement(doc.get());
+    ASSERT_NE(viewEl, nullptr);
+
+    // All attributes and references in this element should be referring to "com.app.test" (0x7f).
+    xml::Attribute* xmlAttr = viewEl->findAttribute(
+            u"http://schemas.android.com/apk/res/com.app.test", u"attr");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+    EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x7f010002));
+    Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get());
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->id);
+    EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000));
+}
+
+} // namespace aapt