AAPT2: Support -c configuration filtering

Change-Id: I1e5855ca73440bdc30c21bcbc1dfdd31a9842363
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index a4f4ba9..f74b93a 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -30,6 +30,7 @@
 	compile/PseudolocaleGenerator.cpp \
 	compile/Pseudolocalizer.cpp \
 	compile/XmlIdCollector.cpp \
+	filter/ConfigFilter.cpp \
 	flatten/Archive.cpp \
 	flatten/TableFlattener.cpp \
 	flatten/XmlFlattener.cpp \
@@ -71,6 +72,7 @@
 	compile/PseudolocaleGenerator_test.cpp \
 	compile/Pseudolocalizer_test.cpp \
 	compile/XmlIdCollector_test.cpp \
+	filter/ConfigFilter_test.cpp \
 	flatten/FileExportWriter_test.cpp \
 	flatten/TableFlattener_test.cpp \
 	flatten/XmlFlattener_test.cpp \
diff --git a/tools/aapt2/Locale.cpp b/tools/aapt2/Locale.cpp
index 0369156..6acf3b0 100644
--- a/tools/aapt2/Locale.cpp
+++ b/tools/aapt2/Locale.cpp
@@ -70,7 +70,7 @@
     return std::all_of(std::begin(str), std::end(str), ::isdigit);
 }
 
