AAPT2: Implement density stripping and initial Split support

When a preferred density is supplied, the closest matching densities
will be selected, the rest stripped from the APK.

Split support will be enabled in a later CL. Command line support is still
needed, but the foundation is ready.

Bug:25958912
Change-Id: I56d599806b4ec4ffa24e17aad48d47130ca05c08
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 3437ac0..d83f6def 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -38,6 +38,7 @@
 #include "process/IResourceTableConsumer.h"
 #include "process/SymbolTable.h"
 #include "proto/ProtoSerialize.h"
+#include "split/TableSplitter.h"
 #include "unflatten/BinaryResourceParser.h"
 #include "util/Files.h"
 #include "util/StringPiece.h"
@@ -63,15 +64,14 @@
     bool noAutoVersion = false;
     bool staticLib = false;
     bool generateNonFinalIds = false;
-    bool verbose = false;
     bool outputToDirectory = false;
     bool autoAddOverlay = false;
     bool doNotCompressAnything = false;
     std::vector<std::string> extensionsToNotCompress;
     Maybe<std::u16string> privateSymbols;
     ManifestFixerOptions manifestFixerOptions;
-    IConfigFilter* configFilter = nullptr;
     std::unordered_set<std::string> products;
+    TableSplitterOptions tableSplitterOptions;
 };
 
 struct LinkContext : public IAaptContext {
@@ -80,6 +80,7 @@
     std::u16string mCompilationPackage;
     uint8_t mPackageId;
     std::unique_ptr<ISymbolTable> mSymbols;
+    bool mVerbose = false;
 
     IDiagnostics* getDiagnostics() override {
         return &mDiagnostics;
@@ -100,8 +101,376 @@
     ISymbolTable* getExternalSymbols() override {
         return mSymbols.get();
     }
+
+    bool verbose() override {
+        return mVerbose;
+    }
 };
 
+static bool copyFileToArchive(io::IFile* file, const std::string& outPath,
+                              uint32_t compressionFlags,
+                              IArchiveWriter* writer, IAaptContext* context) {
+    std::unique_ptr<io::IData> data = file->openAsData();
+    if (!data) {
+        context->getDiagnostics()->error(DiagMessage(file->getSource())
+                                         << "failed to open file");
+        return false;
+    }
+
+    CompiledFileInputStream inputStream(data->data(), data->size());
+    if (!inputStream.CompiledFile()) {
+        context->getDiagnostics()->error(DiagMessage(file->getSource())
+                                         << "invalid compiled file header");
+        return false;
+    }
+
+    if (context->verbose()) {
+        context->getDiagnostics()->note(DiagMessage() << "writing " << outPath << " to archive");
+    }
+
+    if (writer->startEntry(outPath, compressionFlags)) {
+        if (writer->writeEntry(reinterpret_cast<const uint8_t*>(inputStream.data()),
+                               inputStream.size())) {
+            if (writer->finishEntry()) {
+                return true;
+            }
+        }
+    }
+
+    context->getDiagnostics()->error(DiagMessage() << "failed to write file " << outPath);
+    return false;
+}
+
+static bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
+                       bool keepRawValues, IArchiveWriter* writer, IAaptContext* context) {
+    BigBuffer buffer(1024);
+    XmlFlattenerOptions options = {};
+    options.keepRawValues = keepRawValues;
+    options.maxSdkLevel = maxSdkLevel;
+    XmlFlattener flattener(&buffer, options);
+    if (!flattener.consume(context, xmlRes)) {
+        return false;
+    }
+
+    if (context->verbose()) {
+        DiagMessage msg;
+        msg << "writing " << path << " to archive";
+        if (maxSdkLevel) {
+            msg << " maxSdkLevel=" << maxSdkLevel.value();
+        }
+        context->getDiagnostics()->note(msg);
+    }
+
+    if (writer->startEntry(path, ArchiveEntry::kCompress)) {
+        if (writer->writeEntry(buffer)) {
+            if (writer->finishEntry()) {
+                return true;
+            }
+        }
+    }
+    context->getDiagnostics()->error(DiagMessage() << "failed to write " << path << " to archive");
+    return false;
+}
+
+/*static std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len,
+                                                IDiagnostics* diag) {
+    std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
+    BinaryResourceParser parser(diag, table.get(), source, data, len);
+    if (!parser.parse()) {
+        return {};
+    }
+    return table;
+}*/
+
+static std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source,
+                                                      const void* data, size_t len,
+                                                      IDiagnostics* diag) {
+    pb::ResourceTable pbTable;
+    if (!pbTable.ParseFromArray(data, len)) {
+        diag->error(DiagMessage(source) << "invalid compiled table");
+        return {};
+    }
+
+    std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source, diag);
+    if (!table) {
+        return {};
+    }
+    return table;
+}
+
+/**
+ * Inflates an XML file from the source path.
+ */
+static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
+    std::ifstream fin(path, std::ifstream::binary);
+    if (!fin) {
+        diag->error(DiagMessage(path) << strerror(errno));
+        return {};
+    }
+    return xml::inflate(&fin, diag, Source(path));
+}
+
+static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(const Source& source,
+                                                                     const void* data, size_t len,
+                                                                     IDiagnostics* diag) {
+    CompiledFileInputStream inputStream(data, len);
+    if (!inputStream.CompiledFile()) {
+        diag->error(DiagMessage(source) << "invalid compiled file header");
+        return {};
+    }
+
+    const uint8_t* xmlData = reinterpret_cast<const uint8_t*>(inputStream.data());
+    const size_t xmlDataLen = inputStream.size();
+
+    std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source);
+    if (!xmlRes) {
+        return {};
+    }
+    return xmlRes;
+}
+
+static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
+                                                          const void* data, size_t len,
+                                                          IDiagnostics* diag) {
+    CompiledFileInputStream inputStream(data, len);
+    const pb::CompiledFile* pbFile = inputStream.CompiledFile();
+    if (!pbFile) {
+        diag->error(DiagMessage(source) << "invalid compiled file header");
+        return {};
+    }
+
+    std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source, diag);
+    if (!resFile) {
+        return {};
+    }
+    return resFile;
+}
+
+struct ResourceFileFlattenerOptions {
+    bool noAutoVersion = false;
+    bool keepRawValues = false;
+    bool doNotCompressAnything = false;
+    std::vector<std::string> extensionsToNotCompress;
+};
+
+class ResourceFileFlattener {
+public:
+    ResourceFileFlattener(const ResourceFileFlattenerOptions& options,
+                          IAaptContext* context, proguard::KeepSet* keepSet) :
+            mOptions(options), mContext(context), mKeepSet(keepSet) {
+    }
+
+    bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter);
+
+private:
+    struct FileOperation {
+        io::IFile* fileToCopy;
+        std::unique_ptr<xml::XmlResource> xmlToFlatten;
+        std::string dstPath;
+    };
+
+    uint32_t getCompressionFlags(const StringPiece& str);
+
+    std::unique_ptr<xml::XmlResource> linkAndVersionXmlFile(const ResourceEntry* entry,
+                                                            const ResourceFile& fileDesc,
+                                                            io::IFile* file,
+                                                            ResourceTable* table);
+
+    ResourceFileFlattenerOptions mOptions;
+    IAaptContext* mContext;
+    proguard::KeepSet* mKeepSet;
+};
+
+uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) {
+    if (mOptions.doNotCompressAnything) {
+        return 0;
+    }
+
+    for (const std::string& extension : mOptions.extensionsToNotCompress) {
+        if (util::stringEndsWith<char>(str, extension)) {
+            return 0;
+        }
+    }
+    return ArchiveEntry::kCompress;
+}
+
+std::unique_ptr<xml::XmlResource> ResourceFileFlattener::linkAndVersionXmlFile(
+        const ResourceEntry* entry,
+        const ResourceFile& fileDesc,
+        io::IFile* file,
+        ResourceTable* table) {
+    const StringPiece srcPath = file->getSource().path;
+    if (mContext->verbose()) {
+        mContext->getDiagnostics()->note(DiagMessage() << "linking " << srcPath);
+    }
+
+    std::unique_ptr<io::IData> data = file->openAsData();
+    if (!data) {
+        mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file");
+        return {};
+    }
+
+    std::unique_ptr<xml::XmlResource> xmlRes;
+    if (util::stringEndsWith<char>(srcPath, ".flat")) {
+        xmlRes = loadBinaryXmlSkipFileExport(file->getSource(), data->data(), data->size(),
+                                             mContext->getDiagnostics());
+    } else {
+        xmlRes = xml::inflate(data->data(), data->size(), mContext->getDiagnostics(),
+                              file->getSource());
+    }
+
+    if (!xmlRes) {
+        return {};
+    }
+
+    // Copy the the file description header.
+    xmlRes->file = fileDesc;
+
+    XmlReferenceLinker xmlLinker;
+    if (!xmlLinker.consume(mContext, xmlRes.get())) {
+        return {};
+    }
+
+    if (!proguard::collectProguardRules(xmlRes->file.source, xmlRes.get(), mKeepSet)) {
+        return {};
+    }
+
+    if (!mOptions.noAutoVersion) {
+        // Find the first SDK level used that is higher than this defined config and
+        // not superseded by a lower or equal SDK level resource.
+        for (int sdkLevel : xmlLinker.getSdkLevels()) {
+            if (sdkLevel > xmlRes->file.config.sdkVersion) {
+                if (!shouldGenerateVersionedResource(entry, xmlRes->file.config, sdkLevel)) {
+                    // If we shouldn't generate a versioned resource, stop checking.
+                    break;
+                }
+
+                ResourceFile versionedFileDesc = xmlRes->file;
+                versionedFileDesc.config.sdkVersion = sdkLevel;
+
+                if (mContext->verbose()) {
+                    mContext->getDiagnostics()->note(DiagMessage(versionedFileDesc.source)
+                                                     << "auto-versioning resource from config '"
+                                                     << xmlRes->file.config << "' -> '"
+                                                     << versionedFileDesc.config << "'");
+                }
+
+                std::u16string genPath = util::utf8ToUtf16(ResourceUtils::buildResourceFileName(
+                        versionedFileDesc, mContext->getNameMangler()));
+
+                bool added = table->addFileReferenceAllowMangled(versionedFileDesc.name,
+                                                                 versionedFileDesc.config,
+                                                                 versionedFileDesc.source,
+                                                                 genPath,
+                                                                 file,
+                                                                 mContext->getDiagnostics());
+                if (!added) {
+                    return {};
+                }
+                break;
+            }
+        }
+    }
+    return xmlRes;
+}
+
+/**
+ * Do not insert or remove any resources while executing in this function. It will
+ * corrupt the iteration order.
+ */
+bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiveWriter) {
+    bool error = false;
+    std::map<std::pair<ConfigDescription, StringPiece16>, FileOperation> configSortedFiles;
+
+    for (auto& pkg : table->packages) {
+        for (auto& type : pkg->types) {
+            // Sort by config and name, so that we get better locality in the zip file.
+            configSortedFiles.clear();
+            for (auto& entry : type->entries) {
+                // Iterate via indices because auto generated values can be inserted ahead of
+                // the value being processed.
+                for (size_t i = 0; i < entry->values.size(); i++) {
+                    ResourceConfigValue* configValue = entry->values[i].get();
+
+                    FileReference* fileRef = valueCast<FileReference>(configValue->value.get());
+                    if (!fileRef) {
+                        continue;
+                    }
+
+                    io::IFile* file = fileRef->file;
+                    if (!file) {
+                        mContext->getDiagnostics()->error(DiagMessage(fileRef->getSource())
+                                                          << "file not found");
+                        return false;
+                    }
+
+                    FileOperation fileOp;
+                    fileOp.dstPath = util::utf16ToUtf8(*fileRef->path);
+
+                    const StringPiece srcPath = file->getSource().path;
+                    if (type->type != ResourceType::kRaw &&
+                            (util::stringEndsWith<char>(srcPath, ".xml.flat") ||
+                            util::stringEndsWith<char>(srcPath, ".xml"))) {
+                        ResourceFile fileDesc;
+                        fileDesc.config = configValue->config;
+                        fileDesc.name = ResourceName(pkg->name, type->type, entry->name);
+                        fileDesc.source = fileRef->getSource();
+                        fileOp.xmlToFlatten = linkAndVersionXmlFile(entry.get(), fileDesc,
+                                                                    file, table);
+                        if (!fileOp.xmlToFlatten) {
+                            error = true;
+                            continue;
+                        }
+
+                    } else {
+                        fileOp.fileToCopy = file;
+                    }
+
+                    // NOTE(adamlesinski): Explicitly construct a StringPiece16 here, or else
+                    // we end up copying the string in the std::make_pair() method, then creating
+                    // a StringPiece16 from the copy, which would cause us to end up referencing
+                    // garbage in the map.
+                    const StringPiece16 entryName(entry->name);
+                    configSortedFiles[std::make_pair(configValue->config, entryName)] =
+                                      std::move(fileOp);
+                }
+            }
+
+            if (error) {
+                return false;
+            }
+
+            // Now flatten the sorted values.
+            for (auto& mapEntry : configSortedFiles) {
+                const ConfigDescription& config = mapEntry.first.first;
+                const FileOperation& fileOp = mapEntry.second;
+
+                if (fileOp.xmlToFlatten) {
+                    Maybe<size_t> maxSdkLevel;
+                    if (!mOptions.noAutoVersion) {
+                        maxSdkLevel = std::max<size_t>(config.sdkVersion, 1u);
+                    }
+
+                    bool result = flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel,
+                                             mOptions.keepRawValues,
+                                             archiveWriter, mContext);
+                    if (!result) {
+                        error = true;
+                    }
+                } else {
+                    bool result = copyFileToArchive(fileOp.fileToCopy, fileOp.dstPath,
+                                                    getCompressionFlags(fileOp.dstPath),
+                                                    archiveWriter, mContext);
+                    if (!result) {
+                        error = true;
+                    }
+                }
+            }
+        }
+    }
+    return !error;
+}
+
 class LinkCommand {
 public:
     LinkCommand(LinkContext* context, const LinkOptions& options) :
@@ -123,7 +492,7 @@
     std::unique_ptr<ISymbolTable> createSymbolTableFromIncludePaths() {
         AssetManagerSymbolTableBuilder builder;
         for (const std::string& path : mOptions.includePaths) {
-            if (mOptions.verbose) {
+            if (mContext->verbose()) {
                 mContext->getDiagnostics()->note(DiagMessage(path) << "loading include path");
             }
 
@@ -140,125 +509,6 @@
         return builder.build();
     }
 
-    std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len) {
-        std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
-        BinaryResourceParser parser(mContext, table.get(), source, data, len);
-        if (!parser.parse()) {
-            return {};
-        }
-        return table;
-    }
-
-    std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source,
-                                                   const void* data, size_t len) {
-        pb::ResourceTable pbTable;
-        if (!pbTable.ParseFromArray(data, len)) {
-            mContext->getDiagnostics()->error(DiagMessage(source) << "invalid compiled table");
-            return {};
-        }
-
-        std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source,
-                                                                      mContext->getDiagnostics());
-        if (!table) {
-            return {};
-        }
-        return table;
-    }
-
-    /**
-     * Inflates an XML file from the source path.
-     */
-    static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
-        std::ifstream fin(path, std::ifstream::binary);
-        if (!fin) {
-            diag->error(DiagMessage(path) << strerror(errno));
-            return {};
-        }
-
-        return xml::inflate(&fin, diag, Source(path));
-    }
-
-    static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(
-            const Source& source,
-            const void* data, size_t len,
-            IDiagnostics* diag) {
-        CompiledFileInputStream inputStream(data, len);
-        if (!inputStream.CompiledFile()) {
-            diag->error(DiagMessage(source) << "invalid compiled file header");
-            return {};
-        }
-
-        const uint8_t* xmlData = reinterpret_cast<const uint8_t*>(inputStream.data());
-        const size_t xmlDataLen = inputStream.size();
-
-        std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source);
-        if (!xmlRes) {
-            return {};
-        }
-        return xmlRes;
-    }
-
-    static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
-                                                              const void* data, size_t len,
-                                                              IDiagnostics* diag) {
-        CompiledFileInputStream inputStream(data, len);
-        const pb::CompiledFile* pbFile = inputStream.CompiledFile();
-        if (!pbFile) {
-            diag->error(DiagMessage(source) << "invalid compiled file header");
-            return {};
-        }
-
-        std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source,
-                                                                              diag);
-        if (!resFile) {
-            return {};
-        }
-        return resFile;
-    }
-
-    uint32_t getCompressionFlags(const StringPiece& str) {
-        if (mOptions.doNotCompressAnything) {
-            return 0;
-        }
-
-        for (const std::string& extension : mOptions.extensionsToNotCompress) {
-            if (util::stringEndsWith<char>(str, extension)) {
-                return 0;
-            }
-        }
-        return ArchiveEntry::kCompress;
-    }
-
-    bool copyFileToArchive(io::IFile* file, const std::string& outPath,
-                           IArchiveWriter* writer) {
-        std::unique_ptr<io::IData> data = file->openAsData();
-        if (!data) {
-            mContext->getDiagnostics()->error(DiagMessage(file->getSource())
-                                             << "failed to open file");
-            return false;
-        }
-
-        CompiledFileInputStream inputStream(data->data(), data->size());
-        if (!inputStream.CompiledFile()) {
-            mContext->getDiagnostics()->error(DiagMessage(file->getSource())
-                                              << "invalid compiled file header");
-            return false;
-        }
-
-        if (writer->startEntry(outPath, getCompressionFlags(outPath))) {
-            if (writer->writeEntry(reinterpret_cast<const uint8_t*>(inputStream.data()),
-                                   inputStream.size())) {
-                if (writer->finishEntry()) {
-                    return true;
-                }
-            }
-        }
-
-        mContext->getDiagnostics()->error(
-                DiagMessage(mOptions.outputPath) << "failed to write file " << outPath);
-        return false;
-    }
-
     Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) {
         // Make sure the first element is <manifest> with package attribute.
         if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
@@ -349,28 +599,6 @@
         return false;
     }
 