-bool LocaleValue::initFromFilterString(const std::string& str) {
+bool LocaleValue::initFromFilterString(const StringPiece& str) {
      // A locale (as specified in the filter) is an underscore separated name such
      // as "en_US", "en_Latn_US", or "en_US_POSIX".
      std::vector<std::string> parts = util::splitAndLowercase(str, '_');
diff --git a/tools/aapt2/Locale.h b/tools/aapt2/Locale.h
index ceec764..b1c80ab 100644
--- a/tools/aapt2/Locale.h
+++ b/tools/aapt2/Locale.h
@@ -17,6 +17,8 @@
 #ifndef AAPT_LOCALE_VALUE_H
 #define AAPT_LOCALE_VALUE_H
 
+#include "util/StringPiece.h"
+
 #include <androidfw/ResourceTypes.h>
 #include <string>
 #include <vector>
@@ -37,7 +39,7 @@
     /**
      * Initialize this LocaleValue from a config string.
      */
-    bool initFromFilterString(const std::string& config);
+    bool initFromFilterString(const StringPiece& config);
 
     /**
      * Initialize this LocaleValue from parts of a vector.
diff --git a/tools/aapt2/filter/ConfigFilter.cpp b/tools/aapt2/filter/ConfigFilter.cpp
new file mode 100644
index 0000000..68a017d
--- /dev/null
+++ b/tools/aapt2/filter/ConfigFilter.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2016 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 "filter/ConfigFilter.h"
+
+#include <androidfw/ResourceTypes.h>
+
+namespace aapt {
+
+void AxisConfigFilter::addConfig(ConfigDescription config) {
+    uint32_t diffMask = ConfigDescription::defaultConfig().diff(config);
+
+    // Ignore the version
+    diffMask &= ~android::ResTable_config::CONFIG_VERSION;
+
+    // Ignore any densities. Those are best handled in --preferred-density
+    if ((diffMask & android::ResTable_config::CONFIG_DENSITY) != 0) {
+        config.density = 0;
+        diffMask &= ~android::ResTable_config::CONFIG_DENSITY;
+    }
+
+    mConfigs.insert(std::make_pair(config, diffMask));
+    mConfigMask |= diffMask;
+}
+
+bool AxisConfigFilter::match(const ConfigDescription& config) const {
+    const uint32_t mask = ConfigDescription::defaultConfig().diff(config);
+    if ((mConfigMask & mask) == 0) {
+        // The two configurations don't have any common axis.
+        return true;
+    }
+
+    uint32_t matchedAxis = 0;
+    for (const auto& entry : mConfigs) {
+        const ConfigDescription& target = entry.first;
+        const uint32_t diffMask = entry.second;
+        uint32_t diff = target.diff(config);
+        if ((diff & diffMask) == 0) {
+            // Mark the axis that was matched.
+            matchedAxis |= diffMask;
+        } else if ((diff & diffMask) == android::ResTable_config::CONFIG_LOCALE) {
+            // If the locales differ, but the languages are the same and
+            // the locale we are matching only has a language specified,
+            // we match.
+            if (config.language[0] &&
+                    memcmp(config.language, target.language, sizeof(config.language)) == 0) {
+                if (config.country[0] == 0) {
+                    matchedAxis |= android::ResTable_config::CONFIG_LOCALE;
+                }
+            }
+        } else if ((diff & diffMask) == android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE) {
+            // Special case if the smallest screen width doesn't match. We check that the
+            // config being matched has a smaller screen width than the filter specified.
+            if (config.smallestScreenWidthDp != 0 &&
+                    config.smallestScreenWidthDp < target.smallestScreenWidthDp) {
+                matchedAxis |= android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE;
+            }
+        }
+    }
+    return matchedAxis == (mConfigMask & mask);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/filter/ConfigFilter.h b/tools/aapt2/filter/ConfigFilter.h
new file mode 100644
index 0000000..36e9c44
--- /dev/null
+++ b/tools/aapt2/filter/ConfigFilter.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 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_FILTER_CONFIGFILTER_H
+#define AAPT_FILTER_CONFIGFILTER_H
+
+#include "ConfigDescription.h"
+
+#include <set>
+#include <utility>
+
+namespace aapt {
+
+/**
+ * Matches ConfigDescriptions based on some pattern.
+ */
+class IConfigFilter {
+public:
+    virtual ~IConfigFilter() = default;
+
+    /**
+     * Returns true if the filter matches the configuration, false otherwise.
+     */
+    virtual bool match(const ConfigDescription& config) const = 0;
+};
+
+/**
+ * Implements config axis matching. An axis is one component of a configuration, like screen
+ * density or locale. If an axis is specified in the filter, and the axis is specified in
+ * the configuration to match, they must be compatible. Otherwise the configuration to match is
+ * accepted.
+ *
+ * Used when handling "-c" options.
+ */
+class AxisConfigFilter : public IConfigFilter {
+public:
+    void addConfig(ConfigDescription config);
+
+    bool match(const ConfigDescription& config) const override;
+
+private:
+    std::set<std::pair<ConfigDescription, uint32_t>> mConfigs;
+    uint32_t mConfigMask = 0;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_FILTER_CONFIGFILTER_H */
diff --git a/tools/aapt2/filter/ConfigFilter_test.cpp b/tools/aapt2/filter/ConfigFilter_test.cpp
new file mode 100644
index 0000000..f6b4955
--- /dev/null
+++ b/tools/aapt2/filter/ConfigFilter_test.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2016 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 "filter/ConfigFilter.h"
+#include "test/Common.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(ConfigFilterTest, EmptyFilterMatchesAnything) {
+    AxisConfigFilter filter;
+
+    EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi")));
+    EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr")));
+}
+
+TEST(ConfigFilterTest, MatchesConfigWithUnrelatedAxis) {
+    AxisConfigFilter filter;
+    filter.addConfig(test::parseConfigOrDie("fr"));
+
+    EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi")));
+}
+
+TEST(ConfigFilterTest, MatchesConfigWithSameValueAxis) {
+    AxisConfigFilter filter;
+    filter.addConfig(test::parseConfigOrDie("fr"));
+
+    EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr")));
+}
+
+TEST(ConfigFilterTest, MatchesConfigWithSameValueAxisAndOtherUnrelatedAxis) {
+    AxisConfigFilter filter;
+    filter.addConfig(test::parseConfigOrDie("fr"));
+
+    EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-320dpi")));
+}
+
+TEST(ConfigFilterTest, MatchesConfigWithOneMatchingAxis) {
+    AxisConfigFilter filter;
+    filter.addConfig(test::parseConfigOrDie("fr-rFR"));
+    filter.addConfig(test::parseConfigOrDie("sw360dp"));
+    filter.addConfig(test::parseConfigOrDie("normal"));
+    filter.addConfig(test::parseConfigOrDie("en-rUS"));
+
+    EXPECT_TRUE(filter.match(test::parseConfigOrDie("en")));
+}
+
+TEST(ConfigFilterTest, DoesNotMatchConfigWithDifferentValueAxis) {
+    AxisConfigFilter filter;
+    filter.addConfig(test::parseConfigOrDie("fr"));
+
+    EXPECT_FALSE(filter.match(test::parseConfigOrDie("de")));
+}
+
+TEST(ConfigFilterTest, DoesNotMatchWhenOneQualifierIsExplicitlyNotMatched) {
+    AxisConfigFilter filter;
+    filter.addConfig(test::parseConfigOrDie("fr-rFR"));
+    filter.addConfig(test::parseConfigOrDie("en-rUS"));
+    filter.addConfig(test::parseConfigOrDie("normal"));
+    filter.addConfig(test::parseConfigOrDie("large"));
+    filter.addConfig(test::parseConfigOrDie("xxhdpi"));
+    filter.addConfig(test::parseConfigOrDie("sw320dp"));
+
+    EXPECT_FALSE(filter.match(test::parseConfigOrDie("fr-sw600dp-v13")));
+}
+
+TEST(ConfigFilterTest, MatchesSmallestWidthWhenSmaller) {
+    AxisConfigFilter filter;
+    filter.addConfig(test::parseConfigOrDie("sw600dp"));
+
+    EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-sw320dp-v13")));
+}
+
+TEST(ConfigFilterTest, MatchesConfigWithSameLanguageButNoRegionSpecified) {
+    AxisConfigFilter filter;
+    filter.addConfig(test::parseConfigOrDie("de-rDE"));
+
+    EXPECT_TRUE(filter.match(test::parseConfigOrDie("de")));
+}
+
+TEST(ConfigFilterTest, IgnoresVersion) {
+    AxisConfigFilter filter;
+    filter.addConfig(test::parseConfigOrDie("normal-v4"));
+
+    // The configs don't match on any axis besides version, which should be ignored.
+    EXPECT_TRUE(filter.match(test::parseConfigOrDie("sw600dp-v13")));
+}
+
+TEST(ConfigFilterTest, MatchesConfigWithRegion) {
+    AxisConfigFilter filter;
+    filter.addConfig(test::parseConfigOrDie("kok"));
+    filter.addConfig(test::parseConfigOrDie("kok-rIN"));
+    filter.addConfig(test::parseConfigOrDie("kok-v419"));
+
+    EXPECT_TRUE(filter.match(test::parseConfigOrDie("kok-rIN")));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 3ecb2c4..fd76e88 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -17,8 +17,10 @@
 #include "AppInfo.h"
 #include "Debug.h"
 #include "Flags.h"
+#include "Locale.h"
 #include "NameMangler.h"
 #include "compile/IdAssigner.h"
+#include "filter/ConfigFilter.h"
 #include "flatten/Archive.h"
 #include "flatten/TableFlattener.h"
 #include "flatten/XmlFlattener.h"
@@ -64,7 +66,7 @@
     std::vector<std::string> extensionsToNotCompress;
     Maybe<std::u16string> privateSymbols;
     ManifestFixerOptions manifestFixerOptions;
-
+    IConfigFilter* configFilter = nullptr;
 };
 
 struct LinkContext : public IAaptContext {
@@ -97,8 +99,8 @@
 
 class LinkCommand {
 public:
-    LinkCommand(const LinkOptions& options) :
-            mOptions(options), mContext(), mFinalTable(), mFileCollection(nullptr) {
+    LinkCommand(LinkContext* context, const LinkOptions& options) :
+            mOptions(options), mContext(context), mFinalTable(), mFileCollection(nullptr) {
         std::unique_ptr<io::FileCollection> fileCollection =
                 util::make_unique<io::FileCollection>();
 
@@ -117,14 +119,14 @@
         AssetManagerSymbolTableBuilder builder;
         for (const std::string& path : mOptions.includePaths) {
             if (mOptions.verbose) {
-                mContext.getDiagnostics()->note(DiagMessage(path) << "loading include path");
+                mContext->getDiagnostics()->note(DiagMessage(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(
+                mContext->getDiagnostics()->error(
                         DiagMessage(path) << "failed to load include path");
                 return {};
             }
@@ -135,7 +137,7 @@
 
     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);
+        BinaryResourceParser parser(mContext, table.get(), source, data, len);
         if (!parser.parse()) {
             return {};
         }
@@ -207,7 +209,7 @@
                            IArchiveWriter* writer) {
         std::unique_ptr<io::IData> data = file->openAsData();
         if (!data) {
-            mContext.getDiagnostics()->error(DiagMessage(file->getSource())
+            mContext->getDiagnostics()->error(DiagMessage(file->getSource())
                                              << "failed to open file");
             return false;
         }
@@ -215,7 +217,7 @@
         std::string errorStr;
         ssize_t offset = getWrappedDataOffset(data->data(), data->size(), &errorStr);
         if (offset < 0) {
-            mContext.getDiagnostics()->error(DiagMessage(file->getSource()) << errorStr);
+            mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << errorStr);
             return false;
         }
 
@@ -228,7 +230,7 @@
             }
         }
 
-        mContext.getDiagnostics()->error(
+        mContext->getDiagnostics()->error(
                 DiagMessage(mOptions.outputPath) << "failed to write file " << outPath);
         return false;
     }
@@ -252,9 +254,9 @@
      */
     bool verifyNoExternalPackages() {
         auto isExtPackageFunc = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool {
-            return mContext.getCompilationPackage() != pkg->name ||
+            return mContext->getCompilationPackage() != pkg->name ||
                     !pkg->id ||
-                    pkg->id.value() != mContext.getPackageId();
+                    pkg->id.value() != mContext->getPackageId();
         };
 
         bool error = false;
@@ -270,13 +272,13 @@
                             // 'android' package. This is due to legacy reasons.
                             if (valueCast<Id>(configValue.value.get()) &&
                                     package->name == u"android") {
-                                mContext.getDiagnostics()->warn(
+                                mContext->getDiagnostics()->warn(
                                         DiagMessage(configValue.value->getSource())
                                         << "generated id '" << resName
                                         << "' for external package '" << package->name
                                         << "'");
                             } else {
-                                mContext.getDiagnostics()->error(
+                                mContext->getDiagnostics()->error(
                                         DiagMessage(configValue.value->getSource())
                                         << "defined resource '" << resName
                                         << "' for external package '" << package->name
@@ -297,9 +299,9 @@
 
     std::unique_ptr<IArchiveWriter> makeArchiveWriter() {
         if (mOptions.outputToDirectory) {
-            return createDirectoryArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath);
+            return createDirectoryArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath);
         } else {
-            return createZipFileArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath);
+            return createZipFileArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath);
         }
     }
 
@@ -308,7 +310,7 @@
         TableFlattenerOptions options = {};
         options.useExtendedChunks = mOptions.staticLib;
         TableFlattener flattener(&buffer, options);
-        if (!flattener.consume(&mContext, table)) {
+        if (!flattener.consume(mContext, table)) {
             return false;
         }
 
@@ -320,7 +322,7 @@
             }
         }
 
-        mContext.getDiagnostics()->error(
+        mContext->getDiagnostics()->error(
                 DiagMessage() << "failed to write resources.arsc to archive");
         return false;
     }
@@ -332,7 +334,7 @@
         options.keepRawValues = mOptions.staticLib;
         options.maxSdkLevel = maxSdkLevel;
         XmlFlattener flattener(&buffer, options);
-        if (!flattener.consume(&mContext, xmlRes)) {
+        if (!flattener.consume(mContext, xmlRes)) {
             return false;
         }
 
@@ -343,7 +345,7 @@
                 }
             }
         }
-        mContext.getDiagnostics()->error(
+        mContext->getDiagnostics()->error(
                 DiagMessage() << "failed to write " << path << " to archive");
         return false;
     }
@@ -361,13 +363,13 @@
 
         std::ofstream fout(outPath, std::ofstream::binary);
         if (!fout) {
-            mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
+            mContext->getDiagnostics()->error(DiagMessage() << strerror(errno));
             return false;
         }
 
         JavaClassGenerator generator(table, javaOptions);
         if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
-            mContext.getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
+            mContext->getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
             return false;
         }
         return true;
@@ -380,24 +382,24 @@
 
         std::string outPath = mOptions.generateJavaClassPath.value();
         file::appendPath(&outPath,
-                         file::packageToPath(util::utf16ToUtf8(mContext.getCompilationPackage())));
+                         file::packageToPath(util::utf16ToUtf8(mContext->getCompilationPackage())));
         file::mkdirs(outPath);
         file::appendPath(&outPath, "Manifest.java");
 
         std::ofstream fout(outPath, std::ofstream::binary);
         if (!fout) {
-            mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
+            mContext->getDiagnostics()->error(DiagMessage() << strerror(errno));
             return false;
         }
 
         ManifestClassGenerator generator;
-        if (!generator.generate(mContext.getDiagnostics(), mContext.getCompilationPackage(),
+        if (!generator.generate(mContext->getDiagnostics(), mContext->getCompilationPackage(),
                                 manifestXml, &fout)) {
             return false;
         }
 
         if (!fout) {
-            mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
+            mContext->getDiagnostics()->error(DiagMessage() << strerror(errno));
             return false;
         }
         return true;
@@ -410,13 +412,13 @@
 
         std::ofstream fout(mOptions.generateProguardRulesPath.value(), std::ofstream::binary);
         if (!fout) {
-            mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
+            mContext->getDiagnostics()->error(DiagMessage() << strerror(errno));
             return false;
         }
 
         proguard::writeKeepSet(&fout, keepSet);
         if (!fout) {
-            mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
+            mContext->getDiagnostics()->error(DiagMessage() << strerror(errno));
             return false;
         }
         return true;
@@ -425,7 +427,7 @@
     bool mergeStaticLibrary(const std::string& input) {
         // TODO(adamlesinski): Load resources from a static library APK and merge the table into
         // TableMerger.
-        mContext.getDiagnostics()->warn(DiagMessage()
+        mContext->getDiagnostics()->warn(DiagMessage()
                                         << "linking static libraries not supported yet: "
                                         << input);
         return true;
@@ -433,12 +435,12 @@
 
     bool mergeResourceTable(io::IFile* file, bool override) {
         if (mOptions.verbose) {
-            mContext.getDiagnostics()->note(DiagMessage() << "linking " << file->getSource());
+            mContext->getDiagnostics()->note(DiagMessage() << "linking " << file->getSource());
         }
 
         std::unique_ptr<io::IData> data = file->openAsData();
         if (!data) {
-            mContext.getDiagnostics()->error(DiagMessage(file->getSource())
+            mContext->getDiagnostics()->error(DiagMessage(file->getSource())
                                              << "failed to open file");
             return false;
         }
@@ -460,7 +462,7 @@
 
     bool mergeCompiledFile(io::IFile* file, std::unique_ptr<ResourceFile> fileDesc, bool overlay) {
         if (mOptions.verbose) {
-            mContext.getDiagnostics()->note(DiagMessage() << "adding " << file->getSource());
+            mContext->getDiagnostics()->note(DiagMessage() << "adding " << file->getSource());
         }
 
         bool result = false;
@@ -477,12 +479,12 @@
         // Add the exports of this file to the table.
         for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
             if (exportedSymbol.name.package.empty()) {
-                exportedSymbol.name.package = mContext.getCompilationPackage().toString();
+                exportedSymbol.name.package = mContext->getCompilationPackage().toString();
             }
 
             ResourceNameRef resName = exportedSymbol.name;
 
-            Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(
+            Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
                     exportedSymbol.name);
             if (mangledName) {
                 resName = mangledName.value();
@@ -491,7 +493,7 @@
             std::unique_ptr<Id> id = util::make_unique<Id>();
             id->setSource(fileDesc->source.withLine(exportedSymbol.line));
             bool result = mFinalTable.addResourceAllowMangled(resName, {}, std::move(id),
-                                                              mContext.getDiagnostics());
+                                                              mContext->getDiagnostics());
             if (!result) {
                 return false;
             }
@@ -507,7 +509,7 @@
         std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create(
                 input, &errorStr);
         if (!collection) {
-            mContext.getDiagnostics()->error(DiagMessage(input) << errorStr);
+            mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
             return false;
         }
 
@@ -543,12 +545,12 @@
             // Try opening the file and looking for an Export header.
             std::unique_ptr<io::IData> data = file->openAsData();
             if (!data) {
-                mContext.getDiagnostics()->error(DiagMessage(src) << "failed to open");
+                mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open");
                 return false;
             }
 
             std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader(
-                    src, data->data(), data->size(), mContext.getDiagnostics());
+                    src, data->data(), data->size(), mContext->getDiagnostics());
             if (resourceFile) {
                 return mergeCompiledFile(file, std::move(resourceFile), override);
             }
@@ -564,62 +566,63 @@
     int run(const std::vector<std::string>& inputFiles) {
         // Load the AndroidManifest.xml
         std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath,
-                                                                mContext.getDiagnostics());
+                                                                mContext->getDiagnostics());
         if (!manifestXml) {
             return 1;
         }
 
         if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get())) {
-            mContext.mCompilationPackage = maybeAppInfo.value().package;
+            mContext->mCompilationPackage = maybeAppInfo.value().package;
         } else {
-            mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
+            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)
+        if (!util::isJavaPackageName(mContext->mCompilationPackage)) {
+            mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
                                              << "invalid package name '"
-                                             << mContext.mCompilationPackage
+                                             << mContext->mCompilationPackage
                                              << "'");
             return 1;
         }
 
-        mContext.mNameMangler = util::make_unique<NameMangler>(
-                NameManglerPolicy{ mContext.mCompilationPackage });
+        mContext->mNameMangler = util::make_unique<NameMangler>(
+                NameManglerPolicy{ mContext->mCompilationPackage });
 
-        if (mContext.mCompilationPackage == u"android") {
-            mContext.mPackageId = 0x01;
+        if (mContext->mCompilationPackage == u"android") {
+            mContext->mPackageId = 0x01;
         } else {
-            mContext.mPackageId = 0x7f;
+            mContext->mPackageId = 0x7f;
         }
 
-        mContext.mSymbols = createSymbolTableFromIncludePaths();
-        if (!mContext.mSymbols) {
+        mContext->mSymbols = createSymbolTableFromIncludePaths();
+        if (!mContext->mSymbols) {
             return 1;
         }
 
         TableMergerOptions tableMergerOptions;
         tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay;
-        mTableMerger = util::make_unique<TableMerger>(&mContext, &mFinalTable, tableMergerOptions);
+        tableMergerOptions.filter = mOptions.configFilter;
+        mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions);
 
         if (mOptions.verbose) {
-            mContext.getDiagnostics()->note(
-                    DiagMessage() << "linking package '" << mContext.mCompilationPackage << "' "
-                                  << "with package ID " << std::hex << (int) mContext.mPackageId);
+            mContext->getDiagnostics()->note(
+                    DiagMessage() << "linking package '" << mContext->mCompilationPackage << "' "
+                                  << "with package ID " << std::hex << (int) mContext->mPackageId);
         }
 
 
         for (const std::string& input : inputFiles) {
             if (!processFile(input, false)) {
-                mContext.getDiagnostics()->error(DiagMessage() << "failed parsing input");
+                mContext->getDiagnostics()->error(DiagMessage() << "failed parsing input");
                 return 1;
             }
         }
 
         for (const std::string& input : mOptions.overlayFiles) {
             if (!processFile(input, true)) {
-                mContext.getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
+                mContext->getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
                 return 1;
             }
         }
@@ -630,8 +633,8 @@
 
         if (!mOptions.staticLib) {
             PrivateAttributeMover mover;
-            if (!mover.consume(&mContext, &mFinalTable)) {
-                mContext.getDiagnostics()->error(
+            if (!mover.consume(mContext, &mFinalTable)) {
+                mContext->getDiagnostics()->error(
                         DiagMessage() << "failed moving private attributes");
                 return 1;
             }
@@ -639,23 +642,23 @@
 
         {
             IdAssigner idAssigner;
-            if (!idAssigner.consume(&mContext, &mFinalTable)) {
-                mContext.getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
+            if (!idAssigner.consume(mContext, &mFinalTable)) {
+                mContext->getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
                 return 1;
             }
         }
 
-        mContext.mNameMangler = util::make_unique<NameMangler>(NameManglerPolicy{
-                mContext.mCompilationPackage, mTableMerger->getMergedPackages() });
-        mContext.mSymbols = JoinedSymbolTableBuilder()
+        mContext->mNameMangler = util::make_unique<NameMangler>(NameManglerPolicy{
+                mContext->mCompilationPackage, mTableMerger->getMergedPackages() });
+        mContext->mSymbols = JoinedSymbolTableBuilder()
                 .addSymbolTable(util::make_unique<SymbolTableWrapper>(&mFinalTable))
-                .addSymbolTable(std::move(mContext.mSymbols))
+                .addSymbolTable(std::move(mContext->mSymbols))
                 .build();
 
         {
             ReferenceLinker linker;
-            if (!linker.consume(&mContext, &mFinalTable)) {
-                mContext.getDiagnostics()->error(DiagMessage() << "failed linking references");
+            if (!linker.consume(mContext, &mFinalTable)) {
+                mContext->getDiagnostics()->error(DiagMessage() << "failed linking references");
                 return 1;
             }
         }
@@ -664,24 +667,24 @@
 
         std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter();
         if (!archiveWriter) {
-            mContext.getDiagnostics()->error(DiagMessage() << "failed to create archive");
+            mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive");
             return 1;
         }
 
         bool error = false;
         {
             ManifestFixer manifestFixer(mOptions.manifestFixerOptions);
-            if (!manifestFixer.consume(&mContext, manifestXml.get())) {
+            if (!manifestFixer.consume(mContext, manifestXml.get())) {
                 error = true;
             }
 
             // AndroidManifest.xml has no resource name, but the CallSite is built from the name
             // (aka, which package the AndroidManifest.xml is coming from).
             // So we give it a package name so it can see local resources.
-            manifestXml->file.name.package = mContext.getCompilationPackage().toString();
+            manifestXml->file.name.package = mContext->getCompilationPackage().toString();
 
             XmlReferenceLinker manifestLinker;
-            if (manifestLinker.consume(&mContext, manifestXml.get())) {
+            if (manifestLinker.consume(mContext, manifestXml.get())) {
                 if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
                                                                manifestXml.get(),
                                                                &proguardKeepSet)) {
@@ -704,7 +707,7 @@
         }
 
         if (error) {
-            mContext.getDiagnostics()->error(DiagMessage() << "failed processing manifest");
+            mContext->getDiagnostics()->error(DiagMessage() << "failed processing manifest");
             return 1;
         }
 
@@ -718,13 +721,13 @@
                     (util::stringEndsWith<char>(path, ".xml.flat") ||
                     util::stringEndsWith<char>(path, ".xml"))) {
                 if (mOptions.verbose) {
-                    mContext.getDiagnostics()->note(DiagMessage() << "linking " << path);
+                    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())
+                    mContext->getDiagnostics()->error(DiagMessage(file->getSource())
                                                      << "failed to open file");
                     return 1;
                 }
@@ -733,9 +736,9 @@
                 if (util::stringEndsWith<char>(path, ".flat")) {
                     xmlRes = loadBinaryXmlSkipFileExport(file->getSource(),
                                                          data->data(), data->size(),
-                                                         mContext.getDiagnostics());
+                                                         mContext->getDiagnostics());
                 } else {
-                    xmlRes = xml::inflate(data->data(), data->size(), mContext.getDiagnostics(),
+                    xmlRes = xml::inflate(data->data(), data->size(), mContext->getDiagnostics(),
                                           file->getSource());
                 }
 
@@ -751,7 +754,7 @@
                 };
 
                 XmlReferenceLinker xmlLinker;
-                if (xmlLinker.consume(&mContext, xmlRes.get())) {
+                if (xmlLinker.consume(mContext, xmlRes.get())) {
                     if (!proguard::collectProguardRules(xmlRes->file.source, xmlRes.get(),
                                                         &proguardKeepSet)) {
                         error = true;
@@ -778,14 +781,14 @@
                                 xmlRes->file.config.sdkVersion = sdkLevel;
 
                                 std::string genResourcePath = ResourceUtils::buildResourceFileName(
-                                        xmlRes->file, mContext.getNameMangler());
+                                        xmlRes->file, mContext->getNameMangler());
 
                                 bool added = mFinalTable.addFileReference(
                                         xmlRes->file.name,
                                         xmlRes->file.config,
                                         xmlRes->file.source,
                                         util::utf8ToUtf16(genResourcePath),
-                                        mContext.getDiagnostics());
+                                        mContext->getDiagnostics());
                                 if (!added) {
                                     error = true;
                                     continue;
@@ -804,7 +807,7 @@
                 }
             } else {
                 if (mOptions.verbose) {
-                    mContext.getDiagnostics()->note(DiagMessage() << "copying " << path);
+                    mContext->getDiagnostics()->note(DiagMessage() << "copying " << path);
                 }
 
                 if (!copyFileToArchive(fileToMerge.file, fileToMerge.dstPath,
@@ -815,20 +818,20 @@
         }
 
         if (error) {
-            mContext.getDiagnostics()->error(DiagMessage() << "failed linking file resources");
+            mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources");
             return 1;
         }
 
         if (!mOptions.noAutoVersion) {
             AutoVersioner versioner;
-            if (!versioner.consume(&mContext, &mFinalTable)) {
-                mContext.getDiagnostics()->error(DiagMessage() << "failed versioning styles");
+            if (!versioner.consume(mContext, &mFinalTable)) {
+                mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles");
                 return 1;
             }
         }
 
         if (!flattenTable(&mFinalTable, archiveWriter.get())) {
-            mContext.getDiagnostics()->error(DiagMessage() << "failed to write resources.arsc");
+            mContext->getDiagnostics()->error(DiagMessage() << "failed to write resources.arsc");
             return 1;
         }
 
@@ -840,8 +843,8 @@
                 options.useFinal = false;
             }
 
-            const StringPiece16 actualPackage = mContext.getCompilationPackage();
-            StringPiece16 outputPackage = mContext.getCompilationPackage();
+            const StringPiece16 actualPackage = mContext->getCompilationPackage();
+            StringPiece16 outputPackage = mContext->getCompilationPackage();
             if (mOptions.customJavaPackage) {
                 // Override the output java package to the custom one.
                 outputPackage = mOptions.customJavaPackage.value();
@@ -852,7 +855,7 @@
                 // to the original package, and private and public symbols to the private package.
 
                 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
-                if (!writeJavaFile(&mFinalTable, mContext.getCompilationPackage(),
+                if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(),
                                    outputPackage, options)) {
                     return 1;
                 }
@@ -886,7 +889,7 @@
 
 private:
     LinkOptions mOptions;
-    LinkContext mContext;
+    LinkContext* mContext;
     ResourceTable mFinalTable;
 
     ResourceTable mLocalFileTable;
@@ -907,6 +910,7 @@
     Maybe<std::string> versionCode, versionName;
     Maybe<std::string> customJavaPackage;
     std::vector<std::string> extraJavaPackages;
+    Maybe<std::string> configs;
     bool legacyXFlag = false;
     bool requireLocalization = false;
     Flags flags = Flags()
@@ -928,6 +932,8 @@
                             &legacyXFlag)
             .optionalSwitch("-z", "Require localization of strings marked 'suggested'",
                             &requireLocalization)
+            .optionalFlag("-c", "Comma separated list of configurations to include. The default\n"
+                                "is all configurations", &configs)
             .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
                             "by -o",
                             &options.outputToDirectory)
@@ -967,6 +973,8 @@
         return 1;
     }
 
+    LinkContext context;
+
     if (privateSymbolsPackage) {
         options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value());
     }
@@ -1011,7 +1019,31 @@
         }
     }
 
-    LinkCommand cmd(options);
+    AxisConfigFilter filter;
+    if (configs) {
+        for (const StringPiece& configStr : util::tokenize<char>(configs.value(), ',')) {
+            ConfigDescription config;
+            LocaleValue lv;
+            if (lv.initFromFilterString(configStr)) {
+                lv.writeTo(&config);
+            } else if (!ConfigDescription::parse(configStr, &config)) {
+                context.getDiagnostics()->error(
+                        DiagMessage() << "invalid config '" << configStr << "' for -c option");
+                return 1;
+            }
+
+            if (config.density != 0) {
+                context.getDiagnostics()->warn(
+                        DiagMessage() << "ignoring density '" << config << "' for -c option");
+            } else {
+                filter.addConfig(config);
+            }
+        }
+
+        options.configFilter = &filter;
+    }
+
+    LinkCommand cmd(&context, options);
     return cmd.run(flags.getArgs());
 }
 
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 27a23bd..e01a004 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -100,7 +100,7 @@
                 return false;
             }
 
-            mFilesToMerge[ResourceKeyRef{ name, config }] = FileToMerge{
+            mFilesToMerge[ResourceKeyRef(name, config)] = FileToMerge{
                     f, oldFile->getSource(), util::utf16ToUtf8(*newFile->path) };
             return true;
         };
@@ -201,6 +201,9 @@
                 auto iter = std::lower_bound(dstEntry->values.begin(), dstEntry->values.end(),
                                              srcValue.config, cmp::lessThanConfig);
 
+                const bool stripConfig = mOptions.filter ?
+                        !mOptions.filter->match(srcValue.config) : false;
+
                 if (iter != dstEntry->values.end() && iter->config == srcValue.config) {
                     const int collisionResult = ResourceTable::resolveValueCollision(
                             iter->value.get(), srcValue.value.get());
@@ -224,11 +227,15 @@
                         continue;
                     }
 
-                } else {
+                } else if (!stripConfig){
                     // Insert a place holder value. We will fill it in below.
                     iter = dstEntry->values.insert(iter, ResourceConfigValue{ srcValue.config });
                 }
 
+                if (stripConfig) {
+                    continue;
+                }
+
                 if (FileReference* f = valueCast<FileReference>(srcValue.value.get())) {
                     std::unique_ptr<FileReference> newFileRef;
                     if (manglePackage) {
@@ -287,7 +294,7 @@
 
     auto callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
                        FileReference* newFile, FileReference* oldFile) -> bool {
-        mFilesToMerge[ResourceKeyRef{ name, config }] = FileToMerge{
+        mFilesToMerge[ResourceKeyRef(name, config)] = FileToMerge{
                 file, oldFile->getSource(), util::utf16ToUtf8(*newFile->path) };
         return true;
     };
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index e1be5d5..4539679 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -20,6 +20,7 @@
 #include "Resource.h"
 #include "ResourceTable.h"
 #include "ResourceValues.h"
+#include "filter/ConfigFilter.h"
 #include "io/File.h"
 #include "process/IResourceTableConsumer.h"
 #include "util/Util.h"
@@ -51,6 +52,11 @@
      * If true, resources in overlays can be added without previously having existed.
      */
     bool autoAddOverlay = false;
+
+    /**
+     * A filter that removes resources whose configurations don't match.
+     */
+    IConfigFilter* filter = nullptr;
 };
 
 /**
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index fa4afd3..45c8c98 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include "filter/ConfigFilter.h"
 #include "io/FileSystem.h"
 #include "link/TableMerger.h"
 #include "test/Builders.h"
@@ -243,4 +244,36 @@
     ASSERT_FALSE(merger.mergeOverlay({}, tableB.get()));
 }
 
+TEST_F(TableMergerTest, MergeAndStripResourcesNotMatchingFilter) {
+    ResourceTable finalTable;
+    TableMergerOptions options;
+    options.autoAddOverlay = false;
+
+    AxisConfigFilter filter;
+    filter.addConfig(test::parseConfigOrDie("en"));
+    options.filter = &filter;
+
+    test::TestFile fileA("res/layout-en/main.xml"), fileB("res/layout-fr-rFR/main.xml");
+    const ResourceName name = test::parseNameOrDie(u"@com.app.a:layout/main");
+    const ConfigDescription configEn = test::parseConfigOrDie("en");
+    const ConfigDescription configFr = test::parseConfigOrDie("fr-rFR");
+
+    TableMerger merger(mContext.get(), &finalTable, options);
+    ASSERT_TRUE(merger.mergeFile(ResourceFile{ name, configEn }, &fileA));
+    ASSERT_TRUE(merger.mergeFile(ResourceFile{ name, configFr }, &fileB));
+
+    EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(&finalTable,
+                                                              u"@com.app.a:layout/main",
+                                                              configEn));
+    EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(&finalTable,
+                                                              u"@com.app.a:layout/main",
+                                                              configFr));
+
+    EXPECT_NE(merger.getFilesToMerge().end(),
+              merger.getFilesToMerge().find(ResourceKeyRef(name, configEn)));
+
+    EXPECT_EQ(merger.getFilesToMerge().end(),
+              merger.getFilesToMerge().find(ResourceKeyRef(name, configFr)));
+}
+
 } // namespace aapt
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 93a11b9..579a46e 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -84,6 +84,11 @@
                         util::make_unique<FileReference>(mTable->stringPool.makeRef(path)));
     }
 
+    ResourceTableBuilder& addFileReference(const StringPiece16& name, const StringPiece16& path,
+                                           const ConfigDescription& config) {
+        return addValue(name, {}, config,
+                        util::make_unique<FileReference>(mTable->stringPool.makeRef(path)));
+    }
 
     ResourceTableBuilder& addValue(const StringPiece16& name,
                                    std::unique_ptr<Value> value) {