-    bool flattenXml(xml::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;
-        }
-
-        if (writer->startEntry(path, ArchiveEntry::kCompress)) {
-            if (writer->writeEntry(buffer)) {
-                if (writer->finishEntry()) {
-                    return true;
-                }
-            }
-        }
-        mContext->getDiagnostics()->error(
-                DiagMessage() << "failed to write " << path << " to archive");
-        return false;
-    }
 
     bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate,
                        const StringPiece16& outPackage, JavaClassGeneratorOptions javaOptions) {
@@ -456,7 +684,7 @@
     }
 
     bool mergeResourceTable(io::IFile* file, bool override) {
-        if (mOptions.verbose) {
+        if (mContext->verbose()) {
             mContext->getDiagnostics()->note(DiagMessage() << "linking " << file->getSource());
         }
 
@@ -467,8 +695,9 @@
             return false;
         }
 
-        std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(), data->data(),
-                                                               data->size());
+        std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(),
+                                                               data->data(), data->size(),
+                                                               mContext->getDiagnostics());
         if (!table) {
             return false;
         }
@@ -483,7 +712,7 @@
     }
 
     bool mergeCompiledFile(io::IFile* file, std::unique_ptr<ResourceFile> fileDesc, bool overlay) {
-        if (mOptions.verbose) {
+        if (mContext->verbose()) {
             mContext->getDiagnostics()->note(DiagMessage() << "adding " << file->getSource());
         }
 
@@ -628,10 +857,9 @@
 
         TableMergerOptions tableMergerOptions;
         tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay;
-        tableMergerOptions.filter = mOptions.configFilter;
         mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions);
 
-        if (mOptions.verbose) {
+        if (mContext->verbose()) {
             mContext->getDiagnostics()->note(
                     DiagMessage() << "linking package '" << mContext->mCompilationPackage << "' "
                                   << "with package ID " << std::hex << (int) mContext->mPackageId);
@@ -692,6 +920,15 @@
                 mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products");
                 return 1;
             }
+
+            // TODO(adamlesinski): Actually pass in split constraints and handle splits at the file
+            // level.
+            TableSplitter tableSplitter({}, mOptions.tableSplitterOptions);
+            if (!tableSplitter.verifySplitConstraints(mContext)) {
+                return 1;
+            }
+
+            tableSplitter.splitTable(&mFinalTable);
         }
 
         proguard::KeepSet proguardKeepSet;
@@ -728,8 +965,10 @@
                     }
                 }
 
-                if (!flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
-                                archiveWriter.get())) {
+                const bool keepRawValues = mOptions.staticLib;
+                bool result = flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
+                                         keepRawValues, archiveWriter.get(), mContext);
+                if (!result) {
                     error = true;
                 }
             } else {
@@ -742,113 +981,14 @@
             return 1;
         }
 
-        for (auto& mergeEntry : mTableMerger->getFilesToMerge()) {
-            const ResourceKeyRef& key = mergeEntry.first;
-            const FileToMerge& fileToMerge = mergeEntry.second;
+        ResourceFileFlattenerOptions fileFlattenerOptions;
+        fileFlattenerOptions.keepRawValues = mOptions.staticLib;
+        fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything;
+        fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress;
+        fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion;
+        ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, &proguardKeepSet);
 
-            const StringPiece path = fileToMerge.file->getSource().path;
-
-            if (key.name.type != ResourceType::kRaw &&
-                    (util::stringEndsWith<char>(path, ".xml.flat") ||
-                    util::stringEndsWith<char>(path, ".xml"))) {
-                if (mOptions.verbose) {
-                    mContext->getDiagnostics()->note(DiagMessage() << "linking " << path);
-                }
-
-                io::IFile* file = fileToMerge.file;
-                std::unique_ptr<io::IData> data = file->openAsData();
-                if (!data) {
-                    mContext->getDiagnostics()->error(DiagMessage(file->getSource())
-                                                     << "failed to open file");
-                    return 1;
-                }
-
-                std::unique_ptr<xml::XmlResource> xmlRes;
-                if (util::stringEndsWith<char>(path, ".flat")) {
-                    xmlRes = loadBinaryXmlSkipFileExport(file->getSource(),
-                                                         data->data(), data->size(),
-                                                         mContext->getDiagnostics());
-                } else {
-                    xmlRes = xml::inflate(data->data(), data->size(), mContext->getDiagnostics(),
-                                          file->getSource());
-                }
-
-                if (!xmlRes) {
-                    return 1;
-                }
-
-                // Create the file description header.
-                xmlRes->file = ResourceFile{
-                        key.name.toResourceName(),
-                        key.config,
-                        fileToMerge.originalSource,
-                };
-
-                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(), fileToMerge.dstPath, maxSdkLevel,
-                                    archiveWriter.get())) {
-                        error = true;
-                    }
-
-                    if (!mOptions.noAutoVersion) {
-                        Maybe<ResourceTable::SearchResult> result = mFinalTable.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;
-
-                                std::string genResourcePath = ResourceUtils::buildResourceFileName(
-                                        xmlRes->file, mContext->getNameMangler());
-
-                                bool added = mFinalTable.addFileReference(
-                                        xmlRes->file.name,
-                                        xmlRes->file.config,
-                                        xmlRes->file.source,
-                                        util::utf8ToUtf16(genResourcePath),
-                                        mContext->getDiagnostics());
-                                if (!added) {
-                                    error = true;
-                                    continue;
-                                }
-
-                                if (!flattenXml(xmlRes.get(), genResourcePath, sdkLevel,
-                                                archiveWriter.get())) {
-                                    error = true;
-                                }
-                            }
-                        }
-                    }
-
-                } else {
-                    error = true;
-                }
-            } else {
-                if (mOptions.verbose) {
-                    mContext->getDiagnostics()->note(DiagMessage() << "copying " << path);
-                }
-
-                if (!copyFileToArchive(fileToMerge.file, fileToMerge.dstPath,
-                                       archiveWriter.get())) {
-                    error = true;
-                }
-            }
-        }
-
-        if (error) {
+        if (!fileFlattener.flatten(&mFinalTable, archiveWriter.get())) {
             mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources");
             return 1;
         }
@@ -912,8 +1052,10 @@
             }
         }
 
-        if (mOptions.verbose) {
-            Debug::printTable(&mFinalTable);
+        if (mContext->verbose()) {
+            DebugPrintTableOptions debugPrintTableOptions;
+            debugPrintTableOptions.showSources = true;
+            Debug::printTable(&mFinalTable, debugPrintTableOptions);
         }
         return 0;
     }
@@ -934,6 +1076,7 @@
 };
 
 int link(const std::vector<StringPiece>& args) {
+    LinkContext context;
     LinkOptions options;
     Maybe<std::string> privateSymbolsPackage;
     Maybe<std::string> minSdkVersion, targetSdkVersion;
@@ -942,6 +1085,7 @@
     Maybe<std::string> customJavaPackage;
     std::vector<std::string> extraJavaPackages;
     Maybe<std::string> configs;
+    Maybe<std::string> preferredDensity;
     Maybe<std::string> productList;
     bool legacyXFlag = false;
     bool requireLocalization = false;
@@ -966,6 +1110,9 @@
                             &requireLocalization)
             .optionalFlag("-c", "Comma separated list of configurations to include. The default\n"
                                 "is all configurations", &configs)
+            .optionalFlag("--preferred-density",
+                          "Selects the closest matching density and strips out all others.",
+                          &preferredDensity)
             .optionalFlag("--product", "Comma separated list of product names to keep",
                           &productList)
             .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
@@ -1001,14 +1148,12 @@
                           &renameInstrumentationTargetPackage)
             .optionalFlagList("-0", "File extensions not to compress",
                               &options.extensionsToNotCompress)
-            .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
+            .optionalSwitch("-v", "Enables verbose logging", &context.mVerbose);
 
     if (!flags.parse("aapt2 link", args, &std::cerr)) {
         return 1;
     }
 
-    LinkContext context;
-
     if (privateSymbolsPackage) {
         options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value());
     }
@@ -1082,7 +1227,29 @@
             }
         }
 
-        options.configFilter = &filter;
+        options.tableSplitterOptions.configFilter = &filter;
+    }
+
+    if (preferredDensity) {
+        ConfigDescription preferredDensityConfig;
+        if (!ConfigDescription::parse(preferredDensity.value(), &preferredDensityConfig)) {
+            context.getDiagnostics()->error(DiagMessage() << "invalid density '"
+                                            << preferredDensity.value()
+                                            << "' for --preferred-density option");
+            return 1;
+        }
+
+        // Clear the version that can be automatically added.
+        preferredDensityConfig.sdkVersion = 0;
+
+        if (preferredDensityConfig.diff(ConfigDescription::defaultConfig())
+                != ConfigDescription::CONFIG_DENSITY) {
+            context.getDiagnostics()->error(DiagMessage() << "invalid preferred density '"
+                                            << preferredDensity.value() << "'. "
+                                            << "Preferred density must only be a density value");
+            return 1;
+        }
+        options.tableSplitterOptions.preferredDensity = preferredDensityConfig.density;
     }
 
     LinkCommand cmd(&context, options);